diff --git a/.vscode/settings.json b/.vscode/settings.json index df4d1aa2ad..4acae6fe8e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -44,13 +44,19 @@ "emmet.excludeLanguages": [], "typescript.preferences.importModuleSpecifier": "non-relative", "typescript.preferences.quoteStyle": "single", - "json.schemas": [{ - "fileMatch": [ "cgmanifest.json" ], - "url": "./.vscode/cgmanifest.schema.json" - }, { - "fileMatch": [ "cglicenses.json" ], - "url": "./.vscode/cglicenses.schema.json" - } -], -"git.ignoreLimitWarning": true -} + "json.schemas": [ + { + "fileMatch": [ + "cgmanifest.json" + ], + "url": "./.vscode/cgmanifest.schema.json" + }, + { + "fileMatch": [ + "cglicenses.json" + ], + "url": "./.vscode/cglicenses.schema.json" + } + ], + "git.ignoreLimitWarning": true +} \ No newline at end of file diff --git a/.yarnrc b/.yarnrc index 190252d15d..692f879945 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,3 +1,3 @@ disturl "https://atom.io/download/electron" -target "3.1.2" +target "3.1.6" runtime "electron" diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 3cef20bd7d..bcc36817b4 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -343,32 +343,6 @@ END OF emmet NOTICES AND INFORMATION ========================================= The MIT License (MIT) -Copyright (c) 2015 JD Ballard - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -========================================= -END OF error-ex NOTICES AND INFORMATION - -%% escape-string-regexp NOTICES AND INFORMATION BEGIN HERE -========================================= -The MIT License (MIT) - Copyright (c) Sindre Sorhus (sindresorhus.com) Permission is hereby granted, free of charge, to any person obtaining a copy @@ -2465,4 +2439,4 @@ SOFTWARE. -------------------------------END OF THIRD-PARTY NOTICES------------------------------------------- ========================================= -END OF Microsoft.ProgramSynthesis.Detection NOTICES AND INFORMATION \ No newline at end of file +END OF Microsoft.ProgramSynthesis.Detection NOTICES AND INFORMATION diff --git a/azure-pipelines-linux-mac.yml b/azure-pipelines-linux-mac.yml index 99469cd118..5c6115125a 100644 --- a/azure-pipelines-linux-mac.yml +++ b/azure-pipelines-linux-mac.yml @@ -6,8 +6,8 @@ steps: - script: | git submodule update --init --recursive - nvm install 8.9.1 - nvm use 8.9.1 + nvm install 10.15.1 + nvm use 10.15.1 npm i -g yarn displayName: 'preinstall' @@ -29,7 +29,6 @@ steps: - script: | node_modules/.bin/gulp electron node_modules/.bin/gulp compile --max_old_space_size=4096 - node_modules/.bin/gulp optimize-vscode --max_old_space_size=4096 displayName: 'Scripts' - script: | @@ -43,4 +42,4 @@ steps: - script: | yarn run tslint - displayName: 'Run TSLint' \ No newline at end of file + displayName: 'Run TSLint' diff --git a/azure-pipelines-windows.yml b/azure-pipelines-windows.yml index 4c1f641d8f..b66aa62af4 100644 --- a/azure-pipelines-windows.yml +++ b/azure-pipelines-windows.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: '8.9' + versionSpec: '10.15.1' displayName: 'Install Node.js' - script: | @@ -27,4 +27,4 @@ steps: - script: | yarn run tslint - displayName: 'Run TSLint' \ No newline at end of file + displayName: 'Run TSLint' diff --git a/build/azure-pipelines/common/publish.ts b/build/azure-pipelines/common/publish.ts index f094e264f9..e8c8855da0 100644 --- a/build/azure-pipelines/common/publish.ts +++ b/build/azure-pipelines/common/publish.ts @@ -154,9 +154,13 @@ async function publish(commit: string, quality: string, platform: string, type: const queuedBy = process.env['BUILD_QUEUEDBY']!; const sourceBranch = process.env['BUILD_SOURCEBRANCH']!; - const isReleased = quality === 'insider' - && /^master$|^refs\/heads\/master$/.test(sourceBranch) - && /Project Collection Service Accounts|Microsoft.VisualStudio.Services.TFS/.test(queuedBy); + const isReleased = ( + // Insiders: nightly build from master + (quality === 'insider' && /^master$|^refs\/heads\/master$/.test(sourceBranch) && /Project Collection Service Accounts|Microsoft.VisualStudio.Services.TFS/.test(queuedBy)) || + + // Exploration: any build from electron-4.0.x branch + (quality === 'exploration' && /^electron-4.0.x$|^refs\/heads\/electron-4.0.x$/.test(sourceBranch)) + ); console.log('Publishing...'); console.log('Quality:', quality); diff --git a/build/azure-pipelines/darwin/continuous-build-darwin.yml b/build/azure-pipelines/darwin/continuous-build-darwin.yml index 99e17e940a..ffa80168b1 100644 --- a/build/azure-pipelines/darwin/continuous-build-darwin.yml +++ b/build/azure-pipelines/darwin/continuous-build-darwin.yml @@ -1,13 +1,25 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "8.12.0" + versionSpec: "10.15.1" +- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: '**/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' + targetfolder: '**/node_modules, !**/node_modules/**/node_modules' + vstsFeed: '$(ArtifactFeed)' - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: versionSpec: "1.10.1" - script: | yarn displayName: Install Dependencies + condition: ne(variables['CacheRestored'], 'true') +- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 + inputs: + keyfile: '**/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' + targetfolder: '**/node_modules, !**/node_modules/**/node_modules' + vstsFeed: '$(ArtifactFeed)' + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - script: | yarn gulp electron-x64 displayName: Download Electron diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index ff9d32b35c..40b4eb98f8 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "8.12.0" + versionSpec: "10.15.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: @@ -23,7 +23,10 @@ steps: set -e VSCODE_MIXIN_PASSWORD="$(VSCODE_MIXIN_PASSWORD)" \ AZURE_STORAGE_ACCESS_KEY="$(AZURE_STORAGE_ACCESS_KEY)" \ - yarn gulp -- vscode-darwin-min upload-vscode-sourcemaps + yarn gulp -- vscode-darwin-min + VSCODE_MIXIN_PASSWORD="$(VSCODE_MIXIN_PASSWORD)" \ + AZURE_STORAGE_ACCESS_KEY="$(AZURE_STORAGE_ACCESS_KEY)" \ + yarn gulp -- upload-vscode-sourcemaps displayName: Build - script: | diff --git a/build/azure-pipelines/linux/continuous-build-linux.yml b/build/azure-pipelines/linux/continuous-build-linux.yml index fc124c5bf4..484334c7b7 100644 --- a/build/azure-pipelines/linux/continuous-build-linux.yml +++ b/build/azure-pipelines/linux/continuous-build-linux.yml @@ -9,13 +9,25 @@ steps: sudo service xvfb start - task: NodeTool@0 inputs: - versionSpec: "8.12.0" + versionSpec: "10.15.1" +- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: '**/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' + targetfolder: '**/node_modules, !**/node_modules/**/node_modules' + vstsFeed: '$(ArtifactFeed)' - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: versionSpec: "1.10.1" - script: | yarn displayName: Install Dependencies + condition: ne(variables['CacheRestored'], 'true') +- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 + inputs: + keyfile: '**/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' + targetfolder: '**/node_modules, !**/node_modules/**/node_modules' + vstsFeed: '$(ArtifactFeed)' + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - script: | yarn gulp electron-x64 displayName: Download Electron diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 6cf126af1e..be321a474e 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "8.12.0" + versionSpec: "10.15.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/azure-pipelines/linux/snap-build-linux.yml b/build/azure-pipelines/linux/snap-build-linux.yml index 29252107f4..928ff84e06 100644 --- a/build/azure-pipelines/linux/snap-build-linux.yml +++ b/build/azure-pipelines/linux/snap-build-linux.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "8.12.0" + versionSpec: "10.15.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: @@ -16,6 +16,11 @@ steps: - script: | set -e + # Make sure we get latest packages + sudo apt-get update + sudo apt-get upgrade -y + + # Define variables REPO="$(pwd)" ARCH="$(VSCODE_ARCH)" SNAP_ROOT="$REPO/.build/linux/snap/$ARCH" diff --git a/build/azure-pipelines/win32/continuous-build-win32.yml b/build/azure-pipelines/win32/continuous-build-win32.yml index 7145e67e2a..764d7916fe 100644 --- a/build/azure-pipelines/win32/continuous-build-win32.yml +++ b/build/azure-pipelines/win32/continuous-build-win32.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "8.12.0" + versionSpec: "10.15.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: versionSpec: "1.10.1" @@ -9,9 +9,21 @@ steps: inputs: versionSpec: '2.x' addToPath: true +- task: 1ESLighthouseEng.PipelineArtifactCaching.RestoreCacheV1.RestoreCache@1 + inputs: + keyfile: '**/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' + targetfolder: '**/node_modules, !**/node_modules/**/node_modules' + vstsFeed: '$(ArtifactFeed)' - powershell: | yarn displayName: Install Dependencies + condition: ne(variables['CacheRestored'], 'true') +- task: 1ESLighthouseEng.PipelineArtifactCaching.SaveCacheV1.SaveCache@1 + inputs: + keyfile: '**/yarn.lock, !**/node_modules/**/yarn.lock, !**/.*/**/yarn.lock' + targetfolder: '**/node_modules, !**/node_modules/**/node_modules' + vstsFeed: '$(ArtifactFeed)' + condition: and(succeeded(), ne(variables['CacheRestored'], 'true')) - powershell: | yarn gulp electron displayName: Download Electron diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 418af9e10a..ca028b0048 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -1,7 +1,7 @@ steps: - task: NodeTool@0 inputs: - versionSpec: "8.12.0" + versionSpec: "10.15.1" - task: geeklearningio.gl-vsts-tasks-yarn.yarn-installer-task.YarnInstaller@2 inputs: diff --git a/build/download/download.js b/build/download/download.js new file mode 100644 index 0000000000..ce27c8d285 --- /dev/null +++ b/build/download/download.js @@ -0,0 +1,91 @@ +"use strict"; +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +Object.defineProperty(exports, "__esModule", { value: true }); +const https = require("https"); +const fs = require("fs"); +const path = require("path"); +const cp = require("child_process"); +function ensureDir(filepath) { + if (!fs.existsSync(filepath)) { + ensureDir(path.dirname(filepath)); + fs.mkdirSync(filepath); + } +} +function download(options, destination) { + ensureDir(path.dirname(destination)); + return new Promise((c, e) => { + const fd = fs.openSync(destination, 'w'); + const req = https.get(options, (res) => { + res.on('data', (chunk) => { + fs.writeSync(fd, chunk); + }); + res.on('end', () => { + fs.closeSync(fd); + c(); + }); + }); + req.on('error', (reqErr) => { + console.error(`request to ${options.host}${options.path} failed.`); + console.error(reqErr); + e(reqErr); + }); + }); +} +const MARKER_ARGUMENT = `_download_fork_`; +function base64encode(str) { + return Buffer.from(str, 'utf8').toString('base64'); +} +function base64decode(str) { + return Buffer.from(str, 'base64').toString('utf8'); +} +function downloadInExternalProcess(options) { + const url = `https://${options.requestOptions.host}${options.requestOptions.path}`; + console.log(`Downloading ${url}...`); + return new Promise((c, e) => { + const child = cp.fork(__filename, [MARKER_ARGUMENT, base64encode(JSON.stringify(options))], { + stdio: ['pipe', 'pipe', 'pipe', 'ipc'] + }); + let stderr = []; + child.stderr.on('data', (chunk) => { + stderr.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk); + }); + child.on('exit', (code) => { + if (code === 0) { + // normal termination + console.log(`Finished downloading ${url}.`); + c(); + } + else { + // abnormal termination + console.error(Buffer.concat(stderr).toString()); + e(new Error(`Download of ${url} failed.`)); + } + }); + }); +} +exports.downloadInExternalProcess = downloadInExternalProcess; +function _downloadInExternalProcess() { + let options; + try { + options = JSON.parse(base64decode(process.argv[3])); + } + catch (err) { + console.error(`Cannot read arguments`); + console.error(err); + process.exit(-1); + return; + } + download(options.requestOptions, options.destinationPath).then(() => { + process.exit(0); + }, (err) => { + console.error(err); + process.exit(-2); + }); +} +if (process.argv.length >= 4 && process.argv[2] === MARKER_ARGUMENT) { + // running as forked download script + _downloadInExternalProcess(); +} diff --git a/build/download/download.ts b/build/download/download.ts new file mode 100644 index 0000000000..9559c72f58 --- /dev/null +++ b/build/download/download.ts @@ -0,0 +1,111 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as https from 'https'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as cp from 'child_process'; + +function ensureDir(filepath: string) { + if (!fs.existsSync(filepath)) { + ensureDir(path.dirname(filepath)); + fs.mkdirSync(filepath); + } +} + +function download(options: https.RequestOptions, destination: string): Promise { + ensureDir(path.dirname(destination)); + + return new Promise((c, e) => { + const fd = fs.openSync(destination, 'w'); + const req = https.get(options, (res) => { + res.on('data', (chunk) => { + fs.writeSync(fd, chunk); + }); + res.on('end', () => { + fs.closeSync(fd); + c(); + }); + }); + req.on('error', (reqErr) => { + console.error(`request to ${options.host}${options.path} failed.`); + console.error(reqErr); + e(reqErr); + }); + }); +} + +const MARKER_ARGUMENT = `_download_fork_`; + +function base64encode(str: string): string { + return Buffer.from(str, 'utf8').toString('base64'); +} + +function base64decode(str: string): string { + return Buffer.from(str, 'base64').toString('utf8'); +} + +export interface IDownloadRequestOptions { + host: string; + path: string; +} + +export interface IDownloadOptions { + requestOptions: IDownloadRequestOptions; + destinationPath: string; +} + +export function downloadInExternalProcess(options: IDownloadOptions): Promise { + const url = `https://${options.requestOptions.host}${options.requestOptions.path}`; + console.log(`Downloading ${url}...`); + return new Promise((c, e) => { + const child = cp.fork( + __filename, + [MARKER_ARGUMENT, base64encode(JSON.stringify(options))], + { + stdio: ['pipe', 'pipe', 'pipe', 'ipc'] + } + ); + let stderr: Buffer[] = []; + child.stderr.on('data', (chunk) => { + stderr.push(typeof chunk === 'string' ? Buffer.from(chunk) : chunk); + }); + child.on('exit', (code) => { + if (code === 0) { + // normal termination + console.log(`Finished downloading ${url}.`); + c(); + } else { + // abnormal termination + console.error(Buffer.concat(stderr).toString()); + e(new Error(`Download of ${url} failed.`)); + } + }); + }); +} + +function _downloadInExternalProcess() { + let options: IDownloadOptions; + try { + options = JSON.parse(base64decode(process.argv[3])); + } catch (err) { + console.error(`Cannot read arguments`); + console.error(err); + process.exit(-1); + return; + } + + download(options.requestOptions, options.destinationPath).then(() => { + process.exit(0); + }, (err) => { + console.error(err); + process.exit(-2); + }); +} + +if (process.argv.length >= 4 && process.argv[2] === MARKER_ARGUMENT) { + // running as forked download script + _downloadInExternalProcess(); +} diff --git a/build/gulpfile.compile.js b/build/gulpfile.compile.js new file mode 100644 index 0000000000..9c57ad6a8c --- /dev/null +++ b/build/gulpfile.compile.js @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +const util = require('./lib/util'); +const task = require('./lib/task'); +const compilation = require('./lib/compilation'); +const { compileExtensionsBuildTask } = require('./gulpfile.extensions'); + +// Full compile, including nls and inline sources in sourcemaps, for build +const compileClientBuildTask = task.define('compile-client-build', task.series(util.rimraf('out-build'), compilation.compileTask('src', 'out-build', true))); + +// All Build +const compileBuildTask = task.define('compile-build', task.parallel(compileClientBuildTask, compileExtensionsBuildTask)); +exports.compileBuildTask = compileBuildTask; diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 5ca1fdaa4e..4ecced99f3 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -6,6 +6,7 @@ const gulp = require('gulp'); const path = require('path'); const util = require('./lib/util'); +const task = require('./lib/task'); const common = require('./lib/optimize'); const es = require('event-stream'); const File = require('vinyl'); @@ -48,9 +49,6 @@ var editorResources = [ '!**/test/**' ]; -var editorOtherSources = [ -]; - var BUNDLED_FILE_HEADER = [ '/*!-----------------------------------------------------------', ' * Copyright (c) Microsoft Corporation. All rights reserved.', @@ -63,8 +61,7 @@ var BUNDLED_FILE_HEADER = [ const languages = i18n.defaultLanguages.concat([]); // i18n.defaultLanguages.concat(process.env.VSCODE_QUALITY !== 'stable' ? i18n.extraLanguages : []); -gulp.task('clean-editor-src', util.rimraf('out-editor-src')); -gulp.task('extract-editor-src', ['clean-editor-src'], function () { +const extractEditorSrcTask = task.define('extract-editor-src', () => { console.log(`If the build fails, consider tweaking shakeLevel below to a lower value.`); const apiusages = monacoapi.execute().usageContent; const extrausages = fs.readFileSync(path.join(root, 'build', 'monaco', 'monaco.usage.recipe')).toString(); @@ -84,6 +81,7 @@ gulp.task('extract-editor-src', ['clean-editor-src'], function () { 'typings/thenable.d.ts', 'typings/es6-promise.d.ts', 'typings/require-monaco.d.ts', + "typings/lib.es2018.promise.d.ts", 'vs/monaco.d.ts' ], libs: [ @@ -100,15 +98,11 @@ gulp.task('extract-editor-src', ['clean-editor-src'], function () { }); }); -// Full compile, including nls and inline sources in sourcemaps, for build -gulp.task('clean-editor-build', util.rimraf('out-editor-build')); -gulp.task('compile-editor-build', ['clean-editor-build', 'extract-editor-src'], compilation.compileTask('out-editor-src', 'out-editor-build', true)); +const compileEditorAMDTask = task.define('compile-editor-amd', compilation.compileTask('out-editor-src', 'out-editor-build', true)); -gulp.task('clean-optimized-editor', util.rimraf('out-editor')); -gulp.task('optimize-editor', ['clean-optimized-editor', 'compile-editor-build'], common.optimizeTask({ +const optimizeEditorAMDTask = task.define('optimize-editor-amd', common.optimizeTask({ src: 'out-editor-build', entryPoints: editorEntryPoints, - otherSources: editorOtherSources, resources: editorResources, loaderConfig: { paths: { @@ -125,11 +119,9 @@ gulp.task('optimize-editor', ['clean-optimized-editor', 'compile-editor-build'], languages: languages })); -gulp.task('clean-minified-editor', util.rimraf('out-editor-min')); -gulp.task('minify-editor', ['clean-minified-editor', 'optimize-editor'], common.minifyTask('out-editor')); +const minifyEditorAMDTask = task.define('minify-editor-amd', common.minifyTask('out-editor')); -gulp.task('clean-editor-esm', util.rimraf('out-editor-esm')); -gulp.task('extract-editor-esm', ['clean-editor-esm', 'clean-editor-distro', 'extract-editor-src'], function () { +const createESMSourcesAndResourcesTask = task.define('extract-editor-esm', () => { standalone.createESMSourcesAndResources2({ srcFolder: './out-editor-src', outFolder: './out-editor-esm', @@ -151,7 +143,8 @@ gulp.task('extract-editor-esm', ['clean-editor-esm', 'clean-editor-distro', 'ext } }); }); -gulp.task('compile-editor-esm', ['extract-editor-esm', 'clean-editor-distro'], function () { + +const compileEditorESMTask = task.define('compile-editor-esm', () => { if (process.platform === 'win32') { const result = cp.spawnSync(`..\\node_modules\\.bin\\tsc.cmd`, { cwd: path.join(__dirname, '../out-editor-esm') @@ -202,8 +195,16 @@ function toExternalDTS(contents) { return lines.join('\n'); } -gulp.task('clean-editor-distro', util.rimraf('out-monaco-editor-core')); -gulp.task('editor-distro', ['clean-editor-distro', 'compile-editor-esm', 'minify-editor', 'optimize-editor'], function () { +function filterStream(testFunc) { + return es.through(function (data) { + if (!testFunc(data.relative)) { + return; + } + this.emit('data', data); + }); +} + +const finalEditorResourcesTask = task.define('final-editor-resources', () => { return es.merge( // other assets es.merge( @@ -233,6 +234,14 @@ gulp.task('editor-distro', ['clean-editor-distro', 'compile-editor-esm', 'minify })) .pipe(gulp.dest('out-monaco-editor-core')), + // version.txt + gulp.src('build/monaco/version.txt') + .pipe(es.through(function (data) { + data.contents = Buffer.from(`monaco-editor-core: https://github.com/Microsoft/vscode/tree/${sha1}`); + this.emit('data', data); + })) + .pipe(gulp.dest('out-monaco-editor-core')), + // README.md gulp.src('build/monaco/README-npm.md') .pipe(es.through(function (data) { @@ -266,7 +275,7 @@ gulp.task('editor-distro', ['clean-editor-distro', 'compile-editor-esm', 'minify var strContents = data.contents.toString(); var newStr = '//# sourceMappingURL=' + relativePathToMap.replace(/\\/g, '/'); - strContents = strContents.replace(/\/\/\# sourceMappingURL=[^ ]+$/, newStr); + strContents = strContents.replace(/\/\/# sourceMappingURL=[^ ]+$/, newStr); data.contents = Buffer.from(strContents); this.emit('data', data); @@ -282,59 +291,31 @@ gulp.task('editor-distro', ['clean-editor-distro', 'compile-editor-esm', 'minify ); }); -gulp.task('analyze-editor-distro', function () { - // @ts-ignore - var bundleInfo = require('../out-editor/bundleInfo.json'); - var graph = bundleInfo.graph; - var bundles = bundleInfo.bundles; - - var inverseGraph = {}; - Object.keys(graph).forEach(function (module) { - var dependencies = graph[module]; - dependencies.forEach(function (dep) { - inverseGraph[dep] = inverseGraph[dep] || []; - inverseGraph[dep].push(module); - }); - }); - - var detailed = {}; - Object.keys(bundles).forEach(function (entryPoint) { - var included = bundles[entryPoint]; - var includedMap = {}; - included.forEach(function (included) { - includedMap[included] = true; - }); - - var explanation = []; - included.map(function (included) { - if (included.indexOf('!') >= 0) { - return; - } - - var reason = (inverseGraph[included] || []).filter(function (mod) { - return !!includedMap[mod]; - }); - explanation.push({ - module: included, - reason: reason - }); - }); - - detailed[entryPoint] = explanation; - }); - - console.log(JSON.stringify(detailed, null, '\t')); -}); - -function filterStream(testFunc) { - return es.through(function (data) { - if (!testFunc(data.relative)) { - return; - } - this.emit('data', data); - }); -} - +gulp.task('editor-distro', + task.series( + task.parallel( + util.rimraf('out-editor-src'), + util.rimraf('out-editor-build'), + util.rimraf('out-editor-esm'), + util.rimraf('out-monaco-editor-core'), + util.rimraf('out-editor'), + util.rimraf('out-editor-min') + ), + extractEditorSrcTask, + task.parallel( + task.series( + compileEditorAMDTask, + optimizeEditorAMDTask, + minifyEditorAMDTask + ), + task.series( + createESMSourcesAndResourcesTask, + compileEditorESMTask + ) + ), + finalEditorResourcesTask + ) +); //#region monaco type checking @@ -354,6 +335,7 @@ function createTscCompileTask(watch) { let errors = []; let reporter = createReporter(); let report; + // eslint-disable-next-line no-control-regex let magic = /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g; // https://stackoverflow.com/questions/25245716/remove-all-ansi-colors-styles-from-strings child.stdout.on('data', data => { @@ -387,7 +369,10 @@ function createTscCompileTask(watch) { }; } -gulp.task('monaco-typecheck-watch', createTscCompileTask(true)); -gulp.task('monaco-typecheck', createTscCompileTask(false)); +const monacoTypecheckWatchTask = task.define('monaco-typecheck-watch', createTscCompileTask(true)); +exports.monacoTypecheckWatchTask = monacoTypecheckWatchTask; + +const monacoTypecheckTask = task.define('monaco-typecheck', createTscCompileTask(false)); +exports.monacoTypecheckTask = monacoTypecheckTask; //#endregion diff --git a/build/gulpfile.extensions.js b/build/gulpfile.extensions.js index c16e7babcd..0440e35a2e 100644 --- a/build/gulpfile.extensions.js +++ b/build/gulpfile.extensions.js @@ -11,8 +11,8 @@ const path = require('path'); const tsb = require('gulp-tsb'); const es = require('event-stream'); const filter = require('gulp-filter'); -const rimraf = require('rimraf'); const util = require('./lib/util'); +const task = require('./lib/task'); const watcher = require('./lib/watch'); const createReporter = require('./lib/reporter').createReporter; const glob = require('glob'); @@ -43,16 +43,6 @@ const tasks = compilations.map(function (tsconfigFile) { const name = relativeDirname.replace(/\//g, '-'); - // Tasks - const clean = 'clean-extension:' + name; - const compile = 'compile-extension:' + name; - const watch = 'watch-extension:' + name; - - // Build Tasks - const cleanBuild = 'clean-extension-build:' + name; - const compileBuild = 'compile-extension-build:' + name; - const watchBuild = 'watch-extension-build:' + name; - const root = path.join('extensions', relativeDirname); const srcBase = path.join(root, 'src'); const src = path.join(srcBase, '**'); @@ -111,18 +101,18 @@ const tasks = compilations.map(function (tsconfigFile) { const srcOpts = { cwd: path.dirname(__dirname), base: srcBase }; - gulp.task(clean, cb => rimraf(out, cb)); + const cleanTask = task.define(`clean-extension-${name}`, util.rimraf(out)); - gulp.task(compile, [clean], () => { + const compileTask = task.define(`compile-extension:${name}`, task.series(cleanTask, () => { const pipeline = createPipeline(false, true); const input = gulp.src(src, srcOpts); return input .pipe(pipeline()) .pipe(gulp.dest(out)); - }); + })); - gulp.task(watch, [clean], () => { + const watchTask = task.define(`watch-extension:${name}`, task.series(cleanTask, () => { const pipeline = createPipeline(false); const input = gulp.src(src, srcOpts); const watchInput = watcher(src, srcOpts); @@ -130,43 +120,35 @@ const tasks = compilations.map(function (tsconfigFile) { return watchInput .pipe(util.incremental(pipeline, input)) .pipe(gulp.dest(out)); - }); + })); - gulp.task(cleanBuild, cb => rimraf(out, cb)); - - gulp.task(compileBuild, [clean], () => { + const compileBuildTask = task.define(`compile-build-extension-${name}`, task.series(cleanTask, () => { const pipeline = createPipeline(true, true); const input = gulp.src(src, srcOpts); return input .pipe(pipeline()) .pipe(gulp.dest(out)); - }); + })); - gulp.task(watchBuild, [clean], () => { - const pipeline = createPipeline(true); - const input = gulp.src(src, srcOpts); - const watchInput = watcher(src, srcOpts); - - return watchInput - .pipe(util.incremental(() => pipeline(), input)) - .pipe(gulp.dest(out)); - }); + // Tasks + gulp.task(compileTask); + gulp.task(watchTask); return { - clean: clean, - compile: compile, - watch: watch, - cleanBuild: cleanBuild, - compileBuild: compileBuild, - watchBuild: watchBuild + compileTask: compileTask, + watchTask: watchTask, + compileBuildTask: compileBuildTask }; }); -gulp.task('clean-extensions', tasks.map(t => t.clean)); -gulp.task('compile-extensions', tasks.map(t => t.compile)); -gulp.task('watch-extensions', tasks.map(t => t.watch)); +const compileExtensionsTask = task.define('compile-extensions', task.parallel(...tasks.map(t => t.compileTask))); +gulp.task(compileExtensionsTask); +exports.compileExtensionsTask = compileExtensionsTask; -gulp.task('clean-extensions-build', tasks.map(t => t.cleanBuild)); -gulp.task('compile-extensions-build', tasks.map(t => t.compileBuild)); -gulp.task('watch-extensions-build', tasks.map(t => t.watchBuild)); +const watchExtensionsTask = task.define('watch-extensions', task.parallel(...tasks.map(t => t.watchTask))); +gulp.task(watchExtensionsTask); +exports.watchExtensionsTask = watchExtensionsTask; + +const compileExtensionsBuildTask = task.define('compile-extensions-build', task.parallel(...tasks.map(t => t.compileBuildTask))); +exports.compileExtensionsBuildTask = compileExtensionsBuildTask; diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index f21c579004..c151379359 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -81,7 +81,7 @@ const indentationFilter = [ '!src/typings/**/*.d.ts', '!extensions/**/*.d.ts', '!**/*.{svg,exe,png,bmp,scpt,bat,cmd,cur,ttf,woff,eot,md,ps1,template,yaml,yml,d.ts.recipe,ico,icns}', - '!build/{lib,tslintRules}/**/*.js', + '!build/{lib,tslintRules,download}/**/*.js', '!build/**/*.sh', '!build/azure-pipelines/**/*.js', '!build/azure-pipelines/**/*.config', @@ -230,7 +230,7 @@ function hygiene(some) { let formatted = result.dest.replace(/\r\n/gm, '\n'); if (original !== formatted) { - console.error('File not formatted:', file.relative); + console.error("File not formatted. Run the 'Format Document' command to fix it:", file.relative); errorCount++; } cb(null, file); diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index ed5eb6ad03..344ff2f2e3 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -20,6 +20,7 @@ const filter = require('gulp-filter'); const json = require('gulp-json-editor'); const _ = require('underscore'); const util = require('./lib/util'); +const task = require('./lib/task'); const ext = require('./lib/extensions'); const buildfile = require('../src/buildfile'); const common = require('./lib/optimize'); @@ -38,6 +39,7 @@ const deps = require('./dependencies'); const getElectronVersion = require('./lib/electron').getElectronVersion; const createAsar = require('./lib/asar').createAsar; const minimist = require('minimist'); +const { compileBuildTask } = require('./gulpfile.compile'); const productionDependencies = deps.getProductionDependencies(path.dirname(__dirname)); // @ts-ignore @@ -76,15 +78,16 @@ const vscodeResources = [ 'out-build/paths.js', 'out-build/vs/**/*.{svg,png,cur,html}', 'out-build/vs/base/common/performance.js', + 'out-build/vs/base/node/languagePacks.js', 'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh,cpuUsage.sh}', 'out-build/vs/base/browser/ui/octiconLabel/octicons/**', 'out-build/vs/workbench/browser/media/*-theme.css', - 'out-build/vs/workbench/parts/debug/**/*.json', - 'out-build/vs/workbench/parts/execution/**/*.scpt', - 'out-build/vs/workbench/parts/webview/electron-browser/webview-pre.js', + 'out-build/vs/workbench/contrib/debug/**/*.json', + 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', + 'out-build/vs/workbench/contrib/webview/electron-browser/webview-pre.js', 'out-build/vs/**/markdown.css', - 'out-build/vs/workbench/parts/tasks/**/*.json', - 'out-build/vs/workbench/parts/welcome/walkThrough/**/*.md', + 'out-build/vs/workbench/contrib/tasks/**/*.json', + 'out-build/vs/workbench/contrib/welcome/walkThrough/**/*.md', 'out-build/vs/workbench/services/files/**/*.exe', 'out-build/vs/workbench/services/files/**/*.md', 'out-build/vs/code/electron-browser/workbench/**', @@ -121,29 +124,41 @@ const BUNDLED_FILE_HEADER = [ ' *--------------------------------------------------------*/' ].join('\n'); -gulp.task('clean-optimized-vscode', util.rimraf('out-vscode')); -gulp.task('optimize-vscode', ['clean-optimized-vscode', 'compile-build', 'compile-extensions-build'], common.optimizeTask({ - src: 'out-build', - entryPoints: vscodeEntryPoints, - otherSources: [], - resources: vscodeResources, - loaderConfig: common.loaderConfig(nodeModules), - header: BUNDLED_FILE_HEADER, - out: 'out-vscode', - bundleInfo: undefined -})); +const optimizeVSCodeTask = task.define('optimize-vscode', task.series( + task.parallel( + util.rimraf('out-vscode'), + compileBuildTask + ), + common.optimizeTask({ + src: 'out-build', + entryPoints: vscodeEntryPoints, + resources: vscodeResources, + loaderConfig: common.loaderConfig(nodeModules), + header: BUNDLED_FILE_HEADER, + out: 'out-vscode', + bundleInfo: undefined + }) +)); -gulp.task('optimize-index-js', ['optimize-vscode'], () => { - const fullpath = path.join(process.cwd(), 'out-vscode/bootstrap-window.js'); - const contents = fs.readFileSync(fullpath).toString(); - const newContents = contents.replace('[/*BUILD->INSERT_NODE_MODULES*/]', JSON.stringify(nodeModules)); - fs.writeFileSync(fullpath, newContents); -}); +const optimizeIndexJSTask = task.define('optimize-index-js', task.series( + optimizeVSCodeTask, + () => { + const fullpath = path.join(process.cwd(), 'out-vscode/bootstrap-window.js'); + const contents = fs.readFileSync(fullpath).toString(); + const newContents = contents.replace('[/*BUILD->INSERT_NODE_MODULES*/]', JSON.stringify(nodeModules)); + fs.writeFileSync(fullpath, newContents); + } +)); const sourceMappingURLBase = `https://ticino.blob.core.windows.net/sourcemaps/${commit}`; -gulp.task('clean-minified-vscode', util.rimraf('out-vscode-min')); -gulp.task('minify-vscode', ['clean-minified-vscode', 'optimize-index-js'], common.minifyTask('out-vscode', `${sourceMappingURLBase}/core`)); +const minifyVSCodeTask = task.define('minify-vscode', task.series( + task.parallel( + util.rimraf('out-vscode-min'), + optimizeIndexJSTask + ), + common.minifyTask('out-vscode', `${sourceMappingURLBase}/core`) +)); // Package @@ -206,13 +221,11 @@ function getElectron(arch) { }; } -gulp.task('clean-electron', util.rimraf('.build/electron')); -gulp.task('electron', ['clean-electron'], getElectron(process.arch)); -gulp.task('electron-ia32', ['clean-electron'], getElectron('ia32')); -gulp.task('electron-x64', ['clean-electron'], getElectron('x64')); -gulp.task('electron-arm', ['clean-electron'], getElectron('arm')); -gulp.task('electron-arm64', ['clean-electron'], getElectron('arm64')); - +gulp.task(task.define('electron', task.series(util.rimraf('.build/electron'), getElectron(process.arch)))); +gulp.task(task.define('electron-ia32', task.series(util.rimraf('.build/electron'), getElectron('ia32')))); +gulp.task(task.define('electron-x64', task.series(util.rimraf('.build/electron'), getElectron('x64')))); +gulp.task(task.define('electron-arm', task.series(util.rimraf('.build/electron'), getElectron('armv7l')))); +gulp.task(task.define('electron-arm64', task.series(util.rimraf('.build/electron'), getElectron('arm64')))); /** * Compute checksums for some files. @@ -248,15 +261,14 @@ function computeChecksum(filename) { return hash; } -function packageTask(platform, arch, opts) { +function packageTask(platform, arch, sourceFolderName, destinationFolderName, opts) { opts = opts || {}; - // {{SQL CARBON EDIT}} - const destination = path.join(path.dirname(root), 'azuredatastudio') + (platform ? '-' + platform : '') + (arch ? '-' + arch : ''); + const destination = path.join(path.dirname(root), destinationFolderName); platform = platform || process.platform; return () => { - const out = opts.minified ? 'out-vscode-min' : 'out-vscode'; + const out = sourceFolderName; const checksums = computeChecksums(out, [ 'vs/workbench/workbench.main.js', @@ -309,14 +321,13 @@ function packageTask(platform, arch, opts) { const productJsonStream = gulp.src(['product.json'], { base: '.' }) .pipe(json(productJsonUpdate)); - const license = gulp.src(['LICENSES.chromium.html', 'LICENSE.txt', 'ThirdPartyNotices.txt', 'licenses/**'], { base: '.' }); - - const watermark = gulp.src(['resources/letterpress.svg', 'resources/letterpress-dark.svg', 'resources/letterpress-hc.svg'], { base: '.' }); + const license = gulp.src(['LICENSES.chromium.html', 'LICENSE.txt', 'ThirdPartyNotices.txt', 'licenses/**'], { base: '.', allowEmpty: true }); // TODO the API should be copied to `out` during compile, not here const api = gulp.src('src/vs/vscode.d.ts').pipe(rename('out/vs/vscode.d.ts')); // {{SQL CARBON EDIT}} - const dataApi = gulp.src('src/vs/data.d.ts').pipe(rename('out/sql/data.d.ts')); + const dataApi = gulp.src('src/sql/azdata.d.ts').pipe(rename('out/sql/azdata.d.ts')); + const sqlopsAPI = gulp.src('src/sql/sqlops.d.ts').pipe(rename('out/sql/sqlops.d.ts')); const depsSrc = [ ..._.flatten(productionDependencies.map(d => path.relative(root, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`])), @@ -351,6 +362,8 @@ function packageTask(platform, arch, opts) { .pipe(util.cleanNodeModule('vscode-nsfw', ['binding.gyp', 'build/**', 'src/**', 'openpa/**', 'includes/**'], ['**/*.node', '**/*.a'])) // {{SQL CARBON EDIT}} - End .pipe(util.cleanNodeModule('vsda', ['binding.gyp', 'README.md', 'build/**', '*.bat', '*.sh', '*.cpp', '*.h'], ['build/Release/vsda.node'])) + .pipe(util.cleanNodeModule('win-ca-lib', ['**/*'], ['package.json', '**/*.node'])) + .pipe(util.cleanNodeModule('node-addon-api', ['**/*'])) .pipe(createAsar(path.join(process.cwd(), 'node_modules'), ['**/*.node', '**/vscode-ripgrep/bin/*', '**/node-pty/build/Release/*'], 'app/node_modules.asar')); // {{SQL CARBON EDIT}} @@ -361,50 +374,29 @@ function packageTask(platform, arch, opts) { 'node_modules/underscore/**/*.*', 'node_modules/zone.js/**/*.*', 'node_modules/chart.js/**/*.*', + 'node_modules/chartjs-color/**/*.*', + 'node_modules/chartjs-color-string/**/*.*', + 'node_modules/color-convert/**/*.*', + 'node_modules/color-name/**/*.*', + 'node_modules/moment/**/*.*' ], { base: '.', dot: true }); let all = es.merge( packageJsonStream, productJsonStream, license, - watermark, api, // {{SQL CARBON EDIT}} copiedModules, dataApi, + sqlopsAPI, sources, deps ); if (platform === 'win32') { all = es.merge(all, gulp.src([ - 'resources/win32/bower.ico', - 'resources/win32/c.ico', - 'resources/win32/config.ico', - 'resources/win32/cpp.ico', - 'resources/win32/csharp.ico', - 'resources/win32/css.ico', - 'resources/win32/default.ico', - 'resources/win32/go.ico', - 'resources/win32/html.ico', - 'resources/win32/jade.ico', - 'resources/win32/java.ico', - 'resources/win32/javascript.ico', - 'resources/win32/json.ico', - 'resources/win32/less.ico', - 'resources/win32/markdown.ico', - 'resources/win32/php.ico', - 'resources/win32/powershell.ico', - 'resources/win32/python.ico', - 'resources/win32/react.ico', - 'resources/win32/ruby.ico', - 'resources/win32/sass.ico', - 'resources/win32/shell.ico', - 'resources/win32/sql.ico', - 'resources/win32/typescript.ico', - 'resources/win32/vue.ico', - 'resources/win32/xml.ico', - 'resources/win32/yaml.ico', + // {{SQL CARBON EDIT}} remove unused icons 'resources/win32/code_70x70.png', 'resources/win32/code_150x150.png' ], { base: '.' })); @@ -426,7 +418,7 @@ function packageTask(platform, arch, opts) { // result = es.merge(result, gulp.src('resources/completions/**', { base: '.' })); if (platform === 'win32') { - result = es.merge(result, gulp.src('resources/win32/bin/code.js', { base: 'resources/win32' })); + result = es.merge(result, gulp.src('resources/win32/bin/code.js', { base: 'resources/win32', allowEmpty: true })); result = es.merge(result, gulp.src('resources/win32/bin/code.cmd', { base: 'resources/win32' }) .pipe(replace('@@NAME@@', product.nameShort)) @@ -461,38 +453,37 @@ function packageTask(platform, arch, opts) { const buildRoot = path.dirname(root); -// {{SQL CARBON EDIT}} -gulp.task('vscode-win32-x64-azurecore', ['optimize-vscode'], ext.packageExtensionTask('azurecore', 'win32', 'x64')); -gulp.task('vscode-darwin-azurecore', ['optimize-vscode'], ext.packageExtensionTask('azurecore', 'darwin')); -gulp.task('vscode-linux-x64-azurecore', ['optimize-vscode'], ext.packageExtensionTask('azurecore', 'linux', 'x64')); +const BUILD_TARGETS = [ + { platform: 'win32', arch: 'ia32' }, + { platform: 'win32', arch: 'x64' }, + { platform: 'darwin', arch: null, opts: { stats: true } }, + { platform: 'linux', arch: 'ia32' }, + { platform: 'linux', arch: 'x64' }, + { platform: 'linux', arch: 'arm' }, + { platform: 'linux', arch: 'arm64' }, +]; +BUILD_TARGETS.forEach(buildTarget => { + const dashed = (str) => (str ? `-${str}` : ``); + const platform = buildTarget.platform; + const arch = buildTarget.arch; + const opts = buildTarget.opts; -gulp.task('vscode-win32-x64-mssql', ['vscode-linux-x64-azurecore', 'optimize-vscode'], ext.packageExtensionTask('mssql', 'win32', 'x64')); -gulp.task('vscode-darwin-mssql', ['vscode-linux-x64-azurecore', 'optimize-vscode'], ext.packageExtensionTask('mssql', 'darwin')); -gulp.task('vscode-linux-x64-mssql', ['vscode-linux-x64-azurecore', 'optimize-vscode'], ext.packageExtensionTask('mssql', 'linux', 'x64')); + ['', 'min'].forEach(minified => { + const sourceFolderName = `out-vscode${dashed(minified)}`; + const destinationFolderName = `azuredatastudio${dashed(platform)}${dashed(arch)}`; -gulp.task('clean-vscode-win32-ia32', util.rimraf(path.join(buildRoot, 'azuredatastudio-win32-ia32'))); -gulp.task('clean-vscode-win32-x64', util.rimraf(path.join(buildRoot, 'azuredatastudio-win32-x64'))); -gulp.task('clean-vscode-darwin', util.rimraf(path.join(buildRoot, 'azuredatastudio-darwin'))); -gulp.task('clean-vscode-linux-ia32', util.rimraf(path.join(buildRoot, 'azuredatastudio-linux-ia32'))); -gulp.task('clean-vscode-linux-x64', util.rimraf(path.join(buildRoot, 'azuredatastudio-linux-x64'))); -gulp.task('clean-vscode-linux-arm', util.rimraf(path.join(buildRoot, 'azuredatastudio-linux-arm'))); -gulp.task('clean-vscode-linux-arm64', util.rimraf(path.join(buildRoot, 'azuredatastudio-linux-arm64'))); - -gulp.task('vscode-win32-ia32', ['optimize-vscode', 'clean-vscode-win32-ia32'], packageTask('win32', 'ia32')); -gulp.task('vscode-win32-x64', ['vscode-win32-x64-azurecore', 'vscode-win32-x64-mssql', 'optimize-vscode', 'clean-vscode-win32-x64'], packageTask('win32', 'x64')); -gulp.task('vscode-darwin', ['vscode-darwin-azurecore', 'vscode-darwin-mssql', 'optimize-vscode', 'clean-vscode-darwin'], packageTask('darwin', null, { stats: true })); -gulp.task('vscode-linux-ia32', ['optimize-vscode', 'clean-vscode-linux-ia32'], packageTask('linux', 'ia32')); -gulp.task('vscode-linux-x64', ['vscode-linux-x64-azurecore', 'vscode-linux-x64-mssql', 'optimize-vscode', 'clean-vscode-linux-x64'], packageTask('linux', 'x64')); -gulp.task('vscode-linux-arm', ['optimize-vscode', 'clean-vscode-linux-arm'], packageTask('linux', 'arm')); -gulp.task('vscode-linux-arm64', ['optimize-vscode', 'clean-vscode-linux-arm64'], packageTask('linux', 'arm64')); - -gulp.task('vscode-win32-ia32-min', ['minify-vscode', 'clean-vscode-win32-ia32'], packageTask('win32', 'ia32', { minified: true })); -gulp.task('vscode-win32-x64-min', ['minify-vscode', 'clean-vscode-win32-x64'], packageTask('win32', 'x64', { minified: true })); -gulp.task('vscode-darwin-min', ['minify-vscode', 'clean-vscode-darwin'], packageTask('darwin', null, { minified: true, stats: true })); -gulp.task('vscode-linux-ia32-min', ['minify-vscode', 'clean-vscode-linux-ia32'], packageTask('linux', 'ia32', { minified: true })); -gulp.task('vscode-linux-x64-min', ['minify-vscode', 'clean-vscode-linux-x64'], packageTask('linux', 'x64', { minified: true })); -gulp.task('vscode-linux-arm-min', ['minify-vscode', 'clean-vscode-linux-arm'], packageTask('linux', 'arm', { minified: true })); -gulp.task('vscode-linux-arm64-min', ['minify-vscode', 'clean-vscode-linux-arm64'], packageTask('linux', 'arm64', { minified: true })); + const vscodeTask = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series( + task.parallel( + minified ? minifyVSCodeTask : optimizeVSCodeTask, + util.rimraf(path.join(buildRoot, destinationFolderName)) + ), + ext.packageExtensionTask('mssql', platform, arch), + ext.packageExtensionTask('azurecore', platform, arch), + packageTask(platform, arch, sourceFolderName, destinationFolderName, opts) + )); + gulp.task(vscodeTask); + }); +}); // Transifex Localizations @@ -515,30 +506,42 @@ const apiHostname = process.env.TRANSIFEX_API_URL; const apiName = process.env.TRANSIFEX_API_NAME; const apiToken = process.env.TRANSIFEX_API_TOKEN; -gulp.task('vscode-translations-push', ['optimize-vscode'], function () { - const pathToMetadata = './out-vscode/nls.metadata.json'; - const pathToExtensions = './extensions/*'; - const pathToSetup = 'build/win32/**/{Default.isl,messages.en.isl}'; +gulp.task(task.define( + 'vscode-translations-push', + task.series( + optimizeVSCodeTask, + function () { + const pathToMetadata = './out-vscode/nls.metadata.json'; + const pathToExtensions = './extensions/*'; + const pathToSetup = 'build/win32/**/{Default.isl,messages.en.isl}'; - return es.merge( - gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()), - gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()), - gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions()) - ).pipe(i18n.findObsoleteResources(apiHostname, apiName, apiToken) - ).pipe(i18n.pushXlfFiles(apiHostname, apiName, apiToken)); -}); + return es.merge( + gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()), + gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()), + gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions()) + ).pipe(i18n.findObsoleteResources(apiHostname, apiName, apiToken) + ).pipe(i18n.pushXlfFiles(apiHostname, apiName, apiToken)); + } + ) +)); -gulp.task('vscode-translations-export', ['optimize-vscode'], function () { - const pathToMetadata = './out-vscode/nls.metadata.json'; - const pathToExtensions = './extensions/*'; - const pathToSetup = 'build/win32/**/{Default.isl,messages.en.isl}'; +gulp.task(task.define( + 'vscode-translations-export', + task.series( + optimizeVSCodeTask, + function () { + const pathToMetadata = './out-vscode/nls.metadata.json'; + const pathToExtensions = './extensions/*'; + const pathToSetup = 'build/win32/**/{Default.isl,messages.en.isl}'; - return es.merge( - gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()), - gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()), - gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions()) - ).pipe(vfs.dest('../vscode-translations-export')); -}); + return es.merge( + gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()), + gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()), + gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions()) + ).pipe(vfs.dest('../vscode-translations-export')); + } + ) +)); gulp.task('vscode-translations-pull', function () { return es.merge([...i18n.defaultLanguages, ...i18n.extraLanguages].map(language => { @@ -559,7 +562,7 @@ gulp.task('vscode-translations-import', function () { // Sourcemaps -gulp.task('upload-vscode-sourcemaps', ['vscode-darwin-min', 'minify-vscode'], () => { +gulp.task('upload-vscode-sourcemaps', () => { const vs = gulp.src('out-vscode-min/**/*.map', { base: 'out-vscode-min' }) .pipe(es.mapSync(f => { f.path = `${f.base}/core/${f.relative}`; @@ -583,57 +586,8 @@ gulp.task('upload-vscode-sourcemaps', ['vscode-darwin-min', 'minify-vscode'], () })); }); -const allConfigDetailsPath = path.join(os.tmpdir(), 'configuration.json'); -gulp.task('upload-vscode-configuration', ['generate-vscode-configuration'], () => { - if (!shouldSetupSettingsSearch()) { - const branch = process.env.BUILD_SOURCEBRANCH; - console.log(`Only runs on master and release branches, not ${branch}`); - return; - } - - if (!fs.existsSync(allConfigDetailsPath)) { - throw new Error(`configuration file at ${allConfigDetailsPath} does not exist`); - } - - const settingsSearchBuildId = getSettingsSearchBuildId(packageJson); - if (!settingsSearchBuildId) { - throw new Error('Failed to compute build number'); - } - - return gulp.src(allConfigDetailsPath) - .pipe(azure.upload({ - account: process.env.AZURE_STORAGE_ACCOUNT, - key: process.env.AZURE_STORAGE_ACCESS_KEY, - container: 'configuration', - prefix: `${settingsSearchBuildId}/${commit}/` - })); -}); - -function shouldSetupSettingsSearch() { - const branch = process.env.BUILD_SOURCEBRANCH; - return branch && (/\/master$/.test(branch) || branch.indexOf('/release/') >= 0); -} - -function getSettingsSearchBuildId(packageJson) { - try { - const branch = process.env.BUILD_SOURCEBRANCH; - const branchId = branch.indexOf('/release/') >= 0 ? 0 : - /\/master$/.test(branch) ? 1 : - 2; // Some unexpected branch - - const out = cp.execSync(`git rev-list HEAD --count`); - const count = parseInt(out.toString()); - - // - // 1.25.1, 1,234,567 commits, master = 1250112345671 - return util.versionStringToNumber(packageJson.version) * 1e8 + count * 10 + branchId; - } catch (e) { - throw new Error('Could not determine build number: ' + e.toString()); - } -} - // This task is only run for the MacOS build -gulp.task('generate-vscode-configuration', () => { +const generateVSCodeConfigurationTask = task.define('generate-vscode-configuration', () => { return new Promise((resolve, reject) => { const buildDir = process.env['AGENT_BUILDDIRECTORY']; if (!buildDir) { @@ -670,6 +624,61 @@ gulp.task('generate-vscode-configuration', () => { }); }); +const allConfigDetailsPath = path.join(os.tmpdir(), 'configuration.json'); +gulp.task(task.define( + 'upload-vscode-configuration', + task.series( + generateVSCodeConfigurationTask, + () => { + if (!shouldSetupSettingsSearch()) { + const branch = process.env.BUILD_SOURCEBRANCH; + console.log(`Only runs on master and release branches, not ${branch}`); + return; + } + + if (!fs.existsSync(allConfigDetailsPath)) { + throw new Error(`configuration file at ${allConfigDetailsPath} does not exist`); + } + + const settingsSearchBuildId = getSettingsSearchBuildId(packageJson); + if (!settingsSearchBuildId) { + throw new Error('Failed to compute build number'); + } + + return gulp.src(allConfigDetailsPath) + .pipe(azure.upload({ + account: process.env.AZURE_STORAGE_ACCOUNT, + key: process.env.AZURE_STORAGE_ACCESS_KEY, + container: 'configuration', + prefix: `${settingsSearchBuildId}/${commit}/` + })); + } + ) +)); + +function shouldSetupSettingsSearch() { + const branch = process.env.BUILD_SOURCEBRANCH; + return branch && (/\/master$/.test(branch) || branch.indexOf('/release/') >= 0); +} + +function getSettingsSearchBuildId(packageJson) { + try { + const branch = process.env.BUILD_SOURCEBRANCH; + const branchId = branch.indexOf('/release/') >= 0 ? 0 : + /\/master$/.test(branch) ? 1 : + 2; // Some unexpected branch + + const out = cp.execSync(`git rev-list HEAD --count`); + const count = parseInt(out.toString()); + + // + // 1.25.1, 1,234,567 commits, master = 1250112345671 + return util.versionStringToNumber(packageJson.version) * 1e8 + count * 10 + branchId; + } catch (e) { + throw new Error('Could not determine build number: ' + e.toString()); + } +} + // {{SQL CARBON EDIT}} // Install service locally before building carbon diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index 822d139093..8ffb63596c 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -12,6 +12,7 @@ const shell = require('gulp-shell'); const es = require('event-stream'); const vfs = require('vinyl-fs'); const util = require('./lib/util'); +const task = require('./lib/task'); const packageJson = require('../package.json'); const product = require('../product.json'); const rpmDependencies = require('../resources/linux/rpm/dependencies.json'); @@ -42,7 +43,7 @@ function prepareDebPackage(arch) { .pipe(replace('@@NAME_LONG@@', product.nameLong)) .pipe(replace('@@NAME_SHORT@@', product.nameShort)) .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(replace('@@ICON@@', product.applicationName)) + .pipe(replace('@@ICON@@', product.linuxIconName)) .pipe(replace('@@URLPROTOCOL@@', product.urlProtocol)); const appdata = gulp.src('resources/linux/code.appdata.xml', { base: '.' }) @@ -52,7 +53,7 @@ function prepareDebPackage(arch) { .pipe(rename('usr/share/appdata/' + product.applicationName + '.appdata.xml')); const icon = gulp.src('resources/linux/code.png', { base: '.' }) - .pipe(rename('usr/share/pixmaps/' + product.applicationName + '.png')); + .pipe(rename('usr/share/pixmaps/' + product.linuxIconName + '.png')); // const bash_completion = gulp.src('resources/completions/bash/code') // .pipe(rename('usr/share/bash-completion/completions/code')); @@ -133,7 +134,7 @@ function prepareRpmPackage(arch) { .pipe(replace('@@NAME_LONG@@', product.nameLong)) .pipe(replace('@@NAME_SHORT@@', product.nameShort)) .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(replace('@@ICON@@', product.applicationName)) + .pipe(replace('@@ICON@@', product.linuxIconName)) .pipe(replace('@@URLPROTOCOL@@', product.urlProtocol)); const appdata = gulp.src('resources/linux/code.appdata.xml', { base: '.' }) @@ -143,7 +144,7 @@ function prepareRpmPackage(arch) { .pipe(rename('usr/share/appdata/' + product.applicationName + '.appdata.xml')); const icon = gulp.src('resources/linux/code.png', { base: '.' }) - .pipe(rename('BUILD/usr/share/pixmaps/' + product.applicationName + '.png')); + .pipe(rename('BUILD/usr/share/pixmaps/' + product.linuxIconName + '.png')); // const bash_completion = gulp.src('resources/completions/bash/code') // .pipe(rename('BUILD/usr/share/bash-completion/completions/code')); @@ -157,6 +158,7 @@ function prepareRpmPackage(arch) { const spec = gulp.src('resources/linux/rpm/code.spec.template', { base: '.' }) .pipe(replace('@@NAME@@', product.applicationName)) .pipe(replace('@@NAME_LONG@@', product.nameLong)) + .pipe(replace('@@ICON@@', product.linuxIconName)) .pipe(replace('@@VERSION@@', packageJson.version)) .pipe(replace('@@RELEASE@@', linuxPackageRevision)) .pipe(replace('@@ARCHITECTURE@@', rpmArch)) @@ -195,7 +197,8 @@ function getSnapBuildPath(arch) { } function prepareSnapPackage(arch) { - const binaryDir = '../VSCode-linux-' + arch; + // {{SQL CARBON EDIT}} + const binaryDir = '../azuredatastudio-linux-' + arch; const destination = getSnapBuildPath(arch); return function () { @@ -203,11 +206,11 @@ function prepareSnapPackage(arch) { .pipe(replace('@@NAME_LONG@@', product.nameLong)) .pipe(replace('@@NAME_SHORT@@', product.nameShort)) .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(replace('@@ICON@@', `/usr/share/pixmaps/${product.applicationName}.png`)) + .pipe(replace('@@ICON@@', `/usr/share/pixmaps/${product.linuxIconName}.png`)) .pipe(rename(`usr/share/applications/${product.applicationName}.desktop`)); const icon = gulp.src('resources/linux/code.png', { base: '.' }) - .pipe(rename(`usr/share/pixmaps/${product.applicationName}.png`)); + .pipe(rename(`usr/share/pixmaps/${product.linuxIconName}.png`)); const code = gulp.src(binaryDir + '/**/*', { base: binaryDir }) .pipe(rename(function (p) { p.dirname = `usr/share/${product.applicationName}/${p.dirname}`; })); @@ -231,116 +234,36 @@ function buildSnapPackage(arch) { return shell.task(`cd ${snapBuildPath} && snapcraft build`); } -function getFlatpakArch(arch) { - return { x64: 'x86_64', ia32: 'i386', arm: 'arm' }[arch]; -} +const BUILD_TARGETS = [ + { arch: 'ia32' }, + { arch: 'x64' }, + { arch: 'arm' }, + { arch: 'arm64' }, +]; -function prepareFlatpak(arch) { - // {{SQL CARBON EDIT}} - const binaryDir = '../azuredatastudio-linux-' + arch; - const flatpakArch = getFlatpakArch(arch); - const destination = '.build/linux/flatpak/' + flatpakArch; +BUILD_TARGETS.forEach((buildTarget) => { + const arch = buildTarget.arch; - return function () { - // This is not imported in the global scope to avoid requiring ImageMagick - // (or GraphicsMagick) when not building building Flatpak bundles. - const imgResize = require('gulp-image-resize'); - - const all = [16, 24, 32, 48, 64, 128, 192, 256, 512].map(function (size) { - return gulp.src('resources/linux/code.png', { base: '.' }) - .pipe(imgResize({ width: size, height: size, format: "png", noProfile: true })) - .pipe(rename('share/icons/hicolor/' + size + 'x' + size + '/apps/' + flatpakManifest.appId + '.png')); - }); - - all.push(gulp.src('resources/linux/code.desktop', { base: '.' }) - .pipe(replace('Exec=/usr/share/@@NAME@@/@@NAME@@', 'Exec=' + product.applicationName)) - .pipe(replace('@@NAME_LONG@@', product.nameLong)) - .pipe(replace('@@NAME_SHORT@@', product.nameShort)) - .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(rename('share/applications/' + flatpakManifest.appId + '.desktop'))); - - all.push(gulp.src('resources/linux/code.appdata.xml', { base: '.' }) - .pipe(replace('@@NAME_LONG@@', product.nameLong)) - .pipe(replace('@@NAME@@', flatpakManifest.appId)) - .pipe(replace('@@LICENSE@@', product.licenseName)) - .pipe(rename('share/appdata/' + flatpakManifest.appId + '.appdata.xml'))); - - all.push(gulp.src(binaryDir + '/**/*', { base: binaryDir }) - .pipe(rename(function (p) { - p.dirname = 'share/' + product.applicationName + '/' + p.dirname; - }))); - - return es.merge(all).pipe(vfs.dest(destination)); - }; -} - -function buildFlatpak(arch) { - const flatpakArch = getFlatpakArch(arch); - const manifest = {}; - for (var k in flatpakManifest) { - manifest[k] = flatpakManifest[k]; + { + const debArch = getDebPackageArch(arch); + const prepareDebTask = task.define(`vscode-linux-${arch}-prepare-deb`, task.series(util.rimraf(`.build/linux/deb/${debArch}`), prepareDebPackage(arch))); + // gulp.task(prepareDebTask); + const buildDebTask = task.define(`vscode-linux-${arch}-build-deb`, task.series(prepareDebTask, buildDebPackage(arch))); + gulp.task(buildDebTask); } - manifest.files = [ - ['.build/linux/flatpak/' + flatpakArch, '/'], - ]; - const buildOptions = { - arch: flatpakArch, - subject: product.nameLong + ' ' + packageJson.version + '.' + linuxPackageRevision, - }; - // If requested, use the configured path for the OSTree repository. - if (process.env.FLATPAK_REPO) { - buildOptions.repoDir = process.env.FLATPAK_REPO; - } else { - buildOptions.bundlePath = manifest.appId + '-' + flatpakArch + '.flatpak'; + + { + const rpmArch = getRpmPackageArch(arch); + const prepareRpmTask = task.define(`vscode-linux-${arch}-prepare-rpm`, task.series(util.rimraf(`.build/linux/rpm/${rpmArch}`), prepareRpmPackage(arch))); + // gulp.task(prepareRpmTask); + const buildRpmTask = task.define(`vscode-linux-${arch}-build-rpm`, task.series(prepareRpmTask, buildRpmPackage(arch))); + gulp.task(buildRpmTask); } - // Setup PGP signing if requested. - if (process.env.GPG_KEY_ID !== undefined) { - buildOptions.gpgSign = process.env.GPG_KEY_ID; - if (process.env.GPG_HOMEDIR) { - buildOptions.gpgHomedir = process.env.GPG_HOME_DIR; - } + + { + const prepareSnapTask = task.define(`vscode-linux-${arch}-prepare-snap`, task.series(util.rimraf(`.build/linux/snap/${arch}`), prepareSnapPackage(arch))); + gulp.task(prepareSnapTask); + const buildSnapTask = task.define(`vscode-linux-${arch}-build-snap`, task.series(prepareSnapTask, buildSnapPackage(arch))); + gulp.task(buildSnapTask); } - return function (cb) { - require('flatpak-bundler').bundle(manifest, buildOptions, cb); - }; -} - -gulp.task('clean-vscode-linux-ia32-deb', util.rimraf('.build/linux/deb/i386')); -gulp.task('clean-vscode-linux-x64-deb', util.rimraf('.build/linux/deb/amd64')); -gulp.task('clean-vscode-linux-arm-deb', util.rimraf('.build/linux/deb/armhf')); -gulp.task('clean-vscode-linux-arm64-deb', util.rimraf('.build/linux/deb/arm64')); -gulp.task('clean-vscode-linux-ia32-rpm', util.rimraf('.build/linux/rpm/i386')); -gulp.task('clean-vscode-linux-x64-rpm', util.rimraf('.build/linux/rpm/x86_64')); -gulp.task('clean-vscode-linux-arm-rpm', util.rimraf('.build/linux/rpm/armhf')); -gulp.task('clean-vscode-linux-arm64-rpm', util.rimraf('.build/linux/rpm/arm64')); -gulp.task('clean-vscode-linux-ia32-snap', util.rimraf('.build/linux/snap/x64')); -gulp.task('clean-vscode-linux-x64-snap', util.rimraf('.build/linux/snap/x64')); -gulp.task('clean-vscode-linux-arm-snap', util.rimraf('.build/linux/snap/x64')); -gulp.task('clean-vscode-linux-arm64-snap', util.rimraf('.build/linux/snap/x64')); - -gulp.task('vscode-linux-ia32-prepare-deb', ['clean-vscode-linux-ia32-deb'], prepareDebPackage('ia32')); -gulp.task('vscode-linux-x64-prepare-deb', ['clean-vscode-linux-x64-deb'], prepareDebPackage('x64')); -gulp.task('vscode-linux-arm-prepare-deb', ['clean-vscode-linux-arm-deb'], prepareDebPackage('arm')); -gulp.task('vscode-linux-arm64-prepare-deb', ['clean-vscode-linux-arm64-deb'], prepareDebPackage('arm64')); -gulp.task('vscode-linux-ia32-build-deb', ['vscode-linux-ia32-prepare-deb'], buildDebPackage('ia32')); -gulp.task('vscode-linux-x64-build-deb', ['vscode-linux-x64-prepare-deb'], buildDebPackage('x64')); -gulp.task('vscode-linux-arm-build-deb', ['vscode-linux-arm-prepare-deb'], buildDebPackage('arm')); -gulp.task('vscode-linux-arm64-build-deb', ['vscode-linux-arm64-prepare-deb'], buildDebPackage('arm64')); - -gulp.task('vscode-linux-ia32-prepare-rpm', ['clean-vscode-linux-ia32-rpm'], prepareRpmPackage('ia32')); -gulp.task('vscode-linux-x64-prepare-rpm', ['clean-vscode-linux-x64-rpm'], prepareRpmPackage('x64')); -gulp.task('vscode-linux-arm-prepare-rpm', ['clean-vscode-linux-arm-rpm'], prepareRpmPackage('arm')); -gulp.task('vscode-linux-arm64-prepare-rpm', ['clean-vscode-linux-arm64-rpm'], prepareRpmPackage('arm64')); -gulp.task('vscode-linux-ia32-build-rpm', ['vscode-linux-ia32-prepare-rpm'], buildRpmPackage('ia32')); -gulp.task('vscode-linux-x64-build-rpm', ['vscode-linux-x64-prepare-rpm'], buildRpmPackage('x64')); -gulp.task('vscode-linux-arm-build-rpm', ['vscode-linux-arm-prepare-rpm'], buildRpmPackage('arm')); -gulp.task('vscode-linux-arm64-build-rpm', ['vscode-linux-arm64-prepare-rpm'], buildRpmPackage('arm64')); - -gulp.task('vscode-linux-ia32-prepare-snap', ['clean-vscode-linux-ia32-snap'], prepareSnapPackage('ia32')); -gulp.task('vscode-linux-x64-prepare-snap', ['clean-vscode-linux-x64-snap'], prepareSnapPackage('x64')); -gulp.task('vscode-linux-arm-prepare-snap', ['clean-vscode-linux-arm-snap'], prepareSnapPackage('arm')); -gulp.task('vscode-linux-arm64-prepare-snap', ['clean-vscode-linux-arm64-snap'], prepareSnapPackage('arm64')); -gulp.task('vscode-linux-ia32-build-snap', ['vscode-linux-ia32-prepare-snap'], buildSnapPackage('ia32')); -gulp.task('vscode-linux-x64-build-snap', ['vscode-linux-x64-prepare-snap'], buildSnapPackage('x64')); -gulp.task('vscode-linux-arm-build-snap', ['vscode-linux-arm-prepare-snap'], buildSnapPackage('arm')); -gulp.task('vscode-linux-arm64-build-snap', ['vscode-linux-arm64-prepare-snap'], buildSnapPackage('arm64')); +}); diff --git a/build/gulpfile.vscode.win32.js b/build/gulpfile.vscode.win32.js index 1d59abd332..a311e2cd21 100644 --- a/build/gulpfile.vscode.win32.js +++ b/build/gulpfile.vscode.win32.js @@ -12,6 +12,7 @@ const assert = require('assert'); const cp = require('child_process'); const _7z = require('7zip')['7z']; const util = require('./lib/util'); +const task = require('./lib/task'); const pkg = require('../package.json'); const product = require('../product.json'); const vfs = require('vinyl-fs'); @@ -107,8 +108,8 @@ function buildWin32Setup(arch, target) { } function defineWin32SetupTasks(arch, target) { - gulp.task(`clean-vscode-win32-${arch}-${target}-setup`, util.rimraf(setupDir(arch, target))); - gulp.task(`vscode-win32-${arch}-${target}-setup`, [`clean-vscode-win32-${arch}-${target}-setup`], buildWin32Setup(arch, target)); + const cleanTask = util.rimraf(setupDir(arch, target)); + gulp.task(task.define(`vscode-win32-${arch}-${target}-setup`, task.series(cleanTask, buildWin32Setup(arch, target)))); } defineWin32SetupTasks('ia32', 'system'); @@ -126,11 +127,8 @@ function archiveWin32Setup(arch) { }; } -gulp.task('clean-vscode-win32-ia32-archive', util.rimraf(zipDir('ia32'))); -gulp.task('vscode-win32-ia32-archive', ['clean-vscode-win32-ia32-archive'], archiveWin32Setup('ia32')); - -gulp.task('clean-vscode-win32-x64-archive', util.rimraf(zipDir('x64'))); -gulp.task('vscode-win32-x64-archive', ['clean-vscode-win32-x64-archive'], archiveWin32Setup('x64')); +gulp.task(task.define('vscode-win32-ia32-archive', task.series(util.rimraf(zipDir('ia32')), archiveWin32Setup('ia32')))); +gulp.task(task.define('vscode-win32-x64-archive', task.series(util.rimraf(zipDir('x64')), archiveWin32Setup('x64')))); function copyInnoUpdater(arch) { return () => { @@ -139,9 +137,6 @@ function copyInnoUpdater(arch) { }; } -gulp.task('vscode-win32-ia32-copy-inno-updater', copyInnoUpdater('ia32')); -gulp.task('vscode-win32-x64-copy-inno-updater', copyInnoUpdater('x64')); - function patchInnoUpdater(arch) { return cb => { const icon = path.join(repoPath, 'resources', 'win32', 'code.ico'); @@ -149,5 +144,5 @@ function patchInnoUpdater(arch) { }; } -gulp.task('vscode-win32-ia32-inno-updater', ['vscode-win32-ia32-copy-inno-updater'], patchInnoUpdater('ia32')); -gulp.task('vscode-win32-x64-inno-updater', ['vscode-win32-x64-copy-inno-updater'], patchInnoUpdater('x64')); \ No newline at end of file +gulp.task(task.define('vscode-win32-ia32-inno-updater', task.series(copyInnoUpdater('ia32'), patchInnoUpdater('ia32')))); +gulp.task(task.define('vscode-win32-x64-inno-updater', task.series(copyInnoUpdater('x64'), patchInnoUpdater('x64')))); \ No newline at end of file diff --git a/build/lib/builtInExtensions.js b/build/lib/builtInExtensions.js index e90b6ffd9c..72fa076f81 100644 --- a/build/lib/builtInExtensions.js +++ b/build/lib/builtInExtensions.js @@ -14,7 +14,8 @@ const es = require('event-stream'); const rename = require('gulp-rename'); const vfs = require('vinyl-fs'); const ext = require('./extensions'); -const util = require('gulp-util'); +const fancyLog = require('fancy-log'); +const ansiColors = require('ansi-colors'); const root = path.dirname(path.dirname(__dirname)); const builtInExtensions = require('../builtInExtensions.json'); @@ -43,7 +44,7 @@ function isUpToDate(extension) { function syncMarketplaceExtension(extension) { if (isUpToDate(extension)) { - util.log(util.colors.blue('[marketplace]'), `${extension.name}@${extension.version}`, util.colors.green('✔︎')); + fancyLog(ansiColors.blue('[marketplace]'), `${extension.name}@${extension.version}`, ansiColors.green('✔︎')); return es.readArray([]); } @@ -52,13 +53,13 @@ function syncMarketplaceExtension(extension) { return ext.fromMarketplace(extension.name, extension.version, extension.metadata) .pipe(rename(p => p.dirname = `${extension.name}/${p.dirname}`)) .pipe(vfs.dest('.build/builtInExtensions')) - .on('end', () => util.log(util.colors.blue('[marketplace]'), extension.name, util.colors.green('✔︎'))); + .on('end', () => fancyLog(ansiColors.blue('[marketplace]'), extension.name, ansiColors.green('✔︎'))); } function syncExtension(extension, controlState) { switch (controlState) { case 'disabled': - util.log(util.colors.blue('[disabled]'), util.colors.gray(extension.name)); + fancyLog(ansiColors.blue('[disabled]'), ansiColors.gray(extension.name)); return es.readArray([]); case 'marketplace': @@ -66,15 +67,15 @@ function syncExtension(extension, controlState) { default: if (!fs.existsSync(controlState)) { - util.log(util.colors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but that path does not exist.`)); + fancyLog(ansiColors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but that path does not exist.`)); return es.readArray([]); } else if (!fs.existsSync(path.join(controlState, 'package.json'))) { - util.log(util.colors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but there is no 'package.json' file in that directory.`)); + fancyLog(ansiColors.red(`Error: Built-in extension '${extension.name}' is configured to run from '${controlState}' but there is no 'package.json' file in that directory.`)); return es.readArray([]); } - util.log(util.colors.blue('[local]'), `${extension.name}: ${util.colors.cyan(controlState)}`, util.colors.green('✔︎')); + fancyLog(ansiColors.blue('[local]'), `${extension.name}: ${ansiColors.cyan(controlState)}`, ansiColors.green('✔︎')); return es.readArray([]); } } @@ -93,8 +94,8 @@ function writeControlFile(control) { } function main() { - util.log('Syncronizing built-in extensions...'); - util.log(`You can manage built-in extensions with the ${util.colors.cyan('--builtin')} flag`); + fancyLog('Syncronizing built-in extensions...'); + fancyLog(`You can manage built-in extensions with the ${ansiColors.cyan('--builtin')} flag`); const control = readControlFile(); const streams = []; diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 66173d52cc..1c9964bc2c 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -16,7 +16,8 @@ const monacodts = require("../monaco/api"); const nls = require("./nls"); const reporter_1 = require("./reporter"); const util = require("./util"); -const util2 = require("gulp-util"); +const fancyLog = require("fancy-log"); +const ansiColors = require("ansi-colors"); const watch = require('./watch'); const reporter = reporter_1.createReporter(); function getTypeScriptCompilerOptions(src) { @@ -179,7 +180,7 @@ class MonacoGenerator { return r; } _log(message, ...rest) { - util2.log(util2.colors.cyan('[monaco.d.ts]'), message, ...rest); + fancyLog(ansiColors.cyan('[monaco.d.ts]'), message, ...rest); } execute() { const startTime = Date.now(); diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index 189ef7afdf..81a42c3cab 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -17,7 +17,9 @@ import * as monacodts from '../monaco/api'; import * as nls from './nls'; import { createReporter } from './reporter'; import * as util from './util'; -import * as util2 from 'gulp-util'; +import * as fancyLog from 'fancy-log'; +import * as ansiColors from 'ansi-colors'; + const watch = require('./watch'); const reporter = createReporter(); @@ -218,7 +220,7 @@ class MonacoGenerator { } private _log(message: any, ...rest: any[]): void { - util2.log(util2.colors.cyan('[monaco.d.ts]'), message, ...rest); + fancyLog(ansiColors.cyan('[monaco.d.ts]'), message, ...rest); } public execute(): void { diff --git a/build/lib/extensions.js b/build/lib/extensions.js index 7506286ab6..6f0c7ace5e 100644 --- a/build/lib/extensions.js +++ b/build/lib/extensions.js @@ -17,7 +17,8 @@ const remote = require("gulp-remote-src"); const vzip = require('gulp-vinyl-zip'); const filter = require("gulp-filter"); const rename = require("gulp-rename"); -const util = require('gulp-util'); +const fancyLog = require("fancy-log"); +const ansiColors = require("ansi-colors"); const buffer = require('gulp-buffer'); const json = require("gulp-json-editor"); const webpack = require('webpack'); @@ -136,7 +137,7 @@ function fromLocalWebpack(extensionPath, sourceMappingURLBase) { .pipe(packageJsonFilter.restore); const webpackStreams = webpackConfigLocations.map(webpackConfigPath => () => { const webpackDone = (err, stats) => { - util.log(`Bundled extension: ${util.colors.yellow(path.join(path.basename(extensionPath), path.relative(extensionPath, webpackConfigPath)))}...`); + fancyLog(`Bundled extension: ${ansiColors.yellow(path.join(path.basename(extensionPath), path.relative(extensionPath, webpackConfigPath)))}...`); if (err) { result.emit('error', err); } @@ -214,7 +215,7 @@ const baseHeaders = { function fromMarketplace(extensionName, version, metadata) { const [publisher, name] = extensionName.split('.'); const url = `https://marketplace.visualstudio.com/_apis/public/gallery/publishers/${publisher}/vsextensions/${name}/${version}/vspackage`; - util.log('Downloading extension:', util.colors.yellow(`${extensionName}@${version}`), '...'); + fancyLog('Downloading extension:', ansiColors.yellow(`${extensionName}@${version}`), '...'); const options = { base: url, requestOptions: { diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts index f8312f1a2b..2e8d9ae15f 100644 --- a/build/lib/extensions.ts +++ b/build/lib/extensions.ts @@ -17,7 +17,8 @@ import remote = require('gulp-remote-src'); const vzip = require('gulp-vinyl-zip'); import filter = require('gulp-filter'); import rename = require('gulp-rename'); -const util = require('gulp-util'); +import * as fancyLog from 'fancy-log'; +import * as ansiColors from 'ansi-colors'; const buffer = require('gulp-buffer'); import json = require('gulp-json-editor'); const webpack = require('webpack'); @@ -155,7 +156,7 @@ function fromLocalWebpack(extensionPath: string, sourceMappingURLBase: string | const webpackStreams = webpackConfigLocations.map(webpackConfigPath => () => { const webpackDone = (err: any, stats: any) => { - util.log(`Bundled extension: ${util.colors.yellow(path.join(path.basename(extensionPath), path.relative(extensionPath, webpackConfigPath)))}...`); + fancyLog(`Bundled extension: ${ansiColors.yellow(path.join(path.basename(extensionPath), path.relative(extensionPath, webpackConfigPath)))}...`); if (err) { result.emit('error', err); } @@ -249,7 +250,7 @@ export function fromMarketplace(extensionName: string, version: string, metadata const [publisher, name] = extensionName.split('.'); const url = `https://marketplace.visualstudio.com/_apis/public/gallery/publishers/${publisher}/vsextensions/${name}/${version}/vspackage`; - util.log('Downloading extension:', util.colors.yellow(`${extensionName}@${version}`), '...'); + fancyLog('Downloading extension:', ansiColors.yellow(`${extensionName}@${version}`), '...'); const options = { base: url, diff --git a/build/lib/i18n.js b/build/lib/i18n.js index 0d3e7e40a0..f263e0bc92 100644 --- a/build/lib/i18n.js +++ b/build/lib/i18n.js @@ -13,11 +13,12 @@ const xml2js = require("xml2js"); const glob = require("glob"); const https = require("https"); const gulp = require("gulp"); -const util = require("gulp-util"); +const fancyLog = require("fancy-log"); +const ansiColors = require("ansi-colors"); const iconv = require("iconv-lite"); const NUMBER_OF_CONCURRENT_DOWNLOADS = 4; function log(message, ...rest) { - util.log(util.colors.green('[i18n]'), message, ...rest); + fancyLog(ansiColors.green('[i18n]'), message, ...rest); } exports.defaultLanguages = [ { id: 'zh-tw', folderName: 'cht', transifexId: 'zh-hant' }, @@ -490,7 +491,7 @@ function getResource(sourceFile) { else if (/^vs\/code/.test(sourceFile)) { return { name: 'vs/code', project: workbenchProject }; } - else if (/^vs\/workbench\/parts/.test(sourceFile)) { + else if (/^vs\/workbench\/contrib/.test(sourceFile)) { resource = sourceFile.split('/', 4).join('/'); return { name: resource, project: workbenchProject }; } @@ -578,7 +579,7 @@ function createXlfFilesForExtensions() { } return _xlf; } - gulp.src([`./extensions/${extensionName}/package.nls.json`, `./extensions/${extensionName}/**/nls.metadata.json`]).pipe(event_stream_1.through(function (file) { + gulp.src([`./extensions/${extensionName}/package.nls.json`, `./extensions/${extensionName}/**/nls.metadata.json`], { allowEmpty: true }).pipe(event_stream_1.through(function (file) { if (file.isBuffer()) { const buffer = file.contents; const basename = path.basename(file.path); @@ -1038,7 +1039,7 @@ function prepareI18nPackFiles(externalExtensions, resultingTranslationPaths, pse let extensionsPacks = {}; let errors = []; return event_stream_1.through(function (xlf) { - let project = path.dirname(xlf.relative); + let project = path.basename(path.dirname(xlf.relative)); let resource = path.basename(xlf.relative, '.xlf'); let contents = xlf.contents.toString(); let parsePromise = pseudo ? XLF.parsePseudo(contents) : XLF.parse(contents); diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index f8a57d73f6..424fd364c5 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -27,135 +27,151 @@ "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/cli", + "name": "vs/workbench/api/common", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/codeEditor", + "name": "vs/workbench/contrib/cli", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/comments", + "name": "vs/workbench/contrib/codeEditor", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/debug", + "name": "vs/workbench/contrib/codeinset", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/emmet", + "name": "vs/workbench/contrib/comments", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/execution", + "name": "vs/workbench/contrib/debug", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/extensions", + "name": "vs/workbench/contrib/emmet", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/feedback", + "name": "vs/workbench/contrib/extensions", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/files", + "name": "vs/workbench/contrib/externalTerminal", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/html", + "name": "vs/workbench/contrib/feedback", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/markers", + "name": "vs/workbench/contrib/files", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/localizations", + "name": "vs/workbench/contrib/html", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/logs", + "name": "vs/workbench/contrib/issue", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/output", + "name": "vs/workbench/contrib/markers", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/performance", + "name": "vs/workbench/contrib/localizations", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/preferences", + "name": "vs/workbench/contrib/logs", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/quickopen", + "name": "vs/workbench/contrib/output", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/relauncher", + "name": "vs/workbench/contrib/performance", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/scm", + "name": "vs/workbench/contrib/preferences", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/search", + "name": "vs/workbench/contrib/quickopen", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/snippets", + "name": "vs/workbench/contrib/relauncher", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/stats", + "name": "vs/workbench/contrib/scm", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/surveys", + "name": "vs/workbench/contrib/search", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/tasks", + "name": "vs/workbench/contrib/snippets", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/terminal", + "name": "vs/workbench/contrib/format", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/themes", + "name": "vs/workbench/contrib/stats", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/trust", + "name": "vs/workbench/contrib/surveys", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/update", + "name": "vs/workbench/contrib/tasks", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/url", + "name": "vs/workbench/contrib/terminal", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/watermark", + "name": "vs/workbench/contrib/themes", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/webview", + "name": "vs/workbench/contrib/trust", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/welcome", + "name": "vs/workbench/contrib/update", "project": "vscode-workbench" }, { - "name": "vs/workbench/parts/outline", + "name": "vs/workbench/contrib/url", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/watermark", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/webview", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/welcome", + "project": "vscode-workbench" + }, + { + "name": "vs/workbench/contrib/outline", "project": "vscode-workbench" }, { @@ -195,13 +211,17 @@ "project": "vscode-workbench" }, { - "name": "vs/workbench/services/jsonschemas", + "name": "vs/workbench/services/extensionManagement", "project": "vscode-workbench" }, { "name": "vs/workbench/services/files", "project": "vscode-workbench" }, + { + "name": "vs/workbench/services/integrity", + "project": "vscode-workbench" + }, { "name": "vs/workbench/services/keybinding", "project": "vscode-workbench" diff --git a/build/lib/i18n.ts b/build/lib/i18n.ts index 83a2fa7c42..05981eed84 100644 --- a/build/lib/i18n.ts +++ b/build/lib/i18n.ts @@ -13,14 +13,14 @@ import * as xml2js from 'xml2js'; import * as glob from 'glob'; import * as https from 'https'; import * as gulp from 'gulp'; - -import * as util from 'gulp-util'; +import * as fancyLog from 'fancy-log'; +import * as ansiColors from 'ansi-colors'; import * as iconv from 'iconv-lite'; const NUMBER_OF_CONCURRENT_DOWNLOADS = 4; function log(message: any, ...rest: any[]): void { - util.log(util.colors.green('[i18n]'), message, ...rest); + fancyLog(ansiColors.green('[i18n]'), message, ...rest); } export interface Language { @@ -605,7 +605,7 @@ export function getResource(sourceFile: string): Resource { return { name: 'vs/base', project: editorProject }; } else if (/^vs\/code/.test(sourceFile)) { return { name: 'vs/code', project: workbenchProject }; - } else if (/^vs\/workbench\/parts/.test(sourceFile)) { + } else if (/^vs\/workbench\/contrib/.test(sourceFile)) { resource = sourceFile.split('/', 4).join('/'); return { name: resource, project: workbenchProject }; } else if (/^vs\/workbench\/services/.test(sourceFile)) { @@ -692,7 +692,7 @@ export function createXlfFilesForExtensions(): ThroughStream { } return _xlf; } - gulp.src([`./extensions/${extensionName}/package.nls.json`, `./extensions/${extensionName}/**/nls.metadata.json`]).pipe(through(function (file: File) { + gulp.src([`./extensions/${extensionName}/package.nls.json`, `./extensions/${extensionName}/**/nls.metadata.json`], { allowEmpty: true }).pipe(through(function (file: File) { if (file.isBuffer()) { const buffer: Buffer = file.contents as Buffer; const basename = path.basename(file.path); @@ -1194,7 +1194,7 @@ export function prepareI18nPackFiles(externalExtensions: Map, resultingT let extensionsPacks: Map = {}; let errors: any[] = []; return through(function (this: ThroughStream, xlf: File) { - let project = path.dirname(xlf.relative); + let project = path.basename(path.dirname(xlf.relative)); let resource = path.basename(xlf.relative, '.xlf'); let contents = xlf.contents.toString(); let parsePromise = pseudo ? XLF.parsePseudo(contents) : XLF.parse(contents); diff --git a/build/lib/optimize.js b/build/lib/optimize.js index ee1cc429ab..71577f4e63 100644 --- a/build/lib/optimize.js +++ b/build/lib/optimize.js @@ -13,7 +13,8 @@ const flatmap = require("gulp-flatmap"); const sourcemaps = require("gulp-sourcemaps"); const uglify = require("gulp-uglify"); const composer = require("gulp-uglify/composer"); -const gulpUtil = require("gulp-util"); +const fancyLog = require("fancy-log"); +const ansiColors = require("ansi-colors"); const path = require("path"); const pump = require("pump"); const uglifyes = require("uglify-es"); @@ -24,7 +25,7 @@ const stats_1 = require("./stats"); const util = require("./util"); const REPO_ROOT_PATH = path.join(__dirname, '../..'); function log(prefix, message) { - gulpUtil.log(gulpUtil.colors.cyan('[' + prefix + ']'), message); + fancyLog(ansiColors.cyan('[' + prefix + ']'), message); } // {{SQL CARBON EDIT}} function loaderConfig(emptyPaths) { @@ -115,7 +116,6 @@ function toBundleStream(src, bundledFileHeader, bundles) { function optimizeTask(opts) { const src = opts.src; const entryPoints = opts.entryPoints; - const otherSources = opts.otherSources; const resources = opts.resources; const loaderConfig = opts.loaderConfig; const bundledFileHeader = opts.header; @@ -138,7 +138,7 @@ function optimizeTask(opts) { } filteredResources.push('!' + resource); }); - gulp.src(filteredResources, { base: `${src}` }).pipe(resourcesStream); + gulp.src(filteredResources, { base: `${src}`, allowEmpty: true }).pipe(resourcesStream); const bundleInfoArray = []; if (opts.bundleInfo) { bundleInfoArray.push(new VinylFile({ @@ -149,20 +149,7 @@ function optimizeTask(opts) { } es.readArray(bundleInfoArray).pipe(bundleInfoStream); }); - const otherSourcesStream = es.through(); - const otherSourcesStreamArr = []; - gulp.src(otherSources, { base: `${src}` }) - .pipe(es.through(function (data) { - otherSourcesStreamArr.push(toConcatStream(src, bundledFileHeader, [data], data.relative)); - }, function () { - if (!otherSourcesStreamArr.length) { - setTimeout(function () { otherSourcesStream.emit('end'); }, 0); - } - else { - es.merge(otherSourcesStreamArr).pipe(otherSourcesStream); - } - })); - const result = es.merge(loader(src, bundledFileHeader, bundleLoader), bundlesStream, otherSourcesStream, resourcesStream, bundleInfoStream); + const result = es.merge(loader(src, bundledFileHeader, bundleLoader), bundlesStream, resourcesStream, bundleInfoStream); return result .pipe(sourcemaps.write('./', { sourceRoot: undefined, @@ -225,7 +212,12 @@ function minifyTask(src, sourceMapBaseUrl) { return cb => { const jsFilter = filter('**/*.js', { restore: true }); const cssFilter = filter('**/*.css', { restore: true }); - pump(gulp.src([src + '/**', '!' + src + '/**/*.map']), jsFilter, sourcemaps.init({ loadMaps: true }), uglifyWithCopyrights(), jsFilter.restore, cssFilter, minifyCSS({ reduceIdents: false }), cssFilter.restore, sourcemaps.write('./', { + pump(gulp.src([src + '/**', '!' + src + '/**/*.map']), jsFilter, sourcemaps.init({ loadMaps: true }), uglifyWithCopyrights(), jsFilter.restore, cssFilter, minifyCSS({ reduceIdents: false }), cssFilter.restore, sourcemaps.mapSources((sourcePath) => { + if (sourcePath === 'bootstrap-fork.js') { + return 'bootstrap-fork.orig.js'; + } + return sourcePath; + }), sourcemaps.write('./', { sourceMappingURL, sourceRoot: undefined, includeContent: true, diff --git a/build/lib/optimize.ts b/build/lib/optimize.ts index a725039c6c..1d67928e87 100644 --- a/build/lib/optimize.ts +++ b/build/lib/optimize.ts @@ -14,7 +14,8 @@ import * as flatmap from 'gulp-flatmap'; import * as sourcemaps from 'gulp-sourcemaps'; import * as uglify from 'gulp-uglify'; import * as composer from 'gulp-uglify/composer'; -import * as gulpUtil from 'gulp-util'; +import * as fancyLog from 'fancy-log'; +import * as ansiColors from 'ansi-colors'; import * as path from 'path'; import * as pump from 'pump'; import * as sm from 'source-map'; @@ -28,7 +29,7 @@ import * as util from './util'; const REPO_ROOT_PATH = path.join(__dirname, '../..'); function log(prefix: string, message: string): void { - gulpUtil.log(gulpUtil.colors.cyan('[' + prefix + ']'), message); + fancyLog(ansiColors.cyan('[' + prefix + ']'), message); } // {{SQL CARBON EDIT}} @@ -143,10 +144,6 @@ export interface IOptimizeTaskOpts { * (for AMD files, will get bundled and get Copyright treatment) */ entryPoints: bundle.IEntryPoint[]; - /** - * (for non-AMD files that should get Copyright treatment) - */ - otherSources: string[]; /** * (svg, etc.) */ @@ -177,7 +174,6 @@ export interface IOptimizeTaskOpts { export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStream { const src = opts.src; const entryPoints = opts.entryPoints; - const otherSources = opts.otherSources; const resources = opts.resources; const loaderConfig = opts.loaderConfig; const bundledFileHeader = opts.header; @@ -202,7 +198,7 @@ export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStr } filteredResources.push('!' + resource); }); - gulp.src(filteredResources, { base: `${src}` }).pipe(resourcesStream); + gulp.src(filteredResources, { base: `${src}`, allowEmpty: true }).pipe(resourcesStream); const bundleInfoArray: VinylFile[] = []; if (opts.bundleInfo) { @@ -215,24 +211,9 @@ export function optimizeTask(opts: IOptimizeTaskOpts): () => NodeJS.ReadWriteStr es.readArray(bundleInfoArray).pipe(bundleInfoStream); }); - const otherSourcesStream = es.through(); - const otherSourcesStreamArr: NodeJS.ReadWriteStream[] = []; - - gulp.src(otherSources, { base: `${src}` }) - .pipe(es.through(function (data) { - otherSourcesStreamArr.push(toConcatStream(src, bundledFileHeader, [data], data.relative)); - }, function () { - if (!otherSourcesStreamArr.length) { - setTimeout(function () { otherSourcesStream.emit('end'); }, 0); - } else { - es.merge(otherSourcesStreamArr).pipe(otherSourcesStream); - } - })); - const result = es.merge( loader(src, bundledFileHeader, bundleLoader), bundlesStream, - otherSourcesStream, resourcesStream, bundleInfoStream ); @@ -319,6 +300,13 @@ export function minifyTask(src: string, sourceMapBaseUrl?: string): (cb: any) => cssFilter, minifyCSS({ reduceIdents: false }), cssFilter.restore, + (sourcemaps).mapSources((sourcePath: string) => { + if (sourcePath === 'bootstrap-fork.js') { + return 'bootstrap-fork.orig.js'; + } + + return sourcePath; + }), sourcemaps.write('./', { sourceMappingURL, sourceRoot: undefined, diff --git a/build/lib/reporter.js b/build/lib/reporter.js index 4994ff3cd3..72793a3edb 100644 --- a/build/lib/reporter.js +++ b/build/lib/reporter.js @@ -6,7 +6,8 @@ Object.defineProperty(exports, "__esModule", { value: true }); const es = require("event-stream"); const _ = require("underscore"); -const util = require("gulp-util"); +const fancyLog = require("fancy-log"); +const ansiColors = require("ansi-colors"); const fs = require("fs"); const path = require("path"); const allErrors = []; @@ -17,7 +18,7 @@ function onStart() { return; } startTime = new Date().getTime(); - util.log(`Starting ${util.colors.green('compilation')}...`); + fancyLog(`Starting ${ansiColors.green('compilation')}...`); } function onEnd() { if (--count > 0) { @@ -38,7 +39,7 @@ function log() { errors.map(err => { if (!seen.has(err)) { seen.add(err); - util.log(`${util.colors.red('Error')}: ${err}`); + fancyLog(`${ansiColors.red('Error')}: ${err}`); } }); const regex = /^([^(]+)\((\d+),(\d+)\): (.*)$/; @@ -53,7 +54,7 @@ function log() { catch (err) { //noop } - util.log(`Finished ${util.colors.green('compilation')} with ${errors.length} errors after ${util.colors.magenta((new Date().getTime() - startTime) + ' ms')}`); + fancyLog(`Finished ${ansiColors.green('compilation')} with ${errors.length} errors after ${ansiColors.magenta((new Date().getTime() - startTime) + ' ms')}`); } function createReporter() { const errors = []; diff --git a/build/lib/reporter.ts b/build/lib/reporter.ts index dd9bf7ec92..fe7750fa52 100644 --- a/build/lib/reporter.ts +++ b/build/lib/reporter.ts @@ -7,7 +7,8 @@ import * as es from 'event-stream'; import * as _ from 'underscore'; -import * as util from 'gulp-util'; +import * as fancyLog from 'fancy-log'; +import * as ansiColors from 'ansi-colors'; import * as fs from 'fs'; import * as path from 'path'; @@ -21,7 +22,7 @@ function onStart(): void { } startTime = new Date().getTime(); - util.log(`Starting ${util.colors.green('compilation')}...`); + fancyLog(`Starting ${ansiColors.green('compilation')}...`); } function onEnd(): void { @@ -47,7 +48,7 @@ function log(): void { errors.map(err => { if (!seen.has(err)) { seen.add(err); - util.log(`${util.colors.red('Error')}: ${err}`); + fancyLog(`${ansiColors.red('Error')}: ${err}`); } }); @@ -65,7 +66,7 @@ function log(): void { //noop } - util.log(`Finished ${util.colors.green('compilation')} with ${errors.length} errors after ${util.colors.magenta((new Date().getTime() - startTime!) + ' ms')}`); + fancyLog(`Finished ${ansiColors.green('compilation')} with ${errors.length} errors after ${ansiColors.magenta((new Date().getTime() - startTime!) + ' ms')}`); } export interface IReporter { diff --git a/build/lib/standalone.js b/build/lib/standalone.js index 7f2fa42978..f510277342 100644 --- a/build/lib/standalone.js +++ b/build/lib/standalone.js @@ -27,7 +27,7 @@ function writeFile(filePath, contents) { fs.writeFileSync(filePath, contents); } function extractEditor(options) { - const tsConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.json')).toString()); + const tsConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.monaco.json')).toString()); let compilerOptions; if (tsConfig.extends) { compilerOptions = Object.assign({}, require(path.join(options.sourcesRoot, tsConfig.extends)).compilerOptions, tsConfig.compilerOptions); @@ -36,13 +36,11 @@ function extractEditor(options) { compilerOptions = tsConfig.compilerOptions; } tsConfig.compilerOptions = compilerOptions; + compilerOptions.noEmit = false; compilerOptions.noUnusedLocals = false; compilerOptions.preserveConstEnums = false; compilerOptions.declaration = false; compilerOptions.moduleResolution = ts.ModuleResolutionKind.Classic; - delete compilerOptions.types; - delete tsConfig.extends; - tsConfig.exclude = []; options.compilerOptions = compilerOptions; let result = tss.shake(options); for (let fileName in result) { diff --git a/build/lib/standalone.ts b/build/lib/standalone.ts index e8b096cb73..f3de328c63 100644 --- a/build/lib/standalone.ts +++ b/build/lib/standalone.ts @@ -31,7 +31,7 @@ function writeFile(filePath: string, contents: Buffer | string): void { } export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: string }): void { - const tsConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.json')).toString()); + const tsConfig = JSON.parse(fs.readFileSync(path.join(options.sourcesRoot, 'tsconfig.monaco.json')).toString()); let compilerOptions: { [key: string]: any }; if (tsConfig.extends) { compilerOptions = Object.assign({}, require(path.join(options.sourcesRoot, tsConfig.extends)).compilerOptions, tsConfig.compilerOptions); @@ -40,14 +40,12 @@ export function extractEditor(options: tss.ITreeShakingOptions & { destRoot: str } tsConfig.compilerOptions = compilerOptions; + compilerOptions.noEmit = false; compilerOptions.noUnusedLocals = false; compilerOptions.preserveConstEnums = false; compilerOptions.declaration = false; compilerOptions.moduleResolution = ts.ModuleResolutionKind.Classic; - delete compilerOptions.types; - delete tsConfig.extends; - tsConfig.exclude = []; options.compilerOptions = compilerOptions; diff --git a/build/lib/stats.js b/build/lib/stats.js index ec40f79029..5965206eb1 100644 --- a/build/lib/stats.js +++ b/build/lib/stats.js @@ -5,7 +5,8 @@ 'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); const es = require("event-stream"); -const util = require("gulp-util"); +const fancyLog = require("fancy-log"); +const ansiColors = require("ansi-colors"); const appInsights = require("applicationinsights"); class Entry { constructor(name, totalCount, totalSize) { @@ -24,13 +25,13 @@ class Entry { } else { if (this.totalCount === 1) { - return `Stats for '${util.colors.grey(this.name)}': ${Math.round(this.totalSize / 1204)}KB`; + return `Stats for '${ansiColors.grey(this.name)}': ${Math.round(this.totalSize / 1204)}KB`; } else { const count = this.totalCount < 100 - ? util.colors.green(this.totalCount.toString()) - : util.colors.red(this.totalCount.toString()); - return `Stats for '${util.colors.grey(this.name)}': ${count} files, ${Math.round(this.totalSize / 1204)}KB`; + ? ansiColors.green(this.totalCount.toString()) + : ansiColors.red(this.totalCount.toString()); + return `Stats for '${ansiColors.grey(this.name)}': ${count} files, ${Math.round(this.totalSize / 1204)}KB`; } } } @@ -57,13 +58,13 @@ function createStatsStream(group, log) { }, function () { if (log) { if (entry.totalCount === 1) { - util.log(`Stats for '${util.colors.grey(entry.name)}': ${Math.round(entry.totalSize / 1204)}KB`); + fancyLog(`Stats for '${ansiColors.grey(entry.name)}': ${Math.round(entry.totalSize / 1204)}KB`); } else { const count = entry.totalCount < 100 - ? util.colors.green(entry.totalCount.toString()) - : util.colors.red(entry.totalCount.toString()); - util.log(`Stats for '${util.colors.grey(entry.name)}': ${count} files, ${Math.round(entry.totalSize / 1204)}KB`); + ? ansiColors.green(entry.totalCount.toString()) + : ansiColors.red(entry.totalCount.toString()); + fancyLog(`Stats for '${ansiColors.grey(entry.name)}': ${count} files, ${Math.round(entry.totalSize / 1204)}KB`); } } this.emit('end'); diff --git a/build/lib/stats.ts b/build/lib/stats.ts index 26041359ef..b365ecad75 100644 --- a/build/lib/stats.ts +++ b/build/lib/stats.ts @@ -6,7 +6,8 @@ 'use strict'; import * as es from 'event-stream'; -import * as util from 'gulp-util'; +import * as fancyLog from 'fancy-log'; +import * as ansiColors from 'ansi-colors'; import * as File from 'vinyl'; import * as appInsights from 'applicationinsights'; @@ -22,14 +23,14 @@ class Entry { } } else { if (this.totalCount === 1) { - return `Stats for '${util.colors.grey(this.name)}': ${Math.round(this.totalSize / 1204)}KB`; + return `Stats for '${ansiColors.grey(this.name)}': ${Math.round(this.totalSize / 1204)}KB`; } else { const count = this.totalCount < 100 - ? util.colors.green(this.totalCount.toString()) - : util.colors.red(this.totalCount.toString()); + ? ansiColors.green(this.totalCount.toString()) + : ansiColors.red(this.totalCount.toString()); - return `Stats for '${util.colors.grey(this.name)}': ${count} files, ${Math.round(this.totalSize / 1204)}KB`; + return `Stats for '${ansiColors.grey(this.name)}': ${count} files, ${Math.round(this.totalSize / 1204)}KB`; } } } @@ -58,14 +59,14 @@ export function createStatsStream(group: string, log?: boolean): es.ThroughStrea }, function () { if (log) { if (entry.totalCount === 1) { - util.log(`Stats for '${util.colors.grey(entry.name)}': ${Math.round(entry.totalSize / 1204)}KB`); + fancyLog(`Stats for '${ansiColors.grey(entry.name)}': ${Math.round(entry.totalSize / 1204)}KB`); } else { const count = entry.totalCount < 100 - ? util.colors.green(entry.totalCount.toString()) - : util.colors.red(entry.totalCount.toString()); + ? ansiColors.green(entry.totalCount.toString()) + : ansiColors.red(entry.totalCount.toString()); - util.log(`Stats for '${util.colors.grey(entry.name)}': ${count} files, ${Math.round(entry.totalSize / 1204)}KB`); + fancyLog(`Stats for '${ansiColors.grey(entry.name)}': ${count} files, ${Math.round(entry.totalSize / 1204)}KB`); } } diff --git a/build/lib/task.js b/build/lib/task.js new file mode 100644 index 0000000000..4c84bc7d6c --- /dev/null +++ b/build/lib/task.js @@ -0,0 +1,96 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; +Object.defineProperty(exports, "__esModule", { value: true }); +const fancyLog = require("fancy-log"); +const ansiColors = require("ansi-colors"); +function _isPromise(p) { + if (typeof p.then === 'function') { + return true; + } + return false; +} +function _renderTime(time) { + return `${Math.round(time)} ms`; +} +async function _execute(task) { + const name = task.taskName || task.displayName || ``; + if (!task._tasks) { + fancyLog('Starting', ansiColors.cyan(name), '...'); + } + const startTime = process.hrtime(); + await _doExecute(task); + const elapsedArr = process.hrtime(startTime); + const elapsedNanoseconds = (elapsedArr[0] * 1e9 + elapsedArr[1]); + if (!task._tasks) { + fancyLog(`Finished`, ansiColors.cyan(name), 'after', ansiColors.magenta(_renderTime(elapsedNanoseconds / 1e6))); + } +} +async function _doExecute(task) { + // Always invoke as if it were a callback task + return new Promise((resolve, reject) => { + if (task.length === 1) { + // this is a calback task + task((err) => { + if (err) { + return reject(err); + } + resolve(); + }); + return; + } + const taskResult = task(); + if (typeof taskResult === 'undefined') { + // this is a sync task + resolve(); + return; + } + if (_isPromise(taskResult)) { + // this is a promise returning task + taskResult.then(resolve, reject); + return; + } + // this is a stream returning task + taskResult.on('end', _ => resolve()); + taskResult.on('error', err => reject(err)); + }); +} +function series(...tasks) { + const result = async () => { + for (let i = 0; i < tasks.length; i++) { + await _execute(tasks[i]); + } + }; + result._tasks = tasks; + return result; +} +exports.series = series; +function parallel(...tasks) { + const result = async () => { + await Promise.all(tasks.map(t => _execute(t))); + }; + result._tasks = tasks; + return result; +} +exports.parallel = parallel; +function define(name, task) { + if (task._tasks) { + // This is a composite task + const lastTask = task._tasks[task._tasks.length - 1]; + if (lastTask._tasks || lastTask.taskName) { + // This is a composite task without a real task function + // => generate a fake task function + return define(name, series(task, () => Promise.resolve())); + } + lastTask.taskName = name; + task.displayName = name; + return task; + } + // This is a simple task + task.taskName = name; + task.displayName = name; + return task; +} +exports.define = define; diff --git a/build/lib/task.ts b/build/lib/task.ts new file mode 100644 index 0000000000..1ced5f3716 --- /dev/null +++ b/build/lib/task.ts @@ -0,0 +1,125 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as fancyLog from 'fancy-log'; +import * as ansiColors from 'ansi-colors'; + +export interface BaseTask { + displayName?: string; + taskName?: string; + _tasks?: Task[]; +} +export interface PromiseTask extends BaseTask { + (): Promise; +} +export interface StreamTask extends BaseTask { + (): NodeJS.ReadWriteStream; +} +export interface CallbackTask extends BaseTask { + (cb?: (err?: any) => void): void; +} + +export type Task = PromiseTask | StreamTask | CallbackTask; + +function _isPromise(p: Promise | NodeJS.ReadWriteStream): p is Promise { + if (typeof (p).then === 'function') { + return true; + } + return false; +} + +function _renderTime(time: number): string { + return `${Math.round(time)} ms`; +} + +async function _execute(task: Task): Promise { + const name = task.taskName || task.displayName || ``; + if (!task._tasks) { + fancyLog('Starting', ansiColors.cyan(name), '...'); + } + const startTime = process.hrtime(); + await _doExecute(task); + const elapsedArr = process.hrtime(startTime); + const elapsedNanoseconds = (elapsedArr[0] * 1e9 + elapsedArr[1]); + if (!task._tasks) { + fancyLog(`Finished`, ansiColors.cyan(name), 'after', ansiColors.magenta(_renderTime(elapsedNanoseconds / 1e6))); + } +} + +async function _doExecute(task: Task): Promise { + // Always invoke as if it were a callback task + return new Promise((resolve, reject) => { + if (task.length === 1) { + // this is a calback task + task((err) => { + if (err) { + return reject(err); + } + resolve(); + }); + return; + } + + const taskResult = task(); + + if (typeof taskResult === 'undefined') { + // this is a sync task + resolve(); + return; + } + + if (_isPromise(taskResult)) { + // this is a promise returning task + taskResult.then(resolve, reject); + return; + } + + // this is a stream returning task + taskResult.on('end', _ => resolve()); + taskResult.on('error', err => reject(err)); + }); +} + +export function series(...tasks: Task[]): PromiseTask { + const result = async () => { + for (let i = 0; i < tasks.length; i++) { + await _execute(tasks[i]); + } + }; + result._tasks = tasks; + return result; +} + +export function parallel(...tasks: Task[]): PromiseTask { + const result = async () => { + await Promise.all(tasks.map(t => _execute(t))); + }; + result._tasks = tasks; + return result; +} + +export function define(name: string, task: Task): Task { + if (task._tasks) { + // This is a composite task + const lastTask = task._tasks[task._tasks.length - 1]; + + if (lastTask._tasks || lastTask.taskName) { + // This is a composite task without a real task function + // => generate a fake task function + return define(name, series(task, () => Promise.resolve())); + } + + lastTask.taskName = name; + task.displayName = name; + return task; + } + + // This is a simple task + task.taskName = name; + task.displayName = name; + return task; +} diff --git a/build/lib/test/i18n.test.js b/build/lib/test/i18n.test.js index d4aec656f8..cebbc25dbc 100644 --- a/build/lib/test/i18n.test.js +++ b/build/lib/test/i18n.test.js @@ -27,13 +27,13 @@ suite('XLF Parser Tests', () => { }); test('JSON file source path to Transifex resource match', () => { const editorProject = 'vscode-editor', workbenchProject = 'vscode-workbench'; - const platform = { name: 'vs/platform', project: editorProject }, editorContrib = { name: 'vs/editor/contrib', project: editorProject }, editor = { name: 'vs/editor', project: editorProject }, base = { name: 'vs/base', project: editorProject }, code = { name: 'vs/code', project: workbenchProject }, workbenchParts = { name: 'vs/workbench/parts/html', project: workbenchProject }, workbenchServices = { name: 'vs/workbench/services/files', project: workbenchProject }, workbench = { name: 'vs/workbench', project: workbenchProject }; + const platform = { name: 'vs/platform', project: editorProject }, editorContrib = { name: 'vs/editor/contrib', project: editorProject }, editor = { name: 'vs/editor', project: editorProject }, base = { name: 'vs/base', project: editorProject }, code = { name: 'vs/code', project: workbenchProject }, workbenchParts = { name: 'vs/workbench/contrib/html', project: workbenchProject }, workbenchServices = { name: 'vs/workbench/services/files', project: workbenchProject }, workbench = { name: 'vs/workbench', project: workbenchProject }; assert.deepEqual(i18n.getResource('vs/platform/actions/browser/menusExtensionPoint'), platform); assert.deepEqual(i18n.getResource('vs/editor/contrib/clipboard/browser/clipboard'), editorContrib); assert.deepEqual(i18n.getResource('vs/editor/common/modes/modesRegistry'), editor); assert.deepEqual(i18n.getResource('vs/base/common/errorMessage'), base); assert.deepEqual(i18n.getResource('vs/code/electron-main/window'), code); - assert.deepEqual(i18n.getResource('vs/workbench/parts/html/browser/webview'), workbenchParts); + assert.deepEqual(i18n.getResource('vs/workbench/contrib/html/browser/webview'), workbenchParts); assert.deepEqual(i18n.getResource('vs/workbench/services/files/node/fileService'), workbenchServices); assert.deepEqual(i18n.getResource('vs/workbench/browser/parts/panel/panelActions'), workbench); }); diff --git a/build/lib/test/i18n.test.ts b/build/lib/test/i18n.test.ts index a51b893fd0..979493bd3e 100644 --- a/build/lib/test/i18n.test.ts +++ b/build/lib/test/i18n.test.ts @@ -38,7 +38,7 @@ suite('XLF Parser Tests', () => { editor = { name: 'vs/editor', project: editorProject }, base = { name: 'vs/base', project: editorProject }, code = { name: 'vs/code', project: workbenchProject }, - workbenchParts = { name: 'vs/workbench/parts/html', project: workbenchProject }, + workbenchParts = { name: 'vs/workbench/contrib/html', project: workbenchProject }, workbenchServices = { name: 'vs/workbench/services/files', project: workbenchProject }, workbench = { name: 'vs/workbench', project: workbenchProject}; @@ -47,7 +47,7 @@ suite('XLF Parser Tests', () => { assert.deepEqual(i18n.getResource('vs/editor/common/modes/modesRegistry'), editor); assert.deepEqual(i18n.getResource('vs/base/common/errorMessage'), base); assert.deepEqual(i18n.getResource('vs/code/electron-main/window'), code); - assert.deepEqual(i18n.getResource('vs/workbench/parts/html/browser/webview'), workbenchParts); + assert.deepEqual(i18n.getResource('vs/workbench/contrib/html/browser/webview'), workbenchParts); assert.deepEqual(i18n.getResource('vs/workbench/services/files/node/fileService'), workbenchServices); assert.deepEqual(i18n.getResource('vs/workbench/browser/parts/panel/panelActions'), workbench); }); diff --git a/build/lib/tslint/translationRemindRule.js b/build/lib/tslint/translationRemindRule.js index b9cbf91398..ca7dd1ebea 100644 --- a/build/lib/tslint/translationRemindRule.js +++ b/build/lib/tslint/translationRemindRule.js @@ -33,7 +33,7 @@ class TranslationRemindRuleWalker extends Lint.RuleWalker { visitImportLikeDeclaration(node) { const currentFile = node.getSourceFile().fileName; const matchService = currentFile.match(/vs\/workbench\/services\/\w+/); - const matchPart = currentFile.match(/vs\/workbench\/parts\/\w+/); + const matchPart = currentFile.match(/vs\/workbench\/contrib\/\w+/); if (!matchService && !matchPart) { return; } diff --git a/build/lib/tslint/translationRemindRule.ts b/build/lib/tslint/translationRemindRule.ts index a994168f35..d5e5050d21 100644 --- a/build/lib/tslint/translationRemindRule.ts +++ b/build/lib/tslint/translationRemindRule.ts @@ -42,7 +42,7 @@ class TranslationRemindRuleWalker extends Lint.RuleWalker { private visitImportLikeDeclaration(node: ts.ImportDeclaration | ts.ImportEqualsDeclaration) { const currentFile = node.getSourceFile().fileName; const matchService = currentFile.match(/vs\/workbench\/services\/\w+/); - const matchPart = currentFile.match(/vs\/workbench\/parts\/\w+/); + const matchPart = currentFile.match(/vs\/workbench\/contrib\/\w+/); if (!matchService && !matchPart) { return; } diff --git a/build/lib/util.js b/build/lib/util.js index 24db599a6c..40563ca8e5 100644 --- a/build/lib/util.js +++ b/build/lib/util.js @@ -14,6 +14,8 @@ const fs = require("fs"); const _rimraf = require("rimraf"); const git = require("./git"); const VinylFile = require("vinyl"); +const download_1 = require("../download/download"); +const REPO_ROOT = path.join(__dirname, '../../'); const NoCancellationToken = { isCancellationRequested: () => false }; function incremental(streamProvider, initial, supportsCancellation) { const input = es.through(); @@ -180,7 +182,8 @@ function rimraf(dir) { return cb(err); }); }; - return cb => retry(cb); + retry.taskName = `clean-${path.basename(dir)}`; + return retry; } exports.rimraf = rimraf; function getVersion(root) { @@ -220,3 +223,38 @@ function versionStringToNumber(versionStr) { return parseInt(match[1], 10) * 1e4 + parseInt(match[2], 10) * 1e2 + parseInt(match[3], 10); } exports.versionStringToNumber = versionStringToNumber; +function download(requestOptions) { + const result = es.through(); + const filename = path.join(REPO_ROOT, `.build/tmp-${Date.now()}-${path.posix.basename(requestOptions.path)}`); + const opts = { + requestOptions: requestOptions, + destinationPath: filename + }; + download_1.downloadInExternalProcess(opts).then(() => { + fs.stat(filename, (err, stat) => { + if (err) { + result.emit('error', err); + return; + } + fs.readFile(filename, (err, data) => { + if (err) { + result.emit('error', err); + return; + } + fs.unlink(filename, () => { + result.emit('data', new VinylFile({ + path: path.normalize(requestOptions.path), + stat: stat, + base: path.normalize(requestOptions.path), + contents: data + })); + result.emit('end'); + }); + }); + }); + }, (err) => { + result.emit('error', err); + }); + return result; +} +exports.download = download; diff --git a/build/lib/util.ts b/build/lib/util.ts index b13d215d58..95c8d996ef 100644 --- a/build/lib/util.ts +++ b/build/lib/util.ts @@ -17,6 +17,9 @@ import * as git from './git'; import * as VinylFile from 'vinyl'; import { ThroughStream } from 'through'; import * as sm from 'source-map'; +import { IDownloadOptions, downloadInExternalProcess, IDownloadRequestOptions } from '../download/download'; + +const REPO_ROOT = path.join(__dirname, '../../'); export interface ICancellationToken { isCancellationRequested(): boolean; @@ -233,8 +236,8 @@ export function rimraf(dir: string): (cb: any) => void { return cb(err); }); }; - - return cb => retry(cb); + retry.taskName = `clean-${path.basename(dir)}`; + return retry; } export function getVersion(root: string): string | undefined { @@ -280,3 +283,38 @@ export function versionStringToNumber(versionStr: string) { return parseInt(match[1], 10) * 1e4 + parseInt(match[2], 10) * 1e2 + parseInt(match[3], 10); } + +export function download(requestOptions: IDownloadRequestOptions): NodeJS.ReadWriteStream { + const result = es.through(); + const filename = path.join(REPO_ROOT, `.build/tmp-${Date.now()}-${path.posix.basename(requestOptions.path)}`); + const opts: IDownloadOptions = { + requestOptions: requestOptions, + destinationPath: filename + }; + downloadInExternalProcess(opts).then(() => { + fs.stat(filename, (err, stat) => { + if (err) { + result.emit('error', err); + return; + } + fs.readFile(filename, (err, data) => { + if (err) { + result.emit('error', err); + return; + } + fs.unlink(filename, () => { + result.emit('data', new VinylFile({ + path: path.normalize(requestOptions.path), + stat: stat, + base: path.normalize(requestOptions.path), + contents: data + })); + result.emit('end'); + }); + }); + }); + }, (err) => { + result.emit('error', err); + }); + return result; +} diff --git a/build/monaco/ThirdPartyNotices.txt b/build/monaco/ThirdPartyNotices.txt index a459893cc9..1de70ddaab 100644 --- a/build/monaco/ThirdPartyNotices.txt +++ b/build/monaco/ThirdPartyNotices.txt @@ -7,6 +7,31 @@ herein, whether by implication, estoppel or otherwise. +%% nodejs path library (https://github.com/nodejs/node/tree/43dd49c9782848c25e5b03448c8a0f923f13c158) +========================================= +Copyright Joyent, Inc. and other Node contributors. + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to permit +persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included +in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================= +END OF nodejs path library NOTICES AND INFORMATION + %% promise-polyfill version 8.1.0 (https://github.com/taylorhakes/promise-polyfill) ========================================= diff --git a/build/monaco/api.js b/build/monaco/api.js index 4cbe5474ec..e42102f9b5 100644 --- a/build/monaco/api.js +++ b/build/monaco/api.js @@ -7,14 +7,15 @@ Object.defineProperty(exports, "__esModule", { value: true }); const fs = require("fs"); const ts = require("typescript"); const path = require("path"); -const util = require("gulp-util"); +const fancyLog = require("fancy-log"); +const ansiColors = require("ansi-colors"); const dtsv = '2'; const tsfmt = require('../../tsfmt.json'); const SRC = path.join(__dirname, '../../src'); exports.RECIPE_PATH = path.join(__dirname, './monaco.d.ts.recipe'); const DECLARATION_PATH = path.join(__dirname, '../../src/vs/monaco.d.ts'); function logErr(message, ...rest) { - util.log(util.colors.yellow(`[monaco.d.ts]`), message, ...rest); + fancyLog(ansiColors.yellow(`[monaco.d.ts]`), message, ...rest); } function isDeclaration(a) { return (a.kind === ts.SyntaxKind.InterfaceDeclaration diff --git a/build/monaco/api.ts b/build/monaco/api.ts index c45a35e01e..3488fb2f03 100644 --- a/build/monaco/api.ts +++ b/build/monaco/api.ts @@ -6,7 +6,8 @@ import * as fs from 'fs'; import * as ts from 'typescript'; import * as path from 'path'; -import * as util from 'gulp-util'; +import * as fancyLog from 'fancy-log'; +import * as ansiColors from 'ansi-colors'; const dtsv = '2'; @@ -17,7 +18,7 @@ export const RECIPE_PATH = path.join(__dirname, './monaco.d.ts.recipe'); const DECLARATION_PATH = path.join(__dirname, '../../src/vs/monaco.d.ts'); function logErr(message: any, ...rest: any[]): void { - util.log(util.colors.yellow(`[monaco.d.ts]`), message, ...rest); + fancyLog(ansiColors.yellow(`[monaco.d.ts]`), message, ...rest); } type SourceFileGetter = (moduleId: string) => ts.SourceFile | null; diff --git a/build/monaco/monaco.usage.recipe b/build/monaco/monaco.usage.recipe index e75b8585a2..13290a7abb 100644 --- a/build/monaco/monaco.usage.recipe +++ b/build/monaco/monaco.usage.recipe @@ -10,8 +10,10 @@ import { CountBadge } from './vs/base/browser/ui/countBadge/countBadge'; import { SimpleWorkerClient, create as create1 } from './vs/base/common/worker/simpleWorker'; import { create as create2 } from './vs/editor/common/services/editorSimpleWorker'; import { QuickOpenWidget } from './vs/base/parts/quickopen/browser/quickOpenWidget'; +import { WorkbenchAsyncDataTree } from './vs/platform/list/browser/listService'; import { SyncDescriptor0, SyncDescriptor1, SyncDescriptor2, SyncDescriptor3, SyncDescriptor4, SyncDescriptor5, SyncDescriptor6, SyncDescriptor7, SyncDescriptor8 } from './vs/platform/instantiation/common/descriptors'; import { DiffNavigator } from './vs/editor/browser/widget/diffNavigator'; +import { DocumentRangeFormattingEditProvider } from './vs/editor/common/modes'; import * as editorAPI from './vs/editor/editor.api'; (function () { @@ -22,6 +24,7 @@ import * as editorAPI from './vs/editor/editor.api'; a = (b).getWorkspace; // IWorkspaceFolderProvider a = (b).style; // IThemable a = (b).style; // IThemable + a = (>b).style; // IThemable a = (b).userHome; // IUserHomeProvider a = (b).previous; // IDiffNavigator a = (>b).type; @@ -30,6 +33,7 @@ import * as editorAPI from './vs/editor/editor.api'; a = (>b).getProxyObject; // IWorkerClient a = create1; a = create2; + a = (b).extensionId; // injection madness a = (>b).ctor; diff --git a/build/monaco/package.json b/build/monaco/package.json index efd919085b..1962694ce6 100644 --- a/build/monaco/package.json +++ b/build/monaco/package.json @@ -1,7 +1,7 @@ { "name": "monaco-editor-core", "private": true, - "version": "0.14.3", + "version": "0.16.0", "description": "A browser based code editor", "author": "Microsoft Corporation", "license": "MIT", diff --git a/build/monaco/version.txt b/build/monaco/version.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/build/npm/update-grammar.js b/build/npm/update-grammar.js index 3f0b65e688..47598875c7 100644 --- a/build/npm/update-grammar.js +++ b/build/npm/update-grammar.js @@ -12,6 +12,8 @@ var cson = require('cson-parser'); var https = require('https'); var url = require('url'); +let commitDate = '0000-00-00'; + /** * @param {string} urlString */ @@ -120,30 +122,35 @@ exports.update = function (repoId, repoPath, dest, modifyGrammar, version = 'mas try { fs.writeFileSync(dest, JSON.stringify(result, null, '\t').replace(/\n/g, '\r\n')); - // Add commit sha to cgmanifest let cgmanifestRead = JSON.parse(fs.readFileSync('./cgmanifest.json').toString()); let promises = new Array(); - let packageJsonPath = 'https://raw.githubusercontent.com/' + repoId + `/${info.commitSha}/package.json`; - for (let i = 0; i < cgmanifestRead.registrations.length; i++) { - if (cgmanifestRead.registrations[i].component.git.repositoryUrl.substr(cgmanifestRead.registrations[i].component.git.repositoryUrl.length - repoId.length, repoId.length) === repoId) { - cgmanifestRead.registrations[i].component.git.commitHash = info.commitSha; - promises.push(download(packageJsonPath).then(function (packageJson) { - if (packageJson) { - try { - cgmanifestRead.registrations[i].version = JSON.parse(packageJson).version; - } catch (e) { - console.log('File does not exist at' + packageJsonPath); - } - } - })); - break; + const currentCommitDate = info.commitDate.substr(0, 10); + + // Add commit sha to cgmanifest. + if (currentCommitDate > commitDate) { + let packageJsonPath = 'https://raw.githubusercontent.com/' + repoId + `/${info.commitSha}/package.json`; + for (let i = 0; i < cgmanifestRead.registrations.length; i++) { + if (cgmanifestRead.registrations[i].component.git.repositoryUrl.substr(cgmanifestRead.registrations[i].component.git.repositoryUrl.length - repoId.length, repoId.length) === repoId) { + cgmanifestRead.registrations[i].component.git.commitHash = info.commitSha; + commitDate = currentCommitDate; + promises.push(download(packageJsonPath).then(function (packageJson) { + if (packageJson) { + try { + cgmanifestRead.registrations[i].version = JSON.parse(packageJson).version; + } catch (e) { + console.log('Cannot get version. File does not exist at ' + packageJsonPath); + } + } + })); + break; + } } } Promise.all(promises).then(function (allResult) { fs.writeFileSync('./cgmanifest.json', JSON.stringify(cgmanifestRead, null, '\t').replace(/\n/g, '\r\n')); }); if (info) { - console.log('Updated ' + path.basename(dest) + ' to ' + repoId + '@' + info.commitSha.substr(0, 7) + ' (' + info.commitDate.substr(0, 10) + ')'); + console.log('Updated ' + path.basename(dest) + ' to ' + repoId + '@' + info.commitSha.substr(0, 7) + ' (' + currentCommitDate + ')'); } else { console.log('Updated ' + path.basename(dest)); } diff --git a/build/package.json b/build/package.json index c8e7a5e070..641769179d 100644 --- a/build/package.json +++ b/build/package.json @@ -2,9 +2,11 @@ "name": "azuredatastudio-oss-dev-build", "version": "1.0.0", "devDependencies": { + "@types/ansi-colors": "^3.2.0", "@types/azure": "0.9.19", "@types/debounce": "^1.0.0", "@types/documentdb": "1.10.2", + "@types/fancy-log": "^1.3.0", "@types/glob": "^7.1.1", "@types/gulp": "^4.0.5", "@types/gulp-concat": "^0.0.32", @@ -13,7 +15,6 @@ "@types/gulp-rename": "^0.0.33", "@types/gulp-sourcemaps": "^0.0.32", "@types/gulp-uglify": "^3.0.5", - "@types/gulp-util": "^3.0.34", "@types/mime": "0.0.29", "@types/minimatch": "^3.0.3", "@types/minimist": "^1.2.0", @@ -42,7 +43,7 @@ "request": "^2.85.0", "tslint": "^5.9.1", "service-downloader": "github:anthonydresser/service-downloader#0.1.5", - "typescript": "3.2.2", + "typescript": "3.3.1", "vsce": "1.48.0", "xml2js": "^0.4.17" }, diff --git a/build/win32/code.iss b/build/win32/code.iss index de979c66d5..e971bb6830 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -1,7 +1,7 @@ #define LocalizedLanguageFile(Language = "") \ DirExists(RepoDir + "\licenses") && Language != "" \ - ? ('; LicenseFile: "' + RepoDir + '\licenses\LICENSE-' + Language + '.txt"') \ - : '; LicenseFile: "' + RepoDir + '\LICENSE.txt"' + ? ('; LicenseFile: "' + RepoDir + '\licenses\LICENSE-' + Language + '.rtf"') \ + : '; LicenseFile: "' + RepoDir + '\LICENSE.rtf"' [Setup] AppId={#AppId} diff --git a/build/yarn.lock b/build/yarn.lock index 3eaaa8e3cf..279d0fdd91 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -10,6 +10,11 @@ normalize-path "^2.0.1" through2 "^2.0.3" +"@types/ansi-colors@^3.2.0": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@types/ansi-colors/-/ansi-colors-3.2.0.tgz#3e4fe85d9131ce1c6994f3040bd0b25306c16a6e" + integrity sha512-0caWAhXht9N2lOdMzJLXybsSkYCx1QOdxx6pae48tswI9QV3DFX26AoOpy0JxwhCb+zISTqmd6H8t9Zby9BoZg== + "@types/azure@0.9.19": version "0.9.19" resolved "https://registry.yarnpkg.com/@types/azure/-/azure-0.9.19.tgz#1a6a9bd856b437ddecf3f9fc8407a683c869ba02" @@ -31,9 +36,9 @@ "@types/node" "*" "@types/debounce@^1.0.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.2.0.tgz#9ee99259f41018c640b3929e1bb32c3dcecdb192" - integrity sha512-bWG5wapaWgbss9E238T0R6bfo5Fh3OkeoSt245CM7JJwVwpw6MEBCbIxLq5z8KzsE3uJhzcIuQkyiZmzV3M/Dw== + version "1.0.0" + resolved "https://registry.yarnpkg.com/@types/debounce/-/debounce-1.0.0.tgz#417560200331e1bb84d72da85391102c2fcd61b7" + integrity sha1-QXVgIAMx4buE1y2oU5EQLC/NYbc= "@types/documentdb@1.10.2": version "1.10.2" @@ -43,9 +48,14 @@ "@types/node" "*" "@types/events@*": - version "3.0.0" - resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" - integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/events/-/events-1.2.0.tgz#81a6731ce4df43619e5c8c945383b3e62a89ea86" + integrity sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA== + +"@types/fancy-log@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@types/fancy-log/-/fancy-log-1.3.0.tgz#a61ab476e5e628cd07a846330df53b85e05c8ce0" + integrity sha512-mQjDxyOM1Cpocd+vm1kZBP7smwKZ4TNokFeds9LV7OZibmPJFEzY3+xZMrKfUdNT71lv8GoCPD6upKwHxubClw== "@types/form-data@*": version "2.2.1" @@ -79,9 +89,9 @@ "@types/node" "*" "@types/gulp-filter@^3.0.32": - version "3.0.33" - resolved "https://registry.yarnpkg.com/@types/gulp-filter/-/gulp-filter-3.0.33.tgz#353f6a9a5c0edea1a704f50b14f7979179497134" - integrity sha512-LYwn+zTIt1h97RuGhqWT5DoeQQyfyiYIBOtPmeOYDEu0vo9GToiORUO+zBeYnCs5PIfJTAcHkGdhH61OTbSS4w== + version "3.0.32" + resolved "https://registry.yarnpkg.com/@types/gulp-filter/-/gulp-filter-3.0.32.tgz#eeff3e9dbc092268fed01f2421ab00f6c8cb4848" + integrity sha512-JvY4qTxXehoK2yCUxYVxTMvckVbDM5TWHWeUoYJyX31gwFqw4YDD6JNzhuTxI3yHPUTY4BBRTqgm6puQEZVCNg== dependencies: "@types/minimatch" "*" "@types/node" "*" @@ -110,22 +120,12 @@ "@types/node" "*" "@types/gulp-uglify@^3.0.5": - version "3.0.6" - resolved "https://registry.yarnpkg.com/@types/gulp-uglify/-/gulp-uglify-3.0.6.tgz#7c7c38017680bbb8a3d815e23a7026799432ce54" - integrity sha512-NvnNG0lg0+fJHNDK/b4OvLZkn5uHSIgm1XslRqgHal8CHG85sxmcktnNR1XdIUkwYpNbYZkXfvpzNtAxb6cgyQ== + version "3.0.5" + resolved "https://registry.yarnpkg.com/@types/gulp-uglify/-/gulp-uglify-3.0.5.tgz#ddcbbb6bd15a84b8a6c5e2218c2efba98102d135" + integrity sha512-LD2b6gCPugrKI1W188nIp0gm+cAnGGwaTFpPdeZYVXwPHdoCQloy3du0JR62MeMjAwUwlcOb+SKYT6Qgw7yBiA== dependencies: "@types/node" "*" - "@types/uglify-js" "*" - -"@types/gulp-util@^3.0.34": - version "3.0.34" - resolved "https://registry.yarnpkg.com/@types/gulp-util/-/gulp-util-3.0.34.tgz#d1291ebf706d93f46eb8df11344bbfd96247697e" - integrity sha512-E06WN1OfqL5UsMwJ1T7ClgnaXgaPipb7Ee8euMc3KRHLNqxdvWrDir9KA6uevgzBgT7XbjgmzZA2pkzDqBBX7A== - dependencies: - "@types/node" "*" - "@types/through2" "*" - "@types/vinyl" "*" - chalk "^2.2.0" + "@types/uglify-js" "^2" "@types/gulp@^4.0.5": version "4.0.5" @@ -162,9 +162,9 @@ integrity sha1-9o1j24tpw46VWLQHNSXPlsT3qCk= "@types/node@*": - version "11.9.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-11.9.4.tgz#ceb0048a546db453f6248f2d1d95e937a6f00a14" - integrity sha512-Zl8dGvAcEmadgs1tmSPcvwzO1YRsz38bVJQvH1RvRqSR9/5n61Q1ktcDL0ht3FXWR+ZpVmXVwN1LuH4Ax23NsA== + version "8.0.51" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" + integrity sha512-El3+WJk2D/ppWNd2X05aiP5l2k4EwF7KwheknQZls+I26eSICoWRhRIJ56jGgw2dqNGQ5LtNajmBU2ajS28EvQ== "@types/node@8.0.33": version "8.0.33" @@ -179,9 +179,9 @@ "@types/node" "*" "@types/request@^2.47.0": - version "2.48.1" - resolved "https://registry.yarnpkg.com/@types/request/-/request-2.48.1.tgz#e402d691aa6670fbbff1957b15f1270230ab42fa" - integrity sha512-ZgEZ1TiD+KGA9LiAAPPJL68Id2UWfeSO62ijSXZjFJArVV+2pKcsVHmrcu+1oiE3q6eDGiFiSolRc4JHoerBBg== + version "2.47.0" + resolved "https://registry.yarnpkg.com/@types/request/-/request-2.47.0.tgz#76a666cee4cb85dcffea6cd4645227926d9e114e" + integrity sha512-/KXM5oev+nNCLIgBjkwbk8VqxmzI56woD4VUxn95O+YeQ8hJzcSmIZ1IN3WexiqBb6srzDo2bdMbsXxgXNkz5Q== dependencies: "@types/caseless" "*" "@types/form-data" "*" @@ -196,7 +196,7 @@ "@types/glob" "*" "@types/node" "*" -"@types/through2@*", "@types/through2@^2.0.34": +"@types/through2@^2.0.34": version "2.0.34" resolved "https://registry.yarnpkg.com/@types/through2/-/through2-2.0.34.tgz#9c2a259a238dace2a05a2f8e94b786961bc27ac4" integrity sha512-nhRG8+RuG/L+0fAZBQYaRflXKjTrHOKH8MFTChnf+dNVMxA3wHYYrfj0tztK0W51ABXjGfRCDc0vRkecCOrsow== @@ -211,9 +211,9 @@ "@types/node" "*" "@types/tough-cookie@*": - version "2.3.5" - resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.5.tgz#9da44ed75571999b65c37b60c9b2b88db54c585d" - integrity sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg== + version "2.3.2" + resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-2.3.2.tgz#e0d481d8bb282ad8a8c9e100ceb72c995fb5e709" + integrity sha512-vOVmaruQG5EatOU/jM6yU2uCp3Lz6mK1P5Ztu4iJjfM4SVHU9XYktPUQtKlIXuahqXHdEyUarMrBEwg5Cwu+bA== "@types/uglify-es@^3.0.0": version "3.0.0" @@ -223,9 +223,16 @@ "@types/uglify-js" "*" "@types/uglify-js@*": - version "3.0.4" - resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.4.tgz#96beae23df6f561862a830b4288a49e86baac082" - integrity sha512-SudIN9TRJ+v8g5pTG8RRCqfqTMNqgWCKKd3vtynhGzkIIjxaicNAMuY5TRadJ6tzDu3Dotf3ngaMILtmOdmWEQ== + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-3.0.3.tgz#801a5ca1dc642861f47c46d14b700ed2d610840b" + integrity sha512-MAT0BW2ruO0LhQKjvlipLGCF/Yx0y/cj+tT67tK3QIQDrM2+9R78HgJ54VlrE8AbfjYJJBCQCEPM5ZblPVTuww== + dependencies: + source-map "^0.6.1" + +"@types/uglify-js@^2": + version "2.6.31" + resolved "https://registry.yarnpkg.com/@types/uglify-js/-/uglify-js-2.6.31.tgz#c694755eeb6a1bb9f8f321f3ec37cc22ca4c4f6b" + integrity sha512-LjcyGt6CHsgZ0AoofnMwhyxo9hUqz2mgl6IcF+S8B1zdSTxHAvTO/1RPvBAHG3C1ZeAc+AoWA5mb3lDJKtM9Zg== dependencies: source-map "^0.6.1" @@ -240,17 +247,19 @@ integrity sha512-Z4TYuEKn9+RbNVk1Ll2SS4x1JeLHecolIbM/a8gveaHsW0Hr+RQMraZACwTO2VD7JvepgA6UO1A1VrbktQrIbQ== "@types/undertaker@*": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@types/undertaker/-/undertaker-1.2.1.tgz#f39dabdd05661bbf2badb5072a2aa9ae4d6daaa6" - integrity sha512-JNWgZgrvd37nWKt0vT4kZQUlWPS6wN1e5ALpwElX7xt9Gka46Chc3zTTeQ6xykYYrWoIroCSdzn0jgzRf+DKHA== + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/undertaker/-/undertaker-1.2.0.tgz#d39a81074b4f274eb656870fc904a70737e00f8e" + integrity sha512-bx/5nZCGkasXs6qaA3B6SVDjBZqdyk04UO12e0uEPSzjt5H8jEJw0DKe7O7IM0hM2bVHRh70pmOH7PEHqXwzOw== dependencies: + "@types/events" "*" "@types/undertaker-registry" "*" "@types/vinyl-fs@*": - version "2.4.11" - resolved "https://registry.yarnpkg.com/@types/vinyl-fs/-/vinyl-fs-2.4.11.tgz#b98119b8bb2494141eaf649b09fbfeb311161206" - integrity sha512-2OzQSfIr9CqqWMGqmcERE6Hnd2KY3eBVtFaulVo3sJghplUcaeMdL9ZjEiljcQQeHjheWY9RlNmumjIAvsBNaA== + version "2.4.9" + resolved "https://registry.yarnpkg.com/@types/vinyl-fs/-/vinyl-fs-2.4.9.tgz#d312c24b5ba8d2db456d23ee4a66f9d016af82ea" + integrity sha512-Q0EXd6c1fORjiOuK4ZaKdfFcMyFzJlTi56dqktwaWVLIDAzE49wUs3bKnYbZwzyMWoH+NcMWnRuR73S9A0jnRA== dependencies: + "@types/events" "*" "@types/glob-stream" "*" "@types/node" "*" "@types/vinyl" "*" @@ -279,15 +288,23 @@ agent-base@4, agent-base@^4.1.0: dependencies: es6-promisify "^5.0.0" -ajv@^6.5.5: - version "6.9.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.1.tgz#a4d3683d74abc5670e75f0b16520f70a20ea8dc1" - integrity sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA== +ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY= dependencies: - fast-deep-equal "^2.0.1" + co "^4.6.0" + json-stable-stringify "^1.0.1" + +ajv@^5.1.0: + version "5.5.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" + integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" + json-schema-traverse "^0.3.0" ansi-gray@^0.1.1: version "0.1.1" @@ -352,17 +369,20 @@ array-uniq@^1.0.1, array-uniq@^1.0.2: integrity sha1-r2rId6Jcx/dOBYiUdThY39sk/bY= asn1@~0.2.3: - version "0.2.4" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" - integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== - dependencies: - safer-buffer "~2.1.0" + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + integrity sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y= assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + integrity sha1-104bh+ev/A24qttwIfP+SBAasjQ= + asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -373,32 +393,42 @@ atob@^2.1.1: resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + integrity sha1-FDQt0428yU0OW4fXY81jYSwOeU8= + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= -aws4@^1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" - integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== +aws4@^1.2.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" + integrity sha1-g+9cqGCysy5KDe7e6MdxudtXRx4= + +aws4@^1.6.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.7.0.tgz#d4d0e9b9dbfca77bf08eeb0a8a471550fe39e289" + integrity sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w== azure-storage@^2.1.0: - version "2.10.2" - resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-2.10.2.tgz#3bcabdbf10e72fd0990db81116e49023c4a675b6" - integrity sha512-pOyGPya9+NDpAfm5YcFfklo57HfjDbYLXxs4lomPwvRxmb0Di/A+a+RkUmEFzaQ8S13CqxK40bRRB0sjj2ZQxA== + version "2.6.0" + resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-2.6.0.tgz#84747ee54a4bd194bb960f89f3eff89d67acf1cf" + integrity sha1-hHR+5UpL0ZS7lg+J8+/4nWes8c8= dependencies: browserify-mime "~1.2.9" - extend "^3.0.2" + extend "~1.2.1" json-edm-parser "0.1.2" md5.js "1.3.4" readable-stream "~2.0.0" - request "^2.86.0" + request "~2.81.0" underscore "~1.8.3" uuid "^3.0.0" - validator "~9.4.1" - xml2js "0.2.8" - xmlbuilder "^9.0.7" + validator "~3.35.0" + xml2js "0.2.7" + xmlbuilder "0.4.3" babel-code-frame@^6.22.0: version "6.26.0" @@ -420,9 +450,9 @@ base64-js@^1.0.2: integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== bcrypt-pbkdf@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" - integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= + version "1.0.1" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + integrity sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40= dependencies: tweetnacl "^0.14.3" @@ -454,6 +484,27 @@ boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + integrity sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8= + dependencies: + hoek "2.x.x" + +boom@4.x.x: + version "4.3.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" + integrity sha1-T4owBctKfjiJ90kDD9JbluAdLjE= + dependencies: + hoek "4.x.x" + +boom@5.x.x: + version "5.2.0" + resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02" + integrity sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw== + dependencies: + hoek "4.x.x" + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -519,10 +570,10 @@ chalk@^1.0.0, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^2.2.0, chalk@^2.3.0: - version "2.4.2" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" - integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== +chalk@^2.3.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" + integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== dependencies: ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" @@ -550,6 +601,11 @@ clone@^1.0.0: resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" integrity sha1-2jCcwmPfFZlMaIypAheco8fNfH4= +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= + color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -568,14 +624,21 @@ color-support@^1.1.3: integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== colors@^1.1.2: - version "1.3.3" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" - integrity sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg== + version "1.2.1" + resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.1.tgz#f4a3d302976aaf042356ba1ade3b1a2c62d9d794" + integrity sha512-s8+wktIuDSLffCywiwSxQOMqtPxML11a/dtHE17tMn4B1MSWw/C22EKf7M2KGUBcDaVFEGT+S8N02geDXeuNKg== -combined-stream@^1.0.6, combined-stream@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" - integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== +combined-stream@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" + integrity sha1-cj599ugBrFYTETp+RFqbactjKBg= + dependencies: + delayed-stream "~1.0.0" + +combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" + integrity sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk= dependencies: delayed-stream "~1.0.0" @@ -608,6 +671,20 @@ core-util-is@1.0.2, core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + integrity sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g= + dependencies: + boom "2.x.x" + +cryptiles@3.x.x: + version "3.1.2" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" + integrity sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4= + dependencies: + boom "5.x.x" + css-select@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/css-select/-/css-select-1.2.0.tgz#2b3a110539c5355f1cd8d314623e870b121ec858" @@ -619,9 +696,9 @@ css-select@~1.2.0: nth-check "~1.0.1" css-what@2.1: - version "2.1.3" - resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" - integrity sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg== + version "2.1.2" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.2.tgz#c0876d9d0480927d7d4920dcd72af3595649554d" + integrity sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ== css@2.X: version "2.2.4" @@ -788,17 +865,27 @@ documentdb@1.13.0: underscore "1.8.3" dom-serializer@0, dom-serializer@~0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0" - integrity sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA== + version "0.1.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.0.tgz#073c697546ce0780ce23be4a28e293e40bc30c82" + integrity sha1-BzxpdUbOB4DOI75KKOKT5AvDDII= dependencies: - domelementtype "^1.3.0" - entities "^1.1.1" + domelementtype "~1.1.1" + entities "~1.1.1" -domelementtype@1, domelementtype@^1.3.0, domelementtype@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" - integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== +domelementtype@1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.2.1.tgz#578558ef23befac043a1abb0db07635509393479" + integrity sha512-SQVCLFS2E7G5CRCMdn6K9bIhRj1bS6QBWZfF0TUPh4V/BbqrQ619IdSS3/izn0FZ+9l+uODzaZjb08fjOfablA== + +domelementtype@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" + integrity sha1-sXrtguirWeUt2cGbF1bg/BhyBMI= + +domelementtype@~1.1.1: + version "1.1.3" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" + integrity sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs= domhandler@^2.3.0: version "2.4.2" @@ -831,12 +918,11 @@ duplexer2@0.0.2: readable-stream "~1.1.9" ecc-jsbn@~0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" - integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= + version "0.1.1" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + integrity sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU= dependencies: jsbn "~0.1.0" - safer-buffer "^2.1.0" end-of-stream@^1.0.0: version "1.4.1" @@ -882,35 +968,34 @@ eventemitter2@^5.0.1: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452" integrity sha1-YZegldX7a1folC9v1+qtY6CclFI= -extend@^3.0.2, extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== +extend@~1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-1.2.1.tgz#a0f5fd6cfc83a5fe49ef698d60ec8a624dd4576c" + integrity sha1-oPX9bPyDpf5J72mNYOyKYk3UV2w= -extsprintf@1.3.0: +extend@~3.0.0, extend@~3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + integrity sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ= + +extsprintf@1.3.0, extsprintf@^1.2.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= -extsprintf@^1.2.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" - integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= - fancy-log@^1.1.0: - version "1.3.3" - resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.3.tgz#dbc19154f558690150a23953a0adbd035be45fc7" - integrity sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw== + version "1.3.2" + resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" + integrity sha1-9BEl49hPLn2JpD0G2VjI94vha+E= dependencies: ansi-gray "^0.1.1" color-support "^1.1.3" - parse-node-version "^1.0.0" time-stamp "^1.0.0" -fast-deep-equal@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" - integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= +fast-deep-equal@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" + integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= fast-json-stable-stringify@^2.0.0: version "2.0.0" @@ -944,13 +1029,22 @@ forever-agent@~0.6.1: resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -form-data@~2.3.2: - version "2.3.3" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" - integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== +form-data@~2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + integrity sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE= dependencies: asynckit "^0.4.0" - combined-stream "^1.0.6" + combined-stream "^1.0.5" + mime-types "^2.1.12" + +form-data@~2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" + integrity sha1-SXBJi+YEwgwAXU9cI67NIda0kJk= + dependencies: + asynckit "^0.4.0" + combined-stream "1.0.6" mime-types "^2.1.12" fs-constants@^1.0.0: @@ -995,14 +1089,14 @@ getpass@^0.1.1: assert-plus "^1.0.0" github-releases@^0.4.1: - version "0.4.2" - resolved "https://registry.yarnpkg.com/github-releases/-/github-releases-0.4.2.tgz#3dfa80b809a86418531a53f378845c6e6d580c4c" - integrity sha512-SewGhcOQSl2ZwXMv+MVsZQ/UFirWTQgmL2YrXWDQLIsat2pwurwRzuCOzOFIRYOGZxm0VThyq/cjLD20KOaIMg== + version "0.4.1" + resolved "https://registry.yarnpkg.com/github-releases/-/github-releases-0.4.1.tgz#4a13bdf85c4161344271db3d81db08e7379102ff" + integrity sha1-ShO9+FxBYTRCcds9gdsI5zeRAv8= dependencies: minimatch "3.0.4" optimist "0.6.1" prettyjson "1.2.1" - request "^2.88.0" + request "2.81.0" glob@^7.0.3, glob@^7.0.6, glob@^7.1.1, glob@^7.1.3: version "7.1.3" @@ -1028,13 +1122,18 @@ globby@^6.1.0: pinkie-promise "^2.0.0" glogg@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.2.tgz#2d7dd702beda22eb3bffadf880696da6d846313f" - integrity sha512-5mwUoSuBk44Y4EshyiqcH95ZntbDdTQqA3QYSrxmzj28Ai0vXBGMH1ApSANH14j2sIRtqCEyg6PfsuP7ElOEDA== + version "1.0.1" + resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.1.tgz#dcf758e44789cc3f3d32c1f3562a3676e6a34810" + integrity sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw== dependencies: sparkles "^1.0.0" -graceful-fs@4.X, graceful-fs@^4.1.10, graceful-fs@^4.1.2, graceful-fs@^4.1.6: +graceful-fs@4.X: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= + +graceful-fs@^4.1.10, graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.1.15" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== @@ -1100,17 +1199,30 @@ gulplog@^1.0.0: dependencies: glogg "^1.0.0" +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + integrity sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4= + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~5.1.0: - version "5.1.3" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" - integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g== +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + integrity sha1-M0gdDxu/9gDdID11gSpqX7oALio= dependencies: - ajv "^6.5.5" + ajv "^4.9.1" + har-schema "^1.0.5" + +har-validator@~5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" + integrity sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0= + dependencies: + ajv "^5.1.0" har-schema "^2.0.0" has-ansi@^2.0.0: @@ -1140,17 +1252,47 @@ hash-base@^3.0.0: inherits "^2.0.1" safe-buffer "^5.0.1" -htmlparser2@^3.9.1: - version "3.10.1" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" - integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== +hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + integrity sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ= dependencies: - domelementtype "^1.3.1" + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + +hawk@~6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" + integrity sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ== + dependencies: + boom "4.x.x" + cryptiles "3.x.x" + hoek "4.x.x" + sntp "2.x.x" + +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0= + +hoek@4.x.x: + version "4.2.1" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" + integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA== + +htmlparser2@^3.9.1: + version "3.10.0" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464" + integrity sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ== + dependencies: + domelementtype "^1.3.0" domhandler "^2.3.0" domutils "^1.5.1" entities "^1.1.1" inherits "^2.0.1" - readable-stream "^3.1.1" + readable-stream "^3.0.6" http-proxy-agent@^2.1.0: version "2.1.0" @@ -1160,6 +1302,15 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + integrity sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8= + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -1262,9 +1413,9 @@ js-tokens@^3.0.2: integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= js-yaml@^3.7.0: - version "3.12.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600" - integrity sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA== + version "3.12.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.0.tgz#eaed656ec8344f10f527c6bfa1b6e2244de167d1" + integrity sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A== dependencies: argparse "^1.0.7" esprima "^4.0.0" @@ -1281,16 +1432,23 @@ json-edm-parser@0.1.2: dependencies: jsonparse "~1.2.0" -json-schema-traverse@^0.4.1: - version "0.4.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" - integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + integrity sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8= + dependencies: + jsonify "~0.0.0" + json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -1303,6 +1461,11 @@ jsonfile@^2.1.0: optionalDependencies: graceful-fs "^4.1.6" +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= + jsonparse@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.2.0.tgz#5c0c5685107160e72fe7489bddea0b44c2bc67bd" @@ -1324,9 +1487,9 @@ lazy-debug-legacy@0.0.X: integrity sha1-U3cWwHduTPeePtG2IfdljCkRsbE= linkify-it@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.1.0.tgz#c4caf38a6cd7ac2212ef3c7d2bde30a91561f9db" - integrity sha512-4REs8/062kV2DSHxNfq5183zrqXMl7WP0WzABH9IeJI+NLm429FgE1PDecltYfnOoFDFlZGh2T8PfZn0r+GTRg== + version "2.0.3" + resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-2.0.3.tgz#d94a4648f9b1c179d64fa97291268bdb6ce9434f" + integrity sha1-2UpGSPmxwXnWT6lykSaL22zpQ08= dependencies: uc.micro "^1.0.1" @@ -1465,22 +1628,34 @@ mdurl@^1.0.1: resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" integrity sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4= -mime-db@~1.38.0: - version "1.38.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" - integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg== +mime-db@~1.30.0: + version "1.30.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" + integrity sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE= -mime-types@^2.1.12, mime-types@~2.1.19: - version "2.1.22" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.22.tgz#fe6b355a190926ab7698c9a0556a11199b2199bd" - integrity sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog== +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" + integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== + +mime-types@^2.1.12, mime-types@~2.1.7: + version "2.1.17" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" + integrity sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo= dependencies: - mime-db "~1.38.0" + mime-db "~1.30.0" + +mime-types@~2.1.17: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== + dependencies: + mime-db "~1.33.0" mime@^1.3.4: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== + version "1.4.1" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" + integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== minimatch@3.0.4, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" @@ -1529,9 +1704,9 @@ multipipe@^0.1.2: duplexer2 "0.0.2" mute-stream@~0.0.4: - version "0.0.8" - resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" - integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= normalize-path@^2.0.1: version "2.1.1" @@ -1547,10 +1722,10 @@ nth-check@~1.0.1: dependencies: boolbase "~1.0.0" -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== +oauth-sign@~0.8.1, oauth-sign@~0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= object-assign@4.1.0: version "4.1.0" @@ -1605,11 +1780,6 @@ p-map@^1.1.1: resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA== -parse-node-version@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" - integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== - parse-semver@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/parse-semver/-/parse-semver-1.1.1.tgz#9a4afd6df063dc4826f93fba4a99cf223f666cb8" @@ -1634,7 +1804,7 @@ path-is-inside@^1.0.1: resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= -path-parse@^1.0.6: +path-parse@^1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== @@ -1644,6 +1814,11 @@ pend@~1.2.0: resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + integrity sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU= + performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" @@ -1694,30 +1869,25 @@ process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== -psl@^1.1.24: - version "1.1.31" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184" - integrity sha512-/6pt4+C+T+wZUieKR620OpzN/LlnNKuWjy1iFLQ/UG35JqHlR/89MP1d96dUfkf6Dne3TuLQzOYEYshJ+Hx8mw== - punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= -punycode@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" - integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== - q@^1.0.1: version "1.5.1" resolved "https://registry.yarnpkg.com/q/-/q-1.5.1.tgz#7e32f75b41381291d04611f1bf14109ac00651d7" integrity sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc= -qs@~6.5.2: - version "6.5.2" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + integrity sha1-E+JtKK1rD/qpExLNO/cI7TUecjM= + +qs@~6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" + integrity sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A== read@^1.0.7: version "1.0.7" @@ -1726,7 +1896,7 @@ read@^1.0.7: dependencies: mute-stream "~0.0.4" -readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@~2.3.6: +readable-stream@^2.1.5, readable-stream@^2.3.0, readable-stream@^2.3.5: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== @@ -1739,10 +1909,10 @@ readable-stream@^2.3.0, readable-stream@^2.3.5, readable-stream@~2.3.6: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readable-stream@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06" - integrity sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA== +readable-stream@^3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.0.6.tgz#351302e4c68b5abd6a2ed55376a7f9a25be3057a" + integrity sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" @@ -1780,31 +1950,61 @@ replace-ext@0.0.1: resolved "https://registry.yarnpkg.com/replace-ext/-/replace-ext-0.0.1.tgz#29bbd92078a739f0bcce2b4ee41e837953522924" integrity sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ= -request@^2.85.0, request@^2.86.0, request@^2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== +request@2.81.0, request@~2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + integrity sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA= + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + +request@^2.85.0: + version "2.85.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.85.0.tgz#5a03615a47c61420b3eb99b7dba204f83603e1fa" + integrity sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg== dependencies: aws-sign2 "~0.7.0" - aws4 "^1.8.0" + aws4 "^1.6.0" caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" + combined-stream "~1.0.5" + extend "~3.0.1" forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" + form-data "~2.3.1" + har-validator "~5.0.3" + hawk "~6.0.2" http-signature "~1.2.0" is-typedarray "~1.0.0" isstream "~0.1.2" json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" + mime-types "~2.1.17" + oauth-sign "~0.8.2" performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" + qs "~6.5.1" + safe-buffer "^5.1.1" + stringstream "~0.0.5" + tough-cookie "~2.3.3" tunnel-agent "^0.6.0" - uuid "^3.3.2" + uuid "^3.1.0" resolve-url@^0.2.1: version "0.2.1" @@ -1812,11 +2012,11 @@ resolve-url@^0.2.1: integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= resolve@^1.3.2: - version "1.10.0" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" - integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== + version "1.8.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" + integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== dependencies: - path-parse "^1.0.6" + path-parse "^1.0.5" rimraf@^2.2.8: version "2.6.3" @@ -1825,20 +2025,25 @@ rimraf@^2.2.8: dependencies: glob "^7.1.3" -safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@^5.0.1, safe-buffer@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" + integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg== + +safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== -sax@0.5.x: - version "0.5.8" - resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" - integrity sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE= +sax@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.2.tgz#735ffaa39a1cff8ffb9598f0223abdb03a9fb2ea" + integrity sha1-c1/6o5oc/4/7lZjwIjq9sDqfsuo= sax@>=0.6.0: version "1.2.4" @@ -1873,6 +2078,20 @@ semver@^5.1.0, semver@^5.3.0: mkdirp "^0.5.1" tmp "^0.0.33" +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + integrity sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg= + dependencies: + hoek "2.x.x" + +sntp@2.x.x: + version "2.1.0" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" + integrity sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg== + dependencies: + hoek "4.x.x" + source-map-resolve@^0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" @@ -1905,24 +2124,24 @@ sprintf-js@~1.0.2: integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= sshpk@^1.7.0: - version "1.16.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" - integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== + version "1.13.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + integrity sha1-US322mKHFEMW3EwY/hzx2UBzm+M= dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" - bcrypt-pbkdf "^1.0.0" dashdash "^1.12.0" - ecc-jsbn "~0.1.1" getpass "^0.1.1" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" jsbn "~0.1.0" - safer-buffer "^2.0.2" tweetnacl "~0.14.0" -string_decoder@^1.1.1: - version "1.2.0" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" - integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== +string_decoder@^1.1.1, string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== dependencies: safe-buffer "~5.1.0" @@ -1931,12 +2150,10 @@ string_decoder@~0.10.x: resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" +stringstream@~0.0.4, stringstream@~0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + integrity sha1-TkhM1N5aC7vuGORjB3EKioFiGHg= strip-ansi@^3.0.0: version "3.0.1" @@ -1985,11 +2202,11 @@ tar-stream@^1.5.2: xtend "^4.0.0" through2@2.X, through2@^2.0.0, through2@^2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== + version "2.0.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" + integrity sha1-AARWmzfHx0ujnEPzzteNGtlBQL4= dependencies: - readable-stream "~2.3.6" + readable-stream "^2.1.5" xtend "~4.0.1" through@^2.3.8: @@ -2021,12 +2238,18 @@ to-buffer@^1.1.1: resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" integrity sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg== -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== +tough-cookie@~2.3.0: + version "2.3.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561" + integrity sha1-C2GKVWW23qkL80JdBNVe3EdadWE= + dependencies: + punycode "^1.4.1" + +tough-cookie@~2.3.3: + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" + integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA== dependencies: - psl "^1.1.24" punycode "^1.4.1" tslib@^1.8.0, tslib@^1.8.1: @@ -2035,9 +2258,9 @@ tslib@^1.8.0, tslib@^1.8.1: integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== tslint@^5.9.1: - version "5.12.1" - resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.12.1.tgz#8cec9d454cf8a1de9b0a26d7bdbad6de362e52c1" - integrity sha512-sfodBHOucFg6egff8d1BvuofoOQ/nOeYNfbp7LDlKBcLNrL3lmS5zoiDGyOMdT7YsEXAwWpTdAHwOGOc8eRZAw== + version "5.11.0" + resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.11.0.tgz#98f30c02eae3cde7006201e4c33cb08b48581eed" + integrity sha1-mPMMAurjzecAYgHkwzywi0hYHu0= dependencies: babel-code-frame "^6.22.0" builtin-modules "^1.1.1" @@ -2084,15 +2307,15 @@ typed-rest-client@^0.9.0: tunnel "0.0.4" underscore "1.8.3" -typescript@3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.2.tgz#fe8101c46aa123f8353523ebdcf5730c2ae493e5" - integrity sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg== +typescript@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.1.tgz#6de14e1db4b8a006ac535e482c8ba018c55f750b" + integrity sha512-cTmIDFW7O0IHbn1DPYjkiebHxwtCMU+eTy30ZtJNBPF9j2O1ITu5XH2YnBeVRKWHqF+3JQwWJv0Q0aUgX8W7IA== uc.micro@^1.0.1, uc.micro@^1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.6.tgz#9c411a802a409a91fc6cf74081baba34b24499ac" - integrity sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA== + version "1.0.5" + resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.5.tgz#0c65f15f815aa08b560a61ce8b4db7ffc3f45376" + integrity sha512-JoLI4g5zv5qNyT09f4YAvEZIIV1oOjqnewYg5D38dkQljIzpPT296dbIGvKro3digYI1bkb7W6EP1y4uDlmzLg== unbzip2-stream@^1.0.9: version "1.3.3" @@ -2112,13 +2335,6 @@ underscore@^1.8.3: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961" integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg== -uri-js@^4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" - integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== - dependencies: - punycode "^2.1.0" - urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" @@ -2134,15 +2350,20 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1: resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= -uuid@^3.0.0, uuid@^3.3.2: - version "3.3.2" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== +uuid@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + integrity sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g== -validator@~9.4.1: - version "9.4.1" - resolved "https://registry.yarnpkg.com/validator/-/validator-9.4.1.tgz#abf466d398b561cd243050112c6ff1de6cc12663" - integrity sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA== +uuid@^3.1.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.2.1.tgz#12c528bb9d58d0b9265d9a2f6f0fe8be17ff1f14" + integrity sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA== + +validator@~3.35.0: + version "3.35.0" + resolved "https://registry.yarnpkg.com/validator/-/validator-3.35.0.tgz#3f07249402c1fc8fc093c32c6e43d72a79cca1dc" + integrity sha1-PwcklALB/I/Ak8MsbkPXKnnModw= verror@1.10.0: version "1.10.0" @@ -2214,12 +2435,12 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xml2js@0.2.8: - version "0.2.8" - resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.2.8.tgz#9b81690931631ff09d1957549faf54f4f980b3c2" - integrity sha1-m4FpCTFjH/CdGVdUn69U9PmAs8I= +xml2js@0.2.7: + version "0.2.7" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.2.7.tgz#1838518bb01741cae0878bab4915e494c32306af" + integrity sha1-GDhRi7AXQcrgh4urSRXklMMjBq8= dependencies: - sax "0.5.x" + sax "0.5.2" xml2js@^0.4.17: version "0.4.19" @@ -2229,10 +2450,15 @@ xml2js@^0.4.17: sax ">=0.6.0" xmlbuilder "~9.0.1" -xmlbuilder@^9.0.7, xmlbuilder@~9.0.1: - version "9.0.7" - resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d" - integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0= +xmlbuilder@0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-0.4.3.tgz#c4614ba74e0ad196e609c9272cd9e1ddb28a8a58" + integrity sha1-xGFLp04K0ZbmCcknLNnh3bKKilg= + +xmlbuilder@~9.0.1: + version "9.0.4" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.4.tgz#519cb4ca686d005a8420d3496f3f0caeecca580f" + integrity sha1-UZy0ymhtAFqEINNJbz8MruzKWA8= xtend@^4.0.0, xtend@~4.0.1: version "4.0.1" @@ -2248,9 +2474,9 @@ yauzl@^2.3.1, yauzl@^2.4.2: fd-slicer "~1.1.0" yazl@^2.2.2: - version "2.5.1" - resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.5.1.tgz#a3d65d3dd659a5b0937850e8609f22fffa2b5c35" - integrity sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw== + version "2.4.3" + resolved "https://registry.yarnpkg.com/yazl/-/yazl-2.4.3.tgz#ec26e5cc87d5601b9df8432dbdd3cd2e5173a071" + integrity sha1-7CblzIfVYBud+EMtvdPNLlFzoHE= dependencies: buffer-crc32 "~0.2.3" diff --git a/cgmanifest.json b/cgmanifest.json index b7e74e3945..afaebd6177 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -73,12 +73,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "bb28fa8e8e797db249a66405146ad0501eaf411a" + "commitHash": "73158a6419a3e2da9e4d523e1131052abd28fbbb" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "3.1.2" + "version": "3.1.6" }, { "component": { diff --git a/extensions/bat/cgmanifest.json b/extensions/bat/cgmanifest.json index 1e21679d7b..5bc3e285f0 100644 --- a/extensions/bat/cgmanifest.json +++ b/extensions/bat/cgmanifest.json @@ -6,11 +6,11 @@ "git": { "name": "mmims/language-batchfile", "repositoryUrl": "https://github.com/mmims/language-batchfile", - "commitHash": "4b67596631b4ecd2c89c2ec1b2e08a6623438903" + "commitHash": "95ea8c699f7a8296b15767069868532d52631c46" } }, "license": "MIT", - "version": "0.7.4" + "version": "0.7.5" } ], "version": 1 diff --git a/extensions/bat/syntaxes/batchfile.tmLanguage.json b/extensions/bat/syntaxes/batchfile.tmLanguage.json index 26ae88f43c..9eff3c9de2 100644 --- a/extensions/bat/syntaxes/batchfile.tmLanguage.json +++ b/extensions/bat/syntaxes/batchfile.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/mmims/language-batchfile/commit/4b67596631b4ecd2c89c2ec1b2e08a6623438903", + "version": "https://github.com/mmims/language-batchfile/commit/95ea8c699f7a8296b15767069868532d52631c46", "name": "Batch File", "scopeName": "source.batchfile", "patterns": [ @@ -46,7 +46,7 @@ "commands": { "patterns": [ { - "match": "(?<=^|[\\s@])(?i:adprep|append|arp|assoc|at|atmadm|attrib|auditpol|autochk|autoconv|autofmt|bcdboot|bcdedit|bdehdcfg|bitsadmin|bootcfg|brea|cacls|cd|certreq|certutil|change|chcp|chdir|chglogon|chgport|chgusr|chkdsk|chkntfs|choice|cipher|clip|cls|clscluadmin|cluster|cmd|cmdkey|cmstp|color|comp|compact|convert|copy|cprofile|cscript|csvde|date|dcdiag|dcgpofix|dcpromo|defra|del|dfscmd|dfsdiag|dfsrmig|diantz|dir|dirquota|diskcomp|diskcopy|diskpart|diskperf|diskraid|diskshadow|dispdiag|doin|dnscmd|doskey|driverquery|dsacls|dsadd|dsamain|dsdbutil|dsget|dsmgmt|dsmod|dsmove|dsquery|dsrm|edit|endlocal|eraseesentutl|eventcreate|eventquery|eventtriggers|evntcmd|expand|extract|fc|filescrn|find|findstr|finger|flattemp|fonde|forfiles|format|freedisk|fsutil|ftp|ftype|fveupdate|getmac|gettype|gpfixup|gpresult|gpupdate|graftabl|hashgen|hep|helpctr|hostname|icacls|iisreset|inuse|ipconfig|ipxroute|irftp|ismserv|jetpack|klist|ksetup|ktmutil|ktpass|label|ldifd|ldp|lodctr|logman|logoff|lpq|lpr|macfile|makecab|manage-bde|mapadmin|md|mkdir|mklink|mmc|mode|more|mount|mountvol|move|mqbup|mqsvc|mqtgsvc|msdt|msg|msiexec|msinfo32|mstsc|nbtstat|net computer|net group|net localgroup|net print|net session|net share|net start|net stop|net use|net user|net view|net|netcfg|netdiag|netdom|netsh|netstat|nfsadmin|nfsshare|nfsstat|nlb|nlbmgr|nltest|nslookup|ntackup|ntcmdprompt|ntdsutil|ntfrsutl|openfiles|pagefileconfig|path|pathping|pause|pbadmin|pentnt|perfmon|ping|pnpunatten|pnputil|popd|powercfg|powershell|powershell_ise|print|prncnfg|prndrvr|prnjobs|prnmngr|prnport|prnqctl|prompt|pubprn|pushd|pushprinterconnections|pwlauncher|qappsrv|qprocess|query|quser|qwinsta|rasdial|rcp|rd|rdpsign|regentc|recover|redircmp|redirusr|reg|regini|regsvr32|relog|ren|rename|rendom|repadmin|repair-bde|replace|reset session|rxec|risetup|rmdir|robocopy|route|rpcinfo|rpcping|rsh|runas|rundll32|rwinsta|sc|schtasks|scwcmd|secedit|serverceipoptin|servrmanagercmd|serverweroptin|setspn|setx|sfc|shadow|shift|showmount|shutdown|sort|start|storrept|subst|sxstrace|ysocmgr|systeminfo|takeown|tapicfg|taskkill|tasklist|tcmsetup|telnet|tftp|time|timeout|title|tlntadmn|tpmvscmgr|tpmvscmgr|tacerpt|tracert|tree|tscon|tsdiscon|tsecimp|tskill|tsprof|type|typeperf|tzutil|uddiconfig|umount|unlodctr|ver|verifier|verif|vol|vssadmin|w32tm|waitfor|wbadmin|wdsutil|wecutil|wevtutil|where|whoami|winnt|winnt32|winpop|winrm|winrs|winsat|wlbs|mic|wscript|xcopy)(?=$|\\s)", + "match": "(?<=^|[\\s@])(?i:adprep|append|arp|assoc|at|atmadm|attrib|auditpol|autochk|autoconv|autofmt|bcdboot|bcdedit|bdehdcfg|bitsadmin|bootcfg|brea|cacls|cd|certreq|certutil|change|chcp|chdir|chglogon|chgport|chgusr|chkdsk|chkntfs|choice|cipher|clip|cls|clscluadmin|cluster|cmd|cmdkey|cmstp|color|comp|compact|convert|copy|cprofile|cscript|csvde|date|dcdiag|dcgpofix|dcpromo|defra|del|dfscmd|dfsdiag|dfsrmig|diantz|dir|dirquota|diskcomp|diskcopy|diskpart|diskperf|diskraid|diskshadow|dispdiag|doin|dnscmd|doskey|driverquery|dsacls|dsadd|dsamain|dsdbutil|dsget|dsmgmt|dsmod|dsmove|dsquery|dsrm|edit|endlocal|eraseesentutl|eventcreate|eventquery|eventtriggers|evntcmd|expand|extract|fc|filescrn|find|findstr|finger|flattemp|fonde|forfiles|format|freedisk|fsutil|ftp|ftype|fveupdate|getmac|gettype|gpfixup|gpresult|gpupdate|graftabl|hashgen|hep|helpctr|hostname|icacls|iisreset|inuse|ipconfig|ipxroute|irftp|ismserv|jetpack|klist|ksetup|ktmutil|ktpass|label|ldifd|ldp|lodctr|logman|logoff|lpq|lpr|macfile|makecab|manage-bde|mapadmin|md|mkdir|mklink|mmc|mode|more|mount|mountvol|move|mqbup|mqsvc|mqtgsvc|msdt|msg|msiexec|msinfo32|mstsc|nbtstat|net computer|net group|net localgroup|net print|net session|net share|net start|net stop|net use|net user|net view|net|netcfg|netdiag|netdom|netsh|netstat|nfsadmin|nfsshare|nfsstat|nlb|nlbmgr|nltest|nslookup|ntackup|ntcmdprompt|ntdsutil|ntfrsutl|openfiles|pagefileconfig|path|pathping|pause|pbadmin|pentnt|perfmon|ping|pnpunatten|pnputil|popd|powercfg|powershell|powershell_ise|print|prncnfg|prndrvr|prnjobs|prnmngr|prnport|prnqctl|prompt|pubprn|pushd|pushprinterconnections|pwlauncher|qappsrv|qprocess|query|quser|qwinsta|rasdial|rcp|rd|rdpsign|regentc|recover|redircmp|redirusr|reg|regini|regsvr32|relog|ren|rename|rendom|repadmin|repair-bde|replace|reset session|rxec|risetup|rmdir|robocopy|route|rpcinfo|rpcping|rsh|runas|rundll32|rwinsta|scp|sc|schtasks|scwcmd|secedit|serverceipoptin|servrmanagercmd|serverweroptin|setspn|setx|sfc|shadow|shift|showmount|shutdown|sort|ssh|start|storrept|subst|sxstrace|ysocmgr|systeminfo|takeown|tapicfg|taskkill|tasklist|tcmsetup|telnet|tftp|time|timeout|title|tlntadmn|tpmvscmgr|tpmvscmgr|tacerpt|tracert|tree|tscon|tsdiscon|tsecimp|tskill|tsprof|type|typeperf|tzutil|uddiconfig|umount|unlodctr|ver|verifier|verif|vol|vssadmin|w32tm|waitfor|wbadmin|wdsutil|wecutil|wevtutil|where|whoami|winnt|winnt32|winpop|winrm|winrs|winsat|wlbs|mic|wscript|xcopy)(?=$|\\s)", "name": "keyword.command.batchfile" }, { diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index 46f4eba3a9..9a1a95f5fb 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -96,6 +96,6 @@ ] }, "devDependencies": { - "@types/node": "^8.10.25" + "@types/node": "^10.12.21" } } diff --git a/extensions/configuration-editing/src/extension.ts b/extensions/configuration-editing/src/extension.ts index 3422eec6f1..427c9ee620 100644 --- a/extensions/configuration-editing/src/extension.ts +++ b/extensions/configuration-editing/src/extension.ts @@ -22,9 +22,6 @@ const fadedDecoration = vscode.window.createTextEditorDecorationType({ let pendingLaunchJsonDecoration: NodeJS.Timer; export function activate(context: vscode.ExtensionContext): void { - //keybindings.json command-suggestions - context.subscriptions.push(registerKeybindingsCompletions()); - //settings.json suggestions context.subscriptions.push(registerSettingsCompletions()); @@ -94,23 +91,6 @@ function autoFixSettingsJSON(willSaveEvent: vscode.TextDocumentWillSaveEvent): v vscode.workspace.applyEdit(edit)); } -function registerKeybindingsCompletions(): vscode.Disposable { - const commands = vscode.commands.getCommands(true); - - return vscode.languages.registerCompletionItemProvider({ pattern: '**/keybindings.json' }, { - - provideCompletionItems(document, position, _token) { - const location = getLocation(document.getText(), document.offsetAt(position)); - if (location.path[1] === 'command') { - - const range = document.getWordRangeAtPosition(position) || new vscode.Range(position, position); - return commands.then(ids => ids.map(id => newSimpleCompletionItem(JSON.stringify(id), range))); - } - return undefined; - } - }); -} - function registerSettingsCompletions(): vscode.Disposable { return vscode.languages.registerCompletionItemProvider({ language: 'jsonc', pattern: '**/settings.json' }, { provideCompletionItems(document, position, token) { @@ -207,16 +187,6 @@ function provideInstalledExtensionProposals(extensionsContent: IExtensionsConten return undefined; } -function newSimpleCompletionItem(label: string, range: vscode.Range, description?: string, insertText?: string): vscode.CompletionItem { - const item = new vscode.CompletionItem(label); - item.kind = vscode.CompletionItemKind.Value; - item.detail = description; - item.insertText = insertText || label; - item.range = range; - - return item; -} - function updateLaunchJsonDecorations(editor: vscode.TextEditor | undefined): void { if (!editor || path.basename(editor.document.fileName) !== 'launch.json') { return; diff --git a/extensions/configuration-editing/yarn.lock b/extensions/configuration-editing/yarn.lock index 752f2a3a6f..49c4716606 100644 --- a/extensions/configuration-editing/yarn.lock +++ b/extensions/configuration-editing/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/node@^8.10.25": - version "8.10.25" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.25.tgz#801fe4e39372cef18f268db880a5fbfcf71adc7e" - integrity sha512-WXvAXaknB0c2cJ7N44e1kUrVu5K90mSfPPaT5XxfuSMxEWva86EYIwxUZM3jNZ2P1CIC9e2z4WJqpAF69PQxeA== +"@types/node@^10.12.21": + version "10.12.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" + integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== jsonc-parser@2.0.2: version "2.0.2" diff --git a/extensions/dacpac/package.json b/extensions/dacpac/package.json index 1a1274d897..2e6d556c8f 100644 --- a/extensions/dacpac/package.json +++ b/extensions/dacpac/package.json @@ -1,49 +1,54 @@ { - "name": "dacpac", - "displayName": "SQL Server Dacpac", - "description": "SQL Server Dacpac for Azure Data Studio.", - "version": "0.1.0", - "publisher": "Microsoft", - "preview": true, - "engines": { - "vscode": "^1.25.0", - "sqlops": "*" - }, - "license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt", - "icon": "images/sqlserver.png", - "aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412", - "activationEvents": [ - "*" - ], - "main": "./out/main", - "repository": { - "type": "git", - "url": "https://github.com/Microsoft/azuredatastudio.git" - }, - "extensionDependencies": [ - "Microsoft.mssql" - ], - "contributes": { - "commands": [ - { - "command": "dacFx.start", - "title": "Data-tier Application wizard", - "category": "Data-tier Application" - } - ], - "menus": { - "objectExplorer/item/context": [ - { - "command": "dacFx.start", - "when": "connectionProvider == MSSQL && nodeType && nodeType == Database", - "group": "export" - } - ] - } - }, - "dependencies": { - "htmlparser2": "^3.10.1", - "vscode-nls": "^3.2.1" - }, - "devDependencies": {} -} + "name": "dacpac", + "displayName": "SQL Server Dacpac", + "description": "SQL Server Dacpac for Azure Data Studio.", + "version": "0.1.0", + "publisher": "Microsoft", + "preview": true, + "engines": { + "vscode": "^1.25.0", + "sqlops": "*" + }, + "license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt", + "icon": "images/sqlserver.png", + "aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412", + "activationEvents": [ + "*" + ], + "main": "./out/main", + "repository": { + "type": "git", + "url": "https://github.com/Microsoft/azuredatastudio.git" + }, + "extensionDependencies": [ + "Microsoft.mssql" + ], + "contributes": { + "commands": [ + { + "command": "dacFx.start", + "title": "Data-tier Application wizard", + "category": "Data-tier Application" + } + ], + "menus": { + "objectExplorer/item/context": [ + { + "command": "dacFx.start", + "when": "connectionProvider == MSSQL && nodeType && nodeType == Database", + "group": "export" + } + ] + } + }, + "dependencies": { + "htmlparser2": "^3.10.1", + "vscode-nls": "^3.2.1" + }, + "devDependencies": {}, + "__metadata": { + "id": "33", + "publisherDisplayName": "Microsoft", + "publisherId": "Microsoft" + } +} \ No newline at end of file diff --git a/extensions/docker/cgmanifest.json b/extensions/docker/cgmanifest.json index 844d3120d3..dbbf43607e 100644 --- a/extensions/docker/cgmanifest.json +++ b/extensions/docker/cgmanifest.json @@ -15,4 +15,4 @@ } ], "version": 1 -} +} \ No newline at end of file diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index 8ceab79d04..52159b965b 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -53,6 +53,6 @@ }, "devDependencies": { "@types/markdown-it": "0.0.2", - "@types/node": "^8.10.25" + "@types/node": "^10.12.21" } } diff --git a/extensions/extension-editing/yarn.lock b/extensions/extension-editing/yarn.lock index d82f7c212a..720c84f065 100644 --- a/extensions/extension-editing/yarn.lock +++ b/extensions/extension-editing/yarn.lock @@ -7,16 +7,16 @@ resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.2.tgz#5d9ad19e6e6508cdd2f2596df86fd0aade598660" integrity sha1-XZrRnm5lCM3S8llt+G/Qqt5ZhmA= +"@types/node@^10.12.21": + version "10.12.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" + integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== + "@types/node@^6.0.46": version "6.0.78" resolved "https://registry.yarnpkg.com/@types/node/-/node-6.0.78.tgz#5d4a3f579c1524e01ee21bf474e6fba09198f470" integrity sha512-+vD6E8ixntRzzZukoF3uP1iV+ZjVN3koTcaeK+BEoc/kSfGbLDIGC7RmCaUgVpUfN6cWvfczFRERCyKM9mkvXg== -"@types/node@^8.10.25": - version "8.10.25" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.25.tgz#801fe4e39372cef18f268db880a5fbfcf71adc7e" - integrity sha512-WXvAXaknB0c2cJ7N44e1kUrVu5K90mSfPPaT5XxfuSMxEWva86EYIwxUZM3jNZ2P1CIC9e2z4WJqpAF69PQxeA== - argparse@^1.0.7: version "1.0.9" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" diff --git a/extensions/git/cgmanifest.json b/extensions/git/cgmanifest.json index a7e0b63aac..d0bdb9ac44 100644 --- a/extensions/git/cgmanifest.json +++ b/extensions/git/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "textmate/git.tmbundle", "repositoryUrl": "https://github.com/textmate/git.tmbundle", - "commitHash": "93897a78c6e52bef13dadc0d4091d203c5facb40" + "commitHash": "3f6ad2138200db14b57a090ecb2d2e733275ca3e" } }, "licenseDetail": [ @@ -63,4 +63,4 @@ } ], "version": 1 -} +} \ No newline at end of file diff --git a/extensions/git/package.json b/extensions/git/package.json index fdc08df06a..b6f31076e7 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -320,6 +320,16 @@ "title": "%command.pushWithTagsForce%", "category": "Git" }, + { + "command": "git.addRemote", + "title": "%command.addRemote%", + "category": "Git" + }, + { + "command": "git.removeRemote", + "title": "%command.removeRemote%", + "category": "Git" + }, { "command": "git.sync", "title": "%command.sync%", @@ -570,6 +580,14 @@ "command": "git.pushWithTagsForce", "when": "config.git.enabled && config.git.allowForcePush && gitOpenRepositoryCount != 0" }, + { + "command": "git.addRemote", + "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, + { + "command": "git.removeRemote", + "when": "config.git.enabled && gitOpenRepositoryCount != 0" + }, { "command": "git.sync", "when": "config.git.enabled && gitOpenRepositoryCount != 0" @@ -1150,6 +1168,7 @@ "%config.postCommitCommand.sync%" ], "markdownDescription": "%config.postCommitCommand%", + "scope": "resource", "default": "none" }, "git.showInlineOpenFileAction": { @@ -1182,7 +1201,7 @@ "number", "null" ], - "default": null, + "default": 50, "description": "%config.inputValidationSubjectLength%" }, "git.detectSubmodules": { @@ -1425,7 +1444,7 @@ "@types/byline": "4.2.31", "@types/file-type": "^5.2.1", "@types/mocha": "2.2.43", - "@types/node": "^8.10.25", + "@types/node": "^10.12.21", "@types/which": "^1.0.28", "mocha": "^3.2.0" } diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 4f749189fc..21ac86d8fb 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -48,6 +48,8 @@ "command.pushToForce": "Push to... (Force)", "command.pushWithTags": "Push With Tags", "command.pushWithTagsForce": "Push With Tags (Force)", + "command.addRemote": "Add Remote", + "command.removeRemote": "Remove Remote", "command.sync": "Sync", "command.syncRebase": "Sync (Rebase)", "command.publish": "Publish Branch", diff --git a/extensions/git/src/askpass.ts b/extensions/git/src/askpass.ts index e1acad6cfd..1ee9388c44 100644 --- a/extensions/git/src/askpass.ts +++ b/extensions/git/src/askpass.ts @@ -65,7 +65,7 @@ export class Askpass implements Disposable { return ipcHandlePath; } - private onRequest(req: http.ServerRequest, res: http.ServerResponse): void { + private onRequest(req: http.IncomingMessage, res: http.ServerResponse): void { const chunks: string[] = []; req.setEncoding('utf8'); req.on('data', (d: string) => chunks.push(d)); diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index bc616c682b..27bb283c4a 100755 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -649,14 +649,16 @@ export class CommandCenter { if (!(resource instanceof Resource)) { // can happen when called from a keybinding + console.log('WHAT'); resource = this.getSCMResource(); } if (resource) { - const resources = ([resource, ...resourceStates] as Resource[]) - .filter(r => r.type !== Status.DELETED && r.type !== Status.INDEX_DELETED); - - uris = resources.map(r => r.resourceUri); + uris = ([resource, ...resourceStates] as Resource[]) + .filter(r => r.type !== Status.DELETED && r.type !== Status.INDEX_DELETED) + .map(r => r.resourceUri); + } else if (window.activeTextEditor) { + uris = [window.activeTextEditor.document.uri]; } } @@ -665,6 +667,7 @@ export class CommandCenter { } const activeTextEditor = window.activeTextEditor; + for (const uri of uris) { const opts: TextDocumentShowOptions = { preserveFocus, @@ -1458,8 +1461,7 @@ export class CommandCenter { name.trim().replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, branchWhitespaceChar) : name; - const rawBranchName = await window.showInputBox({ - value: defaultName, + const rawBranchName = defaultName || await window.showInputBox({ placeHolder: localize('branch name', "Branch name"), prompt: localize('provide branch name', "Please provide a branch name"), ignoreFocusOut: true, @@ -1480,7 +1482,7 @@ export class CommandCenter { } const picks = [new HEADItem(repository), ...createCheckoutItems(repository)]; - const placeHolder = localize('select a ref to create a new branch from', 'Select a ref to create a new branch from'); + const placeHolder = localize('select a ref to create a new branch from', 'Select a ref to create the \'{0}\' branch from', branchName); const target = await window.showQuickPick(picks, { placeHolder }); if (!target) { @@ -1801,6 +1803,72 @@ export class CommandCenter { await this._push(repository, { pushType: PushType.PushTo, forcePush: true }); } + @command('git.addRemote', { repository: true }) + async addRemote(repository: Repository): Promise { + const remotes = repository.remotes; + + const sanitize = (name: string) => { + name = name.trim(); + return name && name.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, '-'); + }; + + const resultName = await window.showInputBox({ + placeHolder: localize('remote name', "Remote name"), + prompt: localize('provide remote name', "Please provide a remote name"), + ignoreFocusOut: true, + validateInput: (name: string) => { + if (sanitize(name)) { + return null; + } + return localize('remote name format invalid', "Remote name format invalid"); + } + }); + + const name = sanitize(resultName || ''); + + if (!name) { + return; + } + + if (remotes.find(r => r.name === name)) { + window.showErrorMessage(localize('remote already exists', "Remote '{0}' already exists.", name)); + return; + } + + const url = await window.showInputBox({ + placeHolder: localize('remote url', "Remote URL"), + prompt: localize('provide remote URL', "Enter URL for remote \"{0}\"", name), + ignoreFocusOut: true + }); + + if (!url) { + return; + } + + await repository.addRemote(name, url); + } + + @command('git.removeRemote', { repository: true }) + async removeRemote(repository: Repository): Promise { + const remotes = repository.remotes; + + if (remotes.length === 0) { + window.showErrorMessage(localize('no remotes added', "Your repository has no remotes.")); + return; + } + + const picks = remotes.map(r => r.name); + const placeHolder = localize('remove remote', "Pick a remote to remove"); + + const remoteName = await window.showQuickPick(picks, { placeHolder }); + + if (!remoteName) { + return; + } + + await repository.removeRemote(remoteName); + } + private async _sync(repository: Repository, rebase: boolean): Promise { const HEAD = repository.HEAD; @@ -2118,6 +2186,7 @@ export class CommandCenter { uri = uri ? uri : (window.activeTextEditor && window.activeTextEditor.document.uri); this.outputChannel.appendLine(`git.getSCMResource.uri ${uri && uri.toString()}`); + for (const r of this.model.repositories.map(r => r.root)) { this.outputChannel.appendLine(`repo root ${r}`); } diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index 85fed8c42c..4623328768 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -20,7 +20,6 @@ class GitIgnoreDecorationProvider implements DecorationProvider { private disposables: Disposable[] = []; constructor(private model: Model) { - //todo@joh -> events when the ignore status actually changes, not only when the file changes this.onDidChangeDecorations = fireEvent(anyEvent( filterEvent(workspace.onDidSaveTextDocument, e => e.fileName.endsWith('.gitignore')), model.onDidOpenRepository, @@ -119,7 +118,7 @@ class GitDecorationProvider implements DecorationProvider { const uris = new Set([...this.decorations.keys()].concat([...newDecorations.keys()])); this.decorations = newDecorations; - this._onDidChangeDecorations.fire([...uris.values()].map(Uri.parse)); + this._onDidChangeDecorations.fire([...uris.values()].map(value => Uri.parse(value, true))); } private collectDecorationData(group: GitResourceGroup, bucket: Map): void { diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index d38d9b58db..616eaeae6f 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -349,7 +349,7 @@ export class Git { await mkdirp(parentPath); try { - await this.exec(parentPath, ['clone', url, folderPath], { cancellationToken }); + await this.exec(parentPath, ['clone', url.includes(' ') ? encodeURI(url) : url, folderPath], { cancellationToken }); } catch (err) { if (err.stderr) { err.stderr = err.stderr.replace(/^Cloning.+$/m, '').trim(); @@ -1201,7 +1201,7 @@ export class Repository { } async branch(name: string, checkout: boolean, ref?: string): Promise { - const args = checkout ? ['checkout', '-q', '-b', name] : ['branch', '-q', name]; + const args = checkout ? ['checkout', '-q', '-b', name, '--no-track'] : ['branch', '-q', name]; if (ref) { args.push(ref); @@ -1456,14 +1456,14 @@ export class Repository { async createStash(message?: string, includeUntracked?: boolean): Promise { try { - const args = ['stash', 'save']; + const args = ['stash', 'push']; if (includeUntracked) { args.push('-u'); } if (message) { - args.push('--', message); + args.push('-m', message); } await this.run(args); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 4010fc0f4a..26888a11a2 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -206,7 +206,7 @@ export class Resource implements SourceControlResourceState { case Status.INDEX_ADDED: case Status.INTENT_TO_ADD: return new ThemeColor('gitDecoration.addedResourceForeground'); - case Status.INDEX_RENAMED: // todo@joh - special color? + case Status.INDEX_RENAMED: case Status.UNTRACKED: return new ThemeColor('gitDecoration.untrackedResourceForeground'); case Status.IGNORED: @@ -673,20 +673,6 @@ export class Repository implements Disposable { } } - - - - - - - - - - // const subjectThreshold = - - - // Math.max(config.get('inputValidationLength') || 50, config.get('subjectValidationLength') || 50, 0) || 50; - if (line.length <= threshold) { if (setting !== 'always') { return; diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index 1d859c5e73..f13ef93c34 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -26,10 +26,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.51.tgz#b31d716fb8d58eeb95c068a039b9b6292817d5fb" integrity sha512-El3+WJk2D/ppWNd2X05aiP5l2k4EwF7KwheknQZls+I26eSICoWRhRIJ56jGgw2dqNGQ5LtNajmBU2ajS28EvQ== -"@types/node@^8.10.25": - version "8.10.25" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.25.tgz#801fe4e39372cef18f268db880a5fbfcf71adc7e" - integrity sha512-WXvAXaknB0c2cJ7N44e1kUrVu5K90mSfPPaT5XxfuSMxEWva86EYIwxUZM3jNZ2P1CIC9e2z4WJqpAF69PQxeA== +"@types/node@^10.12.21": + version "10.12.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" + integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== "@types/which@^1.0.28": version "1.0.28" diff --git a/extensions/json-language-features/.vscodeignore b/extensions/json-language-features/.vscodeignore index b3cba2c6a8..3011e9dcd0 100644 --- a/extensions/json-language-features/.vscodeignore +++ b/extensions/json-language-features/.vscodeignore @@ -13,5 +13,6 @@ server/build/** server/yarn.lock server/.npmignore yarn.lock +CONTRIBUTING.md server/extension.webpack.config.js extension.webpack.config.js \ No newline at end of file diff --git a/extensions/json-language-features/CONTRIBUTING.md b/extensions/json-language-features/CONTRIBUTING.md new file mode 100644 index 0000000000..f223cae3b7 --- /dev/null +++ b/extensions/json-language-features/CONTRIBUTING.md @@ -0,0 +1,39 @@ +## Setup + +- Clone [Microsoft/vscode](https://github.com/microsoft/vscode) +- Run `yarn` at `/`, this will install + - Dependencies for `/extension/json-language-features/` + - Dependencies for `/extension/json-language-features/server/` + - devDependencies such as `gulp` +- Open `/extensions/json-language-features/` as the workspace in VS Code +- Run the [`Launch Extension`](https://github.com/Microsoft/vscode/blob/master/extensions/json-language-features/.vscode/launch.json) debug target in the Debug View. This will: + - Launch the `preLaunchTask` task to compile the extension + - Launch a new VS Code instance with the `json-language-features` extension loaded + - You should see a notification saying the development version of `json-language-features` overwrites the bundled version of `json-language-features` +- Open a `.json` file to activate the extension. The extension will start the JSON language server process. +- Add `"json.trace.server": "verbose"` to the settings to observe the communication between client and server. +- Debug the language server process by using `Attach to Node Process` command in the VS Code window opened on `json-language-features` +- Run `Reload Window` command in the launched instance to reload the extension + + +### Contribute to vscode-json-languageservice + +[Microsoft/vscode-json-languageservice](https://github.com/Microsoft/vscode-json-languageservice) is the library that implements the language smarts for JSON. +The JSON language server forwards most the of requests to the service library. +If you want to fix JSON issues or make improvements, you should make changes at [Microsoft/vscode-json-languageservice](https://github.com/Microsoft/vscode-json-languageservice). + +However, within this extension, you can run a development version of `vscode-json-languageservice` to debug code or test language features interactively: + +#### Linking `vscode-json-languageservice` in `json-language-features/server/` + +- Clone [Microsoft/vscode-json-languageservice](https://github.com/Microsoft/vscode-json-languageservice) +- Run `yarn` in `vscode-json-languageservice` +- Run `yarn link` in `vscode-json-languageservice`. This will compile and link `vscode-json-languageservice` +- In `json-language-features/server/`, run `npm link vscode-json-languageservice` + +#### Testing the development version of `vscode-json-languageservice` + +- Open both `vscode-json-languageservice` and this extension in a single workspace with [multi-root workspace](https://code.visualstudio.com/docs/editor/multi-root-workspaces) feature +- Run `yarn watch` at `json-languagefeatures/server/` to recompile this extension with the linked version of `vscode-json-languageservice` +- Make some changes in `vscode-json-languageservice` +- Now when you run `Launch Extension` debug target, the launched instance will use your development version of `vscode-json-languageservice`. You can interactively test the language features. \ No newline at end of file diff --git a/extensions/json-language-features/client/src/jsonMain.ts b/extensions/json-language-features/client/src/jsonMain.ts index 83f0e13783..6a889d7cea 100644 --- a/extensions/json-language-features/client/src/jsonMain.ts +++ b/extensions/json-language-features/client/src/jsonMain.ts @@ -6,9 +6,11 @@ import * as path from 'path'; import * as fs from 'fs'; import * as nls from 'vscode-nls'; +import { xhr, XHRResponse, getErrorStatusDescription } from 'request-light'; + const localize = nls.loadMessageBundle(); -import { workspace, window, languages, commands, ExtensionContext, extensions, Uri, LanguageConfiguration, Diagnostic, StatusBarAlignment, TextEditor, TextDocument, Position, SelectionRange, Range, SelectionRangeKind } from 'vscode'; +import { workspace, window, languages, commands, ExtensionContext, extensions, Uri, LanguageConfiguration, Diagnostic, StatusBarAlignment, TextEditor, TextDocument, Position, SelectionRange } from 'vscode'; import { LanguageClient, LanguageClientOptions, RequestType, ServerOptions, TransportKind, NotificationType, DidChangeConfigurationNotification, HandleDiagnosticsSignature } from 'vscode-languageclient'; import TelemetryReporter from 'vscode-extension-telemetry'; @@ -93,6 +95,9 @@ export function activate(context: ExtensionContext) { let clientOptions: LanguageClientOptions = { // Register the server for json documents documentSelector, + initializationOptions: { + handledSchemaProtocols: ['file'] // language server only loads file-URI. Fetching schemas with other protocols ('http'...) are made on the client. + }, synchronize: { // Synchronize the setting section 'json' to the server configurationSection: ['json', 'http'], @@ -138,11 +143,20 @@ export function activate(context: ExtensionContext) { // handle content request client.onRequest(VSCodeContentRequest.type, (uriPath: string) => { let uri = Uri.parse(uriPath); - return workspace.openTextDocument(uri).then(doc => { - return doc.getText(); - }, error => { - return Promise.reject(error); - }); + if (uri.scheme !== 'http' && uri.scheme !== 'https') { + return workspace.openTextDocument(uri).then(doc => { + return doc.getText(); + }, error => { + return Promise.reject(error); + }); + } else { + const headers = { 'Accept-Encoding': 'gzip, deflate' }; + return xhr({ url: uriPath, followRedirects: 5, headers }).then(response => { + return response.responseText; + }, (error: XHRResponse) => { + return Promise.reject(error.responseText || getErrorStatusDescription(error.status) || error.toString()); + }); + } }); let handleContentChange = (uri: Uri) => { @@ -200,15 +214,17 @@ export function activate(context: ExtensionContext) { documentSelector.forEach(selector => { toDispose.push(languages.registerSelectionRangeProvider(selector, { - async provideSelectionRanges(document: TextDocument, position: Position): Promise { + async provideSelectionRanges(document: TextDocument, positions: Position[]): Promise { const textDocument = client.code2ProtocolConverter.asTextDocumentIdentifier(document); - const rawRanges = await client.sendRequest('$/textDocument/selectionRange', { textDocument, position }); - if (Array.isArray(rawRanges)) { - return rawRanges.map(r => { - return { - range: client.protocol2CodeConverter.asRange(r), - kind: SelectionRangeKind.Declaration - }; + const rawResult = await client.sendRequest('$/textDocument/selectionRanges', { textDocument, positions: positions.map(client.code2ProtocolConverter.asPosition) }); + if (Array.isArray(rawResult)) { + return rawResult.map(rawSelectionRanges => { + return rawSelectionRanges.reduceRight((parent: SelectionRange | undefined, selectionRange: SelectionRange) => { + return { + range: client.protocol2CodeConverter.asRange(selectionRange.range), + parent, + }; + }, undefined)!; }); } return []; diff --git a/extensions/json-language-features/extension.webpack.config.js b/extensions/json-language-features/extension.webpack.config.js index 7917ffcf30..57819b3149 100644 --- a/extensions/json-language-features/extension.webpack.config.js +++ b/extensions/json-language-features/extension.webpack.config.js @@ -9,6 +9,7 @@ const withDefaults = require('../shared.webpack.config'); const path = require('path'); +var webpack = require('webpack'); module.exports = withDefaults({ context: path.join(__dirname, 'client'), @@ -18,5 +19,9 @@ module.exports = withDefaults({ output: { filename: 'jsonMain.js', path: path.join(__dirname, 'client', 'dist') - } + }, + plugins: [ + new webpack.IgnorePlugin(/vertx/) // request-light dependendeny + ] + }); diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 6e982dac41..9dc4f87d9f 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -102,10 +102,11 @@ }, "dependencies": { "vscode-extension-telemetry": "0.1.1", - "vscode-languageclient": "^5.1.0", - "vscode-nls": "^4.0.0" + "vscode-languageclient": "^5.2.1", + "vscode-nls": "^4.0.0", + "request-light": "^0.2.4" }, "devDependencies": { - "@types/node": "^8.10.25" + "@types/node": "^10.12.21" } -} \ No newline at end of file +} diff --git a/extensions/json-language-features/server/build/filesFillIn.js b/extensions/json-language-features/server/build/filesFillIn.js deleted file mode 100644 index 389046bae1..0000000000 --- a/extensions/json-language-features/server/build/filesFillIn.js +++ /dev/null @@ -1,5 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -module.exports = {}; \ No newline at end of file diff --git a/extensions/json-language-features/server/extension.webpack.config.js b/extensions/json-language-features/server/extension.webpack.config.js index 0cc8ff2da1..2a77c2ba2f 100644 --- a/extensions/json-language-features/server/extension.webpack.config.js +++ b/extensions/json-language-features/server/extension.webpack.config.js @@ -21,10 +21,6 @@ module.exports = withDefaults({ path: path.join(__dirname, 'dist') }, plugins: [ - new webpack.NormalModuleReplacementPlugin( - /[/\\]vscode-languageserver[/\\]lib[/\\]files\.js/, - require.resolve('./build/filesFillIn') - ), - new webpack.IgnorePlugin(/vertx/) - ], + new webpack.IgnorePlugin(/vertx/) // request-light dependendeny + ] }); diff --git a/extensions/json-language-features/server/package.json b/extensions/json-language-features/server/package.json index 0868d9aa04..fc2323a773 100644 --- a/extensions/json-language-features/server/package.json +++ b/extensions/json-language-features/server/package.json @@ -12,16 +12,16 @@ }, "main": "./out/jsonServerMain", "dependencies": { - "jsonc-parser": "^2.0.2", + "jsonc-parser": "^2.0.3", "request-light": "^0.2.4", - "vscode-json-languageservice": "^3.3.0-next.0", - "vscode-languageserver": "^5.1.0", + "vscode-json-languageservice": "^3.3.0-next.6", + "vscode-languageserver": "^5.3.0-next.2", "vscode-nls": "^4.0.0", "vscode-uri": "^1.0.6" }, "devDependencies": { "@types/mocha": "2.2.33", - "@types/node": "^8.10.25" + "@types/node": "^10.12.21" }, "scripts": { "prepublishOnly": "npm run clean && npm run test", diff --git a/extensions/json-language-features/server/src/jsonServerMain.ts b/extensions/json-language-features/server/src/jsonServerMain.ts index 15e7ea72d4..31be048d24 100644 --- a/extensions/json-language-features/server/src/jsonServerMain.ts +++ b/extensions/json-language-features/server/src/jsonServerMain.ts @@ -13,7 +13,6 @@ import { xhr, XHRResponse, configure as configureHttpRequests, getErrorStatusDes import * as fs from 'fs'; import URI from 'vscode-uri'; import * as URL from 'url'; -import { startsWith } from './utils/strings'; import { formatError, runSafe, runSafeAsync } from './utils/runner'; import { JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration } from 'vscode-json-languageservice'; import { getLanguageModelCache } from './languageModelCache'; @@ -57,46 +56,51 @@ const workspaceContext = { return URL.resolve(resource, relativePath); } }; +function getSchemaRequestService(handledSchemas: { [schema: string]: boolean }) { -const schemaRequestService = (uri: string): Thenable => { - if (startsWith(uri, 'file://')) { - const fsPath = URI.parse(uri).fsPath; - return new Promise((c, e) => { - fs.readFile(fsPath, 'UTF-8', (err, result) => { - err ? e(err.message || err.toString()) : c(result.toString()); - }); - }); - } else if (startsWith(uri, 'vscode://')) { + return (uri: string): Thenable => { + const protocol = uri.substr(0, uri.indexOf(':')); + + if (!handledSchemas || handledSchemas[protocol]) { + if (protocol === 'file') { + const fsPath = URI.parse(uri).fsPath; + return new Promise((c, e) => { + fs.readFile(fsPath, 'UTF-8', (err, result) => { + err ? e(err.message || err.toString()) : c(result.toString()); + }); + }); + } else if (protocol === 'http' || protocol === 'https') { + if (uri.indexOf('//schema.management.azure.com/') !== -1) { + /* __GDPR__ + "json.schema" : { + "schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + connection.telemetry.logEvent({ + key: 'json.schema', + value: { + schemaURL: uri + } + }); + } + const headers = { 'Accept-Encoding': 'gzip, deflate' }; + return xhr({ url: uri, followRedirects: 5, headers }).then(response => { + return response.responseText; + }, (error: XHRResponse) => { + return Promise.reject(error.responseText || getErrorStatusDescription(error.status) || error.toString()); + }); + } + } return connection.sendRequest(VSCodeContentRequest.type, uri).then(responseText => { return responseText; }, error => { return Promise.reject(error.message); }); - } - if (uri.indexOf('//schema.management.azure.com/') !== -1) { - /* __GDPR__ - "json.schema" : { - "schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - connection.telemetry.logEvent({ - key: 'json.schema', - value: { - schemaURL: uri - } - }); - } - const headers = { 'Accept-Encoding': 'gzip, deflate' }; - return xhr({ url: uri, followRedirects: 5, headers }).then(response => { - return response.responseText; - }, (error: XHRResponse) => { - return Promise.reject(error.responseText || getErrorStatusDescription(error.status) || error.toString()); - }); -}; + }; +} // create the JSON language service let languageService = getLanguageService({ - schemaRequestService, workspaceContext, contributions: [], }); @@ -117,8 +121,10 @@ let hierarchicalDocumentSymbolSupport = false; // in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { + const handledProtocols = params.initializationOptions && params.initializationOptions['handledSchemaProtocols']; + languageService = getLanguageService({ - schemaRequestService, + schemaRequestService: getSchemaRequestService(handledProtocols), workspaceContext, contributions: [], clientCapabilities: params.capabilities @@ -427,12 +433,12 @@ connection.onFoldingRanges((params, token) => { }, null, `Error while computing folding ranges for ${params.textDocument.uri}`, token); }); -connection.onRequest('$/textDocument/selectionRange', async (params, token) => { +connection.onRequest('$/textDocument/selectionRanges', async (params, token) => { return runSafe(() => { const document = documents.get(params.textDocument.uri); if (document) { const jsonDocument = getJSONDocument(document); - return languageService.getSelectionRanges(document, params.position, jsonDocument); + return languageService.getSelectionRanges(document, params.positions, jsonDocument); } return []; }, [], `Error while computing selection ranges for ${params.textDocument.uri}`, token); diff --git a/extensions/json-language-features/server/src/utils/strings.ts b/extensions/json-language-features/server/src/utils/strings.ts index ce50718186..62346a1da6 100644 --- a/extensions/json-language-features/server/src/utils/strings.ts +++ b/extensions/json-language-features/server/src/utils/strings.ts @@ -3,20 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -export function startsWith(haystack: string, needle: string): boolean { - if (haystack.length < needle.length) { - return false; - } - - for (let i = 0; i < needle.length; i++) { - if (haystack[i] !== needle[i]) { - return false; - } - } - - return true; -} - /** * Determines if haystack ends with needle. */ diff --git a/extensions/json-language-features/server/yarn.lock b/extensions/json-language-features/server/yarn.lock index 1c933ebfb3..bb8489b6d8 100644 --- a/extensions/json-language-features/server/yarn.lock +++ b/extensions/json-language-features/server/yarn.lock @@ -7,10 +7,10 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.33.tgz#d79a0061ec270379f4d9e225f4096fb436669def" integrity sha1-15oAYewnA3n02eIl9AlvtDZmne8= -"@types/node@^8.10.25": - version "8.10.25" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.25.tgz#801fe4e39372cef18f268db880a5fbfcf71adc7e" - integrity sha512-WXvAXaknB0c2cJ7N44e1kUrVu5K90mSfPPaT5XxfuSMxEWva86EYIwxUZM3jNZ2P1CIC9e2z4WJqpAF69PQxeA== +"@types/node@^10.12.21": + version "10.12.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" + integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== agent-base@4, agent-base@^4.1.0: version "4.1.2" @@ -54,10 +54,10 @@ https-proxy-agent@^2.2.1: agent-base "^4.1.0" debug "^3.1.0" -jsonc-parser@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.0.2.tgz#42fcf56d70852a043fadafde51ddb4a85649978d" - integrity sha512-TSU435K5tEKh3g7bam1AFf+uZrISheoDsLlpmAo6wWZYqjsnd09lHYK1Qo+moK4Ikifev1Gdpa69g4NELKnCrQ== +jsonc-parser@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-2.0.3.tgz#6d4199ccab7f21ff5d2a4225050c54e981fb21a2" + integrity sha512-WJi9y9ABL01C8CxTKxRRQkkSpY/x2bo4Gy0WuiZGrInxQqgxQpvkBCLNcDYcHOSdhx4ODgbFcgAvfL49C+PHgQ== ms@2.0.0: version "2.0.0" @@ -73,13 +73,13 @@ request-light@^0.2.4: https-proxy-agent "^2.2.1" vscode-nls "^4.0.0" -vscode-json-languageservice@^3.3.0-next.0: - version "3.3.0-next.0" - resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.3.0-next.0.tgz#c17db95d0eacc24f80d3b3f120ab5e03943769a0" - integrity sha512-YZXL3yHzbr0/Ar5dGdeM/f5Y0l41z/Y4QSQTdL3Hl3ScuY76IPcDEnf7iuk9yx+QoPfEHFCBDv5Rg6XVcMl8Tg== +vscode-json-languageservice@^3.3.0-next.6: + version "3.3.0-next.6" + resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-3.3.0-next.6.tgz#711f121b44ba443a89f3fb01a01c611f2547079f" + integrity sha512-i1tyLiodWc7y6lR9C4cat+OUSptj8Duk1Ybm1FaMzhNfOTFttSiwrBw1otNb+QwI65VEj7EAEBQHRLeQOWznMw== dependencies: - jsonc-parser "^2.0.2" - vscode-languageserver-types "^3.13.0" + jsonc-parser "^2.0.3" + vscode-languageserver-types "^3.14.0" vscode-nls "^4.0.0" vscode-uri "^1.0.6" @@ -88,25 +88,25 @@ vscode-jsonrpc@^4.0.0: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-4.0.0.tgz#a7bf74ef3254d0a0c272fab15c82128e378b3be9" integrity sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg== -vscode-languageserver-protocol@3.13.0: - version "3.13.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.13.0.tgz#710d8e42119bb3affb1416e1e104bd6b4d503595" - integrity sha512-2ZGKwI+P2ovQll2PGAp+2UfJH+FK9eait86VBUdkPd9HRlm8e58aYT9pV/NYanHOcp3pL6x2yTLVCFMcTer0mg== +vscode-languageserver-protocol@3.15.0-next.1: + version "3.15.0-next.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.0-next.1.tgz#1e45e224d7eef8c79b4bed75b9dcb1930d2ab8ed" + integrity sha512-LXF0d9s3vxFBxVQ4aKl/XghdEMAncGt3dh4urIYa9Is43g3MfIQL9fC44YZtP+XXOrI2rpZU8lRNN01U1V6CDg== dependencies: vscode-jsonrpc "^4.0.0" - vscode-languageserver-types "3.13.0" + vscode-languageserver-types "3.14.0" -vscode-languageserver-types@3.13.0, vscode-languageserver-types@^3.13.0: - version "3.13.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.13.0.tgz#b704b024cef059f7b326611c99b9c8753c0a18b4" - integrity sha512-BnJIxS+5+8UWiNKCP7W3g9FlE7fErFw0ofP5BXJe7c2tl0VeWh+nNHFbwAS2vmVC4a5kYxHBjRy0UeOtziemVA== +vscode-languageserver-types@3.14.0, vscode-languageserver-types@^3.14.0: + version "3.14.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz#d3b5952246d30e5241592b6dde8280e03942e743" + integrity sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A== -vscode-languageserver@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-5.1.0.tgz#012a28f154cc7a848c443d217894942e4c3eeb39" - integrity sha512-CIsrgx2Y5VHS317g/HwkSTWYBIQmy0DwEyZPmB2pEpVOhYFwVsYpbiJwHIIyLQsQtmRaO4eA2xM8KPjNSdXpBw== +vscode-languageserver@^5.3.0-next.2: + version "5.3.0-next.2" + resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-5.3.0-next.2.tgz#31ce4c34d68b517b400ca9e211e43f8d868b8dcc" + integrity sha512-n5onRw9naMrRHp2jnOn+ZwN1n+tTfzftWLPonjp1FWf/iCZWIlnw2TyF/Hn+SDGhLoVtoghmxhwEQaxEAfLHvw== dependencies: - vscode-languageserver-protocol "3.13.0" + vscode-languageserver-protocol "3.15.0-next.1" vscode-uri "^1.0.6" vscode-nls@^4.0.0: diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 019393942b..758f96fd51 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -2,10 +2,17 @@ # yarn lockfile v1 -"@types/node@^8.10.25": - version "8.10.25" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.25.tgz#801fe4e39372cef18f268db880a5fbfcf71adc7e" - integrity sha512-WXvAXaknB0c2cJ7N44e1kUrVu5K90mSfPPaT5XxfuSMxEWva86EYIwxUZM3jNZ2P1CIC9e2z4WJqpAF69PQxeA== +"@types/node@^10.12.21": + version "10.12.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" + integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== + +agent-base@4, agent-base@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" + integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== + dependencies: + es6-promisify "^5.0.0" applicationinsights@1.0.8: version "1.0.8" @@ -16,6 +23,20 @@ applicationinsights@1.0.8: diagnostic-channel-publishers "0.2.1" zone.js "0.7.6" +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + diagnostic-channel-publishers@0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3" @@ -28,6 +49,53 @@ diagnostic-channel@0.2.0: dependencies: semver "^5.3.0" +es6-promise@^4.0.3: + version "4.2.6" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.6.tgz#b685edd8258886365ea62b57d30de28fadcd974f" + integrity sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q== + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + +http-proxy-agent@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" + integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== + dependencies: + agent-base "4" + debug "3.1.0" + +https-proxy-agent@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" + integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== + dependencies: + agent-base "^4.1.0" + debug "^3.1.0" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +ms@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + +request-light@^0.2.4: + version "0.2.4" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.4.tgz#3cea29c126682e6bcadf7915353322eeba01a755" + integrity sha512-pM9Fq5jRnSb+82V7M97rp8FE9/YNeP2L9eckB4Szd7lyeclSIx02aIpPO/6e4m6Dy31+FBN/zkFMTd2HkNO3ow== + dependencies: + http-proxy-agent "^2.1.0" + https-proxy-agent "^2.2.1" + vscode-nls "^4.0.0" + semver@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" @@ -50,26 +118,26 @@ vscode-jsonrpc@^4.0.0: resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-4.0.0.tgz#a7bf74ef3254d0a0c272fab15c82128e378b3be9" integrity sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg== -vscode-languageclient@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-5.1.0.tgz#650ab0dc9fd0daaade058a8471aaff5bc3f9580e" - integrity sha512-Z95Kps8UqD4o17HE3uCkZuvenOsxHVH46dKmaGVpGixEFZigPaVuVxLM/JWeIY9aRenoC0ZD9CK1O7L4jpffKg== +vscode-languageclient@^5.2.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-5.2.1.tgz#7cfc83a294c409f58cfa2b910a8cfeaad0397193" + integrity sha512-7jrS/9WnV0ruqPamN1nE7qCxn0phkH5LjSgSp9h6qoJGoeAKzwKz/PF6M+iGA/aklx4GLZg1prddhEPQtuXI1Q== dependencies: semver "^5.5.0" - vscode-languageserver-protocol "3.13.0" + vscode-languageserver-protocol "3.14.1" -vscode-languageserver-protocol@3.13.0: - version "3.13.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.13.0.tgz#710d8e42119bb3affb1416e1e104bd6b4d503595" - integrity sha512-2ZGKwI+P2ovQll2PGAp+2UfJH+FK9eait86VBUdkPd9HRlm8e58aYT9pV/NYanHOcp3pL6x2yTLVCFMcTer0mg== +vscode-languageserver-protocol@3.14.1: + version "3.14.1" + resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.14.1.tgz#b8aab6afae2849c84a8983d39a1cf742417afe2f" + integrity sha512-IL66BLb2g20uIKog5Y2dQ0IiigW0XKrvmWiOvc0yXw80z3tMEzEnHjaGAb3ENuU7MnQqgnYJ1Cl2l9RvNgDi4g== dependencies: vscode-jsonrpc "^4.0.0" - vscode-languageserver-types "3.13.0" + vscode-languageserver-types "3.14.0" -vscode-languageserver-types@3.13.0: - version "3.13.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.13.0.tgz#b704b024cef059f7b326611c99b9c8753c0a18b4" - integrity sha512-BnJIxS+5+8UWiNKCP7W3g9FlE7fErFw0ofP5BXJe7c2tl0VeWh+nNHFbwAS2vmVC4a5kYxHBjRy0UeOtziemVA== +vscode-languageserver-types@3.14.0: + version "3.14.0" + resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz#d3b5952246d30e5241592b6dde8280e03942e743" + integrity sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A== vscode-nls@^4.0.0: version "4.0.0" diff --git a/extensions/json/cgmanifest.json b/extensions/json/cgmanifest.json index d0b23b7e06..fabb7a93ab 100644 --- a/extensions/json/cgmanifest.json +++ b/extensions/json/cgmanifest.json @@ -14,4 +14,4 @@ } ], "version": 1 -} +} \ No newline at end of file diff --git a/extensions/json/package.json b/extensions/json/package.json index 517346cd34..fd0dd54ff2 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -24,11 +24,13 @@ ".jshintrc", ".jscsrc", ".eslintrc", + ".swcrc", ".webmanifest", ".js.map", ".css.map" ], "filenames": [ + "composer.lock", ".watchmanconfig", ".ember-cli" ], @@ -70,4 +72,4 @@ } ] } -} +} \ No newline at end of file diff --git a/extensions/markdown-language-features/cgmanifest.json b/extensions/markdown-language-features/cgmanifest.json index 89c68532e2..71df78ef41 100644 --- a/extensions/markdown-language-features/cgmanifest.json +++ b/extensions/markdown-language-features/cgmanifest.json @@ -1,28 +1,5 @@ { "registrations": [ - { - "component": { - "type": "git", - "git": { - "name": "chriskempson/tomorrow-theme", - "repositoryUrl": "https://github.com/chriskempson/tomorrow-theme", - "commitHash": "0e0d35ac303f99b8aa182091ebeaee81cf2183a0" - } - }, - "licenseDetail": [ - "Copyright (C) 2013 Chris Kempson", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,", - "and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." - ], - "license": "MIT", - "version": "0.0.0" - }, { "component": { "type": "git", @@ -52,4 +29,4 @@ } ], "version": 1 -} +} \ No newline at end of file diff --git a/extensions/markdown-language-features/media/index.js b/extensions/markdown-language-features/media/index.js index 01dcaeaaed..50ff09c4dd 100644 --- a/extensions/markdown-language-features/media/index.js +++ b/extensions/markdown-language-features/media/index.js @@ -643,12 +643,12 @@ const messaging_1 = __webpack_require__(/*! ./messaging */ "./preview-src/messag const scroll_sync_1 = __webpack_require__(/*! ./scroll-sync */ "./preview-src/scroll-sync.ts"); const settings_1 = __webpack_require__(/*! ./settings */ "./preview-src/settings.ts"); const throttle = __webpack_require__(/*! lodash.throttle */ "./node_modules/lodash.throttle/index.js"); -var scrollDisabled = true; +let scrollDisabled = true; const marker = new activeLineMarker_1.ActiveLineMarker(); const settings = settings_1.getSettings(); const vscode = acquireVsCodeApi(); // Set VS Code state -const state = settings_1.getData('data-state'); +let state = settings_1.getData('data-state'); vscode.setState(state); const messaging = messaging_1.createPosterForVsCode(vscode); window.cspAlerter.setPoster(messaging); @@ -762,6 +762,8 @@ if (settings.scrollEditorWithPreview) { const line = scroll_sync_1.getEditorLineNumberForPageOffset(window.scrollY); if (typeof line === 'number' && !isNaN(line)) { messaging.postMessage('revealLine', { line }); + state.line = line; + vscode.setState(state); } } }, 50)); @@ -825,11 +827,13 @@ const getCodeLineElements = (() => { let elements; return () => { if (!elements) { - elements = ([{ element: document.body, line: 0 }]).concat(Array.prototype.map.call(document.getElementsByClassName('code-line'), (element) => { + elements = [{ element: document.body, line: 0 }]; + for (const element of document.getElementsByClassName('code-line')) { const line = +element.getAttribute('data-line'); - return { element, line }; - }) - .filter((x) => !isNaN(x.line))); + if (!isNaN(line)) { + elements.push({ element: element, line }); + } + } } return elements; }; @@ -979,4 +983,4 @@ exports.getSettings = getSettings; /***/ }) /******/ }); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vbm9kZV9tb2R1bGVzL2xvZGFzaC50aHJvdHRsZS9pbmRleC5qcyIsIndlYnBhY2s6Ly8vKHdlYnBhY2spL2J1aWxkaW4vZ2xvYmFsLmpzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2FjdGl2ZUxpbmVNYXJrZXIudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvZXZlbnRzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2luZGV4LnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL21lc3NhZ2luZy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zY3JvbGwtc3luYy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zZXR0aW5ncy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQUs7QUFDTDtBQUNBOztBQUVBO0FBQ0E7QUFDQSx5REFBaUQsY0FBYztBQUMvRDs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBMkIsMEJBQTBCLEVBQUU7QUFDdkQseUNBQWlDLGVBQWU7QUFDaEQ7QUFDQTtBQUNBOztBQUVBO0FBQ0EsOERBQXNELCtEQUErRDs7QUFFckg7QUFDQTs7O0FBR0E7QUFDQTs7Ozs7Ozs7Ozs7O0FDbkVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYSxPQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLFNBQVM7QUFDcEIsV0FBVyxPQUFPO0FBQ2xCLFdBQVcsT0FBTyxZQUFZO0FBQzlCLFdBQVcsUUFBUTtBQUNuQjtBQUNBLFdBQVcsT0FBTztBQUNsQjtBQUNBLFdBQVcsUUFBUTtBQUNuQjtBQUNBLGFBQWEsU0FBUztBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBLDhDQUE4QyxrQkFBa0I7QUFDaEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLFNBQVM7QUFDcEIsV0FBVyxPQUFPO0FBQ2xCLFdBQVcsT0FBTyxZQUFZO0FBQzlCLFdBQVcsUUFBUTtBQUNuQjtBQUNBLFdBQVcsUUFBUTtBQUNuQjtBQUNBLGFBQWEsU0FBUztBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtREFBbUQsb0JBQW9CO0FBQ3ZFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLEVBQUU7QUFDYixhQUFhLFFBQVE7QUFDckI7QUFDQTtBQUNBLGdCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsRUFBRTtBQUNiLGFBQWEsUUFBUTtBQUNyQjtBQUNBO0FBQ0Esb0JBQW9CO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLEVBQUU7QUFDYixhQUFhLFFBQVE7QUFDckI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVyxFQUFFO0FBQ2IsYUFBYSxPQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOzs7Ozs7Ozs7Ozs7O0FDdGJBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLENBQUM7O0FBRUQ7QUFDQTtBQUNBO0FBQ0EsQ0FBQztBQUNEO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsNENBQTRDOztBQUU1Qzs7Ozs7Ozs7Ozs7Ozs7O0FDbkJBOzs7Z0dBR2dHO0FBQ2hHLCtGQUF5RDtBQUV6RDtJQUdDLDhCQUE4QixDQUFDLElBQVk7UUFDMUMsTUFBTSxFQUFFLFFBQVEsRUFBRSxHQUFHLHNDQUF3QixDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3BELElBQUksQ0FBQyxPQUFPLENBQUMsUUFBUSxJQUFJLFFBQVEsQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUM1QyxDQUFDO0lBRUQsT0FBTyxDQUFDLE1BQStCO1FBQ3RDLElBQUksQ0FBQyxvQkFBb0IsQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLENBQUM7UUFDekMsSUFBSSxDQUFDLGtCQUFrQixDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ2hDLElBQUksQ0FBQyxRQUFRLEdBQUcsTUFBTSxDQUFDO0lBQ3hCLENBQUM7SUFFRCxvQkFBb0IsQ0FBQyxPQUFnQztRQUNwRCxFQUFFLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7WUFDZCxNQUFNLENBQUM7UUFDUixDQUFDO1FBQ0QsT0FBTyxDQUFDLFNBQVMsR0FBRyxPQUFPLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyx1QkFBdUIsRUFBRSxFQUFFLENBQUMsQ0FBQztJQUM1RSxDQUFDO0lBRUQsa0JBQWtCLENBQUMsT0FBZ0M7UUFDbEQsRUFBRSxDQUFDLENBQUMsQ0FBQyxPQUFPLENBQUMsQ0FBQyxDQUFDO1lBQ2QsTUFBTSxDQUFDO1FBQ1IsQ0FBQztRQUNELE9BQU8sQ0FBQyxTQUFTLElBQUksbUJBQW1CLENBQUM7SUFDMUMsQ0FBQztDQUNEO0FBM0JELDRDQTJCQzs7Ozs7Ozs7Ozs7Ozs7QUNqQ0Q7OztnR0FHZ0c7O0FBRWhHLDRCQUFtQyxDQUFhO0lBQy9DLEVBQUUsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxVQUFVLEtBQUssU0FBUyxJQUFJLFFBQVEsQ0FBQyxVQUFVLEtBQUssZUFBZSxDQUFDLENBQUMsQ0FBQztRQUNsRixRQUFRLENBQUMsZ0JBQWdCLENBQUMsa0JBQWtCLEVBQUUsQ0FBQyxDQUFDLENBQUM7SUFDbEQsQ0FBQztJQUFDLElBQUksQ0FBQyxDQUFDO1FBQ1AsQ0FBQyxFQUFFLENBQUM7SUFDTCxDQUFDO0FBQ0YsQ0FBQztBQU5ELGdEQU1DOzs7Ozs7Ozs7Ozs7OztBQ1hEOzs7Z0dBR2dHOztBQUVoRyw4R0FBc0Q7QUFDdEQsZ0ZBQThDO0FBQzlDLHlGQUFvRDtBQUNwRCwrRkFBMkY7QUFDM0Ysc0ZBQWtEO0FBQ2xELHVHQUE2QztBQUk3QyxJQUFJLGNBQWMsR0FBRyxJQUFJLENBQUM7QUFDMUIsTUFBTSxNQUFNLEdBQUcsSUFBSSxtQ0FBZ0IsRUFBRSxDQUFDO0FBQ3RDLE1BQU0sUUFBUSxHQUFHLHNCQUFXLEVBQUUsQ0FBQztBQUUvQixNQUFNLE1BQU0sR0FBRyxnQkFBZ0IsRUFBRSxDQUFDO0FBRWxDLG9CQUFvQjtBQUNwQixNQUFNLEtBQUssR0FBRyxrQkFBTyxDQUFDLFlBQVksQ0FBQyxDQUFDO0FBQ3BDLE1BQU0sQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7QUFFdkIsTUFBTSxTQUFTLEdBQUcsaUNBQXFCLENBQUMsTUFBTSxDQUFDLENBQUM7QUFFaEQsTUFBTSxDQUFDLFVBQVUsQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLENBQUM7QUFDdkMsTUFBTSxDQUFDLG1CQUFtQixDQUFDLFNBQVMsQ0FBQyxTQUFTLENBQUMsQ0FBQztBQUVoRCxNQUFNLENBQUMsTUFBTSxHQUFHLEdBQUcsRUFBRTtJQUNwQixnQkFBZ0IsRUFBRSxDQUFDO0FBQ3BCLENBQUMsQ0FBQztBQUVGLDJCQUFrQixDQUFDLEdBQUcsRUFBRTtJQUN2QixFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsdUJBQXVCLENBQUMsQ0FBQyxDQUFDO1FBQ3RDLFVBQVUsQ0FBQyxHQUFHLEVBQUU7WUFDZixNQUFNLFdBQVcsR0FBRyxDQUFDLFFBQVEsQ0FBQyxJQUFJLENBQUM7WUFDbkMsRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUN6QixjQUFjLEdBQUcsSUFBSSxDQUFDO2dCQUN0QixzQ0FBd0IsQ0FBQyxXQUFXLENBQUMsQ0FBQztZQUN2QyxDQUFDO1FBQ0YsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDO0lBQ1AsQ0FBQztBQUNGLENBQUMsQ0FBQyxDQUFDO0FBRUgsTUFBTSxZQUFZLEdBQUcsQ0FBQyxHQUFHLEVBQUU7SUFDMUIsTUFBTSxRQUFRLEdBQUcsUUFBUSxDQUFDLENBQUMsSUFBWSxFQUFFLEVBQUU7UUFDMUMsY0FBYyxHQUFHLElBQUksQ0FBQztRQUN0QixzQ0FBd0IsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNoQyxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFFUCxNQUFNLENBQUMsQ0FBQyxJQUFZLEVBQUUsUUFBYSxFQUFFLEVBQUU7UUFDdEMsRUFBRSxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBQ2xCLFFBQVEsQ0FBQyxJQUFJLEdBQUcsSUFBSSxDQUFDO1lBQ3JCLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNoQixDQUFDO0lBQ0YsQ0FBQyxDQUFDO0FBQ0gsQ0FBQyxDQUFDLEVBQUUsQ0FBQztBQUVMLElBQUksZ0JBQWdCLEdBQUcsUUFBUSxDQUFDLEdBQUcsRUFBRTtJQUNwQyxNQUFNLFNBQVMsR0FBb0QsRUFBRSxDQUFDO0lBQ3RFLElBQUksTUFBTSxHQUFHLFFBQVEsQ0FBQyxvQkFBb0IsQ0FBQyxLQUFLLENBQUMsQ0FBQztJQUNsRCxFQUFFLENBQUMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO1FBQ1osSUFBSSxDQUFDLENBQUM7UUFDTixHQUFHLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxNQUFNLENBQUMsTUFBTSxFQUFFLENBQUMsRUFBRSxFQUFFLENBQUM7WUFDcEMsTUFBTSxHQUFHLEdBQUcsTUFBTSxDQUFDLENBQUMsQ0FBQyxDQUFDO1lBRXRCLEVBQUUsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxTQUFTLENBQUMsUUFBUSxDQUFDLFNBQVMsQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDdkMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUM7WUFDakMsQ0FBQztZQUVELFNBQVMsQ0FBQyxJQUFJLENBQUM7Z0JBQ2QsRUFBRSxFQUFFLEdBQUcsQ0FBQyxFQUFFO2dCQUNWLE1BQU0sRUFBRSxHQUFHLENBQUMsTUFBTTtnQkFDbEIsS0FBSyxFQUFFLEdBQUcsQ0FBQyxLQUFLO2FBQ2hCLENBQUMsQ0FBQztRQUNKLENBQUM7UUFFRCxTQUFTLENBQUMsV0FBVyxDQUFDLGlCQUFpQixFQUFFLFNBQVMsQ0FBQyxDQUFDO0lBQ3JELENBQUM7QUFDRixDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFFUCxNQUFNLENBQUMsZ0JBQWdCLENBQUMsUUFBUSxFQUFFLEdBQUcsRUFBRTtJQUN0QyxjQUFjLEdBQUcsSUFBSSxDQUFDO0lBQ3RCLGdCQUFnQixFQUFFLENBQUM7QUFDcEIsQ0FBQyxFQUFFLElBQUksQ0FBQyxDQUFDO0FBRVQsTUFBTSxDQUFDLGdCQUFnQixDQUFDLFNBQVMsRUFBRSxLQUFLLENBQUMsRUFBRTtJQUMxQyxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLE1BQU0sS0FBSyxRQUFRLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztRQUMzQyxNQUFNLENBQUM7SUFDUixDQUFDO0lBRUQsTUFBTSxDQUFDLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBQ3pCLEtBQUssZ0NBQWdDO1lBQ3BDLE1BQU0sQ0FBQyw4QkFBOEIsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ3ZELEtBQUssQ0FBQztRQUVQLEtBQUssWUFBWTtZQUNoQixZQUFZLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsUUFBUSxDQUFDLENBQUM7WUFDeEMsS0FBSyxDQUFDO0lBQ1IsQ0FBQztBQUNGLENBQUMsRUFBRSxLQUFLLENBQUMsQ0FBQztBQUVWLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxVQUFVLEVBQUUsS0FBSyxDQUFDLEVBQUU7SUFDN0MsRUFBRSxDQUFDLENBQUMsQ0FBQyxRQUFRLENBQUMsMkJBQTJCLENBQUMsQ0FBQyxDQUFDO1FBQzNDLE1BQU0sQ0FBQztJQUNSLENBQUM7SUFFRCx5QkFBeUI7SUFDekIsR0FBRyxDQUFDLENBQUMsSUFBSSxJQUFJLEdBQUcsS0FBSyxDQUFDLE1BQXFCLEVBQUUsSUFBSSxFQUFFLElBQUksR0FBRyxJQUFJLENBQUMsVUFBeUIsRUFBRSxDQUFDO1FBQzFGLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxPQUFPLEtBQUssR0FBRyxDQUFDLENBQUMsQ0FBQztZQUMxQixNQUFNLENBQUM7UUFDUixDQUFDO0lBQ0YsQ0FBQztJQUVELE1BQU0sTUFBTSxHQUFHLEtBQUssQ0FBQyxLQUFLLENBQUM7SUFDM0IsTUFBTSxJQUFJLEdBQUcsOENBQWdDLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDdEQsRUFBRSxDQUFDLENBQUMsT0FBTyxJQUFJLEtBQUssUUFBUSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUM5QyxTQUFTLENBQUMsV0FBVyxDQUFDLFVBQVUsRUFBRSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztJQUMvRCxDQUFDO0FBQ0YsQ0FBQyxDQUFDLENBQUM7QUFFSCxRQUFRLENBQUMsZ0JBQWdCLENBQUMsT0FBTyxFQUFFLEtBQUssQ0FBQyxFQUFFO0lBQzFDLEVBQUUsQ0FBQyxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUNaLE1BQU0sQ0FBQztJQUNSLENBQUM7SUFFRCxJQUFJLElBQUksR0FBUSxLQUFLLENBQUMsTUFBTSxDQUFDO0lBQzdCLE9BQU8sSUFBSSxFQUFFLENBQUM7UUFDYixFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUMsT0FBTyxJQUFJLElBQUksQ0FBQyxPQUFPLEtBQUssR0FBRyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1lBQ3ZELEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsTUFBTSxDQUFDLENBQUMsVUFBVSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDL0MsS0FBSyxDQUFDO1lBQ1AsQ0FBQztZQUNELEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLFNBQVMsQ0FBQyxJQUFJLElBQUksQ0FBQyxJQUFJLENBQUMsVUFBVSxDQUFDLGtCQUFrQixDQUFDLENBQUMsQ0FBQyxDQUFDO2dCQUNqRixNQUFNLENBQUMsSUFBSSxFQUFFLFFBQVEsQ0FBQyxHQUFHLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLGdDQUFnQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQztnQkFDNUYsU0FBUyxDQUFDLFdBQVcsQ0FBQyxXQUFXLEVBQUUsRUFBRSxJQUFJLEVBQUUsUUFBUSxFQUFFLENBQUMsQ0FBQztnQkFDdkQsS0FBSyxDQUFDLGNBQWMsRUFBRSxDQUFDO2dCQUN2QixLQUFLLENBQUMsZUFBZSxFQUFFLENBQUM7Z0JBQ3hCLEtBQUssQ0FBQztZQUNQLENBQUM7WUFDRCxLQUFLLENBQUM7UUFDUCxDQUFDO1FBQ0QsSUFBSSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUM7SUFDeEIsQ0FBQztBQUNGLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQztBQUVULEVBQUUsQ0FBQyxDQUFDLFFBQVEsQ0FBQyx1QkFBdUIsQ0FBQyxDQUFDLENBQUM7SUFDdEMsTUFBTSxDQUFDLGdCQUFnQixDQUFDLFFBQVEsRUFBRSxRQUFRLENBQUMsR0FBRyxFQUFFO1FBQy9DLEVBQUUsQ0FBQyxDQUFDLGNBQWMsQ0FBQyxDQUFDLENBQUM7WUFDcEIsY0FBYyxHQUFHLEtBQUssQ0FBQztRQUN4QixDQUFDO1FBQUMsSUFBSSxDQUFDLENBQUM7WUFDUCxNQUFNLElBQUksR0FBRyw4Q0FBZ0MsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDOUQsRUFBRSxDQUFDLENBQUMsT0FBTyxJQUFJLEtBQUssUUFBUSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztnQkFDOUMsU0FBUyxDQUFDLFdBQVcsQ0FBQyxZQUFZLEVBQUUsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUFDO1lBQy9DLENBQUM7UUFDRixDQUFDO0lBQ0YsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUM7QUFDVCxDQUFDOzs7Ozs7Ozs7Ozs7OztBQzdKRDs7O2dHQUdnRzs7QUFFaEcsc0ZBQXlDO0FBUzVCLDZCQUFxQixHQUFHLENBQUMsTUFBVyxFQUFFLEVBQUU7SUFDcEQsTUFBTSxDQUFDLElBQUk7UUFDVixXQUFXLENBQUMsSUFBWSxFQUFFLElBQVk7WUFDckMsTUFBTSxDQUFDLFdBQVcsQ0FBQztnQkFDbEIsSUFBSTtnQkFDSixNQUFNLEVBQUUsc0JBQVcsRUFBRSxDQUFDLE1BQU07Z0JBQzVCLElBQUk7YUFDSixDQUFDLENBQUM7UUFDSixDQUFDO0tBQ0QsQ0FBQztBQUNILENBQUMsQ0FBQzs7Ozs7Ozs7Ozs7Ozs7QUN4QkY7OztnR0FHZ0c7O0FBRWhHLHNGQUF5QztBQUd6QyxlQUFlLEdBQVcsRUFBRSxHQUFXLEVBQUUsS0FBYTtJQUNyRCxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLENBQUMsQ0FBQztBQUM1QyxDQUFDO0FBRUQsbUJBQW1CLElBQVk7SUFDOUIsTUFBTSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsc0JBQVcsRUFBRSxDQUFDLFNBQVMsR0FBRyxDQUFDLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDcEQsQ0FBQztBQVFELE1BQU0sbUJBQW1CLEdBQUcsQ0FBQyxHQUFHLEVBQUU7SUFDakMsSUFBSSxRQUEyQixDQUFDO0lBQ2hDLE1BQU0sQ0FBQyxHQUFHLEVBQUU7UUFDWCxFQUFFLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUM7WUFDZixRQUFRLEdBQUcsQ0FBQyxDQUFDLEVBQUUsT0FBTyxFQUFFLFFBQVEsQ0FBQyxJQUFJLEVBQUUsSUFBSSxFQUFFLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUNqRixRQUFRLENBQUMsc0JBQXNCLENBQUMsV0FBVyxDQUFDLEVBQzVDLENBQUMsT0FBWSxFQUFFLEVBQUU7Z0JBQ2hCLE1BQU0sSUFBSSxHQUFHLENBQUMsT0FBTyxDQUFDLFlBQVksQ0FBQyxXQUFXLENBQUMsQ0FBQztnQkFDaEQsTUFBTSxDQUFDLEVBQUUsT0FBTyxFQUFFLElBQUksRUFBRSxDQUFDO1lBQzFCLENBQUMsQ0FBQztpQkFDRCxNQUFNLENBQUMsQ0FBQyxDQUFNLEVBQUUsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7UUFDdkMsQ0FBQztRQUNELE1BQU0sQ0FBQyxRQUFRLENBQUM7SUFDakIsQ0FBQyxDQUFDO0FBQ0gsQ0FBQyxDQUFDLEVBQUUsQ0FBQztBQUVMOzs7OztHQUtHO0FBQ0gsa0NBQXlDLFVBQWtCO0lBQzFELE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDMUMsTUFBTSxLQUFLLEdBQUcsbUJBQW1CLEVBQUUsQ0FBQztJQUNwQyxJQUFJLFFBQVEsR0FBRyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksSUFBSSxDQUFDO0lBQ2hDLEdBQUcsQ0FBQyxDQUFDLE1BQU0sS0FBSyxJQUFJLEtBQUssQ0FBQyxDQUFDLENBQUM7UUFDM0IsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLElBQUksS0FBSyxVQUFVLENBQUMsQ0FBQyxDQUFDO1lBQy9CLE1BQU0sQ0FBQyxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxDQUFDO1FBQzdDLENBQUM7UUFBQyxJQUFJLENBQUMsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLElBQUksR0FBRyxVQUFVLENBQUMsQ0FBQyxDQUFDO1lBQ3BDLE1BQU0sQ0FBQyxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLENBQUM7UUFDbEMsQ0FBQztRQUNELFFBQVEsR0FBRyxLQUFLLENBQUM7SUFDbEIsQ0FBQztJQUNELE1BQU0sQ0FBQyxFQUFFLFFBQVEsRUFBRSxDQUFDO0FBQ3JCLENBQUM7QUFiRCw0REFhQztBQUVEOztHQUVHO0FBQ0gscUNBQTRDLE1BQWM7SUFDekQsTUFBTSxLQUFLLEdBQUcsbUJBQW1CLEVBQUUsQ0FBQztJQUNwQyxNQUFNLFFBQVEsR0FBRyxNQUFNLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQztJQUN6QyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUNaLElBQUksRUFBRSxHQUFHLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDO0lBQzFCLE9BQU8sRUFBRSxHQUFHLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQztRQUNwQixNQUFNLEdBQUcsR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxHQUFHLEVBQUUsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQ3RDLE1BQU0sTUFBTSxHQUFHLEtBQUssQ0FBQyxHQUFHLENBQUMsQ0FBQyxPQUFPLENBQUMscUJBQXFCLEVBQUUsQ0FBQztRQUMxRCxFQUFFLENBQUMsQ0FBQyxNQUFNLENBQUMsR0FBRyxHQUFHLE1BQU0sQ0FBQyxNQUFNLElBQUksUUFBUSxDQUFDLENBQUMsQ0FBQztZQUM1QyxFQUFFLEdBQUcsR0FBRyxDQUFDO1FBQ1YsQ0FBQztRQUNELElBQUksQ0FBQyxDQUFDO1lBQ0wsRUFBRSxHQUFHLEdBQUcsQ0FBQztRQUNWLENBQUM7SUFDRixDQUFDO0lBQ0QsTUFBTSxTQUFTLEdBQUcsS0FBSyxDQUFDLEVBQUUsQ0FBQyxDQUFDO0lBQzVCLE1BQU0sUUFBUSxHQUFHLFNBQVMsQ0FBQyxPQUFPLENBQUMscUJBQXFCLEVBQUUsQ0FBQztJQUMzRCxFQUFFLENBQUMsQ0FBQyxFQUFFLElBQUksQ0FBQyxJQUFJLFFBQVEsQ0FBQyxHQUFHLEdBQUcsUUFBUSxDQUFDLENBQUMsQ0FBQztRQUN4QyxNQUFNLFNBQVMsR0FBRyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDNUIsTUFBTSxDQUFDLEVBQUUsUUFBUSxFQUFFLFNBQVMsRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLENBQUM7SUFDakQsQ0FBQztJQUNELE1BQU0sQ0FBQyxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsQ0FBQztBQUNoQyxDQUFDO0FBdEJELGtFQXNCQztBQUVEOztHQUVHO0FBQ0gsa0NBQXlDLElBQVk7SUFDcEQsRUFBRSxDQUFDLENBQUMsQ0FBQyxzQkFBVyxFQUFFLENBQUMsdUJBQXVCLENBQUMsQ0FBQyxDQUFDO1FBQzVDLE1BQU0sQ0FBQztJQUNSLENBQUM7SUFFRCxFQUFFLENBQUMsQ0FBQyxJQUFJLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztRQUNmLE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQztRQUNqQyxNQUFNLENBQUM7SUFDUixDQUFDO0lBRUQsTUFBTSxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsR0FBRyx3QkFBd0IsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUMxRCxFQUFFLENBQUMsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxDQUFDLENBQUM7UUFDZixNQUFNLENBQUM7SUFDUixDQUFDO0lBQ0QsSUFBSSxRQUFRLEdBQUcsQ0FBQyxDQUFDO0lBQ2pCLE1BQU0sSUFBSSxHQUFHLFFBQVEsQ0FBQyxPQUFPLENBQUMscUJBQXFCLEVBQUUsQ0FBQztJQUN0RCxNQUFNLFdBQVcsR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDO0lBQzdCLEVBQUUsQ0FBQyxDQUFDLElBQUksSUFBSSxJQUFJLENBQUMsSUFBSSxLQUFLLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBQ3pDLDhEQUE4RDtRQUM5RCxNQUFNLGVBQWUsR0FBRyxDQUFDLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxHQUFHLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM3RSxNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUMsR0FBRyxHQUFHLFdBQVcsQ0FBQztRQUM3RSxRQUFRLEdBQUcsV0FBVyxHQUFHLGVBQWUsR0FBRyxhQUFhLENBQUM7SUFDMUQsQ0FBQztJQUFDLElBQUksQ0FBQyxDQUFDO1FBQ1AsTUFBTSxpQkFBaUIsR0FBRyxJQUFJLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNsRCxRQUFRLEdBQUcsV0FBVyxHQUFHLENBQUMsSUFBSSxDQUFDLE1BQU0sR0FBRyxpQkFBaUIsQ0FBQyxDQUFDO0lBQzVELENBQUM7SUFDRCxNQUFNLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsTUFBTSxDQUFDLE9BQU8sR0FBRyxRQUFRLENBQUMsQ0FBQyxDQUFDO0FBQ3ZFLENBQUM7QUEzQkQsNERBMkJDO0FBRUQsMENBQWlELE1BQWM7SUFDOUQsTUFBTSxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsR0FBRywyQkFBMkIsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUMvRCxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsQ0FBQyxDQUFDO1FBQ2QsTUFBTSxjQUFjLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO1FBQ2hFLE1BQU0sa0JBQWtCLEdBQUcsQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDLE9BQU8sR0FBRyxjQUFjLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDMUUsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztZQUNWLE1BQU0sdUJBQXVCLEdBQUcsa0JBQWtCLEdBQUcsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUMsR0FBRyxHQUFHLGNBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUNySCxNQUFNLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxHQUFHLHVCQUF1QixHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDbkYsTUFBTSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN4QixDQUFDO1FBQ0QsSUFBSSxDQUFDLENBQUM7WUFDTCxNQUFNLHFCQUFxQixHQUFHLGtCQUFrQixHQUFHLENBQUMsY0FBYyxDQUFDLE1BQU0sQ0FBQyxDQUFDO1lBQzNFLE1BQU0sSUFBSSxHQUFHLFFBQVEsQ0FBQyxJQUFJLEdBQUcscUJBQXFCLENBQUM7WUFDbkQsTUFBTSxDQUFDLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN4QixDQUFDO0lBQ0YsQ0FBQztJQUNELE1BQU0sQ0FBQyxJQUFJLENBQUM7QUFDYixDQUFDO0FBakJELDRFQWlCQzs7Ozs7Ozs7Ozs7Ozs7QUN2SUQ7OztnR0FHZ0c7O0FBWWhHLElBQUksY0FBYyxHQUFnQyxTQUFTLENBQUM7QUFFNUQsaUJBQXdCLEdBQVc7SUFDbEMsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDLGNBQWMsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQ3hFLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7UUFDYixNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3ZDLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7WUFDVixNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN6QixDQUFDO0lBQ0YsQ0FBQztJQUVELE1BQU0sSUFBSSxLQUFLLENBQUMsMkJBQTJCLEdBQUcsRUFBRSxDQUFDLENBQUM7QUFDbkQsQ0FBQztBQVZELDBCQVVDO0FBRUQ7SUFDQyxFQUFFLENBQUMsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDO1FBQ3BCLE1BQU0sQ0FBQyxjQUFjLENBQUM7SUFDdkIsQ0FBQztJQUVELGNBQWMsR0FBRyxPQUFPLENBQUMsZUFBZSxDQUFDLENBQUM7SUFDMUMsRUFBRSxDQUFDLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQztRQUNwQixNQUFNLENBQUMsY0FBYyxDQUFDO0lBQ3ZCLENBQUM7SUFFRCxNQUFNLElBQUksS0FBSyxDQUFDLHlCQUF5QixDQUFDLENBQUM7QUFDNUMsQ0FBQztBQVhELGtDQVdDIiwiZmlsZSI6ImluZGV4LmpzIiwic291cmNlc0NvbnRlbnQiOlsiIFx0Ly8gVGhlIG1vZHVsZSBjYWNoZVxuIFx0dmFyIGluc3RhbGxlZE1vZHVsZXMgPSB7fTtcblxuIFx0Ly8gVGhlIHJlcXVpcmUgZnVuY3Rpb25cbiBcdGZ1bmN0aW9uIF9fd2VicGFja19yZXF1aXJlX18obW9kdWxlSWQpIHtcblxuIFx0XHQvLyBDaGVjayBpZiBtb2R1bGUgaXMgaW4gY2FjaGVcbiBcdFx0aWYoaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0pIHtcbiBcdFx0XHRyZXR1cm4gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0uZXhwb3J0cztcbiBcdFx0fVxuIFx0XHQvLyBDcmVhdGUgYSBuZXcgbW9kdWxlIChhbmQgcHV0IGl0IGludG8gdGhlIGNhY2hlKVxuIFx0XHR2YXIgbW9kdWxlID0gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0gPSB7XG4gXHRcdFx0aTogbW9kdWxlSWQsXG4gXHRcdFx0bDogZmFsc2UsXG4gXHRcdFx0ZXhwb3J0czoge31cbiBcdFx0fTtcblxuIFx0XHQvLyBFeGVjdXRlIHRoZSBtb2R1bGUgZnVuY3Rpb25cbiBcdFx0bW9kdWxlc1ttb2R1bGVJZF0uY2FsbChtb2R1bGUuZXhwb3J0cywgbW9kdWxlLCBtb2R1bGUuZXhwb3J0cywgX193ZWJwYWNrX3JlcXVpcmVfXyk7XG5cbiBcdFx0Ly8gRmxhZyB0aGUgbW9kdWxlIGFzIGxvYWRlZFxuIFx0XHRtb2R1bGUubCA9IHRydWU7XG5cbiBcdFx0Ly8gUmV0dXJuIHRoZSBleHBvcnRzIG9mIHRoZSBtb2R1bGVcbiBcdFx0cmV0dXJuIG1vZHVsZS5leHBvcnRzO1xuIFx0fVxuXG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlcyBvYmplY3QgKF9fd2VicGFja19tb2R1bGVzX18pXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm0gPSBtb2R1bGVzO1xuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZSBjYWNoZVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5jID0gaW5zdGFsbGVkTW9kdWxlcztcblxuIFx0Ly8gZGVmaW5lIGdldHRlciBmdW5jdGlvbiBmb3IgaGFybW9ueSBleHBvcnRzXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmQgPSBmdW5jdGlvbihleHBvcnRzLCBuYW1lLCBnZXR0ZXIpIHtcbiBcdFx0aWYoIV9fd2VicGFja19yZXF1aXJlX18ubyhleHBvcnRzLCBuYW1lKSkge1xuIFx0XHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBuYW1lLCB7XG4gXHRcdFx0XHRjb25maWd1cmFibGU6IGZhbHNlLFxuIFx0XHRcdFx0ZW51bWVyYWJsZTogdHJ1ZSxcbiBcdFx0XHRcdGdldDogZ2V0dGVyXG4gXHRcdFx0fSk7XG4gXHRcdH1cbiBcdH07XG5cbiBcdC8vIGRlZmluZSBfX2VzTW9kdWxlIG9uIGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uciA9IGZ1bmN0aW9uKGV4cG9ydHMpIHtcbiBcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywgeyB2YWx1ZTogdHJ1ZSB9KTtcbiBcdH07XG5cbiBcdC8vIGdldERlZmF1bHRFeHBvcnQgZnVuY3Rpb24gZm9yIGNvbXBhdGliaWxpdHkgd2l0aCBub24taGFybW9ueSBtb2R1bGVzXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm4gPSBmdW5jdGlvbihtb2R1bGUpIHtcbiBcdFx0dmFyIGdldHRlciA9IG1vZHVsZSAmJiBtb2R1bGUuX19lc01vZHVsZSA/XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0RGVmYXVsdCgpIHsgcmV0dXJuIG1vZHVsZVsnZGVmYXVsdCddOyB9IDpcbiBcdFx0XHRmdW5jdGlvbiBnZXRNb2R1bGVFeHBvcnRzKCkgeyByZXR1cm4gbW9kdWxlOyB9O1xuIFx0XHRfX3dlYnBhY2tfcmVxdWlyZV9fLmQoZ2V0dGVyLCAnYScsIGdldHRlcik7XG4gXHRcdHJldHVybiBnZXR0ZXI7XG4gXHR9O1xuXG4gXHQvLyBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGxcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubyA9IGZ1bmN0aW9uKG9iamVjdCwgcHJvcGVydHkpIHsgcmV0dXJuIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChvYmplY3QsIHByb3BlcnR5KTsgfTtcblxuIFx0Ly8gX193ZWJwYWNrX3B1YmxpY19wYXRoX19cbiBcdF9fd2VicGFja19yZXF1aXJlX18ucCA9IFwiXCI7XG5cblxuIFx0Ly8gTG9hZCBlbnRyeSBtb2R1bGUgYW5kIHJldHVybiBleHBvcnRzXG4gXHRyZXR1cm4gX193ZWJwYWNrX3JlcXVpcmVfXyhfX3dlYnBhY2tfcmVxdWlyZV9fLnMgPSBcIi4vcHJldmlldy1zcmMvaW5kZXgudHNcIik7XG4iLCIvKipcbiAqIGxvZGFzaCAoQ3VzdG9tIEJ1aWxkKSA8aHR0cHM6Ly9sb2Rhc2guY29tLz5cbiAqIEJ1aWxkOiBgbG9kYXNoIG1vZHVsYXJpemUgZXhwb3J0cz1cIm5wbVwiIC1vIC4vYFxuICogQ29weXJpZ2h0IGpRdWVyeSBGb3VuZGF0aW9uIGFuZCBvdGhlciBjb250cmlidXRvcnMgPGh0dHBzOi8vanF1ZXJ5Lm9yZy8+XG4gKiBSZWxlYXNlZCB1bmRlciBNSVQgbGljZW5zZSA8aHR0cHM6Ly9sb2Rhc2guY29tL2xpY2Vuc2U+XG4gKiBCYXNlZCBvbiBVbmRlcnNjb3JlLmpzIDEuOC4zIDxodHRwOi8vdW5kZXJzY29yZWpzLm9yZy9MSUNFTlNFPlxuICogQ29weXJpZ2h0IEplcmVteSBBc2hrZW5hcywgRG9jdW1lbnRDbG91ZCBhbmQgSW52ZXN0aWdhdGl2ZSBSZXBvcnRlcnMgJiBFZGl0b3JzXG4gKi9cblxuLyoqIFVzZWQgYXMgdGhlIGBUeXBlRXJyb3JgIG1lc3NhZ2UgZm9yIFwiRnVuY3Rpb25zXCIgbWV0aG9kcy4gKi9cbnZhciBGVU5DX0VSUk9SX1RFWFQgPSAnRXhwZWN0ZWQgYSBmdW5jdGlvbic7XG5cbi8qKiBVc2VkIGFzIHJlZmVyZW5jZXMgZm9yIHZhcmlvdXMgYE51bWJlcmAgY29uc3RhbnRzLiAqL1xudmFyIE5BTiA9IDAgLyAwO1xuXG4vKiogYE9iamVjdCN0b1N0cmluZ2AgcmVzdWx0IHJlZmVyZW5jZXMuICovXG52YXIgc3ltYm9sVGFnID0gJ1tvYmplY3QgU3ltYm9sXSc7XG5cbi8qKiBVc2VkIHRvIG1hdGNoIGxlYWRpbmcgYW5kIHRyYWlsaW5nIHdoaXRlc3BhY2UuICovXG52YXIgcmVUcmltID0gL15cXHMrfFxccyskL2c7XG5cbi8qKiBVc2VkIHRvIGRldGVjdCBiYWQgc2lnbmVkIGhleGFkZWNpbWFsIHN0cmluZyB2YWx1ZXMuICovXG52YXIgcmVJc0JhZEhleCA9IC9eWy0rXTB4WzAtOWEtZl0rJC9pO1xuXG4vKiogVXNlZCB0byBkZXRlY3QgYmluYXJ5IHN0cmluZyB2YWx1ZXMuICovXG52YXIgcmVJc0JpbmFyeSA9IC9eMGJbMDFdKyQvaTtcblxuLyoqIFVzZWQgdG8gZGV0ZWN0IG9jdGFsIHN0cmluZyB2YWx1ZXMuICovXG52YXIgcmVJc09jdGFsID0gL14wb1swLTddKyQvaTtcblxuLyoqIEJ1aWx0LWluIG1ldGhvZCByZWZlcmVuY2VzIHdpdGhvdXQgYSBkZXBlbmRlbmN5IG9uIGByb290YC4gKi9cbnZhciBmcmVlUGFyc2VJbnQgPSBwYXJzZUludDtcblxuLyoqIERldGVjdCBmcmVlIHZhcmlhYmxlIGBnbG9iYWxgIGZyb20gTm9kZS5qcy4gKi9cbnZhciBmcmVlR2xvYmFsID0gdHlwZW9mIGdsb2JhbCA9PSAnb2JqZWN0JyAmJiBnbG9iYWwgJiYgZ2xvYmFsLk9iamVjdCA9PT0gT2JqZWN0ICYmIGdsb2JhbDtcblxuLyoqIERldGVjdCBmcmVlIHZhcmlhYmxlIGBzZWxmYC4gKi9cbnZhciBmcmVlU2VsZiA9IHR5cGVvZiBzZWxmID09ICdvYmplY3QnICYmIHNlbGYgJiYgc2VsZi5PYmplY3QgPT09IE9iamVjdCAmJiBzZWxmO1xuXG4vKiogVXNlZCBhcyBhIHJlZmVyZW5jZSB0byB0aGUgZ2xvYmFsIG9iamVjdC4gKi9cbnZhciByb290ID0gZnJlZUdsb2JhbCB8fCBmcmVlU2VsZiB8fCBGdW5jdGlvbigncmV0dXJuIHRoaXMnKSgpO1xuXG4vKiogVXNlZCBmb3IgYnVpbHQtaW4gbWV0aG9kIHJlZmVyZW5jZXMuICovXG52YXIgb2JqZWN0UHJvdG8gPSBPYmplY3QucHJvdG90eXBlO1xuXG4vKipcbiAqIFVzZWQgdG8gcmVzb2x2ZSB0aGVcbiAqIFtgdG9TdHJpbmdUYWdgXShodHRwOi8vZWNtYS1pbnRlcm5hdGlvbmFsLm9yZy9lY21hLTI2Mi83LjAvI3NlYy1vYmplY3QucHJvdG90eXBlLnRvc3RyaW5nKVxuICogb2YgdmFsdWVzLlxuICovXG52YXIgb2JqZWN0VG9TdHJpbmcgPSBvYmplY3RQcm90by50b1N0cmluZztcblxuLyogQnVpbHQtaW4gbWV0aG9kIHJlZmVyZW5jZXMgZm9yIHRob3NlIHdpdGggdGhlIHNhbWUgbmFtZSBhcyBvdGhlciBgbG9kYXNoYCBtZXRob2RzLiAqL1xudmFyIG5hdGl2ZU1heCA9IE1hdGgubWF4LFxuICAgIG5hdGl2ZU1pbiA9IE1hdGgubWluO1xuXG4vKipcbiAqIEdldHMgdGhlIHRpbWVzdGFtcCBvZiB0aGUgbnVtYmVyIG9mIG1pbGxpc2Vjb25kcyB0aGF0IGhhdmUgZWxhcHNlZCBzaW5jZVxuICogdGhlIFVuaXggZXBvY2ggKDEgSmFudWFyeSAxOTcwIDAwOjAwOjAwIFVUQykuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSAyLjQuMFxuICogQGNhdGVnb3J5IERhdGVcbiAqIEByZXR1cm5zIHtudW1iZXJ9IFJldHVybnMgdGhlIHRpbWVzdGFtcC5cbiAqIEBleGFtcGxlXG4gKlxuICogXy5kZWZlcihmdW5jdGlvbihzdGFtcCkge1xuICogICBjb25zb2xlLmxvZyhfLm5vdygpIC0gc3RhbXApO1xuICogfSwgXy5ub3coKSk7XG4gKiAvLyA9PiBMb2dzIHRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIGl0IHRvb2sgZm9yIHRoZSBkZWZlcnJlZCBpbnZvY2F0aW9uLlxuICovXG52YXIgbm93ID0gZnVuY3Rpb24oKSB7XG4gIHJldHVybiByb290LkRhdGUubm93KCk7XG59O1xuXG4vKipcbiAqIENyZWF0ZXMgYSBkZWJvdW5jZWQgZnVuY3Rpb24gdGhhdCBkZWxheXMgaW52b2tpbmcgYGZ1bmNgIHVudGlsIGFmdGVyIGB3YWl0YFxuICogbWlsbGlzZWNvbmRzIGhhdmUgZWxhcHNlZCBzaW5jZSB0aGUgbGFzdCB0aW1lIHRoZSBkZWJvdW5jZWQgZnVuY3Rpb24gd2FzXG4gKiBpbnZva2VkLiBUaGUgZGVib3VuY2VkIGZ1bmN0aW9uIGNvbWVzIHdpdGggYSBgY2FuY2VsYCBtZXRob2QgdG8gY2FuY2VsXG4gKiBkZWxheWVkIGBmdW5jYCBpbnZvY2F0aW9ucyBhbmQgYSBgZmx1c2hgIG1ldGhvZCB0byBpbW1lZGlhdGVseSBpbnZva2UgdGhlbS5cbiAqIFByb3ZpZGUgYG9wdGlvbnNgIHRvIGluZGljYXRlIHdoZXRoZXIgYGZ1bmNgIHNob3VsZCBiZSBpbnZva2VkIG9uIHRoZVxuICogbGVhZGluZyBhbmQvb3IgdHJhaWxpbmcgZWRnZSBvZiB0aGUgYHdhaXRgIHRpbWVvdXQuIFRoZSBgZnVuY2AgaXMgaW52b2tlZFxuICogd2l0aCB0aGUgbGFzdCBhcmd1bWVudHMgcHJvdmlkZWQgdG8gdGhlIGRlYm91bmNlZCBmdW5jdGlvbi4gU3Vic2VxdWVudFxuICogY2FsbHMgdG8gdGhlIGRlYm91bmNlZCBmdW5jdGlvbiByZXR1cm4gdGhlIHJlc3VsdCBvZiB0aGUgbGFzdCBgZnVuY2BcbiAqIGludm9jYXRpb24uXG4gKlxuICogKipOb3RlOioqIElmIGBsZWFkaW5nYCBhbmQgYHRyYWlsaW5nYCBvcHRpb25zIGFyZSBgdHJ1ZWAsIGBmdW5jYCBpc1xuICogaW52b2tlZCBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dCBvbmx5IGlmIHRoZSBkZWJvdW5jZWQgZnVuY3Rpb25cbiAqIGlzIGludm9rZWQgbW9yZSB0aGFuIG9uY2UgZHVyaW5nIHRoZSBgd2FpdGAgdGltZW91dC5cbiAqXG4gKiBJZiBgd2FpdGAgaXMgYDBgIGFuZCBgbGVhZGluZ2AgaXMgYGZhbHNlYCwgYGZ1bmNgIGludm9jYXRpb24gaXMgZGVmZXJyZWRcbiAqIHVudGlsIHRvIHRoZSBuZXh0IHRpY2ssIHNpbWlsYXIgdG8gYHNldFRpbWVvdXRgIHdpdGggYSB0aW1lb3V0IG9mIGAwYC5cbiAqXG4gKiBTZWUgW0RhdmlkIENvcmJhY2hvJ3MgYXJ0aWNsZV0oaHR0cHM6Ly9jc3MtdHJpY2tzLmNvbS9kZWJvdW5jaW5nLXRocm90dGxpbmctZXhwbGFpbmVkLWV4YW1wbGVzLylcbiAqIGZvciBkZXRhaWxzIG92ZXIgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gYF8uZGVib3VuY2VgIGFuZCBgXy50aHJvdHRsZWAuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSAwLjEuMFxuICogQGNhdGVnb3J5IEZ1bmN0aW9uXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBmdW5jIFRoZSBmdW5jdGlvbiB0byBkZWJvdW5jZS5cbiAqIEBwYXJhbSB7bnVtYmVyfSBbd2FpdD0wXSBUaGUgbnVtYmVyIG9mIG1pbGxpc2Vjb25kcyB0byBkZWxheS5cbiAqIEBwYXJhbSB7T2JqZWN0fSBbb3B0aW9ucz17fV0gVGhlIG9wdGlvbnMgb2JqZWN0LlxuICogQHBhcmFtIHtib29sZWFufSBbb3B0aW9ucy5sZWFkaW5nPWZhbHNlXVxuICogIFNwZWNpZnkgaW52b2tpbmcgb24gdGhlIGxlYWRpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEBwYXJhbSB7bnVtYmVyfSBbb3B0aW9ucy5tYXhXYWl0XVxuICogIFRoZSBtYXhpbXVtIHRpbWUgYGZ1bmNgIGlzIGFsbG93ZWQgdG8gYmUgZGVsYXllZCBiZWZvcmUgaXQncyBpbnZva2VkLlxuICogQHBhcmFtIHtib29sZWFufSBbb3B0aW9ucy50cmFpbGluZz10cnVlXVxuICogIFNwZWNpZnkgaW52b2tpbmcgb24gdGhlIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQuXG4gKiBAcmV0dXJucyB7RnVuY3Rpb259IFJldHVybnMgdGhlIG5ldyBkZWJvdW5jZWQgZnVuY3Rpb24uXG4gKiBAZXhhbXBsZVxuICpcbiAqIC8vIEF2b2lkIGNvc3RseSBjYWxjdWxhdGlvbnMgd2hpbGUgdGhlIHdpbmRvdyBzaXplIGlzIGluIGZsdXguXG4gKiBqUXVlcnkod2luZG93KS5vbigncmVzaXplJywgXy5kZWJvdW5jZShjYWxjdWxhdGVMYXlvdXQsIDE1MCkpO1xuICpcbiAqIC8vIEludm9rZSBgc2VuZE1haWxgIHdoZW4gY2xpY2tlZCwgZGVib3VuY2luZyBzdWJzZXF1ZW50IGNhbGxzLlxuICogalF1ZXJ5KGVsZW1lbnQpLm9uKCdjbGljaycsIF8uZGVib3VuY2Uoc2VuZE1haWwsIDMwMCwge1xuICogICAnbGVhZGluZyc6IHRydWUsXG4gKiAgICd0cmFpbGluZyc6IGZhbHNlXG4gKiB9KSk7XG4gKlxuICogLy8gRW5zdXJlIGBiYXRjaExvZ2AgaXMgaW52b2tlZCBvbmNlIGFmdGVyIDEgc2Vjb25kIG9mIGRlYm91bmNlZCBjYWxscy5cbiAqIHZhciBkZWJvdW5jZWQgPSBfLmRlYm91bmNlKGJhdGNoTG9nLCAyNTAsIHsgJ21heFdhaXQnOiAxMDAwIH0pO1xuICogdmFyIHNvdXJjZSA9IG5ldyBFdmVudFNvdXJjZSgnL3N0cmVhbScpO1xuICogalF1ZXJ5KHNvdXJjZSkub24oJ21lc3NhZ2UnLCBkZWJvdW5jZWQpO1xuICpcbiAqIC8vIENhbmNlbCB0aGUgdHJhaWxpbmcgZGVib3VuY2VkIGludm9jYXRpb24uXG4gKiBqUXVlcnkod2luZG93KS5vbigncG9wc3RhdGUnLCBkZWJvdW5jZWQuY2FuY2VsKTtcbiAqL1xuZnVuY3Rpb24gZGVib3VuY2UoZnVuYywgd2FpdCwgb3B0aW9ucykge1xuICB2YXIgbGFzdEFyZ3MsXG4gICAgICBsYXN0VGhpcyxcbiAgICAgIG1heFdhaXQsXG4gICAgICByZXN1bHQsXG4gICAgICB0aW1lcklkLFxuICAgICAgbGFzdENhbGxUaW1lLFxuICAgICAgbGFzdEludm9rZVRpbWUgPSAwLFxuICAgICAgbGVhZGluZyA9IGZhbHNlLFxuICAgICAgbWF4aW5nID0gZmFsc2UsXG4gICAgICB0cmFpbGluZyA9IHRydWU7XG5cbiAgaWYgKHR5cGVvZiBmdW5jICE9ICdmdW5jdGlvbicpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKEZVTkNfRVJST1JfVEVYVCk7XG4gIH1cbiAgd2FpdCA9IHRvTnVtYmVyKHdhaXQpIHx8IDA7XG4gIGlmIChpc09iamVjdChvcHRpb25zKSkge1xuICAgIGxlYWRpbmcgPSAhIW9wdGlvbnMubGVhZGluZztcbiAgICBtYXhpbmcgPSAnbWF4V2FpdCcgaW4gb3B0aW9ucztcbiAgICBtYXhXYWl0ID0gbWF4aW5nID8gbmF0aXZlTWF4KHRvTnVtYmVyKG9wdGlvbnMubWF4V2FpdCkgfHwgMCwgd2FpdCkgOiBtYXhXYWl0O1xuICAgIHRyYWlsaW5nID0gJ3RyYWlsaW5nJyBpbiBvcHRpb25zID8gISFvcHRpb25zLnRyYWlsaW5nIDogdHJhaWxpbmc7XG4gIH1cblxuICBmdW5jdGlvbiBpbnZva2VGdW5jKHRpbWUpIHtcbiAgICB2YXIgYXJncyA9IGxhc3RBcmdzLFxuICAgICAgICB0aGlzQXJnID0gbGFzdFRoaXM7XG5cbiAgICBsYXN0QXJncyA9IGxhc3RUaGlzID0gdW5kZWZpbmVkO1xuICAgIGxhc3RJbnZva2VUaW1lID0gdGltZTtcbiAgICByZXN1bHQgPSBmdW5jLmFwcGx5KHRoaXNBcmcsIGFyZ3MpO1xuICAgIHJldHVybiByZXN1bHQ7XG4gIH1cblxuICBmdW5jdGlvbiBsZWFkaW5nRWRnZSh0aW1lKSB7XG4gICAgLy8gUmVzZXQgYW55IGBtYXhXYWl0YCB0aW1lci5cbiAgICBsYXN0SW52b2tlVGltZSA9IHRpbWU7XG4gICAgLy8gU3RhcnQgdGhlIHRpbWVyIGZvciB0aGUgdHJhaWxpbmcgZWRnZS5cbiAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHdhaXQpO1xuICAgIC8vIEludm9rZSB0aGUgbGVhZGluZyBlZGdlLlxuICAgIHJldHVybiBsZWFkaW5nID8gaW52b2tlRnVuYyh0aW1lKSA6IHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIHJlbWFpbmluZ1dhaXQodGltZSkge1xuICAgIHZhciB0aW1lU2luY2VMYXN0Q2FsbCA9IHRpbWUgLSBsYXN0Q2FsbFRpbWUsXG4gICAgICAgIHRpbWVTaW5jZUxhc3RJbnZva2UgPSB0aW1lIC0gbGFzdEludm9rZVRpbWUsXG4gICAgICAgIHJlc3VsdCA9IHdhaXQgLSB0aW1lU2luY2VMYXN0Q2FsbDtcblxuICAgIHJldHVybiBtYXhpbmcgPyBuYXRpdmVNaW4ocmVzdWx0LCBtYXhXYWl0IC0gdGltZVNpbmNlTGFzdEludm9rZSkgOiByZXN1bHQ7XG4gIH1cblxuICBmdW5jdGlvbiBzaG91bGRJbnZva2UodGltZSkge1xuICAgIHZhciB0aW1lU2luY2VMYXN0Q2FsbCA9IHRpbWUgLSBsYXN0Q2FsbFRpbWUsXG4gICAgICAgIHRpbWVTaW5jZUxhc3RJbnZva2UgPSB0aW1lIC0gbGFzdEludm9rZVRpbWU7XG5cbiAgICAvLyBFaXRoZXIgdGhpcyBpcyB0aGUgZmlyc3QgY2FsbCwgYWN0aXZpdHkgaGFzIHN0b3BwZWQgYW5kIHdlJ3JlIGF0IHRoZVxuICAgIC8vIHRyYWlsaW5nIGVkZ2UsIHRoZSBzeXN0ZW0gdGltZSBoYXMgZ29uZSBiYWNrd2FyZHMgYW5kIHdlJ3JlIHRyZWF0aW5nXG4gICAgLy8gaXQgYXMgdGhlIHRyYWlsaW5nIGVkZ2UsIG9yIHdlJ3ZlIGhpdCB0aGUgYG1heFdhaXRgIGxpbWl0LlxuICAgIHJldHVybiAobGFzdENhbGxUaW1lID09PSB1bmRlZmluZWQgfHwgKHRpbWVTaW5jZUxhc3RDYWxsID49IHdhaXQpIHx8XG4gICAgICAodGltZVNpbmNlTGFzdENhbGwgPCAwKSB8fCAobWF4aW5nICYmIHRpbWVTaW5jZUxhc3RJbnZva2UgPj0gbWF4V2FpdCkpO1xuICB9XG5cbiAgZnVuY3Rpb24gdGltZXJFeHBpcmVkKCkge1xuICAgIHZhciB0aW1lID0gbm93KCk7XG4gICAgaWYgKHNob3VsZEludm9rZSh0aW1lKSkge1xuICAgICAgcmV0dXJuIHRyYWlsaW5nRWRnZSh0aW1lKTtcbiAgICB9XG4gICAgLy8gUmVzdGFydCB0aGUgdGltZXIuXG4gICAgdGltZXJJZCA9IHNldFRpbWVvdXQodGltZXJFeHBpcmVkLCByZW1haW5pbmdXYWl0KHRpbWUpKTtcbiAgfVxuXG4gIGZ1bmN0aW9uIHRyYWlsaW5nRWRnZSh0aW1lKSB7XG4gICAgdGltZXJJZCA9IHVuZGVmaW5lZDtcblxuICAgIC8vIE9ubHkgaW52b2tlIGlmIHdlIGhhdmUgYGxhc3RBcmdzYCB3aGljaCBtZWFucyBgZnVuY2AgaGFzIGJlZW5cbiAgICAvLyBkZWJvdW5jZWQgYXQgbGVhc3Qgb25jZS5cbiAgICBpZiAodHJhaWxpbmcgJiYgbGFzdEFyZ3MpIHtcbiAgICAgIHJldHVybiBpbnZva2VGdW5jKHRpbWUpO1xuICAgIH1cbiAgICBsYXN0QXJncyA9IGxhc3RUaGlzID0gdW5kZWZpbmVkO1xuICAgIHJldHVybiByZXN1bHQ7XG4gIH1cblxuICBmdW5jdGlvbiBjYW5jZWwoKSB7XG4gICAgaWYgKHRpbWVySWQgIT09IHVuZGVmaW5lZCkge1xuICAgICAgY2xlYXJUaW1lb3V0KHRpbWVySWQpO1xuICAgIH1cbiAgICBsYXN0SW52b2tlVGltZSA9IDA7XG4gICAgbGFzdEFyZ3MgPSBsYXN0Q2FsbFRpbWUgPSBsYXN0VGhpcyA9IHRpbWVySWQgPSB1bmRlZmluZWQ7XG4gIH1cblxuICBmdW5jdGlvbiBmbHVzaCgpIHtcbiAgICByZXR1cm4gdGltZXJJZCA9PT0gdW5kZWZpbmVkID8gcmVzdWx0IDogdHJhaWxpbmdFZGdlKG5vdygpKTtcbiAgfVxuXG4gIGZ1bmN0aW9uIGRlYm91bmNlZCgpIHtcbiAgICB2YXIgdGltZSA9IG5vdygpLFxuICAgICAgICBpc0ludm9raW5nID0gc2hvdWxkSW52b2tlKHRpbWUpO1xuXG4gICAgbGFzdEFyZ3MgPSBhcmd1bWVudHM7XG4gICAgbGFzdFRoaXMgPSB0aGlzO1xuICAgIGxhc3RDYWxsVGltZSA9IHRpbWU7XG5cbiAgICBpZiAoaXNJbnZva2luZykge1xuICAgICAgaWYgKHRpbWVySWQgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICByZXR1cm4gbGVhZGluZ0VkZ2UobGFzdENhbGxUaW1lKTtcbiAgICAgIH1cbiAgICAgIGlmIChtYXhpbmcpIHtcbiAgICAgICAgLy8gSGFuZGxlIGludm9jYXRpb25zIGluIGEgdGlnaHQgbG9vcC5cbiAgICAgICAgdGltZXJJZCA9IHNldFRpbWVvdXQodGltZXJFeHBpcmVkLCB3YWl0KTtcbiAgICAgICAgcmV0dXJuIGludm9rZUZ1bmMobGFzdENhbGxUaW1lKTtcbiAgICAgIH1cbiAgICB9XG4gICAgaWYgKHRpbWVySWQgPT09IHVuZGVmaW5lZCkge1xuICAgICAgdGltZXJJZCA9IHNldFRpbWVvdXQodGltZXJFeHBpcmVkLCB3YWl0KTtcbiAgICB9XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuICBkZWJvdW5jZWQuY2FuY2VsID0gY2FuY2VsO1xuICBkZWJvdW5jZWQuZmx1c2ggPSBmbHVzaDtcbiAgcmV0dXJuIGRlYm91bmNlZDtcbn1cblxuLyoqXG4gKiBDcmVhdGVzIGEgdGhyb3R0bGVkIGZ1bmN0aW9uIHRoYXQgb25seSBpbnZva2VzIGBmdW5jYCBhdCBtb3N0IG9uY2UgcGVyXG4gKiBldmVyeSBgd2FpdGAgbWlsbGlzZWNvbmRzLiBUaGUgdGhyb3R0bGVkIGZ1bmN0aW9uIGNvbWVzIHdpdGggYSBgY2FuY2VsYFxuICogbWV0aG9kIHRvIGNhbmNlbCBkZWxheWVkIGBmdW5jYCBpbnZvY2F0aW9ucyBhbmQgYSBgZmx1c2hgIG1ldGhvZCB0b1xuICogaW1tZWRpYXRlbHkgaW52b2tlIHRoZW0uIFByb3ZpZGUgYG9wdGlvbnNgIHRvIGluZGljYXRlIHdoZXRoZXIgYGZ1bmNgXG4gKiBzaG91bGQgYmUgaW52b2tlZCBvbiB0aGUgbGVhZGluZyBhbmQvb3IgdHJhaWxpbmcgZWRnZSBvZiB0aGUgYHdhaXRgXG4gKiB0aW1lb3V0LiBUaGUgYGZ1bmNgIGlzIGludm9rZWQgd2l0aCB0aGUgbGFzdCBhcmd1bWVudHMgcHJvdmlkZWQgdG8gdGhlXG4gKiB0aHJvdHRsZWQgZnVuY3Rpb24uIFN1YnNlcXVlbnQgY2FsbHMgdG8gdGhlIHRocm90dGxlZCBmdW5jdGlvbiByZXR1cm4gdGhlXG4gKiByZXN1bHQgb2YgdGhlIGxhc3QgYGZ1bmNgIGludm9jYXRpb24uXG4gKlxuICogKipOb3RlOioqIElmIGBsZWFkaW5nYCBhbmQgYHRyYWlsaW5nYCBvcHRpb25zIGFyZSBgdHJ1ZWAsIGBmdW5jYCBpc1xuICogaW52b2tlZCBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dCBvbmx5IGlmIHRoZSB0aHJvdHRsZWQgZnVuY3Rpb25cbiAqIGlzIGludm9rZWQgbW9yZSB0aGFuIG9uY2UgZHVyaW5nIHRoZSBgd2FpdGAgdGltZW91dC5cbiAqXG4gKiBJZiBgd2FpdGAgaXMgYDBgIGFuZCBgbGVhZGluZ2AgaXMgYGZhbHNlYCwgYGZ1bmNgIGludm9jYXRpb24gaXMgZGVmZXJyZWRcbiAqIHVudGlsIHRvIHRoZSBuZXh0IHRpY2ssIHNpbWlsYXIgdG8gYHNldFRpbWVvdXRgIHdpdGggYSB0aW1lb3V0IG9mIGAwYC5cbiAqXG4gKiBTZWUgW0RhdmlkIENvcmJhY2hvJ3MgYXJ0aWNsZV0oaHR0cHM6Ly9jc3MtdHJpY2tzLmNvbS9kZWJvdW5jaW5nLXRocm90dGxpbmctZXhwbGFpbmVkLWV4YW1wbGVzLylcbiAqIGZvciBkZXRhaWxzIG92ZXIgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gYF8udGhyb3R0bGVgIGFuZCBgXy5kZWJvdW5jZWAuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSAwLjEuMFxuICogQGNhdGVnb3J5IEZ1bmN0aW9uXG4gKiBAcGFyYW0ge0Z1bmN0aW9ufSBmdW5jIFRoZSBmdW5jdGlvbiB0byB0aHJvdHRsZS5cbiAqIEBwYXJhbSB7bnVtYmVyfSBbd2FpdD0wXSBUaGUgbnVtYmVyIG9mIG1pbGxpc2Vjb25kcyB0byB0aHJvdHRsZSBpbnZvY2F0aW9ucyB0by5cbiAqIEBwYXJhbSB7T2JqZWN0fSBbb3B0aW9ucz17fV0gVGhlIG9wdGlvbnMgb2JqZWN0LlxuICogQHBhcmFtIHtib29sZWFufSBbb3B0aW9ucy5sZWFkaW5nPXRydWVdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgbGVhZGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHBhcmFtIHtib29sZWFufSBbb3B0aW9ucy50cmFpbGluZz10cnVlXVxuICogIFNwZWNpZnkgaW52b2tpbmcgb24gdGhlIHRyYWlsaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQuXG4gKiBAcmV0dXJucyB7RnVuY3Rpb259IFJldHVybnMgdGhlIG5ldyB0aHJvdHRsZWQgZnVuY3Rpb24uXG4gKiBAZXhhbXBsZVxuICpcbiAqIC8vIEF2b2lkIGV4Y2Vzc2l2ZWx5IHVwZGF0aW5nIHRoZSBwb3NpdGlvbiB3aGlsZSBzY3JvbGxpbmcuXG4gKiBqUXVlcnkod2luZG93KS5vbignc2Nyb2xsJywgXy50aHJvdHRsZSh1cGRhdGVQb3NpdGlvbiwgMTAwKSk7XG4gKlxuICogLy8gSW52b2tlIGByZW5ld1Rva2VuYCB3aGVuIHRoZSBjbGljayBldmVudCBpcyBmaXJlZCwgYnV0IG5vdCBtb3JlIHRoYW4gb25jZSBldmVyeSA1IG1pbnV0ZXMuXG4gKiB2YXIgdGhyb3R0bGVkID0gXy50aHJvdHRsZShyZW5ld1Rva2VuLCAzMDAwMDAsIHsgJ3RyYWlsaW5nJzogZmFsc2UgfSk7XG4gKiBqUXVlcnkoZWxlbWVudCkub24oJ2NsaWNrJywgdGhyb3R0bGVkKTtcbiAqXG4gKiAvLyBDYW5jZWwgdGhlIHRyYWlsaW5nIHRocm90dGxlZCBpbnZvY2F0aW9uLlxuICogalF1ZXJ5KHdpbmRvdykub24oJ3BvcHN0YXRlJywgdGhyb3R0bGVkLmNhbmNlbCk7XG4gKi9cbmZ1bmN0aW9uIHRocm90dGxlKGZ1bmMsIHdhaXQsIG9wdGlvbnMpIHtcbiAgdmFyIGxlYWRpbmcgPSB0cnVlLFxuICAgICAgdHJhaWxpbmcgPSB0cnVlO1xuXG4gIGlmICh0eXBlb2YgZnVuYyAhPSAnZnVuY3Rpb24nKSB7XG4gICAgdGhyb3cgbmV3IFR5cGVFcnJvcihGVU5DX0VSUk9SX1RFWFQpO1xuICB9XG4gIGlmIChpc09iamVjdChvcHRpb25zKSkge1xuICAgIGxlYWRpbmcgPSAnbGVhZGluZycgaW4gb3B0aW9ucyA/ICEhb3B0aW9ucy5sZWFkaW5nIDogbGVhZGluZztcbiAgICB0cmFpbGluZyA9ICd0cmFpbGluZycgaW4gb3B0aW9ucyA/ICEhb3B0aW9ucy50cmFpbGluZyA6IHRyYWlsaW5nO1xuICB9XG4gIHJldHVybiBkZWJvdW5jZShmdW5jLCB3YWl0LCB7XG4gICAgJ2xlYWRpbmcnOiBsZWFkaW5nLFxuICAgICdtYXhXYWl0Jzogd2FpdCxcbiAgICAndHJhaWxpbmcnOiB0cmFpbGluZ1xuICB9KTtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyB0aGVcbiAqIFtsYW5ndWFnZSB0eXBlXShodHRwOi8vd3d3LmVjbWEtaW50ZXJuYXRpb25hbC5vcmcvZWNtYS0yNjIvNy4wLyNzZWMtZWNtYXNjcmlwdC1sYW5ndWFnZS10eXBlcylcbiAqIG9mIGBPYmplY3RgLiAoZS5nLiBhcnJheXMsIGZ1bmN0aW9ucywgb2JqZWN0cywgcmVnZXhlcywgYG5ldyBOdW1iZXIoMClgLCBhbmQgYG5ldyBTdHJpbmcoJycpYClcbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDAuMS4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBhbiBvYmplY3QsIGVsc2UgYGZhbHNlYC5cbiAqIEBleGFtcGxlXG4gKlxuICogXy5pc09iamVjdCh7fSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdChbMSwgMiwgM10pO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3QoXy5ub29wKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0KG51bGwpO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNPYmplY3QodmFsdWUpIHtcbiAgdmFyIHR5cGUgPSB0eXBlb2YgdmFsdWU7XG4gIHJldHVybiAhIXZhbHVlICYmICh0eXBlID09ICdvYmplY3QnIHx8IHR5cGUgPT0gJ2Z1bmN0aW9uJyk7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgb2JqZWN0LWxpa2UuIEEgdmFsdWUgaXMgb2JqZWN0LWxpa2UgaWYgaXQncyBub3QgYG51bGxgXG4gKiBhbmQgaGFzIGEgYHR5cGVvZmAgcmVzdWx0IG9mIFwib2JqZWN0XCIuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSA0LjAuMFxuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgb2JqZWN0LWxpa2UsIGVsc2UgYGZhbHNlYC5cbiAqIEBleGFtcGxlXG4gKlxuICogXy5pc09iamVjdExpa2Uoe30pO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3RMaWtlKFsxLCAyLCAzXSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdExpa2UoXy5ub29wKTtcbiAqIC8vID0+IGZhbHNlXG4gKlxuICogXy5pc09iamVjdExpa2UobnVsbCk7XG4gKiAvLyA9PiBmYWxzZVxuICovXG5mdW5jdGlvbiBpc09iamVjdExpa2UodmFsdWUpIHtcbiAgcmV0dXJuICEhdmFsdWUgJiYgdHlwZW9mIHZhbHVlID09ICdvYmplY3QnO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiBgdmFsdWVgIGlzIGNsYXNzaWZpZWQgYXMgYSBgU3ltYm9sYCBwcmltaXRpdmUgb3Igb2JqZWN0LlxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgNC4wLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIGEgc3ltYm9sLCBlbHNlIGBmYWxzZWAuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8uaXNTeW1ib2woU3ltYm9sLml0ZXJhdG9yKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzU3ltYm9sKCdhYmMnKTtcbiAqIC8vID0+IGZhbHNlXG4gKi9cbmZ1bmN0aW9uIGlzU3ltYm9sKHZhbHVlKSB7XG4gIHJldHVybiB0eXBlb2YgdmFsdWUgPT0gJ3N5bWJvbCcgfHxcbiAgICAoaXNPYmplY3RMaWtlKHZhbHVlKSAmJiBvYmplY3RUb1N0cmluZy5jYWxsKHZhbHVlKSA9PSBzeW1ib2xUYWcpO1xufVxuXG4vKipcbiAqIENvbnZlcnRzIGB2YWx1ZWAgdG8gYSBudW1iZXIuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSA0LjAuMFxuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIHByb2Nlc3MuXG4gKiBAcmV0dXJucyB7bnVtYmVyfSBSZXR1cm5zIHRoZSBudW1iZXIuXG4gKiBAZXhhbXBsZVxuICpcbiAqIF8udG9OdW1iZXIoMy4yKTtcbiAqIC8vID0+IDMuMlxuICpcbiAqIF8udG9OdW1iZXIoTnVtYmVyLk1JTl9WQUxVRSk7XG4gKiAvLyA9PiA1ZS0zMjRcbiAqXG4gKiBfLnRvTnVtYmVyKEluZmluaXR5KTtcbiAqIC8vID0+IEluZmluaXR5XG4gKlxuICogXy50b051bWJlcignMy4yJyk7XG4gKiAvLyA9PiAzLjJcbiAqL1xuZnVuY3Rpb24gdG9OdW1iZXIodmFsdWUpIHtcbiAgaWYgKHR5cGVvZiB2YWx1ZSA9PSAnbnVtYmVyJykge1xuICAgIHJldHVybiB2YWx1ZTtcbiAgfVxuICBpZiAoaXNTeW1ib2wodmFsdWUpKSB7XG4gICAgcmV0dXJuIE5BTjtcbiAgfVxuICBpZiAoaXNPYmplY3QodmFsdWUpKSB7XG4gICAgdmFyIG90aGVyID0gdHlwZW9mIHZhbHVlLnZhbHVlT2YgPT0gJ2Z1bmN0aW9uJyA/IHZhbHVlLnZhbHVlT2YoKSA6IHZhbHVlO1xuICAgIHZhbHVlID0gaXNPYmplY3Qob3RoZXIpID8gKG90aGVyICsgJycpIDogb3RoZXI7XG4gIH1cbiAgaWYgKHR5cGVvZiB2YWx1ZSAhPSAnc3RyaW5nJykge1xuICAgIHJldHVybiB2YWx1ZSA9PT0gMCA/IHZhbHVlIDogK3ZhbHVlO1xuICB9XG4gIHZhbHVlID0gdmFsdWUucmVwbGFjZShyZVRyaW0sICcnKTtcbiAgdmFyIGlzQmluYXJ5ID0gcmVJc0JpbmFyeS50ZXN0KHZhbHVlKTtcbiAgcmV0dXJuIChpc0JpbmFyeSB8fCByZUlzT2N0YWwudGVzdCh2YWx1ZSkpXG4gICAgPyBmcmVlUGFyc2VJbnQodmFsdWUuc2xpY2UoMiksIGlzQmluYXJ5ID8gMiA6IDgpXG4gICAgOiAocmVJc0JhZEhleC50ZXN0KHZhbHVlKSA/IE5BTiA6ICt2YWx1ZSk7XG59XG5cbm1vZHVsZS5leHBvcnRzID0gdGhyb3R0bGU7XG4iLCJ2YXIgZztcclxuXHJcbi8vIFRoaXMgd29ya3MgaW4gbm9uLXN0cmljdCBtb2RlXHJcbmcgPSAoZnVuY3Rpb24oKSB7XHJcblx0cmV0dXJuIHRoaXM7XHJcbn0pKCk7XHJcblxyXG50cnkge1xyXG5cdC8vIFRoaXMgd29ya3MgaWYgZXZhbCBpcyBhbGxvd2VkIChzZWUgQ1NQKVxyXG5cdGcgPSBnIHx8IEZ1bmN0aW9uKFwicmV0dXJuIHRoaXNcIikoKSB8fCAoMSwgZXZhbCkoXCJ0aGlzXCIpO1xyXG59IGNhdGNoIChlKSB7XHJcblx0Ly8gVGhpcyB3b3JrcyBpZiB0aGUgd2luZG93IHJlZmVyZW5jZSBpcyBhdmFpbGFibGVcclxuXHRpZiAodHlwZW9mIHdpbmRvdyA9PT0gXCJvYmplY3RcIikgZyA9IHdpbmRvdztcclxufVxyXG5cclxuLy8gZyBjYW4gc3RpbGwgYmUgdW5kZWZpbmVkLCBidXQgbm90aGluZyB0byBkbyBhYm91dCBpdC4uLlxyXG4vLyBXZSByZXR1cm4gdW5kZWZpbmVkLCBpbnN0ZWFkIG9mIG5vdGhpbmcgaGVyZSwgc28gaXQnc1xyXG4vLyBlYXNpZXIgdG8gaGFuZGxlIHRoaXMgY2FzZS4gaWYoIWdsb2JhbCkgeyAuLi59XHJcblxyXG5tb2R1bGUuZXhwb3J0cyA9IGc7XHJcbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuaW1wb3J0IHsgZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lIH0gZnJvbSAnLi9zY3JvbGwtc3luYyc7XG5cbmV4cG9ydCBjbGFzcyBBY3RpdmVMaW5lTWFya2VyIHtcblx0cHJpdmF0ZSBfY3VycmVudDogYW55O1xuXG5cdG9uRGlkQ2hhbmdlVGV4dEVkaXRvclNlbGVjdGlvbihsaW5lOiBudW1iZXIpIHtcblx0XHRjb25zdCB7IHByZXZpb3VzIH0gPSBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUobGluZSk7XG5cdFx0dGhpcy5fdXBkYXRlKHByZXZpb3VzICYmIHByZXZpb3VzLmVsZW1lbnQpO1xuXHR9XG5cblx0X3VwZGF0ZShiZWZvcmU6IEhUTUxFbGVtZW50IHwgdW5kZWZpbmVkKSB7XG5cdFx0dGhpcy5fdW5tYXJrQWN0aXZlRWxlbWVudCh0aGlzLl9jdXJyZW50KTtcblx0XHR0aGlzLl9tYXJrQWN0aXZlRWxlbWVudChiZWZvcmUpO1xuXHRcdHRoaXMuX2N1cnJlbnQgPSBiZWZvcmU7XG5cdH1cblxuXHRfdW5tYXJrQWN0aXZlRWxlbWVudChlbGVtZW50OiBIVE1MRWxlbWVudCB8IHVuZGVmaW5lZCkge1xuXHRcdGlmICghZWxlbWVudCkge1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblx0XHRlbGVtZW50LmNsYXNzTmFtZSA9IGVsZW1lbnQuY2xhc3NOYW1lLnJlcGxhY2UoL1xcYmNvZGUtYWN0aXZlLWxpbmVcXGIvZywgJycpO1xuXHR9XG5cblx0X21hcmtBY3RpdmVFbGVtZW50KGVsZW1lbnQ6IEhUTUxFbGVtZW50IHwgdW5kZWZpbmVkKSB7XG5cdFx0aWYgKCFlbGVtZW50KSB7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXHRcdGVsZW1lbnQuY2xhc3NOYW1lICs9ICcgY29kZS1hY3RpdmUtbGluZSc7XG5cdH1cbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuZXhwb3J0IGZ1bmN0aW9uIG9uY2VEb2N1bWVudExvYWRlZChmOiAoKSA9PiB2b2lkKSB7XG5cdGlmIChkb2N1bWVudC5yZWFkeVN0YXRlID09PSAnbG9hZGluZycgfHwgZG9jdW1lbnQucmVhZHlTdGF0ZSA9PT0gJ3VuaW5pdGlhbGl6ZWQnKSB7XG5cdFx0ZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsIGYpO1xuXHR9IGVsc2Uge1xuXHRcdGYoKTtcblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5pbXBvcnQgeyBBY3RpdmVMaW5lTWFya2VyIH0gZnJvbSAnLi9hY3RpdmVMaW5lTWFya2VyJztcbmltcG9ydCB7IG9uY2VEb2N1bWVudExvYWRlZCB9IGZyb20gJy4vZXZlbnRzJztcbmltcG9ydCB7IGNyZWF0ZVBvc3RlckZvclZzQ29kZSB9IGZyb20gJy4vbWVzc2FnaW5nJztcbmltcG9ydCB7IGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0LCBzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUgfSBmcm9tICcuL3Njcm9sbC1zeW5jJztcbmltcG9ydCB7IGdldFNldHRpbmdzLCBnZXREYXRhIH0gZnJvbSAnLi9zZXR0aW5ncyc7XG5pbXBvcnQgdGhyb3R0bGUgPSByZXF1aXJlKCdsb2Rhc2gudGhyb3R0bGUnKTtcblxuZGVjbGFyZSB2YXIgYWNxdWlyZVZzQ29kZUFwaTogYW55O1xuXG52YXIgc2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuY29uc3QgbWFya2VyID0gbmV3IEFjdGl2ZUxpbmVNYXJrZXIoKTtcbmNvbnN0IHNldHRpbmdzID0gZ2V0U2V0dGluZ3MoKTtcblxuY29uc3QgdnNjb2RlID0gYWNxdWlyZVZzQ29kZUFwaSgpO1xuXG4vLyBTZXQgVlMgQ29kZSBzdGF0ZVxuY29uc3Qgc3RhdGUgPSBnZXREYXRhKCdkYXRhLXN0YXRlJyk7XG52c2NvZGUuc2V0U3RhdGUoc3RhdGUpO1xuXG5jb25zdCBtZXNzYWdpbmcgPSBjcmVhdGVQb3N0ZXJGb3JWc0NvZGUodnNjb2RlKTtcblxud2luZG93LmNzcEFsZXJ0ZXIuc2V0UG9zdGVyKG1lc3NhZ2luZyk7XG53aW5kb3cuc3R5bGVMb2FkaW5nTW9uaXRvci5zZXRQb3N0ZXIobWVzc2FnaW5nKTtcblxud2luZG93Lm9ubG9hZCA9ICgpID0+IHtcblx0dXBkYXRlSW1hZ2VTaXplcygpO1xufTtcblxub25jZURvY3VtZW50TG9hZGVkKCgpID0+IHtcblx0aWYgKHNldHRpbmdzLnNjcm9sbFByZXZpZXdXaXRoRWRpdG9yKSB7XG5cdFx0c2V0VGltZW91dCgoKSA9PiB7XG5cdFx0XHRjb25zdCBpbml0aWFsTGluZSA9ICtzZXR0aW5ncy5saW5lO1xuXHRcdFx0aWYgKCFpc05hTihpbml0aWFsTGluZSkpIHtcblx0XHRcdFx0c2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuXHRcdFx0XHRzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUoaW5pdGlhbExpbmUpO1xuXHRcdFx0fVxuXHRcdH0sIDApO1xuXHR9XG59KTtcblxuY29uc3Qgb25VcGRhdGVWaWV3ID0gKCgpID0+IHtcblx0Y29uc3QgZG9TY3JvbGwgPSB0aHJvdHRsZSgobGluZTogbnVtYmVyKSA9PiB7XG5cdFx0c2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuXHRcdHNjcm9sbFRvUmV2ZWFsU291cmNlTGluZShsaW5lKTtcblx0fSwgNTApO1xuXG5cdHJldHVybiAobGluZTogbnVtYmVyLCBzZXR0aW5nczogYW55KSA9PiB7XG5cdFx0aWYgKCFpc05hTihsaW5lKSkge1xuXHRcdFx0c2V0dGluZ3MubGluZSA9IGxpbmU7XG5cdFx0XHRkb1Njcm9sbChsaW5lKTtcblx0XHR9XG5cdH07XG59KSgpO1xuXG5sZXQgdXBkYXRlSW1hZ2VTaXplcyA9IHRocm90dGxlKCgpID0+IHtcblx0Y29uc3QgaW1hZ2VJbmZvOiB7IGlkOiBzdHJpbmcsIGhlaWdodDogbnVtYmVyLCB3aWR0aDogbnVtYmVyIH1bXSA9IFtdO1xuXHRsZXQgaW1hZ2VzID0gZG9jdW1lbnQuZ2V0RWxlbWVudHNCeVRhZ05hbWUoJ2ltZycpO1xuXHRpZiAoaW1hZ2VzKSB7XG5cdFx0bGV0IGk7XG5cdFx0Zm9yIChpID0gMDsgaSA8IGltYWdlcy5sZW5ndGg7IGkrKykge1xuXHRcdFx0Y29uc3QgaW1nID0gaW1hZ2VzW2ldO1xuXG5cdFx0XHRpZiAoaW1nLmNsYXNzTGlzdC5jb250YWlucygnbG9hZGluZycpKSB7XG5cdFx0XHRcdGltZy5jbGFzc0xpc3QucmVtb3ZlKCdsb2FkaW5nJyk7XG5cdFx0XHR9XG5cblx0XHRcdGltYWdlSW5mby5wdXNoKHtcblx0XHRcdFx0aWQ6IGltZy5pZCxcblx0XHRcdFx0aGVpZ2h0OiBpbWcuaGVpZ2h0LFxuXHRcdFx0XHR3aWR0aDogaW1nLndpZHRoXG5cdFx0XHR9KTtcblx0XHR9XG5cblx0XHRtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ2NhY2hlSW1hZ2VTaXplcycsIGltYWdlSW5mbyk7XG5cdH1cbn0sIDUwKTtcblxud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ3Jlc2l6ZScsICgpID0+IHtcblx0c2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuXHR1cGRhdGVJbWFnZVNpemVzKCk7XG59LCB0cnVlKTtcblxud2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ21lc3NhZ2UnLCBldmVudCA9PiB7XG5cdGlmIChldmVudC5kYXRhLnNvdXJjZSAhPT0gc2V0dGluZ3Muc291cmNlKSB7XG5cdFx0cmV0dXJuO1xuXHR9XG5cblx0c3dpdGNoIChldmVudC5kYXRhLnR5cGUpIHtcblx0XHRjYXNlICdvbkRpZENoYW5nZVRleHRFZGl0b3JTZWxlY3Rpb24nOlxuXHRcdFx0bWFya2VyLm9uRGlkQ2hhbmdlVGV4dEVkaXRvclNlbGVjdGlvbihldmVudC5kYXRhLmxpbmUpO1xuXHRcdFx0YnJlYWs7XG5cblx0XHRjYXNlICd1cGRhdGVWaWV3Jzpcblx0XHRcdG9uVXBkYXRlVmlldyhldmVudC5kYXRhLmxpbmUsIHNldHRpbmdzKTtcblx0XHRcdGJyZWFrO1xuXHR9XG59LCBmYWxzZSk7XG5cbmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2RibGNsaWNrJywgZXZlbnQgPT4ge1xuXHRpZiAoIXNldHRpbmdzLmRvdWJsZUNsaWNrVG9Td2l0Y2hUb0VkaXRvcikge1xuXHRcdHJldHVybjtcblx0fVxuXG5cdC8vIElnbm9yZSBjbGlja3Mgb24gbGlua3Ncblx0Zm9yIChsZXQgbm9kZSA9IGV2ZW50LnRhcmdldCBhcyBIVE1MRWxlbWVudDsgbm9kZTsgbm9kZSA9IG5vZGUucGFyZW50Tm9kZSBhcyBIVE1MRWxlbWVudCkge1xuXHRcdGlmIChub2RlLnRhZ05hbWUgPT09ICdBJykge1xuXHRcdFx0cmV0dXJuO1xuXHRcdH1cblx0fVxuXG5cdGNvbnN0IG9mZnNldCA9IGV2ZW50LnBhZ2VZO1xuXHRjb25zdCBsaW5lID0gZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQob2Zmc2V0KTtcblx0aWYgKHR5cGVvZiBsaW5lID09PSAnbnVtYmVyJyAmJiAhaXNOYU4obGluZSkpIHtcblx0XHRtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ2RpZENsaWNrJywgeyBsaW5lOiBNYXRoLmZsb29yKGxpbmUpIH0pO1xuXHR9XG59KTtcblxuZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignY2xpY2snLCBldmVudCA9PiB7XG5cdGlmICghZXZlbnQpIHtcblx0XHRyZXR1cm47XG5cdH1cblxuXHRsZXQgbm9kZTogYW55ID0gZXZlbnQudGFyZ2V0O1xuXHR3aGlsZSAobm9kZSkge1xuXHRcdGlmIChub2RlLnRhZ05hbWUgJiYgbm9kZS50YWdOYW1lID09PSAnQScgJiYgbm9kZS5ocmVmKSB7XG5cdFx0XHRpZiAobm9kZS5nZXRBdHRyaWJ1dGUoJ2hyZWYnKS5zdGFydHNXaXRoKCcjJykpIHtcblx0XHRcdFx0YnJlYWs7XG5cdFx0XHR9XG5cdFx0XHRpZiAobm9kZS5ocmVmLnN0YXJ0c1dpdGgoJ2ZpbGU6Ly8nKSB8fCBub2RlLmhyZWYuc3RhcnRzV2l0aCgndnNjb2RlLXJlc291cmNlOicpKSB7XG5cdFx0XHRcdGNvbnN0IFtwYXRoLCBmcmFnbWVudF0gPSBub2RlLmhyZWYucmVwbGFjZSgvXihmaWxlOlxcL1xcL3x2c2NvZGUtcmVzb3VyY2U6KS9pLCAnJykuc3BsaXQoJyMnKTtcblx0XHRcdFx0bWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdjbGlja0xpbmsnLCB7IHBhdGgsIGZyYWdtZW50IH0pO1xuXHRcdFx0XHRldmVudC5wcmV2ZW50RGVmYXVsdCgpO1xuXHRcdFx0XHRldmVudC5zdG9wUHJvcGFnYXRpb24oKTtcblx0XHRcdFx0YnJlYWs7XG5cdFx0XHR9XG5cdFx0XHRicmVhaztcblx0XHR9XG5cdFx0bm9kZSA9IG5vZGUucGFyZW50Tm9kZTtcblx0fVxufSwgdHJ1ZSk7XG5cbmlmIChzZXR0aW5ncy5zY3JvbGxFZGl0b3JXaXRoUHJldmlldykge1xuXHR3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignc2Nyb2xsJywgdGhyb3R0bGUoKCkgPT4ge1xuXHRcdGlmIChzY3JvbGxEaXNhYmxlZCkge1xuXHRcdFx0c2Nyb2xsRGlzYWJsZWQgPSBmYWxzZTtcblx0XHR9IGVsc2Uge1xuXHRcdFx0Y29uc3QgbGluZSA9IGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KHdpbmRvdy5zY3JvbGxZKTtcblx0XHRcdGlmICh0eXBlb2YgbGluZSA9PT0gJ251bWJlcicgJiYgIWlzTmFOKGxpbmUpKSB7XG5cdFx0XHRcdG1lc3NhZ2luZy5wb3N0TWVzc2FnZSgncmV2ZWFsTGluZScsIHsgbGluZSB9KTtcblx0XHRcdH1cblx0XHR9XG5cdH0sIDUwKSk7XG59IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmltcG9ydCB7IGdldFNldHRpbmdzIH0gZnJvbSAnLi9zZXR0aW5ncyc7XG5cbmV4cG9ydCBpbnRlcmZhY2UgTWVzc2FnZVBvc3RlciB7XG5cdC8qKlxuXHQgKiBQb3N0IGEgbWVzc2FnZSB0byB0aGUgbWFya2Rvd24gZXh0ZW5zaW9uXG5cdCAqL1xuXHRwb3N0TWVzc2FnZSh0eXBlOiBzdHJpbmcsIGJvZHk6IG9iamVjdCk6IHZvaWQ7XG59XG5cbmV4cG9ydCBjb25zdCBjcmVhdGVQb3N0ZXJGb3JWc0NvZGUgPSAodnNjb2RlOiBhbnkpID0+IHtcblx0cmV0dXJuIG5ldyBjbGFzcyBpbXBsZW1lbnRzIE1lc3NhZ2VQb3N0ZXIge1xuXHRcdHBvc3RNZXNzYWdlKHR5cGU6IHN0cmluZywgYm9keTogb2JqZWN0KTogdm9pZCB7XG5cdFx0XHR2c2NvZGUucG9zdE1lc3NhZ2Uoe1xuXHRcdFx0XHR0eXBlLFxuXHRcdFx0XHRzb3VyY2U6IGdldFNldHRpbmdzKCkuc291cmNlLFxuXHRcdFx0XHRib2R5XG5cdFx0XHR9KTtcblx0XHR9XG5cdH07XG59O1xuXG4iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuaW1wb3J0IHsgZ2V0U2V0dGluZ3MgfSBmcm9tICcuL3NldHRpbmdzJztcblxuXG5mdW5jdGlvbiBjbGFtcChtaW46IG51bWJlciwgbWF4OiBudW1iZXIsIHZhbHVlOiBudW1iZXIpIHtcblx0cmV0dXJuIE1hdGgubWluKG1heCwgTWF0aC5tYXgobWluLCB2YWx1ZSkpO1xufVxuXG5mdW5jdGlvbiBjbGFtcExpbmUobGluZTogbnVtYmVyKSB7XG5cdHJldHVybiBjbGFtcCgwLCBnZXRTZXR0aW5ncygpLmxpbmVDb3VudCAtIDEsIGxpbmUpO1xufVxuXG5cbmV4cG9ydCBpbnRlcmZhY2UgQ29kZUxpbmVFbGVtZW50IHtcblx0ZWxlbWVudDogSFRNTEVsZW1lbnQ7XG5cdGxpbmU6IG51bWJlcjtcbn1cblxuY29uc3QgZ2V0Q29kZUxpbmVFbGVtZW50cyA9ICgoKSA9PiB7XG5cdGxldCBlbGVtZW50czogQ29kZUxpbmVFbGVtZW50W107XG5cdHJldHVybiAoKSA9PiB7XG5cdFx0aWYgKCFlbGVtZW50cykge1xuXHRcdFx0ZWxlbWVudHMgPSAoW3sgZWxlbWVudDogZG9jdW1lbnQuYm9keSwgbGluZTogMCB9XSkuY29uY2F0KEFycmF5LnByb3RvdHlwZS5tYXAuY2FsbChcblx0XHRcdFx0ZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgnY29kZS1saW5lJyksXG5cdFx0XHRcdChlbGVtZW50OiBhbnkpID0+IHtcblx0XHRcdFx0XHRjb25zdCBsaW5lID0gK2VsZW1lbnQuZ2V0QXR0cmlidXRlKCdkYXRhLWxpbmUnKTtcblx0XHRcdFx0XHRyZXR1cm4geyBlbGVtZW50LCBsaW5lIH07XG5cdFx0XHRcdH0pXG5cdFx0XHRcdC5maWx0ZXIoKHg6IGFueSkgPT4gIWlzTmFOKHgubGluZSkpKTtcblx0XHR9XG5cdFx0cmV0dXJuIGVsZW1lbnRzO1xuXHR9O1xufSkoKTtcblxuLyoqXG4gKiBGaW5kIHRoZSBodG1sIGVsZW1lbnRzIHRoYXQgbWFwIHRvIGEgc3BlY2lmaWMgdGFyZ2V0IGxpbmUgaW4gdGhlIGVkaXRvci5cbiAqXG4gKiBJZiBhbiBleGFjdCBtYXRjaCwgcmV0dXJucyBhIHNpbmdsZSBlbGVtZW50LiBJZiB0aGUgbGluZSBpcyBiZXR3ZWVuIGVsZW1lbnRzLFxuICogcmV0dXJucyB0aGUgZWxlbWVudCBwcmlvciB0byBhbmQgdGhlIGVsZW1lbnQgYWZ0ZXIgdGhlIGdpdmVuIGxpbmUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUodGFyZ2V0TGluZTogbnVtYmVyKTogeyBwcmV2aW91czogQ29kZUxpbmVFbGVtZW50OyBuZXh0PzogQ29kZUxpbmVFbGVtZW50OyB9IHtcblx0Y29uc3QgbGluZU51bWJlciA9IE1hdGguZmxvb3IodGFyZ2V0TGluZSk7XG5cdGNvbnN0IGxpbmVzID0gZ2V0Q29kZUxpbmVFbGVtZW50cygpO1xuXHRsZXQgcHJldmlvdXMgPSBsaW5lc1swXSB8fCBudWxsO1xuXHRmb3IgKGNvbnN0IGVudHJ5IG9mIGxpbmVzKSB7XG5cdFx0aWYgKGVudHJ5LmxpbmUgPT09IGxpbmVOdW1iZXIpIHtcblx0XHRcdHJldHVybiB7IHByZXZpb3VzOiBlbnRyeSwgbmV4dDogdW5kZWZpbmVkIH07XG5cdFx0fSBlbHNlIGlmIChlbnRyeS5saW5lID4gbGluZU51bWJlcikge1xuXHRcdFx0cmV0dXJuIHsgcHJldmlvdXMsIG5leHQ6IGVudHJ5IH07XG5cdFx0fVxuXHRcdHByZXZpb3VzID0gZW50cnk7XG5cdH1cblx0cmV0dXJuIHsgcHJldmlvdXMgfTtcbn1cblxuLyoqXG4gKiBGaW5kIHRoZSBodG1sIGVsZW1lbnRzIHRoYXQgYXJlIGF0IGEgc3BlY2lmaWMgcGl4ZWwgb2Zmc2V0IG9uIHRoZSBwYWdlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0KG9mZnNldDogbnVtYmVyKTogeyBwcmV2aW91czogQ29kZUxpbmVFbGVtZW50OyBuZXh0PzogQ29kZUxpbmVFbGVtZW50OyB9IHtcblx0Y29uc3QgbGluZXMgPSBnZXRDb2RlTGluZUVsZW1lbnRzKCk7XG5cdGNvbnN0IHBvc2l0aW9uID0gb2Zmc2V0IC0gd2luZG93LnNjcm9sbFk7XG5cdGxldCBsbyA9IC0xO1xuXHRsZXQgaGkgPSBsaW5lcy5sZW5ndGggLSAxO1xuXHR3aGlsZSAobG8gKyAxIDwgaGkpIHtcblx0XHRjb25zdCBtaWQgPSBNYXRoLmZsb29yKChsbyArIGhpKSAvIDIpO1xuXHRcdGNvbnN0IGJvdW5kcyA9IGxpbmVzW21pZF0uZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcblx0XHRpZiAoYm91bmRzLnRvcCArIGJvdW5kcy5oZWlnaHQgPj0gcG9zaXRpb24pIHtcblx0XHRcdGhpID0gbWlkO1xuXHRcdH1cblx0XHRlbHNlIHtcblx0XHRcdGxvID0gbWlkO1xuXHRcdH1cblx0fVxuXHRjb25zdCBoaUVsZW1lbnQgPSBsaW5lc1toaV07XG5cdGNvbnN0IGhpQm91bmRzID0gaGlFbGVtZW50LmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG5cdGlmIChoaSA+PSAxICYmIGhpQm91bmRzLnRvcCA+IHBvc2l0aW9uKSB7XG5cdFx0Y29uc3QgbG9FbGVtZW50ID0gbGluZXNbbG9dO1xuXHRcdHJldHVybiB7IHByZXZpb3VzOiBsb0VsZW1lbnQsIG5leHQ6IGhpRWxlbWVudCB9O1xuXHR9XG5cdHJldHVybiB7IHByZXZpb3VzOiBoaUVsZW1lbnQgfTtcbn1cblxuLyoqXG4gKiBBdHRlbXB0IHRvIHJldmVhbCB0aGUgZWxlbWVudCBmb3IgYSBzb3VyY2UgbGluZSBpbiB0aGUgZWRpdG9yLlxuICovXG5leHBvcnQgZnVuY3Rpb24gc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGxpbmU6IG51bWJlcikge1xuXHRpZiAoIWdldFNldHRpbmdzKCkuc2Nyb2xsUHJldmlld1dpdGhFZGl0b3IpIHtcblx0XHRyZXR1cm47XG5cdH1cblxuXHRpZiAobGluZSA8PSAwKSB7XG5cdFx0d2luZG93LnNjcm9sbCh3aW5kb3cuc2Nyb2xsWCwgMCk7XG5cdFx0cmV0dXJuO1xuXHR9XG5cblx0Y29uc3QgeyBwcmV2aW91cywgbmV4dCB9ID0gZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lKGxpbmUpO1xuXHRpZiAoIXByZXZpb3VzKSB7XG5cdFx0cmV0dXJuO1xuXHR9XG5cdGxldCBzY3JvbGxUbyA9IDA7XG5cdGNvbnN0IHJlY3QgPSBwcmV2aW91cy5lbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuXHRjb25zdCBwcmV2aW91c1RvcCA9IHJlY3QudG9wO1xuXHRpZiAobmV4dCAmJiBuZXh0LmxpbmUgIT09IHByZXZpb3VzLmxpbmUpIHtcblx0XHQvLyBCZXR3ZWVuIHR3byBlbGVtZW50cy4gR28gdG8gcGVyY2VudGFnZSBvZmZzZXQgYmV0d2VlbiB0aGVtLlxuXHRcdGNvbnN0IGJldHdlZW5Qcm9ncmVzcyA9IChsaW5lIC0gcHJldmlvdXMubGluZSkgLyAobmV4dC5saW5lIC0gcHJldmlvdXMubGluZSk7XG5cdFx0Y29uc3QgZWxlbWVudE9mZnNldCA9IG5leHQuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS50b3AgLSBwcmV2aW91c1RvcDtcblx0XHRzY3JvbGxUbyA9IHByZXZpb3VzVG9wICsgYmV0d2VlblByb2dyZXNzICogZWxlbWVudE9mZnNldDtcblx0fSBlbHNlIHtcblx0XHRjb25zdCBwcm9ncmVzc0luRWxlbWVudCA9IGxpbmUgLSBNYXRoLmZsb29yKGxpbmUpO1xuXHRcdHNjcm9sbFRvID0gcHJldmlvdXNUb3AgKyAocmVjdC5oZWlnaHQgKiBwcm9ncmVzc0luRWxlbWVudCk7XG5cdH1cblx0d2luZG93LnNjcm9sbCh3aW5kb3cuc2Nyb2xsWCwgTWF0aC5tYXgoMSwgd2luZG93LnNjcm9sbFkgKyBzY3JvbGxUbykpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQob2Zmc2V0OiBudW1iZXIpIHtcblx0Y29uc3QgeyBwcmV2aW91cywgbmV4dCB9ID0gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0KG9mZnNldCk7XG5cdGlmIChwcmV2aW91cykge1xuXHRcdGNvbnN0IHByZXZpb3VzQm91bmRzID0gcHJldmlvdXMuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcblx0XHRjb25zdCBvZmZzZXRGcm9tUHJldmlvdXMgPSAob2Zmc2V0IC0gd2luZG93LnNjcm9sbFkgLSBwcmV2aW91c0JvdW5kcy50b3ApO1xuXHRcdGlmIChuZXh0KSB7XG5cdFx0XHRjb25zdCBwcm9ncmVzc0JldHdlZW5FbGVtZW50cyA9IG9mZnNldEZyb21QcmV2aW91cyAvIChuZXh0LmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkudG9wIC0gcHJldmlvdXNCb3VuZHMudG9wKTtcblx0XHRcdGNvbnN0IGxpbmUgPSBwcmV2aW91cy5saW5lICsgcHJvZ3Jlc3NCZXR3ZWVuRWxlbWVudHMgKiAobmV4dC5saW5lIC0gcHJldmlvdXMubGluZSk7XG5cdFx0XHRyZXR1cm4gY2xhbXBMaW5lKGxpbmUpO1xuXHRcdH1cblx0XHRlbHNlIHtcblx0XHRcdGNvbnN0IHByb2dyZXNzV2l0aGluRWxlbWVudCA9IG9mZnNldEZyb21QcmV2aW91cyAvIChwcmV2aW91c0JvdW5kcy5oZWlnaHQpO1xuXHRcdFx0Y29uc3QgbGluZSA9IHByZXZpb3VzLmxpbmUgKyBwcm9ncmVzc1dpdGhpbkVsZW1lbnQ7XG5cdFx0XHRyZXR1cm4gY2xhbXBMaW5lKGxpbmUpO1xuXHRcdH1cblx0fVxuXHRyZXR1cm4gbnVsbDtcbn1cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5leHBvcnQgaW50ZXJmYWNlIFByZXZpZXdTZXR0aW5ncyB7XG5cdHNvdXJjZTogc3RyaW5nO1xuXHRsaW5lOiBudW1iZXI7XG5cdGxpbmVDb3VudDogbnVtYmVyO1xuXHRzY3JvbGxQcmV2aWV3V2l0aEVkaXRvcj86IGJvb2xlYW47XG5cdHNjcm9sbEVkaXRvcldpdGhQcmV2aWV3OiBib29sZWFuO1xuXHRkaXNhYmxlU2VjdXJpdHlXYXJuaW5nczogYm9vbGVhbjtcblx0ZG91YmxlQ2xpY2tUb1N3aXRjaFRvRWRpdG9yOiBib29sZWFuO1xufVxuXG5sZXQgY2FjaGVkU2V0dGluZ3M6IFByZXZpZXdTZXR0aW5ncyB8IHVuZGVmaW5lZCA9IHVuZGVmaW5lZDtcblxuZXhwb3J0IGZ1bmN0aW9uIGdldERhdGEoa2V5OiBzdHJpbmcpOiBQcmV2aWV3U2V0dGluZ3Mge1xuXHRjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3ZzY29kZS1tYXJrZG93bi1wcmV2aWV3LWRhdGEnKTtcblx0aWYgKGVsZW1lbnQpIHtcblx0XHRjb25zdCBkYXRhID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoa2V5KTtcblx0XHRpZiAoZGF0YSkge1xuXHRcdFx0cmV0dXJuIEpTT04ucGFyc2UoZGF0YSk7XG5cdFx0fVxuXHR9XG5cblx0dGhyb3cgbmV3IEVycm9yKGBDb3VsZCBub3QgbG9hZCBkYXRhIGZvciAke2tleX1gKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGdldFNldHRpbmdzKCk6IFByZXZpZXdTZXR0aW5ncyB7XG5cdGlmIChjYWNoZWRTZXR0aW5ncykge1xuXHRcdHJldHVybiBjYWNoZWRTZXR0aW5ncztcblx0fVxuXG5cdGNhY2hlZFNldHRpbmdzID0gZ2V0RGF0YSgnZGF0YS1zZXR0aW5ncycpO1xuXHRpZiAoY2FjaGVkU2V0dGluZ3MpIHtcblx0XHRyZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG5cdH1cblxuXHR0aHJvdyBuZXcgRXJyb3IoJ0NvdWxkIG5vdCBsb2FkIHNldHRpbmdzJyk7XG59XG4iXSwic291cmNlUm9vdCI6IiJ9 \ No newline at end of file +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vbm9kZV9tb2R1bGVzL2xvZGFzaC50aHJvdHRsZS9pbmRleC5qcyIsIndlYnBhY2s6Ly8vKHdlYnBhY2spL2J1aWxkaW4vZ2xvYmFsLmpzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2FjdGl2ZUxpbmVNYXJrZXIudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvZXZlbnRzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2luZGV4LnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL21lc3NhZ2luZy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zY3JvbGwtc3luYy50cyIsIndlYnBhY2s6Ly8vLi9wcmV2aWV3LXNyYy9zZXR0aW5ncy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGFBQUs7QUFDTDtBQUNBOztBQUVBO0FBQ0E7QUFDQSx5REFBaUQsY0FBYztBQUMvRDs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBMkIsMEJBQTBCLEVBQUU7QUFDdkQseUNBQWlDLGVBQWU7QUFDaEQ7QUFDQTtBQUNBOztBQUVBO0FBQ0EsOERBQXNELCtEQUErRDs7QUFFckg7QUFDQTs7O0FBR0E7QUFDQTs7Ozs7Ozs7Ozs7O0FDbkVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsYUFBYSxPQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBSTtBQUNKO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLFNBQVM7QUFDcEIsV0FBVyxPQUFPO0FBQ2xCLFdBQVcsT0FBTyxZQUFZO0FBQzlCLFdBQVcsUUFBUTtBQUNuQjtBQUNBLFdBQVcsT0FBTztBQUNsQjtBQUNBLFdBQVcsUUFBUTtBQUNuQjtBQUNBLGFBQWEsU0FBUztBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFJO0FBQ0o7QUFDQTtBQUNBLDhDQUE4QyxrQkFBa0I7QUFDaEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLFNBQVM7QUFDcEIsV0FBVyxPQUFPO0FBQ2xCLFdBQVcsT0FBTyxZQUFZO0FBQzlCLFdBQVcsUUFBUTtBQUNuQjtBQUNBLFdBQVcsUUFBUTtBQUNuQjtBQUNBLGFBQWEsU0FBUztBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtREFBbUQsb0JBQW9CO0FBQ3ZFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLEdBQUc7QUFDSDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLEVBQUU7QUFDYixhQUFhLFFBQVE7QUFDckI7QUFDQTtBQUNBLGdCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFdBQVcsRUFBRTtBQUNiLGFBQWEsUUFBUTtBQUNyQjtBQUNBO0FBQ0Esb0JBQW9CO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxXQUFXLEVBQUU7QUFDYixhQUFhLFFBQVE7QUFDckI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsV0FBVyxFQUFFO0FBQ2IsYUFBYSxPQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOzs7Ozs7Ozs7Ozs7O0FDdGJBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLENBQUM7O0FBRUQ7QUFDQTtBQUNBO0FBQ0EsQ0FBQztBQUNEO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsNENBQTRDOztBQUU1Qzs7Ozs7Ozs7Ozs7Ozs7O0FDbkJBOzs7Z0dBR2dHO0FBQ2hHLCtGQUF5RDtBQUV6RCxNQUFhLGdCQUFnQjtJQUc1Qiw4QkFBOEIsQ0FBQyxJQUFZO1FBQzFDLE1BQU0sRUFBRSxRQUFRLEVBQUUsR0FBRyxzQ0FBd0IsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUNwRCxJQUFJLENBQUMsT0FBTyxDQUFDLFFBQVEsSUFBSSxRQUFRLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDNUMsQ0FBQztJQUVELE9BQU8sQ0FBQyxNQUErQjtRQUN0QyxJQUFJLENBQUMsb0JBQW9CLENBQUMsSUFBSSxDQUFDLFFBQVEsQ0FBQyxDQUFDO1FBQ3pDLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNoQyxJQUFJLENBQUMsUUFBUSxHQUFHLE1BQU0sQ0FBQztJQUN4QixDQUFDO0lBRUQsb0JBQW9CLENBQUMsT0FBZ0M7UUFDcEQsSUFBSSxDQUFDLE9BQU8sRUFBRTtZQUNiLE9BQU87U0FDUDtRQUNELE9BQU8sQ0FBQyxTQUFTLEdBQUcsT0FBTyxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsdUJBQXVCLEVBQUUsRUFBRSxDQUFDLENBQUM7SUFDNUUsQ0FBQztJQUVELGtCQUFrQixDQUFDLE9BQWdDO1FBQ2xELElBQUksQ0FBQyxPQUFPLEVBQUU7WUFDYixPQUFPO1NBQ1A7UUFDRCxPQUFPLENBQUMsU0FBUyxJQUFJLG1CQUFtQixDQUFDO0lBQzFDLENBQUM7Q0FDRDtBQTNCRCw0Q0EyQkM7Ozs7Ozs7Ozs7Ozs7O0FDakNEOzs7Z0dBR2dHOztBQUVoRyxTQUFnQixrQkFBa0IsQ0FBQyxDQUFhO0lBQy9DLElBQUksUUFBUSxDQUFDLFVBQVUsS0FBSyxTQUFTLElBQUksUUFBUSxDQUFDLFVBQW9CLEtBQUssZUFBZSxFQUFFO1FBQzNGLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyxrQkFBa0IsRUFBRSxDQUFDLENBQUMsQ0FBQztLQUNqRDtTQUFNO1FBQ04sQ0FBQyxFQUFFLENBQUM7S0FDSjtBQUNGLENBQUM7QUFORCxnREFNQzs7Ozs7Ozs7Ozs7Ozs7QUNYRDs7O2dHQUdnRzs7QUFFaEcsOEdBQXNEO0FBQ3RELGdGQUE4QztBQUM5Qyx5RkFBb0Q7QUFDcEQsK0ZBQTJGO0FBQzNGLHNGQUFrRDtBQUNsRCx1R0FBNkM7QUFJN0MsSUFBSSxjQUFjLEdBQUcsSUFBSSxDQUFDO0FBQzFCLE1BQU0sTUFBTSxHQUFHLElBQUksbUNBQWdCLEVBQUUsQ0FBQztBQUN0QyxNQUFNLFFBQVEsR0FBRyxzQkFBVyxFQUFFLENBQUM7QUFFL0IsTUFBTSxNQUFNLEdBQUcsZ0JBQWdCLEVBQUUsQ0FBQztBQUVsQyxvQkFBb0I7QUFDcEIsSUFBSSxLQUFLLEdBQUcsa0JBQU8sQ0FBQyxZQUFZLENBQUMsQ0FBQztBQUNsQyxNQUFNLENBQUMsUUFBUSxDQUFDLEtBQUssQ0FBQyxDQUFDO0FBRXZCLE1BQU0sU0FBUyxHQUFHLGlDQUFxQixDQUFDLE1BQU0sQ0FBQyxDQUFDO0FBRWhELE1BQU0sQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQyxDQUFDO0FBQ3ZDLE1BQU0sQ0FBQyxtQkFBbUIsQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLENBQUM7QUFFaEQsTUFBTSxDQUFDLE1BQU0sR0FBRyxHQUFHLEVBQUU7SUFDcEIsZ0JBQWdCLEVBQUUsQ0FBQztBQUNwQixDQUFDLENBQUM7QUFFRiwyQkFBa0IsQ0FBQyxHQUFHLEVBQUU7SUFDdkIsSUFBSSxRQUFRLENBQUMsdUJBQXVCLEVBQUU7UUFDckMsVUFBVSxDQUFDLEdBQUcsRUFBRTtZQUNmLE1BQU0sV0FBVyxHQUFHLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQztZQUNuQyxJQUFJLENBQUMsS0FBSyxDQUFDLFdBQVcsQ0FBQyxFQUFFO2dCQUN4QixjQUFjLEdBQUcsSUFBSSxDQUFDO2dCQUN0QixzQ0FBd0IsQ0FBQyxXQUFXLENBQUMsQ0FBQzthQUN0QztRQUNGLENBQUMsRUFBRSxDQUFDLENBQUMsQ0FBQztLQUNOO0FBQ0YsQ0FBQyxDQUFDLENBQUM7QUFFSCxNQUFNLFlBQVksR0FBRyxDQUFDLEdBQUcsRUFBRTtJQUMxQixNQUFNLFFBQVEsR0FBRyxRQUFRLENBQUMsQ0FBQyxJQUFZLEVBQUUsRUFBRTtRQUMxQyxjQUFjLEdBQUcsSUFBSSxDQUFDO1FBQ3RCLHNDQUF3QixDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ2hDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQztJQUVQLE9BQU8sQ0FBQyxJQUFZLEVBQUUsUUFBYSxFQUFFLEVBQUU7UUFDdEMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRTtZQUNqQixRQUFRLENBQUMsSUFBSSxHQUFHLElBQUksQ0FBQztZQUNyQixRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDZjtJQUNGLENBQUMsQ0FBQztBQUNILENBQUMsQ0FBQyxFQUFFLENBQUM7QUFFTCxJQUFJLGdCQUFnQixHQUFHLFFBQVEsQ0FBQyxHQUFHLEVBQUU7SUFDcEMsTUFBTSxTQUFTLEdBQW9ELEVBQUUsQ0FBQztJQUN0RSxJQUFJLE1BQU0sR0FBRyxRQUFRLENBQUMsb0JBQW9CLENBQUMsS0FBSyxDQUFDLENBQUM7SUFDbEQsSUFBSSxNQUFNLEVBQUU7UUFDWCxJQUFJLENBQUMsQ0FBQztRQUNOLEtBQUssQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsTUFBTSxDQUFDLE1BQU0sRUFBRSxDQUFDLEVBQUUsRUFBRTtZQUNuQyxNQUFNLEdBQUcsR0FBRyxNQUFNLENBQUMsQ0FBQyxDQUFDLENBQUM7WUFFdEIsSUFBSSxHQUFHLENBQUMsU0FBUyxDQUFDLFFBQVEsQ0FBQyxTQUFTLENBQUMsRUFBRTtnQkFDdEMsR0FBRyxDQUFDLFNBQVMsQ0FBQyxNQUFNLENBQUMsU0FBUyxDQUFDLENBQUM7YUFDaEM7WUFFRCxTQUFTLENBQUMsSUFBSSxDQUFDO2dCQUNkLEVBQUUsRUFBRSxHQUFHLENBQUMsRUFBRTtnQkFDVixNQUFNLEVBQUUsR0FBRyxDQUFDLE1BQU07Z0JBQ2xCLEtBQUssRUFBRSxHQUFHLENBQUMsS0FBSzthQUNoQixDQUFDLENBQUM7U0FDSDtRQUVELFNBQVMsQ0FBQyxXQUFXLENBQUMsaUJBQWlCLEVBQUUsU0FBUyxDQUFDLENBQUM7S0FDcEQ7QUFDRixDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUM7QUFFUCxNQUFNLENBQUMsZ0JBQWdCLENBQUMsUUFBUSxFQUFFLEdBQUcsRUFBRTtJQUN0QyxjQUFjLEdBQUcsSUFBSSxDQUFDO0lBQ3RCLGdCQUFnQixFQUFFLENBQUM7QUFDcEIsQ0FBQyxFQUFFLElBQUksQ0FBQyxDQUFDO0FBRVQsTUFBTSxDQUFDLGdCQUFnQixDQUFDLFNBQVMsRUFBRSxLQUFLLENBQUMsRUFBRTtJQUMxQyxJQUFJLEtBQUssQ0FBQyxJQUFJLENBQUMsTUFBTSxLQUFLLFFBQVEsQ0FBQyxNQUFNLEVBQUU7UUFDMUMsT0FBTztLQUNQO0lBRUQsUUFBUSxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRTtRQUN4QixLQUFLLGdDQUFnQztZQUNwQyxNQUFNLENBQUMsOEJBQThCLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsQ0FBQztZQUN2RCxNQUFNO1FBRVAsS0FBSyxZQUFZO1lBQ2hCLFlBQVksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsQ0FBQztZQUN4QyxNQUFNO0tBQ1A7QUFDRixDQUFDLEVBQUUsS0FBSyxDQUFDLENBQUM7QUFFVixRQUFRLENBQUMsZ0JBQWdCLENBQUMsVUFBVSxFQUFFLEtBQUssQ0FBQyxFQUFFO0lBQzdDLElBQUksQ0FBQyxRQUFRLENBQUMsMkJBQTJCLEVBQUU7UUFDMUMsT0FBTztLQUNQO0lBRUQseUJBQXlCO0lBQ3pCLEtBQUssSUFBSSxJQUFJLEdBQUcsS0FBSyxDQUFDLE1BQXFCLEVBQUUsSUFBSSxFQUFFLElBQUksR0FBRyxJQUFJLENBQUMsVUFBeUIsRUFBRTtRQUN6RixJQUFJLElBQUksQ0FBQyxPQUFPLEtBQUssR0FBRyxFQUFFO1lBQ3pCLE9BQU87U0FDUDtLQUNEO0lBRUQsTUFBTSxNQUFNLEdBQUcsS0FBSyxDQUFDLEtBQUssQ0FBQztJQUMzQixNQUFNLElBQUksR0FBRyw4Q0FBZ0MsQ0FBQyxNQUFNLENBQUMsQ0FBQztJQUN0RCxJQUFJLE9BQU8sSUFBSSxLQUFLLFFBQVEsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsRUFBRTtRQUM3QyxTQUFTLENBQUMsV0FBVyxDQUFDLFVBQVUsRUFBRSxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsQ0FBQztLQUM5RDtBQUNGLENBQUMsQ0FBQyxDQUFDO0FBRUgsUUFBUSxDQUFDLGdCQUFnQixDQUFDLE9BQU8sRUFBRSxLQUFLLENBQUMsRUFBRTtJQUMxQyxJQUFJLENBQUMsS0FBSyxFQUFFO1FBQ1gsT0FBTztLQUNQO0lBRUQsSUFBSSxJQUFJLEdBQVEsS0FBSyxDQUFDLE1BQU0sQ0FBQztJQUM3QixPQUFPLElBQUksRUFBRTtRQUNaLElBQUksSUFBSSxDQUFDLE9BQU8sSUFBSSxJQUFJLENBQUMsT0FBTyxLQUFLLEdBQUcsSUFBSSxJQUFJLENBQUMsSUFBSSxFQUFFO1lBQ3RELElBQUksSUFBSSxDQUFDLFlBQVksQ0FBQyxNQUFNLENBQUMsQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDLEVBQUU7Z0JBQzlDLE1BQU07YUFDTjtZQUNELElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsU0FBUyxDQUFDLElBQUksSUFBSSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsa0JBQWtCLENBQUMsRUFBRTtnQkFDaEYsTUFBTSxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsR0FBRyxJQUFJLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxnQ0FBZ0MsRUFBRSxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7Z0JBQzVGLFNBQVMsQ0FBQyxXQUFXLENBQUMsV0FBVyxFQUFFLEVBQUUsSUFBSSxFQUFFLFFBQVEsRUFBRSxDQUFDLENBQUM7Z0JBQ3ZELEtBQUssQ0FBQyxjQUFjLEVBQUUsQ0FBQztnQkFDdkIsS0FBSyxDQUFDLGVBQWUsRUFBRSxDQUFDO2dCQUN4QixNQUFNO2FBQ047WUFDRCxNQUFNO1NBQ047UUFDRCxJQUFJLEdBQUcsSUFBSSxDQUFDLFVBQVUsQ0FBQztLQUN2QjtBQUNGLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQztBQUVULElBQUksUUFBUSxDQUFDLHVCQUF1QixFQUFFO0lBQ3JDLE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxRQUFRLEVBQUUsUUFBUSxDQUFDLEdBQUcsRUFBRTtRQUMvQyxJQUFJLGNBQWMsRUFBRTtZQUNuQixjQUFjLEdBQUcsS0FBSyxDQUFDO1NBQ3ZCO2FBQU07WUFDTixNQUFNLElBQUksR0FBRyw4Q0FBZ0MsQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLENBQUM7WUFDOUQsSUFBSSxPQUFPLElBQUksS0FBSyxRQUFRLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLEVBQUU7Z0JBQzdDLFNBQVMsQ0FBQyxXQUFXLENBQUMsWUFBWSxFQUFFLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztnQkFDOUMsS0FBSyxDQUFDLElBQUksR0FBRyxJQUFJLENBQUM7Z0JBQ2xCLE1BQU0sQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7YUFDdkI7U0FDRDtJQUNGLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDO0NBQ1I7Ozs7Ozs7Ozs7Ozs7O0FDL0pEOzs7Z0dBR2dHOztBQUVoRyxzRkFBeUM7QUFTNUIsNkJBQXFCLEdBQUcsQ0FBQyxNQUFXLEVBQUUsRUFBRTtJQUNwRCxPQUFPLElBQUk7UUFDVixXQUFXLENBQUMsSUFBWSxFQUFFLElBQVk7WUFDckMsTUFBTSxDQUFDLFdBQVcsQ0FBQztnQkFDbEIsSUFBSTtnQkFDSixNQUFNLEVBQUUsc0JBQVcsRUFBRSxDQUFDLE1BQU07Z0JBQzVCLElBQUk7YUFDSixDQUFDLENBQUM7UUFDSixDQUFDO0tBQ0QsQ0FBQztBQUNILENBQUMsQ0FBQzs7Ozs7Ozs7Ozs7Ozs7QUN4QkY7OztnR0FHZ0c7O0FBRWhHLHNGQUF5QztBQUd6QyxTQUFTLEtBQUssQ0FBQyxHQUFXLEVBQUUsR0FBVyxFQUFFLEtBQWE7SUFDckQsT0FBTyxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxJQUFJLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxLQUFLLENBQUMsQ0FBQyxDQUFDO0FBQzVDLENBQUM7QUFFRCxTQUFTLFNBQVMsQ0FBQyxJQUFZO0lBQzlCLE9BQU8sS0FBSyxDQUFDLENBQUMsRUFBRSxzQkFBVyxFQUFFLENBQUMsU0FBUyxHQUFHLENBQUMsRUFBRSxJQUFJLENBQUMsQ0FBQztBQUNwRCxDQUFDO0FBUUQsTUFBTSxtQkFBbUIsR0FBRyxDQUFDLEdBQUcsRUFBRTtJQUNqQyxJQUFJLFFBQTJCLENBQUM7SUFDaEMsT0FBTyxHQUFHLEVBQUU7UUFDWCxJQUFJLENBQUMsUUFBUSxFQUFFO1lBQ2QsUUFBUSxHQUFHLENBQUMsRUFBRSxPQUFPLEVBQUUsUUFBUSxDQUFDLElBQUksRUFBRSxJQUFJLEVBQUUsQ0FBQyxFQUFFLENBQUMsQ0FBQztZQUNqRCxLQUFLLE1BQU0sT0FBTyxJQUFJLFFBQVEsQ0FBQyxzQkFBc0IsQ0FBQyxXQUFXLENBQUMsRUFBRTtnQkFDbkUsTUFBTSxJQUFJLEdBQUcsQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLFdBQVcsQ0FBRSxDQUFDO2dCQUNqRCxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxFQUFFO29CQUNqQixRQUFRLENBQUMsSUFBSSxDQUFDLEVBQUUsT0FBTyxFQUFFLE9BQXNCLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztpQkFDekQ7YUFDRDtTQUNEO1FBQ0QsT0FBTyxRQUFRLENBQUM7SUFDakIsQ0FBQyxDQUFDO0FBQ0gsQ0FBQyxDQUFDLEVBQUUsQ0FBQztBQUVMOzs7OztHQUtHO0FBQ0gsU0FBZ0Isd0JBQXdCLENBQUMsVUFBa0I7SUFDMUQsTUFBTSxVQUFVLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxVQUFVLENBQUMsQ0FBQztJQUMxQyxNQUFNLEtBQUssR0FBRyxtQkFBbUIsRUFBRSxDQUFDO0lBQ3BDLElBQUksUUFBUSxHQUFHLEtBQUssQ0FBQyxDQUFDLENBQUMsSUFBSSxJQUFJLENBQUM7SUFDaEMsS0FBSyxNQUFNLEtBQUssSUFBSSxLQUFLLEVBQUU7UUFDMUIsSUFBSSxLQUFLLENBQUMsSUFBSSxLQUFLLFVBQVUsRUFBRTtZQUM5QixPQUFPLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLENBQUM7U0FDNUM7YUFBTSxJQUFJLEtBQUssQ0FBQyxJQUFJLEdBQUcsVUFBVSxFQUFFO1lBQ25DLE9BQU8sRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxDQUFDO1NBQ2pDO1FBQ0QsUUFBUSxHQUFHLEtBQUssQ0FBQztLQUNqQjtJQUNELE9BQU8sRUFBRSxRQUFRLEVBQUUsQ0FBQztBQUNyQixDQUFDO0FBYkQsNERBYUM7QUFFRDs7R0FFRztBQUNILFNBQWdCLDJCQUEyQixDQUFDLE1BQWM7SUFDekQsTUFBTSxLQUFLLEdBQUcsbUJBQW1CLEVBQUUsQ0FBQztJQUNwQyxNQUFNLFFBQVEsR0FBRyxNQUFNLEdBQUcsTUFBTSxDQUFDLE9BQU8sQ0FBQztJQUN6QyxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUMsQ0FBQztJQUNaLElBQUksRUFBRSxHQUFHLEtBQUssQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDO0lBQzFCLE9BQU8sRUFBRSxHQUFHLENBQUMsR0FBRyxFQUFFLEVBQUU7UUFDbkIsTUFBTSxHQUFHLEdBQUcsSUFBSSxDQUFDLEtBQUssQ0FBQyxDQUFDLEVBQUUsR0FBRyxFQUFFLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUN0QyxNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUM7UUFDMUQsSUFBSSxNQUFNLENBQUMsR0FBRyxHQUFHLE1BQU0sQ0FBQyxNQUFNLElBQUksUUFBUSxFQUFFO1lBQzNDLEVBQUUsR0FBRyxHQUFHLENBQUM7U0FDVDthQUNJO1lBQ0osRUFBRSxHQUFHLEdBQUcsQ0FBQztTQUNUO0tBQ0Q7SUFDRCxNQUFNLFNBQVMsR0FBRyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7SUFDNUIsTUFBTSxRQUFRLEdBQUcsU0FBUyxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO0lBQzNELElBQUksRUFBRSxJQUFJLENBQUMsSUFBSSxRQUFRLENBQUMsR0FBRyxHQUFHLFFBQVEsRUFBRTtRQUN2QyxNQUFNLFNBQVMsR0FBRyxLQUFLLENBQUMsRUFBRSxDQUFDLENBQUM7UUFDNUIsT0FBTyxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxDQUFDO0tBQ2hEO0lBQ0QsT0FBTyxFQUFFLFFBQVEsRUFBRSxTQUFTLEVBQUUsQ0FBQztBQUNoQyxDQUFDO0FBdEJELGtFQXNCQztBQUVEOztHQUVHO0FBQ0gsU0FBZ0Isd0JBQXdCLENBQUMsSUFBWTtJQUNwRCxJQUFJLENBQUMsc0JBQVcsRUFBRSxDQUFDLHVCQUF1QixFQUFFO1FBQzNDLE9BQU87S0FDUDtJQUVELElBQUksSUFBSSxJQUFJLENBQUMsRUFBRTtRQUNkLE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxDQUFDLENBQUMsQ0FBQztRQUNqQyxPQUFPO0tBQ1A7SUFFRCxNQUFNLEVBQUUsUUFBUSxFQUFFLElBQUksRUFBRSxHQUFHLHdCQUF3QixDQUFDLElBQUksQ0FBQyxDQUFDO0lBQzFELElBQUksQ0FBQyxRQUFRLEVBQUU7UUFDZCxPQUFPO0tBQ1A7SUFDRCxJQUFJLFFBQVEsR0FBRyxDQUFDLENBQUM7SUFDakIsTUFBTSxJQUFJLEdBQUcsUUFBUSxDQUFDLE9BQU8sQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO0lBQ3RELE1BQU0sV0FBVyxHQUFHLElBQUksQ0FBQyxHQUFHLENBQUM7SUFDN0IsSUFBSSxJQUFJLElBQUksSUFBSSxDQUFDLElBQUksS0FBSyxRQUFRLENBQUMsSUFBSSxFQUFFO1FBQ3hDLDhEQUE4RDtRQUM5RCxNQUFNLGVBQWUsR0FBRyxDQUFDLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsSUFBSSxHQUFHLFFBQVEsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM3RSxNQUFNLGFBQWEsR0FBRyxJQUFJLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUMsR0FBRyxHQUFHLFdBQVcsQ0FBQztRQUM3RSxRQUFRLEdBQUcsV0FBVyxHQUFHLGVBQWUsR0FBRyxhQUFhLENBQUM7S0FDekQ7U0FBTTtRQUNOLE1BQU0saUJBQWlCLEdBQUcsSUFBSSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDbEQsUUFBUSxHQUFHLFdBQVcsR0FBRyxDQUFDLElBQUksQ0FBQyxNQUFNLEdBQUcsaUJBQWlCLENBQUMsQ0FBQztLQUMzRDtJQUNELE1BQU0sQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxNQUFNLENBQUMsT0FBTyxHQUFHLFFBQVEsQ0FBQyxDQUFDLENBQUM7QUFDdkUsQ0FBQztBQTNCRCw0REEyQkM7QUFFRCxTQUFnQixnQ0FBZ0MsQ0FBQyxNQUFjO0lBQzlELE1BQU0sRUFBRSxRQUFRLEVBQUUsSUFBSSxFQUFFLEdBQUcsMkJBQTJCLENBQUMsTUFBTSxDQUFDLENBQUM7SUFDL0QsSUFBSSxRQUFRLEVBQUU7UUFDYixNQUFNLGNBQWMsR0FBRyxRQUFRLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUM7UUFDaEUsTUFBTSxrQkFBa0IsR0FBRyxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUMsT0FBTyxHQUFHLGNBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUMxRSxJQUFJLElBQUksRUFBRTtZQUNULE1BQU0sdUJBQXVCLEdBQUcsa0JBQWtCLEdBQUcsQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLHFCQUFxQixFQUFFLENBQUMsR0FBRyxHQUFHLGNBQWMsQ0FBQyxHQUFHLENBQUMsQ0FBQztZQUNySCxNQUFNLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxHQUFHLHVCQUF1QixHQUFHLENBQUMsSUFBSSxDQUFDLElBQUksR0FBRyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDbkYsT0FBTyxTQUFTLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDdkI7YUFDSTtZQUNKLE1BQU0scUJBQXFCLEdBQUcsa0JBQWtCLEdBQUcsQ0FBQyxjQUFjLENBQUMsTUFBTSxDQUFDLENBQUM7WUFDM0UsTUFBTSxJQUFJLEdBQUcsUUFBUSxDQUFDLElBQUksR0FBRyxxQkFBcUIsQ0FBQztZQUNuRCxPQUFPLFNBQVMsQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUN2QjtLQUNEO0lBQ0QsT0FBTyxJQUFJLENBQUM7QUFDYixDQUFDO0FBakJELDRFQWlCQzs7Ozs7Ozs7Ozs7Ozs7QUN2SUQ7OztnR0FHZ0c7O0FBWWhHLElBQUksY0FBYyxHQUFnQyxTQUFTLENBQUM7QUFFNUQsU0FBZ0IsT0FBTyxDQUFDLEdBQVc7SUFDbEMsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDLGNBQWMsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQ3hFLElBQUksT0FBTyxFQUFFO1FBQ1osTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN2QyxJQUFJLElBQUksRUFBRTtZQUNULE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUN4QjtLQUNEO0lBRUQsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsR0FBRyxFQUFFLENBQUMsQ0FBQztBQUNuRCxDQUFDO0FBVkQsMEJBVUM7QUFFRCxTQUFnQixXQUFXO0lBQzFCLElBQUksY0FBYyxFQUFFO1FBQ25CLE9BQU8sY0FBYyxDQUFDO0tBQ3RCO0lBRUQsY0FBYyxHQUFHLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUMxQyxJQUFJLGNBQWMsRUFBRTtRQUNuQixPQUFPLGNBQWMsQ0FBQztLQUN0QjtJQUVELE1BQU0sSUFBSSxLQUFLLENBQUMseUJBQXlCLENBQUMsQ0FBQztBQUM1QyxDQUFDO0FBWEQsa0NBV0MiLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHtcbiBcdFx0XHRcdGNvbmZpZ3VyYWJsZTogZmFsc2UsXG4gXHRcdFx0XHRlbnVtZXJhYmxlOiB0cnVlLFxuIFx0XHRcdFx0Z2V0OiBnZXR0ZXJcbiBcdFx0XHR9KTtcbiBcdFx0fVxuIFx0fTtcblxuIFx0Ly8gZGVmaW5lIF9fZXNNb2R1bGUgb24gZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5yID0gZnVuY3Rpb24oZXhwb3J0cykge1xuIFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7IHZhbHVlOiB0cnVlIH0pO1xuIFx0fTtcblxuIFx0Ly8gZ2V0RGVmYXVsdEV4cG9ydCBmdW5jdGlvbiBmb3IgY29tcGF0aWJpbGl0eSB3aXRoIG5vbi1oYXJtb255IG1vZHVsZXNcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubiA9IGZ1bmN0aW9uKG1vZHVsZSkge1xuIFx0XHR2YXIgZ2V0dGVyID0gbW9kdWxlICYmIG1vZHVsZS5fX2VzTW9kdWxlID9cbiBcdFx0XHRmdW5jdGlvbiBnZXREZWZhdWx0KCkgeyByZXR1cm4gbW9kdWxlWydkZWZhdWx0J107IH0gOlxuIFx0XHRcdGZ1bmN0aW9uIGdldE1vZHVsZUV4cG9ydHMoKSB7IHJldHVybiBtb2R1bGU7IH07XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18uZChnZXR0ZXIsICdhJywgZ2V0dGVyKTtcbiBcdFx0cmV0dXJuIGdldHRlcjtcbiBcdH07XG5cbiBcdC8vIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbFxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5vID0gZnVuY3Rpb24ob2JqZWN0LCBwcm9wZXJ0eSkgeyByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwgcHJvcGVydHkpOyB9O1xuXG4gXHQvLyBfX3dlYnBhY2tfcHVibGljX3BhdGhfX1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5wID0gXCJcIjtcblxuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKF9fd2VicGFja19yZXF1aXJlX18ucyA9IFwiLi9wcmV2aWV3LXNyYy9pbmRleC50c1wiKTtcbiIsIi8qKlxuICogbG9kYXNoIChDdXN0b20gQnVpbGQpIDxodHRwczovL2xvZGFzaC5jb20vPlxuICogQnVpbGQ6IGBsb2Rhc2ggbW9kdWxhcml6ZSBleHBvcnRzPVwibnBtXCIgLW8gLi9gXG4gKiBDb3B5cmlnaHQgalF1ZXJ5IEZvdW5kYXRpb24gYW5kIG90aGVyIGNvbnRyaWJ1dG9ycyA8aHR0cHM6Ly9qcXVlcnkub3JnLz5cbiAqIFJlbGVhc2VkIHVuZGVyIE1JVCBsaWNlbnNlIDxodHRwczovL2xvZGFzaC5jb20vbGljZW5zZT5cbiAqIEJhc2VkIG9uIFVuZGVyc2NvcmUuanMgMS44LjMgPGh0dHA6Ly91bmRlcnNjb3JlanMub3JnL0xJQ0VOU0U+XG4gKiBDb3B5cmlnaHQgSmVyZW15IEFzaGtlbmFzLCBEb2N1bWVudENsb3VkIGFuZCBJbnZlc3RpZ2F0aXZlIFJlcG9ydGVycyAmIEVkaXRvcnNcbiAqL1xuXG4vKiogVXNlZCBhcyB0aGUgYFR5cGVFcnJvcmAgbWVzc2FnZSBmb3IgXCJGdW5jdGlvbnNcIiBtZXRob2RzLiAqL1xudmFyIEZVTkNfRVJST1JfVEVYVCA9ICdFeHBlY3RlZCBhIGZ1bmN0aW9uJztcblxuLyoqIFVzZWQgYXMgcmVmZXJlbmNlcyBmb3IgdmFyaW91cyBgTnVtYmVyYCBjb25zdGFudHMuICovXG52YXIgTkFOID0gMCAvIDA7XG5cbi8qKiBgT2JqZWN0I3RvU3RyaW5nYCByZXN1bHQgcmVmZXJlbmNlcy4gKi9cbnZhciBzeW1ib2xUYWcgPSAnW29iamVjdCBTeW1ib2xdJztcblxuLyoqIFVzZWQgdG8gbWF0Y2ggbGVhZGluZyBhbmQgdHJhaWxpbmcgd2hpdGVzcGFjZS4gKi9cbnZhciByZVRyaW0gPSAvXlxccyt8XFxzKyQvZztcblxuLyoqIFVzZWQgdG8gZGV0ZWN0IGJhZCBzaWduZWQgaGV4YWRlY2ltYWwgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzQmFkSGV4ID0gL15bLStdMHhbMC05YS1mXSskL2k7XG5cbi8qKiBVc2VkIHRvIGRldGVjdCBiaW5hcnkgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzQmluYXJ5ID0gL14wYlswMV0rJC9pO1xuXG4vKiogVXNlZCB0byBkZXRlY3Qgb2N0YWwgc3RyaW5nIHZhbHVlcy4gKi9cbnZhciByZUlzT2N0YWwgPSAvXjBvWzAtN10rJC9pO1xuXG4vKiogQnVpbHQtaW4gbWV0aG9kIHJlZmVyZW5jZXMgd2l0aG91dCBhIGRlcGVuZGVuY3kgb24gYHJvb3RgLiAqL1xudmFyIGZyZWVQYXJzZUludCA9IHBhcnNlSW50O1xuXG4vKiogRGV0ZWN0IGZyZWUgdmFyaWFibGUgYGdsb2JhbGAgZnJvbSBOb2RlLmpzLiAqL1xudmFyIGZyZWVHbG9iYWwgPSB0eXBlb2YgZ2xvYmFsID09ICdvYmplY3QnICYmIGdsb2JhbCAmJiBnbG9iYWwuT2JqZWN0ID09PSBPYmplY3QgJiYgZ2xvYmFsO1xuXG4vKiogRGV0ZWN0IGZyZWUgdmFyaWFibGUgYHNlbGZgLiAqL1xudmFyIGZyZWVTZWxmID0gdHlwZW9mIHNlbGYgPT0gJ29iamVjdCcgJiYgc2VsZiAmJiBzZWxmLk9iamVjdCA9PT0gT2JqZWN0ICYmIHNlbGY7XG5cbi8qKiBVc2VkIGFzIGEgcmVmZXJlbmNlIHRvIHRoZSBnbG9iYWwgb2JqZWN0LiAqL1xudmFyIHJvb3QgPSBmcmVlR2xvYmFsIHx8IGZyZWVTZWxmIHx8IEZ1bmN0aW9uKCdyZXR1cm4gdGhpcycpKCk7XG5cbi8qKiBVc2VkIGZvciBidWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcy4gKi9cbnZhciBvYmplY3RQcm90byA9IE9iamVjdC5wcm90b3R5cGU7XG5cbi8qKlxuICogVXNlZCB0byByZXNvbHZlIHRoZVxuICogW2B0b1N0cmluZ1RhZ2BdKGh0dHA6Ly9lY21hLWludGVybmF0aW9uYWwub3JnL2VjbWEtMjYyLzcuMC8jc2VjLW9iamVjdC5wcm90b3R5cGUudG9zdHJpbmcpXG4gKiBvZiB2YWx1ZXMuXG4gKi9cbnZhciBvYmplY3RUb1N0cmluZyA9IG9iamVjdFByb3RvLnRvU3RyaW5nO1xuXG4vKiBCdWlsdC1pbiBtZXRob2QgcmVmZXJlbmNlcyBmb3IgdGhvc2Ugd2l0aCB0aGUgc2FtZSBuYW1lIGFzIG90aGVyIGBsb2Rhc2hgIG1ldGhvZHMuICovXG52YXIgbmF0aXZlTWF4ID0gTWF0aC5tYXgsXG4gICAgbmF0aXZlTWluID0gTWF0aC5taW47XG5cbi8qKlxuICogR2V0cyB0aGUgdGltZXN0YW1wIG9mIHRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRoYXQgaGF2ZSBlbGFwc2VkIHNpbmNlXG4gKiB0aGUgVW5peCBlcG9jaCAoMSBKYW51YXJ5IDE5NzAgMDA6MDA6MDAgVVRDKS5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDIuNC4wXG4gKiBAY2F0ZWdvcnkgRGF0ZVxuICogQHJldHVybnMge251bWJlcn0gUmV0dXJucyB0aGUgdGltZXN0YW1wLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmRlZmVyKGZ1bmN0aW9uKHN0YW1wKSB7XG4gKiAgIGNvbnNvbGUubG9nKF8ubm93KCkgLSBzdGFtcCk7XG4gKiB9LCBfLm5vdygpKTtcbiAqIC8vID0+IExvZ3MgdGhlIG51bWJlciBvZiBtaWxsaXNlY29uZHMgaXQgdG9vayBmb3IgdGhlIGRlZmVycmVkIGludm9jYXRpb24uXG4gKi9cbnZhciBub3cgPSBmdW5jdGlvbigpIHtcbiAgcmV0dXJuIHJvb3QuRGF0ZS5ub3coKTtcbn07XG5cbi8qKlxuICogQ3JlYXRlcyBhIGRlYm91bmNlZCBmdW5jdGlvbiB0aGF0IGRlbGF5cyBpbnZva2luZyBgZnVuY2AgdW50aWwgYWZ0ZXIgYHdhaXRgXG4gKiBtaWxsaXNlY29uZHMgaGF2ZSBlbGFwc2VkIHNpbmNlIHRoZSBsYXN0IHRpbWUgdGhlIGRlYm91bmNlZCBmdW5jdGlvbiB3YXNcbiAqIGludm9rZWQuIFRoZSBkZWJvdW5jZWQgZnVuY3Rpb24gY29tZXMgd2l0aCBhIGBjYW5jZWxgIG1ldGhvZCB0byBjYW5jZWxcbiAqIGRlbGF5ZWQgYGZ1bmNgIGludm9jYXRpb25zIGFuZCBhIGBmbHVzaGAgbWV0aG9kIHRvIGltbWVkaWF0ZWx5IGludm9rZSB0aGVtLlxuICogUHJvdmlkZSBgb3B0aW9uc2AgdG8gaW5kaWNhdGUgd2hldGhlciBgZnVuY2Agc2hvdWxkIGJlIGludm9rZWQgb24gdGhlXG4gKiBsZWFkaW5nIGFuZC9vciB0cmFpbGluZyBlZGdlIG9mIHRoZSBgd2FpdGAgdGltZW91dC4gVGhlIGBmdW5jYCBpcyBpbnZva2VkXG4gKiB3aXRoIHRoZSBsYXN0IGFyZ3VtZW50cyBwcm92aWRlZCB0byB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uLiBTdWJzZXF1ZW50XG4gKiBjYWxscyB0byB0aGUgZGVib3VuY2VkIGZ1bmN0aW9uIHJldHVybiB0aGUgcmVzdWx0IG9mIHRoZSBsYXN0IGBmdW5jYFxuICogaW52b2NhdGlvbi5cbiAqXG4gKiAqKk5vdGU6KiogSWYgYGxlYWRpbmdgIGFuZCBgdHJhaWxpbmdgIG9wdGlvbnMgYXJlIGB0cnVlYCwgYGZ1bmNgIGlzXG4gKiBpbnZva2VkIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0IG9ubHkgaWYgdGhlIGRlYm91bmNlZCBmdW5jdGlvblxuICogaXMgaW52b2tlZCBtb3JlIHRoYW4gb25jZSBkdXJpbmcgdGhlIGB3YWl0YCB0aW1lb3V0LlxuICpcbiAqIElmIGB3YWl0YCBpcyBgMGAgYW5kIGBsZWFkaW5nYCBpcyBgZmFsc2VgLCBgZnVuY2AgaW52b2NhdGlvbiBpcyBkZWZlcnJlZFxuICogdW50aWwgdG8gdGhlIG5leHQgdGljaywgc2ltaWxhciB0byBgc2V0VGltZW91dGAgd2l0aCBhIHRpbWVvdXQgb2YgYDBgLlxuICpcbiAqIFNlZSBbRGF2aWQgQ29yYmFjaG8ncyBhcnRpY2xlXShodHRwczovL2Nzcy10cmlja3MuY29tL2RlYm91bmNpbmctdGhyb3R0bGluZy1leHBsYWluZWQtZXhhbXBsZXMvKVxuICogZm9yIGRldGFpbHMgb3ZlciB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBgXy5kZWJvdW5jZWAgYW5kIGBfLnRocm90dGxlYC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDAuMS4wXG4gKiBAY2F0ZWdvcnkgRnVuY3Rpb25cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIGRlYm91bmNlLlxuICogQHBhcmFtIHtudW1iZXJ9IFt3YWl0PTBdIFRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIGRlbGF5LlxuICogQHBhcmFtIHtPYmplY3R9IFtvcHRpb25zPXt9XSBUaGUgb3B0aW9ucyBvYmplY3QuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmxlYWRpbmc9ZmFsc2VdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgbGVhZGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0LlxuICogQHBhcmFtIHtudW1iZXJ9IFtvcHRpb25zLm1heFdhaXRdXG4gKiAgVGhlIG1heGltdW0gdGltZSBgZnVuY2AgaXMgYWxsb3dlZCB0byBiZSBkZWxheWVkIGJlZm9yZSBpdCdzIGludm9rZWQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLnRyYWlsaW5nPXRydWVdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IGRlYm91bmNlZCBmdW5jdGlvbi5cbiAqIEBleGFtcGxlXG4gKlxuICogLy8gQXZvaWQgY29zdGx5IGNhbGN1bGF0aW9ucyB3aGlsZSB0aGUgd2luZG93IHNpemUgaXMgaW4gZmx1eC5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdyZXNpemUnLCBfLmRlYm91bmNlKGNhbGN1bGF0ZUxheW91dCwgMTUwKSk7XG4gKlxuICogLy8gSW52b2tlIGBzZW5kTWFpbGAgd2hlbiBjbGlja2VkLCBkZWJvdW5jaW5nIHN1YnNlcXVlbnQgY2FsbHMuXG4gKiBqUXVlcnkoZWxlbWVudCkub24oJ2NsaWNrJywgXy5kZWJvdW5jZShzZW5kTWFpbCwgMzAwLCB7XG4gKiAgICdsZWFkaW5nJzogdHJ1ZSxcbiAqICAgJ3RyYWlsaW5nJzogZmFsc2VcbiAqIH0pKTtcbiAqXG4gKiAvLyBFbnN1cmUgYGJhdGNoTG9nYCBpcyBpbnZva2VkIG9uY2UgYWZ0ZXIgMSBzZWNvbmQgb2YgZGVib3VuY2VkIGNhbGxzLlxuICogdmFyIGRlYm91bmNlZCA9IF8uZGVib3VuY2UoYmF0Y2hMb2csIDI1MCwgeyAnbWF4V2FpdCc6IDEwMDAgfSk7XG4gKiB2YXIgc291cmNlID0gbmV3IEV2ZW50U291cmNlKCcvc3RyZWFtJyk7XG4gKiBqUXVlcnkoc291cmNlKS5vbignbWVzc2FnZScsIGRlYm91bmNlZCk7XG4gKlxuICogLy8gQ2FuY2VsIHRoZSB0cmFpbGluZyBkZWJvdW5jZWQgaW52b2NhdGlvbi5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdwb3BzdGF0ZScsIGRlYm91bmNlZC5jYW5jZWwpO1xuICovXG5mdW5jdGlvbiBkZWJvdW5jZShmdW5jLCB3YWl0LCBvcHRpb25zKSB7XG4gIHZhciBsYXN0QXJncyxcbiAgICAgIGxhc3RUaGlzLFxuICAgICAgbWF4V2FpdCxcbiAgICAgIHJlc3VsdCxcbiAgICAgIHRpbWVySWQsXG4gICAgICBsYXN0Q2FsbFRpbWUsXG4gICAgICBsYXN0SW52b2tlVGltZSA9IDAsXG4gICAgICBsZWFkaW5nID0gZmFsc2UsXG4gICAgICBtYXhpbmcgPSBmYWxzZSxcbiAgICAgIHRyYWlsaW5nID0gdHJ1ZTtcblxuICBpZiAodHlwZW9mIGZ1bmMgIT0gJ2Z1bmN0aW9uJykge1xuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoRlVOQ19FUlJPUl9URVhUKTtcbiAgfVxuICB3YWl0ID0gdG9OdW1iZXIod2FpdCkgfHwgMDtcbiAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgbGVhZGluZyA9ICEhb3B0aW9ucy5sZWFkaW5nO1xuICAgIG1heGluZyA9ICdtYXhXYWl0JyBpbiBvcHRpb25zO1xuICAgIG1heFdhaXQgPSBtYXhpbmcgPyBuYXRpdmVNYXgodG9OdW1iZXIob3B0aW9ucy5tYXhXYWl0KSB8fCAwLCB3YWl0KSA6IG1heFdhaXQ7XG4gICAgdHJhaWxpbmcgPSAndHJhaWxpbmcnIGluIG9wdGlvbnMgPyAhIW9wdGlvbnMudHJhaWxpbmcgOiB0cmFpbGluZztcbiAgfVxuXG4gIGZ1bmN0aW9uIGludm9rZUZ1bmModGltZSkge1xuICAgIHZhciBhcmdzID0gbGFzdEFyZ3MsXG4gICAgICAgIHRoaXNBcmcgPSBsYXN0VGhpcztcblxuICAgIGxhc3RBcmdzID0gbGFzdFRoaXMgPSB1bmRlZmluZWQ7XG4gICAgbGFzdEludm9rZVRpbWUgPSB0aW1lO1xuICAgIHJlc3VsdCA9IGZ1bmMuYXBwbHkodGhpc0FyZywgYXJncyk7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGxlYWRpbmdFZGdlKHRpbWUpIHtcbiAgICAvLyBSZXNldCBhbnkgYG1heFdhaXRgIHRpbWVyLlxuICAgIGxhc3RJbnZva2VUaW1lID0gdGltZTtcbiAgICAvLyBTdGFydCB0aGUgdGltZXIgZm9yIHRoZSB0cmFpbGluZyBlZGdlLlxuICAgIHRpbWVySWQgPSBzZXRUaW1lb3V0KHRpbWVyRXhwaXJlZCwgd2FpdCk7XG4gICAgLy8gSW52b2tlIHRoZSBsZWFkaW5nIGVkZ2UuXG4gICAgcmV0dXJuIGxlYWRpbmcgPyBpbnZva2VGdW5jKHRpbWUpIDogcmVzdWx0O1xuICB9XG5cbiAgZnVuY3Rpb24gcmVtYWluaW5nV2FpdCh0aW1lKSB7XG4gICAgdmFyIHRpbWVTaW5jZUxhc3RDYWxsID0gdGltZSAtIGxhc3RDYWxsVGltZSxcbiAgICAgICAgdGltZVNpbmNlTGFzdEludm9rZSA9IHRpbWUgLSBsYXN0SW52b2tlVGltZSxcbiAgICAgICAgcmVzdWx0ID0gd2FpdCAtIHRpbWVTaW5jZUxhc3RDYWxsO1xuXG4gICAgcmV0dXJuIG1heGluZyA/IG5hdGl2ZU1pbihyZXN1bHQsIG1heFdhaXQgLSB0aW1lU2luY2VMYXN0SW52b2tlKSA6IHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIHNob3VsZEludm9rZSh0aW1lKSB7XG4gICAgdmFyIHRpbWVTaW5jZUxhc3RDYWxsID0gdGltZSAtIGxhc3RDYWxsVGltZSxcbiAgICAgICAgdGltZVNpbmNlTGFzdEludm9rZSA9IHRpbWUgLSBsYXN0SW52b2tlVGltZTtcblxuICAgIC8vIEVpdGhlciB0aGlzIGlzIHRoZSBmaXJzdCBjYWxsLCBhY3Rpdml0eSBoYXMgc3RvcHBlZCBhbmQgd2UncmUgYXQgdGhlXG4gICAgLy8gdHJhaWxpbmcgZWRnZSwgdGhlIHN5c3RlbSB0aW1lIGhhcyBnb25lIGJhY2t3YXJkcyBhbmQgd2UncmUgdHJlYXRpbmdcbiAgICAvLyBpdCBhcyB0aGUgdHJhaWxpbmcgZWRnZSwgb3Igd2UndmUgaGl0IHRoZSBgbWF4V2FpdGAgbGltaXQuXG4gICAgcmV0dXJuIChsYXN0Q2FsbFRpbWUgPT09IHVuZGVmaW5lZCB8fCAodGltZVNpbmNlTGFzdENhbGwgPj0gd2FpdCkgfHxcbiAgICAgICh0aW1lU2luY2VMYXN0Q2FsbCA8IDApIHx8IChtYXhpbmcgJiYgdGltZVNpbmNlTGFzdEludm9rZSA+PSBtYXhXYWl0KSk7XG4gIH1cblxuICBmdW5jdGlvbiB0aW1lckV4cGlyZWQoKSB7XG4gICAgdmFyIHRpbWUgPSBub3coKTtcbiAgICBpZiAoc2hvdWxkSW52b2tlKHRpbWUpKSB7XG4gICAgICByZXR1cm4gdHJhaWxpbmdFZGdlKHRpbWUpO1xuICAgIH1cbiAgICAvLyBSZXN0YXJ0IHRoZSB0aW1lci5cbiAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHJlbWFpbmluZ1dhaXQodGltZSkpO1xuICB9XG5cbiAgZnVuY3Rpb24gdHJhaWxpbmdFZGdlKHRpbWUpIHtcbiAgICB0aW1lcklkID0gdW5kZWZpbmVkO1xuXG4gICAgLy8gT25seSBpbnZva2UgaWYgd2UgaGF2ZSBgbGFzdEFyZ3NgIHdoaWNoIG1lYW5zIGBmdW5jYCBoYXMgYmVlblxuICAgIC8vIGRlYm91bmNlZCBhdCBsZWFzdCBvbmNlLlxuICAgIGlmICh0cmFpbGluZyAmJiBsYXN0QXJncykge1xuICAgICAgcmV0dXJuIGludm9rZUZ1bmModGltZSk7XG4gICAgfVxuICAgIGxhc3RBcmdzID0gbGFzdFRoaXMgPSB1bmRlZmluZWQ7XG4gICAgcmV0dXJuIHJlc3VsdDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGNhbmNlbCgpIHtcbiAgICBpZiAodGltZXJJZCAhPT0gdW5kZWZpbmVkKSB7XG4gICAgICBjbGVhclRpbWVvdXQodGltZXJJZCk7XG4gICAgfVxuICAgIGxhc3RJbnZva2VUaW1lID0gMDtcbiAgICBsYXN0QXJncyA9IGxhc3RDYWxsVGltZSA9IGxhc3RUaGlzID0gdGltZXJJZCA9IHVuZGVmaW5lZDtcbiAgfVxuXG4gIGZ1bmN0aW9uIGZsdXNoKCkge1xuICAgIHJldHVybiB0aW1lcklkID09PSB1bmRlZmluZWQgPyByZXN1bHQgOiB0cmFpbGluZ0VkZ2Uobm93KCkpO1xuICB9XG5cbiAgZnVuY3Rpb24gZGVib3VuY2VkKCkge1xuICAgIHZhciB0aW1lID0gbm93KCksXG4gICAgICAgIGlzSW52b2tpbmcgPSBzaG91bGRJbnZva2UodGltZSk7XG5cbiAgICBsYXN0QXJncyA9IGFyZ3VtZW50cztcbiAgICBsYXN0VGhpcyA9IHRoaXM7XG4gICAgbGFzdENhbGxUaW1lID0gdGltZTtcblxuICAgIGlmIChpc0ludm9raW5nKSB7XG4gICAgICBpZiAodGltZXJJZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgIHJldHVybiBsZWFkaW5nRWRnZShsYXN0Q2FsbFRpbWUpO1xuICAgICAgfVxuICAgICAgaWYgKG1heGluZykge1xuICAgICAgICAvLyBIYW5kbGUgaW52b2NhdGlvbnMgaW4gYSB0aWdodCBsb29wLlxuICAgICAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHdhaXQpO1xuICAgICAgICByZXR1cm4gaW52b2tlRnVuYyhsYXN0Q2FsbFRpbWUpO1xuICAgICAgfVxuICAgIH1cbiAgICBpZiAodGltZXJJZCA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICB0aW1lcklkID0gc2V0VGltZW91dCh0aW1lckV4cGlyZWQsIHdhaXQpO1xuICAgIH1cbiAgICByZXR1cm4gcmVzdWx0O1xuICB9XG4gIGRlYm91bmNlZC5jYW5jZWwgPSBjYW5jZWw7XG4gIGRlYm91bmNlZC5mbHVzaCA9IGZsdXNoO1xuICByZXR1cm4gZGVib3VuY2VkO1xufVxuXG4vKipcbiAqIENyZWF0ZXMgYSB0aHJvdHRsZWQgZnVuY3Rpb24gdGhhdCBvbmx5IGludm9rZXMgYGZ1bmNgIGF0IG1vc3Qgb25jZSBwZXJcbiAqIGV2ZXJ5IGB3YWl0YCBtaWxsaXNlY29uZHMuIFRoZSB0aHJvdHRsZWQgZnVuY3Rpb24gY29tZXMgd2l0aCBhIGBjYW5jZWxgXG4gKiBtZXRob2QgdG8gY2FuY2VsIGRlbGF5ZWQgYGZ1bmNgIGludm9jYXRpb25zIGFuZCBhIGBmbHVzaGAgbWV0aG9kIHRvXG4gKiBpbW1lZGlhdGVseSBpbnZva2UgdGhlbS4gUHJvdmlkZSBgb3B0aW9uc2AgdG8gaW5kaWNhdGUgd2hldGhlciBgZnVuY2BcbiAqIHNob3VsZCBiZSBpbnZva2VkIG9uIHRoZSBsZWFkaW5nIGFuZC9vciB0cmFpbGluZyBlZGdlIG9mIHRoZSBgd2FpdGBcbiAqIHRpbWVvdXQuIFRoZSBgZnVuY2AgaXMgaW52b2tlZCB3aXRoIHRoZSBsYXN0IGFyZ3VtZW50cyBwcm92aWRlZCB0byB0aGVcbiAqIHRocm90dGxlZCBmdW5jdGlvbi4gU3Vic2VxdWVudCBjYWxscyB0byB0aGUgdGhyb3R0bGVkIGZ1bmN0aW9uIHJldHVybiB0aGVcbiAqIHJlc3VsdCBvZiB0aGUgbGFzdCBgZnVuY2AgaW52b2NhdGlvbi5cbiAqXG4gKiAqKk5vdGU6KiogSWYgYGxlYWRpbmdgIGFuZCBgdHJhaWxpbmdgIG9wdGlvbnMgYXJlIGB0cnVlYCwgYGZ1bmNgIGlzXG4gKiBpbnZva2VkIG9uIHRoZSB0cmFpbGluZyBlZGdlIG9mIHRoZSB0aW1lb3V0IG9ubHkgaWYgdGhlIHRocm90dGxlZCBmdW5jdGlvblxuICogaXMgaW52b2tlZCBtb3JlIHRoYW4gb25jZSBkdXJpbmcgdGhlIGB3YWl0YCB0aW1lb3V0LlxuICpcbiAqIElmIGB3YWl0YCBpcyBgMGAgYW5kIGBsZWFkaW5nYCBpcyBgZmFsc2VgLCBgZnVuY2AgaW52b2NhdGlvbiBpcyBkZWZlcnJlZFxuICogdW50aWwgdG8gdGhlIG5leHQgdGljaywgc2ltaWxhciB0byBgc2V0VGltZW91dGAgd2l0aCBhIHRpbWVvdXQgb2YgYDBgLlxuICpcbiAqIFNlZSBbRGF2aWQgQ29yYmFjaG8ncyBhcnRpY2xlXShodHRwczovL2Nzcy10cmlja3MuY29tL2RlYm91bmNpbmctdGhyb3R0bGluZy1leHBsYWluZWQtZXhhbXBsZXMvKVxuICogZm9yIGRldGFpbHMgb3ZlciB0aGUgZGlmZmVyZW5jZXMgYmV0d2VlbiBgXy50aHJvdHRsZWAgYW5kIGBfLmRlYm91bmNlYC5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDAuMS4wXG4gKiBAY2F0ZWdvcnkgRnVuY3Rpb25cbiAqIEBwYXJhbSB7RnVuY3Rpb259IGZ1bmMgVGhlIGZ1bmN0aW9uIHRvIHRocm90dGxlLlxuICogQHBhcmFtIHtudW1iZXJ9IFt3YWl0PTBdIFRoZSBudW1iZXIgb2YgbWlsbGlzZWNvbmRzIHRvIHRocm90dGxlIGludm9jYXRpb25zIHRvLlxuICogQHBhcmFtIHtPYmplY3R9IFtvcHRpb25zPXt9XSBUaGUgb3B0aW9ucyBvYmplY3QuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLmxlYWRpbmc9dHJ1ZV1cbiAqICBTcGVjaWZ5IGludm9raW5nIG9uIHRoZSBsZWFkaW5nIGVkZ2Ugb2YgdGhlIHRpbWVvdXQuXG4gKiBAcGFyYW0ge2Jvb2xlYW59IFtvcHRpb25zLnRyYWlsaW5nPXRydWVdXG4gKiAgU3BlY2lmeSBpbnZva2luZyBvbiB0aGUgdHJhaWxpbmcgZWRnZSBvZiB0aGUgdGltZW91dC5cbiAqIEByZXR1cm5zIHtGdW5jdGlvbn0gUmV0dXJucyB0aGUgbmV3IHRocm90dGxlZCBmdW5jdGlvbi5cbiAqIEBleGFtcGxlXG4gKlxuICogLy8gQXZvaWQgZXhjZXNzaXZlbHkgdXBkYXRpbmcgdGhlIHBvc2l0aW9uIHdoaWxlIHNjcm9sbGluZy5cbiAqIGpRdWVyeSh3aW5kb3cpLm9uKCdzY3JvbGwnLCBfLnRocm90dGxlKHVwZGF0ZVBvc2l0aW9uLCAxMDApKTtcbiAqXG4gKiAvLyBJbnZva2UgYHJlbmV3VG9rZW5gIHdoZW4gdGhlIGNsaWNrIGV2ZW50IGlzIGZpcmVkLCBidXQgbm90IG1vcmUgdGhhbiBvbmNlIGV2ZXJ5IDUgbWludXRlcy5cbiAqIHZhciB0aHJvdHRsZWQgPSBfLnRocm90dGxlKHJlbmV3VG9rZW4sIDMwMDAwMCwgeyAndHJhaWxpbmcnOiBmYWxzZSB9KTtcbiAqIGpRdWVyeShlbGVtZW50KS5vbignY2xpY2snLCB0aHJvdHRsZWQpO1xuICpcbiAqIC8vIENhbmNlbCB0aGUgdHJhaWxpbmcgdGhyb3R0bGVkIGludm9jYXRpb24uXG4gKiBqUXVlcnkod2luZG93KS5vbigncG9wc3RhdGUnLCB0aHJvdHRsZWQuY2FuY2VsKTtcbiAqL1xuZnVuY3Rpb24gdGhyb3R0bGUoZnVuYywgd2FpdCwgb3B0aW9ucykge1xuICB2YXIgbGVhZGluZyA9IHRydWUsXG4gICAgICB0cmFpbGluZyA9IHRydWU7XG5cbiAgaWYgKHR5cGVvZiBmdW5jICE9ICdmdW5jdGlvbicpIHtcbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKEZVTkNfRVJST1JfVEVYVCk7XG4gIH1cbiAgaWYgKGlzT2JqZWN0KG9wdGlvbnMpKSB7XG4gICAgbGVhZGluZyA9ICdsZWFkaW5nJyBpbiBvcHRpb25zID8gISFvcHRpb25zLmxlYWRpbmcgOiBsZWFkaW5nO1xuICAgIHRyYWlsaW5nID0gJ3RyYWlsaW5nJyBpbiBvcHRpb25zID8gISFvcHRpb25zLnRyYWlsaW5nIDogdHJhaWxpbmc7XG4gIH1cbiAgcmV0dXJuIGRlYm91bmNlKGZ1bmMsIHdhaXQsIHtcbiAgICAnbGVhZGluZyc6IGxlYWRpbmcsXG4gICAgJ21heFdhaXQnOiB3YWl0LFxuICAgICd0cmFpbGluZyc6IHRyYWlsaW5nXG4gIH0pO1xufVxuXG4vKipcbiAqIENoZWNrcyBpZiBgdmFsdWVgIGlzIHRoZVxuICogW2xhbmd1YWdlIHR5cGVdKGh0dHA6Ly93d3cuZWNtYS1pbnRlcm5hdGlvbmFsLm9yZy9lY21hLTI2Mi83LjAvI3NlYy1lY21hc2NyaXB0LWxhbmd1YWdlLXR5cGVzKVxuICogb2YgYE9iamVjdGAuIChlLmcuIGFycmF5cywgZnVuY3Rpb25zLCBvYmplY3RzLCByZWdleGVzLCBgbmV3IE51bWJlcigwKWAsIGFuZCBgbmV3IFN0cmluZygnJylgKVxuICpcbiAqIEBzdGF0aWNcbiAqIEBtZW1iZXJPZiBfXG4gKiBAc2luY2UgMC4xLjBcbiAqIEBjYXRlZ29yeSBMYW5nXG4gKiBAcGFyYW0geyp9IHZhbHVlIFRoZSB2YWx1ZSB0byBjaGVjay5cbiAqIEByZXR1cm5zIHtib29sZWFufSBSZXR1cm5zIGB0cnVlYCBpZiBgdmFsdWVgIGlzIGFuIG9iamVjdCwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzT2JqZWN0KHt9KTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0KFsxLCAyLCAzXSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdChfLm5vb3ApO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNPYmplY3QobnVsbCk7XG4gKiAvLyA9PiBmYWxzZVxuICovXG5mdW5jdGlvbiBpc09iamVjdCh2YWx1ZSkge1xuICB2YXIgdHlwZSA9IHR5cGVvZiB2YWx1ZTtcbiAgcmV0dXJuICEhdmFsdWUgJiYgKHR5cGUgPT0gJ29iamVjdCcgfHwgdHlwZSA9PSAnZnVuY3Rpb24nKTtcbn1cblxuLyoqXG4gKiBDaGVja3MgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZS4gQSB2YWx1ZSBpcyBvYmplY3QtbGlrZSBpZiBpdCdzIG5vdCBgbnVsbGBcbiAqIGFuZCBoYXMgYSBgdHlwZW9mYCByZXN1bHQgb2YgXCJvYmplY3RcIi5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gY2hlY2suXG4gKiBAcmV0dXJucyB7Ym9vbGVhbn0gUmV0dXJucyBgdHJ1ZWAgaWYgYHZhbHVlYCBpcyBvYmplY3QtbGlrZSwgZWxzZSBgZmFsc2VgLlxuICogQGV4YW1wbGVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZSh7fSk7XG4gKiAvLyA9PiB0cnVlXG4gKlxuICogXy5pc09iamVjdExpa2UoWzEsIDIsIDNdKTtcbiAqIC8vID0+IHRydWVcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShfLm5vb3ApO1xuICogLy8gPT4gZmFsc2VcbiAqXG4gKiBfLmlzT2JqZWN0TGlrZShudWxsKTtcbiAqIC8vID0+IGZhbHNlXG4gKi9cbmZ1bmN0aW9uIGlzT2JqZWN0TGlrZSh2YWx1ZSkge1xuICByZXR1cm4gISF2YWx1ZSAmJiB0eXBlb2YgdmFsdWUgPT0gJ29iamVjdCc7XG59XG5cbi8qKlxuICogQ2hlY2tzIGlmIGB2YWx1ZWAgaXMgY2xhc3NpZmllZCBhcyBhIGBTeW1ib2xgIHByaW1pdGl2ZSBvciBvYmplY3QuXG4gKlxuICogQHN0YXRpY1xuICogQG1lbWJlck9mIF9cbiAqIEBzaW5jZSA0LjAuMFxuICogQGNhdGVnb3J5IExhbmdcbiAqIEBwYXJhbSB7Kn0gdmFsdWUgVGhlIHZhbHVlIHRvIGNoZWNrLlxuICogQHJldHVybnMge2Jvb2xlYW59IFJldHVybnMgYHRydWVgIGlmIGB2YWx1ZWAgaXMgYSBzeW1ib2wsIGVsc2UgYGZhbHNlYC5cbiAqIEBleGFtcGxlXG4gKlxuICogXy5pc1N5bWJvbChTeW1ib2wuaXRlcmF0b3IpO1xuICogLy8gPT4gdHJ1ZVxuICpcbiAqIF8uaXNTeW1ib2woJ2FiYycpO1xuICogLy8gPT4gZmFsc2VcbiAqL1xuZnVuY3Rpb24gaXNTeW1ib2wodmFsdWUpIHtcbiAgcmV0dXJuIHR5cGVvZiB2YWx1ZSA9PSAnc3ltYm9sJyB8fFxuICAgIChpc09iamVjdExpa2UodmFsdWUpICYmIG9iamVjdFRvU3RyaW5nLmNhbGwodmFsdWUpID09IHN5bWJvbFRhZyk7XG59XG5cbi8qKlxuICogQ29udmVydHMgYHZhbHVlYCB0byBhIG51bWJlci5cbiAqXG4gKiBAc3RhdGljXG4gKiBAbWVtYmVyT2YgX1xuICogQHNpbmNlIDQuMC4wXG4gKiBAY2F0ZWdvcnkgTGFuZ1xuICogQHBhcmFtIHsqfSB2YWx1ZSBUaGUgdmFsdWUgdG8gcHJvY2Vzcy5cbiAqIEByZXR1cm5zIHtudW1iZXJ9IFJldHVybnMgdGhlIG51bWJlci5cbiAqIEBleGFtcGxlXG4gKlxuICogXy50b051bWJlcigzLjIpO1xuICogLy8gPT4gMy4yXG4gKlxuICogXy50b051bWJlcihOdW1iZXIuTUlOX1ZBTFVFKTtcbiAqIC8vID0+IDVlLTMyNFxuICpcbiAqIF8udG9OdW1iZXIoSW5maW5pdHkpO1xuICogLy8gPT4gSW5maW5pdHlcbiAqXG4gKiBfLnRvTnVtYmVyKCczLjInKTtcbiAqIC8vID0+IDMuMlxuICovXG5mdW5jdGlvbiB0b051bWJlcih2YWx1ZSkge1xuICBpZiAodHlwZW9mIHZhbHVlID09ICdudW1iZXInKSB7XG4gICAgcmV0dXJuIHZhbHVlO1xuICB9XG4gIGlmIChpc1N5bWJvbCh2YWx1ZSkpIHtcbiAgICByZXR1cm4gTkFOO1xuICB9XG4gIGlmIChpc09iamVjdCh2YWx1ZSkpIHtcbiAgICB2YXIgb3RoZXIgPSB0eXBlb2YgdmFsdWUudmFsdWVPZiA9PSAnZnVuY3Rpb24nID8gdmFsdWUudmFsdWVPZigpIDogdmFsdWU7XG4gICAgdmFsdWUgPSBpc09iamVjdChvdGhlcikgPyAob3RoZXIgKyAnJykgOiBvdGhlcjtcbiAgfVxuICBpZiAodHlwZW9mIHZhbHVlICE9ICdzdHJpbmcnKSB7XG4gICAgcmV0dXJuIHZhbHVlID09PSAwID8gdmFsdWUgOiArdmFsdWU7XG4gIH1cbiAgdmFsdWUgPSB2YWx1ZS5yZXBsYWNlKHJlVHJpbSwgJycpO1xuICB2YXIgaXNCaW5hcnkgPSByZUlzQmluYXJ5LnRlc3QodmFsdWUpO1xuICByZXR1cm4gKGlzQmluYXJ5IHx8IHJlSXNPY3RhbC50ZXN0KHZhbHVlKSlcbiAgICA/IGZyZWVQYXJzZUludCh2YWx1ZS5zbGljZSgyKSwgaXNCaW5hcnkgPyAyIDogOClcbiAgICA6IChyZUlzQmFkSGV4LnRlc3QodmFsdWUpID8gTkFOIDogK3ZhbHVlKTtcbn1cblxubW9kdWxlLmV4cG9ydHMgPSB0aHJvdHRsZTtcbiIsInZhciBnO1xyXG5cclxuLy8gVGhpcyB3b3JrcyBpbiBub24tc3RyaWN0IG1vZGVcclxuZyA9IChmdW5jdGlvbigpIHtcclxuXHRyZXR1cm4gdGhpcztcclxufSkoKTtcclxuXHJcbnRyeSB7XHJcblx0Ly8gVGhpcyB3b3JrcyBpZiBldmFsIGlzIGFsbG93ZWQgKHNlZSBDU1ApXHJcblx0ZyA9IGcgfHwgRnVuY3Rpb24oXCJyZXR1cm4gdGhpc1wiKSgpIHx8ICgxLCBldmFsKShcInRoaXNcIik7XHJcbn0gY2F0Y2ggKGUpIHtcclxuXHQvLyBUaGlzIHdvcmtzIGlmIHRoZSB3aW5kb3cgcmVmZXJlbmNlIGlzIGF2YWlsYWJsZVxyXG5cdGlmICh0eXBlb2Ygd2luZG93ID09PSBcIm9iamVjdFwiKSBnID0gd2luZG93O1xyXG59XHJcblxyXG4vLyBnIGNhbiBzdGlsbCBiZSB1bmRlZmluZWQsIGJ1dCBub3RoaW5nIHRvIGRvIGFib3V0IGl0Li4uXHJcbi8vIFdlIHJldHVybiB1bmRlZmluZWQsIGluc3RlYWQgb2Ygbm90aGluZyBoZXJlLCBzbyBpdCdzXHJcbi8vIGVhc2llciB0byBoYW5kbGUgdGhpcyBjYXNlLiBpZighZ2xvYmFsKSB7IC4uLn1cclxuXHJcbm1vZHVsZS5leHBvcnRzID0gZztcclxuIiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5pbXBvcnQgeyBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUgfSBmcm9tICcuL3Njcm9sbC1zeW5jJztcblxuZXhwb3J0IGNsYXNzIEFjdGl2ZUxpbmVNYXJrZXIge1xuXHRwcml2YXRlIF9jdXJyZW50OiBhbnk7XG5cblx0b25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uKGxpbmU6IG51bWJlcikge1xuXHRcdGNvbnN0IHsgcHJldmlvdXMgfSA9IGdldEVsZW1lbnRzRm9yU291cmNlTGluZShsaW5lKTtcblx0XHR0aGlzLl91cGRhdGUocHJldmlvdXMgJiYgcHJldmlvdXMuZWxlbWVudCk7XG5cdH1cblxuXHRfdXBkYXRlKGJlZm9yZTogSFRNTEVsZW1lbnQgfCB1bmRlZmluZWQpIHtcblx0XHR0aGlzLl91bm1hcmtBY3RpdmVFbGVtZW50KHRoaXMuX2N1cnJlbnQpO1xuXHRcdHRoaXMuX21hcmtBY3RpdmVFbGVtZW50KGJlZm9yZSk7XG5cdFx0dGhpcy5fY3VycmVudCA9IGJlZm9yZTtcblx0fVxuXG5cdF91bm1hcmtBY3RpdmVFbGVtZW50KGVsZW1lbnQ6IEhUTUxFbGVtZW50IHwgdW5kZWZpbmVkKSB7XG5cdFx0aWYgKCFlbGVtZW50KSB7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXHRcdGVsZW1lbnQuY2xhc3NOYW1lID0gZWxlbWVudC5jbGFzc05hbWUucmVwbGFjZSgvXFxiY29kZS1hY3RpdmUtbGluZVxcYi9nLCAnJyk7XG5cdH1cblxuXHRfbWFya0FjdGl2ZUVsZW1lbnQoZWxlbWVudDogSFRNTEVsZW1lbnQgfCB1bmRlZmluZWQpIHtcblx0XHRpZiAoIWVsZW1lbnQpIHtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cdFx0ZWxlbWVudC5jbGFzc05hbWUgKz0gJyBjb2RlLWFjdGl2ZS1saW5lJztcblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5leHBvcnQgZnVuY3Rpb24gb25jZURvY3VtZW50TG9hZGVkKGY6ICgpID0+IHZvaWQpIHtcblx0aWYgKGRvY3VtZW50LnJlYWR5U3RhdGUgPT09ICdsb2FkaW5nJyB8fCBkb2N1bWVudC5yZWFkeVN0YXRlIGFzIHN0cmluZyA9PT0gJ3VuaW5pdGlhbGl6ZWQnKSB7XG5cdFx0ZG9jdW1lbnQuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsIGYpO1xuXHR9IGVsc2Uge1xuXHRcdGYoKTtcblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5pbXBvcnQgeyBBY3RpdmVMaW5lTWFya2VyIH0gZnJvbSAnLi9hY3RpdmVMaW5lTWFya2VyJztcbmltcG9ydCB7IG9uY2VEb2N1bWVudExvYWRlZCB9IGZyb20gJy4vZXZlbnRzJztcbmltcG9ydCB7IGNyZWF0ZVBvc3RlckZvclZzQ29kZSB9IGZyb20gJy4vbWVzc2FnaW5nJztcbmltcG9ydCB7IGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0LCBzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUgfSBmcm9tICcuL3Njcm9sbC1zeW5jJztcbmltcG9ydCB7IGdldFNldHRpbmdzLCBnZXREYXRhIH0gZnJvbSAnLi9zZXR0aW5ncyc7XG5pbXBvcnQgdGhyb3R0bGUgPSByZXF1aXJlKCdsb2Rhc2gudGhyb3R0bGUnKTtcblxuZGVjbGFyZSB2YXIgYWNxdWlyZVZzQ29kZUFwaTogYW55O1xuXG5sZXQgc2Nyb2xsRGlzYWJsZWQgPSB0cnVlO1xuY29uc3QgbWFya2VyID0gbmV3IEFjdGl2ZUxpbmVNYXJrZXIoKTtcbmNvbnN0IHNldHRpbmdzID0gZ2V0U2V0dGluZ3MoKTtcblxuY29uc3QgdnNjb2RlID0gYWNxdWlyZVZzQ29kZUFwaSgpO1xuXG4vLyBTZXQgVlMgQ29kZSBzdGF0ZVxubGV0IHN0YXRlID0gZ2V0RGF0YSgnZGF0YS1zdGF0ZScpO1xudnNjb2RlLnNldFN0YXRlKHN0YXRlKTtcblxuY29uc3QgbWVzc2FnaW5nID0gY3JlYXRlUG9zdGVyRm9yVnNDb2RlKHZzY29kZSk7XG5cbndpbmRvdy5jc3BBbGVydGVyLnNldFBvc3RlcihtZXNzYWdpbmcpO1xud2luZG93LnN0eWxlTG9hZGluZ01vbml0b3Iuc2V0UG9zdGVyKG1lc3NhZ2luZyk7XG5cbndpbmRvdy5vbmxvYWQgPSAoKSA9PiB7XG5cdHVwZGF0ZUltYWdlU2l6ZXMoKTtcbn07XG5cbm9uY2VEb2N1bWVudExvYWRlZCgoKSA9PiB7XG5cdGlmIChzZXR0aW5ncy5zY3JvbGxQcmV2aWV3V2l0aEVkaXRvcikge1xuXHRcdHNldFRpbWVvdXQoKCkgPT4ge1xuXHRcdFx0Y29uc3QgaW5pdGlhbExpbmUgPSArc2V0dGluZ3MubGluZTtcblx0XHRcdGlmICghaXNOYU4oaW5pdGlhbExpbmUpKSB7XG5cdFx0XHRcdHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcblx0XHRcdFx0c2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGluaXRpYWxMaW5lKTtcblx0XHRcdH1cblx0XHR9LCAwKTtcblx0fVxufSk7XG5cbmNvbnN0IG9uVXBkYXRlVmlldyA9ICgoKSA9PiB7XG5cdGNvbnN0IGRvU2Nyb2xsID0gdGhyb3R0bGUoKGxpbmU6IG51bWJlcikgPT4ge1xuXHRcdHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcblx0XHRzY3JvbGxUb1JldmVhbFNvdXJjZUxpbmUobGluZSk7XG5cdH0sIDUwKTtcblxuXHRyZXR1cm4gKGxpbmU6IG51bWJlciwgc2V0dGluZ3M6IGFueSkgPT4ge1xuXHRcdGlmICghaXNOYU4obGluZSkpIHtcblx0XHRcdHNldHRpbmdzLmxpbmUgPSBsaW5lO1xuXHRcdFx0ZG9TY3JvbGwobGluZSk7XG5cdFx0fVxuXHR9O1xufSkoKTtcblxubGV0IHVwZGF0ZUltYWdlU2l6ZXMgPSB0aHJvdHRsZSgoKSA9PiB7XG5cdGNvbnN0IGltYWdlSW5mbzogeyBpZDogc3RyaW5nLCBoZWlnaHQ6IG51bWJlciwgd2lkdGg6IG51bWJlciB9W10gPSBbXTtcblx0bGV0IGltYWdlcyA9IGRvY3VtZW50LmdldEVsZW1lbnRzQnlUYWdOYW1lKCdpbWcnKTtcblx0aWYgKGltYWdlcykge1xuXHRcdGxldCBpO1xuXHRcdGZvciAoaSA9IDA7IGkgPCBpbWFnZXMubGVuZ3RoOyBpKyspIHtcblx0XHRcdGNvbnN0IGltZyA9IGltYWdlc1tpXTtcblxuXHRcdFx0aWYgKGltZy5jbGFzc0xpc3QuY29udGFpbnMoJ2xvYWRpbmcnKSkge1xuXHRcdFx0XHRpbWcuY2xhc3NMaXN0LnJlbW92ZSgnbG9hZGluZycpO1xuXHRcdFx0fVxuXG5cdFx0XHRpbWFnZUluZm8ucHVzaCh7XG5cdFx0XHRcdGlkOiBpbWcuaWQsXG5cdFx0XHRcdGhlaWdodDogaW1nLmhlaWdodCxcblx0XHRcdFx0d2lkdGg6IGltZy53aWR0aFxuXHRcdFx0fSk7XG5cdFx0fVxuXG5cdFx0bWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdjYWNoZUltYWdlU2l6ZXMnLCBpbWFnZUluZm8pO1xuXHR9XG59LCA1MCk7XG5cbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdyZXNpemUnLCAoKSA9PiB7XG5cdHNjcm9sbERpc2FibGVkID0gdHJ1ZTtcblx0dXBkYXRlSW1hZ2VTaXplcygpO1xufSwgdHJ1ZSk7XG5cbndpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgZXZlbnQgPT4ge1xuXHRpZiAoZXZlbnQuZGF0YS5zb3VyY2UgIT09IHNldHRpbmdzLnNvdXJjZSkge1xuXHRcdHJldHVybjtcblx0fVxuXG5cdHN3aXRjaCAoZXZlbnQuZGF0YS50eXBlKSB7XG5cdFx0Y2FzZSAnb25EaWRDaGFuZ2VUZXh0RWRpdG9yU2VsZWN0aW9uJzpcblx0XHRcdG1hcmtlci5vbkRpZENoYW5nZVRleHRFZGl0b3JTZWxlY3Rpb24oZXZlbnQuZGF0YS5saW5lKTtcblx0XHRcdGJyZWFrO1xuXG5cdFx0Y2FzZSAndXBkYXRlVmlldyc6XG5cdFx0XHRvblVwZGF0ZVZpZXcoZXZlbnQuZGF0YS5saW5lLCBzZXR0aW5ncyk7XG5cdFx0XHRicmVhaztcblx0fVxufSwgZmFsc2UpO1xuXG5kb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdkYmxjbGljaycsIGV2ZW50ID0+IHtcblx0aWYgKCFzZXR0aW5ncy5kb3VibGVDbGlja1RvU3dpdGNoVG9FZGl0b3IpIHtcblx0XHRyZXR1cm47XG5cdH1cblxuXHQvLyBJZ25vcmUgY2xpY2tzIG9uIGxpbmtzXG5cdGZvciAobGV0IG5vZGUgPSBldmVudC50YXJnZXQgYXMgSFRNTEVsZW1lbnQ7IG5vZGU7IG5vZGUgPSBub2RlLnBhcmVudE5vZGUgYXMgSFRNTEVsZW1lbnQpIHtcblx0XHRpZiAobm9kZS50YWdOYW1lID09PSAnQScpIHtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cdH1cblxuXHRjb25zdCBvZmZzZXQgPSBldmVudC5wYWdlWTtcblx0Y29uc3QgbGluZSA9IGdldEVkaXRvckxpbmVOdW1iZXJGb3JQYWdlT2Zmc2V0KG9mZnNldCk7XG5cdGlmICh0eXBlb2YgbGluZSA9PT0gJ251bWJlcicgJiYgIWlzTmFOKGxpbmUpKSB7XG5cdFx0bWVzc2FnaW5nLnBvc3RNZXNzYWdlKCdkaWRDbGljaycsIHsgbGluZTogTWF0aC5mbG9vcihsaW5lKSB9KTtcblx0fVxufSk7XG5cbmRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ2NsaWNrJywgZXZlbnQgPT4ge1xuXHRpZiAoIWV2ZW50KSB7XG5cdFx0cmV0dXJuO1xuXHR9XG5cblx0bGV0IG5vZGU6IGFueSA9IGV2ZW50LnRhcmdldDtcblx0d2hpbGUgKG5vZGUpIHtcblx0XHRpZiAobm9kZS50YWdOYW1lICYmIG5vZGUudGFnTmFtZSA9PT0gJ0EnICYmIG5vZGUuaHJlZikge1xuXHRcdFx0aWYgKG5vZGUuZ2V0QXR0cmlidXRlKCdocmVmJykuc3RhcnRzV2l0aCgnIycpKSB7XG5cdFx0XHRcdGJyZWFrO1xuXHRcdFx0fVxuXHRcdFx0aWYgKG5vZGUuaHJlZi5zdGFydHNXaXRoKCdmaWxlOi8vJykgfHwgbm9kZS5ocmVmLnN0YXJ0c1dpdGgoJ3ZzY29kZS1yZXNvdXJjZTonKSkge1xuXHRcdFx0XHRjb25zdCBbcGF0aCwgZnJhZ21lbnRdID0gbm9kZS5ocmVmLnJlcGxhY2UoL14oZmlsZTpcXC9cXC98dnNjb2RlLXJlc291cmNlOikvaSwgJycpLnNwbGl0KCcjJyk7XG5cdFx0XHRcdG1lc3NhZ2luZy5wb3N0TWVzc2FnZSgnY2xpY2tMaW5rJywgeyBwYXRoLCBmcmFnbWVudCB9KTtcblx0XHRcdFx0ZXZlbnQucHJldmVudERlZmF1bHQoKTtcblx0XHRcdFx0ZXZlbnQuc3RvcFByb3BhZ2F0aW9uKCk7XG5cdFx0XHRcdGJyZWFrO1xuXHRcdFx0fVxuXHRcdFx0YnJlYWs7XG5cdFx0fVxuXHRcdG5vZGUgPSBub2RlLnBhcmVudE5vZGU7XG5cdH1cbn0sIHRydWUpO1xuXG5pZiAoc2V0dGluZ3Muc2Nyb2xsRWRpdG9yV2l0aFByZXZpZXcpIHtcblx0d2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ3Njcm9sbCcsIHRocm90dGxlKCgpID0+IHtcblx0XHRpZiAoc2Nyb2xsRGlzYWJsZWQpIHtcblx0XHRcdHNjcm9sbERpc2FibGVkID0gZmFsc2U7XG5cdFx0fSBlbHNlIHtcblx0XHRcdGNvbnN0IGxpbmUgPSBnZXRFZGl0b3JMaW5lTnVtYmVyRm9yUGFnZU9mZnNldCh3aW5kb3cuc2Nyb2xsWSk7XG5cdFx0XHRpZiAodHlwZW9mIGxpbmUgPT09ICdudW1iZXInICYmICFpc05hTihsaW5lKSkge1xuXHRcdFx0XHRtZXNzYWdpbmcucG9zdE1lc3NhZ2UoJ3JldmVhbExpbmUnLCB7IGxpbmUgfSk7XG5cdFx0XHRcdHN0YXRlLmxpbmUgPSBsaW5lO1xuXHRcdFx0XHR2c2NvZGUuc2V0U3RhdGUoc3RhdGUpO1xuXHRcdFx0fVxuXHRcdH1cblx0fSwgNTApKTtcbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuaW1wb3J0IHsgZ2V0U2V0dGluZ3MgfSBmcm9tICcuL3NldHRpbmdzJztcblxuZXhwb3J0IGludGVyZmFjZSBNZXNzYWdlUG9zdGVyIHtcblx0LyoqXG5cdCAqIFBvc3QgYSBtZXNzYWdlIHRvIHRoZSBtYXJrZG93biBleHRlbnNpb25cblx0ICovXG5cdHBvc3RNZXNzYWdlKHR5cGU6IHN0cmluZywgYm9keTogb2JqZWN0KTogdm9pZDtcbn1cblxuZXhwb3J0IGNvbnN0IGNyZWF0ZVBvc3RlckZvclZzQ29kZSA9ICh2c2NvZGU6IGFueSkgPT4ge1xuXHRyZXR1cm4gbmV3IGNsYXNzIGltcGxlbWVudHMgTWVzc2FnZVBvc3RlciB7XG5cdFx0cG9zdE1lc3NhZ2UodHlwZTogc3RyaW5nLCBib2R5OiBvYmplY3QpOiB2b2lkIHtcblx0XHRcdHZzY29kZS5wb3N0TWVzc2FnZSh7XG5cdFx0XHRcdHR5cGUsXG5cdFx0XHRcdHNvdXJjZTogZ2V0U2V0dGluZ3MoKS5zb3VyY2UsXG5cdFx0XHRcdGJvZHlcblx0XHRcdH0pO1xuXHRcdH1cblx0fTtcbn07XG5cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5pbXBvcnQgeyBnZXRTZXR0aW5ncyB9IGZyb20gJy4vc2V0dGluZ3MnO1xuXG5cbmZ1bmN0aW9uIGNsYW1wKG1pbjogbnVtYmVyLCBtYXg6IG51bWJlciwgdmFsdWU6IG51bWJlcikge1xuXHRyZXR1cm4gTWF0aC5taW4obWF4LCBNYXRoLm1heChtaW4sIHZhbHVlKSk7XG59XG5cbmZ1bmN0aW9uIGNsYW1wTGluZShsaW5lOiBudW1iZXIpIHtcblx0cmV0dXJuIGNsYW1wKDAsIGdldFNldHRpbmdzKCkubGluZUNvdW50IC0gMSwgbGluZSk7XG59XG5cblxuZXhwb3J0IGludGVyZmFjZSBDb2RlTGluZUVsZW1lbnQge1xuXHRlbGVtZW50OiBIVE1MRWxlbWVudDtcblx0bGluZTogbnVtYmVyO1xufVxuXG5jb25zdCBnZXRDb2RlTGluZUVsZW1lbnRzID0gKCgpID0+IHtcblx0bGV0IGVsZW1lbnRzOiBDb2RlTGluZUVsZW1lbnRbXTtcblx0cmV0dXJuICgpID0+IHtcblx0XHRpZiAoIWVsZW1lbnRzKSB7XG5cdFx0XHRlbGVtZW50cyA9IFt7IGVsZW1lbnQ6IGRvY3VtZW50LmJvZHksIGxpbmU6IDAgfV07XG5cdFx0XHRmb3IgKGNvbnN0IGVsZW1lbnQgb2YgZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgnY29kZS1saW5lJykpIHtcblx0XHRcdFx0Y29uc3QgbGluZSA9ICtlbGVtZW50LmdldEF0dHJpYnV0ZSgnZGF0YS1saW5lJykhO1xuXHRcdFx0XHRpZiAoIWlzTmFOKGxpbmUpKSB7XG5cdFx0XHRcdFx0ZWxlbWVudHMucHVzaCh7IGVsZW1lbnQ6IGVsZW1lbnQgYXMgSFRNTEVsZW1lbnQsIGxpbmUgfSk7XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHR9XG5cdFx0cmV0dXJuIGVsZW1lbnRzO1xuXHR9O1xufSkoKTtcblxuLyoqXG4gKiBGaW5kIHRoZSBodG1sIGVsZW1lbnRzIHRoYXQgbWFwIHRvIGEgc3BlY2lmaWMgdGFyZ2V0IGxpbmUgaW4gdGhlIGVkaXRvci5cbiAqXG4gKiBJZiBhbiBleGFjdCBtYXRjaCwgcmV0dXJucyBhIHNpbmdsZSBlbGVtZW50LiBJZiB0aGUgbGluZSBpcyBiZXR3ZWVuIGVsZW1lbnRzLFxuICogcmV0dXJucyB0aGUgZWxlbWVudCBwcmlvciB0byBhbmQgdGhlIGVsZW1lbnQgYWZ0ZXIgdGhlIGdpdmVuIGxpbmUuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBnZXRFbGVtZW50c0ZvclNvdXJjZUxpbmUodGFyZ2V0TGluZTogbnVtYmVyKTogeyBwcmV2aW91czogQ29kZUxpbmVFbGVtZW50OyBuZXh0PzogQ29kZUxpbmVFbGVtZW50OyB9IHtcblx0Y29uc3QgbGluZU51bWJlciA9IE1hdGguZmxvb3IodGFyZ2V0TGluZSk7XG5cdGNvbnN0IGxpbmVzID0gZ2V0Q29kZUxpbmVFbGVtZW50cygpO1xuXHRsZXQgcHJldmlvdXMgPSBsaW5lc1swXSB8fCBudWxsO1xuXHRmb3IgKGNvbnN0IGVudHJ5IG9mIGxpbmVzKSB7XG5cdFx0aWYgKGVudHJ5LmxpbmUgPT09IGxpbmVOdW1iZXIpIHtcblx0XHRcdHJldHVybiB7IHByZXZpb3VzOiBlbnRyeSwgbmV4dDogdW5kZWZpbmVkIH07XG5cdFx0fSBlbHNlIGlmIChlbnRyeS5saW5lID4gbGluZU51bWJlcikge1xuXHRcdFx0cmV0dXJuIHsgcHJldmlvdXMsIG5leHQ6IGVudHJ5IH07XG5cdFx0fVxuXHRcdHByZXZpb3VzID0gZW50cnk7XG5cdH1cblx0cmV0dXJuIHsgcHJldmlvdXMgfTtcbn1cblxuLyoqXG4gKiBGaW5kIHRoZSBodG1sIGVsZW1lbnRzIHRoYXQgYXJlIGF0IGEgc3BlY2lmaWMgcGl4ZWwgb2Zmc2V0IG9uIHRoZSBwYWdlLlxuICovXG5leHBvcnQgZnVuY3Rpb24gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0KG9mZnNldDogbnVtYmVyKTogeyBwcmV2aW91czogQ29kZUxpbmVFbGVtZW50OyBuZXh0PzogQ29kZUxpbmVFbGVtZW50OyB9IHtcblx0Y29uc3QgbGluZXMgPSBnZXRDb2RlTGluZUVsZW1lbnRzKCk7XG5cdGNvbnN0IHBvc2l0aW9uID0gb2Zmc2V0IC0gd2luZG93LnNjcm9sbFk7XG5cdGxldCBsbyA9IC0xO1xuXHRsZXQgaGkgPSBsaW5lcy5sZW5ndGggLSAxO1xuXHR3aGlsZSAobG8gKyAxIDwgaGkpIHtcblx0XHRjb25zdCBtaWQgPSBNYXRoLmZsb29yKChsbyArIGhpKSAvIDIpO1xuXHRcdGNvbnN0IGJvdW5kcyA9IGxpbmVzW21pZF0uZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcblx0XHRpZiAoYm91bmRzLnRvcCArIGJvdW5kcy5oZWlnaHQgPj0gcG9zaXRpb24pIHtcblx0XHRcdGhpID0gbWlkO1xuXHRcdH1cblx0XHRlbHNlIHtcblx0XHRcdGxvID0gbWlkO1xuXHRcdH1cblx0fVxuXHRjb25zdCBoaUVsZW1lbnQgPSBsaW5lc1toaV07XG5cdGNvbnN0IGhpQm91bmRzID0gaGlFbGVtZW50LmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCk7XG5cdGlmIChoaSA+PSAxICYmIGhpQm91bmRzLnRvcCA+IHBvc2l0aW9uKSB7XG5cdFx0Y29uc3QgbG9FbGVtZW50ID0gbGluZXNbbG9dO1xuXHRcdHJldHVybiB7IHByZXZpb3VzOiBsb0VsZW1lbnQsIG5leHQ6IGhpRWxlbWVudCB9O1xuXHR9XG5cdHJldHVybiB7IHByZXZpb3VzOiBoaUVsZW1lbnQgfTtcbn1cblxuLyoqXG4gKiBBdHRlbXB0IHRvIHJldmVhbCB0aGUgZWxlbWVudCBmb3IgYSBzb3VyY2UgbGluZSBpbiB0aGUgZWRpdG9yLlxuICovXG5leHBvcnQgZnVuY3Rpb24gc2Nyb2xsVG9SZXZlYWxTb3VyY2VMaW5lKGxpbmU6IG51bWJlcikge1xuXHRpZiAoIWdldFNldHRpbmdzKCkuc2Nyb2xsUHJldmlld1dpdGhFZGl0b3IpIHtcblx0XHRyZXR1cm47XG5cdH1cblxuXHRpZiAobGluZSA8PSAwKSB7XG5cdFx0d2luZG93LnNjcm9sbCh3aW5kb3cuc2Nyb2xsWCwgMCk7XG5cdFx0cmV0dXJuO1xuXHR9XG5cblx0Y29uc3QgeyBwcmV2aW91cywgbmV4dCB9ID0gZ2V0RWxlbWVudHNGb3JTb3VyY2VMaW5lKGxpbmUpO1xuXHRpZiAoIXByZXZpb3VzKSB7XG5cdFx0cmV0dXJuO1xuXHR9XG5cdGxldCBzY3JvbGxUbyA9IDA7XG5cdGNvbnN0IHJlY3QgPSBwcmV2aW91cy5lbGVtZW50LmdldEJvdW5kaW5nQ2xpZW50UmVjdCgpO1xuXHRjb25zdCBwcmV2aW91c1RvcCA9IHJlY3QudG9wO1xuXHRpZiAobmV4dCAmJiBuZXh0LmxpbmUgIT09IHByZXZpb3VzLmxpbmUpIHtcblx0XHQvLyBCZXR3ZWVuIHR3byBlbGVtZW50cy4gR28gdG8gcGVyY2VudGFnZSBvZmZzZXQgYmV0d2VlbiB0aGVtLlxuXHRcdGNvbnN0IGJldHdlZW5Qcm9ncmVzcyA9IChsaW5lIC0gcHJldmlvdXMubGluZSkgLyAobmV4dC5saW5lIC0gcHJldmlvdXMubGluZSk7XG5cdFx0Y29uc3QgZWxlbWVudE9mZnNldCA9IG5leHQuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKS50b3AgLSBwcmV2aW91c1RvcDtcblx0XHRzY3JvbGxUbyA9IHByZXZpb3VzVG9wICsgYmV0d2VlblByb2dyZXNzICogZWxlbWVudE9mZnNldDtcblx0fSBlbHNlIHtcblx0XHRjb25zdCBwcm9ncmVzc0luRWxlbWVudCA9IGxpbmUgLSBNYXRoLmZsb29yKGxpbmUpO1xuXHRcdHNjcm9sbFRvID0gcHJldmlvdXNUb3AgKyAocmVjdC5oZWlnaHQgKiBwcm9ncmVzc0luRWxlbWVudCk7XG5cdH1cblx0d2luZG93LnNjcm9sbCh3aW5kb3cuc2Nyb2xsWCwgTWF0aC5tYXgoMSwgd2luZG93LnNjcm9sbFkgKyBzY3JvbGxUbykpO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RWRpdG9yTGluZU51bWJlckZvclBhZ2VPZmZzZXQob2Zmc2V0OiBudW1iZXIpIHtcblx0Y29uc3QgeyBwcmV2aW91cywgbmV4dCB9ID0gZ2V0TGluZUVsZW1lbnRzQXRQYWdlT2Zmc2V0KG9mZnNldCk7XG5cdGlmIChwcmV2aW91cykge1xuXHRcdGNvbnN0IHByZXZpb3VzQm91bmRzID0gcHJldmlvdXMuZWxlbWVudC5nZXRCb3VuZGluZ0NsaWVudFJlY3QoKTtcblx0XHRjb25zdCBvZmZzZXRGcm9tUHJldmlvdXMgPSAob2Zmc2V0IC0gd2luZG93LnNjcm9sbFkgLSBwcmV2aW91c0JvdW5kcy50b3ApO1xuXHRcdGlmIChuZXh0KSB7XG5cdFx0XHRjb25zdCBwcm9ncmVzc0JldHdlZW5FbGVtZW50cyA9IG9mZnNldEZyb21QcmV2aW91cyAvIChuZXh0LmVsZW1lbnQuZ2V0Qm91bmRpbmdDbGllbnRSZWN0KCkudG9wIC0gcHJldmlvdXNCb3VuZHMudG9wKTtcblx0XHRcdGNvbnN0IGxpbmUgPSBwcmV2aW91cy5saW5lICsgcHJvZ3Jlc3NCZXR3ZWVuRWxlbWVudHMgKiAobmV4dC5saW5lIC0gcHJldmlvdXMubGluZSk7XG5cdFx0XHRyZXR1cm4gY2xhbXBMaW5lKGxpbmUpO1xuXHRcdH1cblx0XHRlbHNlIHtcblx0XHRcdGNvbnN0IHByb2dyZXNzV2l0aGluRWxlbWVudCA9IG9mZnNldEZyb21QcmV2aW91cyAvIChwcmV2aW91c0JvdW5kcy5oZWlnaHQpO1xuXHRcdFx0Y29uc3QgbGluZSA9IHByZXZpb3VzLmxpbmUgKyBwcm9ncmVzc1dpdGhpbkVsZW1lbnQ7XG5cdFx0XHRyZXR1cm4gY2xhbXBMaW5lKGxpbmUpO1xuXHRcdH1cblx0fVxuXHRyZXR1cm4gbnVsbDtcbn1cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5leHBvcnQgaW50ZXJmYWNlIFByZXZpZXdTZXR0aW5ncyB7XG5cdHNvdXJjZTogc3RyaW5nO1xuXHRsaW5lOiBudW1iZXI7XG5cdGxpbmVDb3VudDogbnVtYmVyO1xuXHRzY3JvbGxQcmV2aWV3V2l0aEVkaXRvcj86IGJvb2xlYW47XG5cdHNjcm9sbEVkaXRvcldpdGhQcmV2aWV3OiBib29sZWFuO1xuXHRkaXNhYmxlU2VjdXJpdHlXYXJuaW5nczogYm9vbGVhbjtcblx0ZG91YmxlQ2xpY2tUb1N3aXRjaFRvRWRpdG9yOiBib29sZWFuO1xufVxuXG5sZXQgY2FjaGVkU2V0dGluZ3M6IFByZXZpZXdTZXR0aW5ncyB8IHVuZGVmaW5lZCA9IHVuZGVmaW5lZDtcblxuZXhwb3J0IGZ1bmN0aW9uIGdldERhdGEoa2V5OiBzdHJpbmcpOiBQcmV2aWV3U2V0dGluZ3Mge1xuXHRjb25zdCBlbGVtZW50ID0gZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ3ZzY29kZS1tYXJrZG93bi1wcmV2aWV3LWRhdGEnKTtcblx0aWYgKGVsZW1lbnQpIHtcblx0XHRjb25zdCBkYXRhID0gZWxlbWVudC5nZXRBdHRyaWJ1dGUoa2V5KTtcblx0XHRpZiAoZGF0YSkge1xuXHRcdFx0cmV0dXJuIEpTT04ucGFyc2UoZGF0YSk7XG5cdFx0fVxuXHR9XG5cblx0dGhyb3cgbmV3IEVycm9yKGBDb3VsZCBub3QgbG9hZCBkYXRhIGZvciAke2tleX1gKTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGdldFNldHRpbmdzKCk6IFByZXZpZXdTZXR0aW5ncyB7XG5cdGlmIChjYWNoZWRTZXR0aW5ncykge1xuXHRcdHJldHVybiBjYWNoZWRTZXR0aW5ncztcblx0fVxuXG5cdGNhY2hlZFNldHRpbmdzID0gZ2V0RGF0YSgnZGF0YS1zZXR0aW5ncycpO1xuXHRpZiAoY2FjaGVkU2V0dGluZ3MpIHtcblx0XHRyZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG5cdH1cblxuXHR0aHJvdyBuZXcgRXJyb3IoJ0NvdWxkIG5vdCBsb2FkIHNldHRpbmdzJyk7XG59XG4iXSwic291cmNlUm9vdCI6IiJ9 \ No newline at end of file diff --git a/extensions/markdown-language-features/media/pre.js b/extensions/markdown-language-features/media/pre.js index d5e0aff952..0342311b95 100644 --- a/extensions/markdown-language-features/media/pre.js +++ b/extensions/markdown-language-features/media/pre.js @@ -277,4 +277,4 @@ exports.getStrings = getStrings; /***/ }) /******/ }); -//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvY3NwLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2xvYWRpbmcudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvcHJlLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL3NldHRpbmdzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL3N0cmluZ3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOzs7QUFHQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFLO0FBQ0w7QUFDQTs7QUFFQTtBQUNBO0FBQ0EseURBQWlELGNBQWM7QUFDL0Q7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsbUNBQTJCLDBCQUEwQixFQUFFO0FBQ3ZELHlDQUFpQyxlQUFlO0FBQ2hEO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLDhEQUFzRCwrREFBK0Q7O0FBRXJIO0FBQ0E7OztBQUdBO0FBQ0E7Ozs7Ozs7Ozs7Ozs7O0FDbkVBOzs7Z0dBR2dHOztBQUdoRyxzRkFBeUM7QUFDekMsbUZBQXVDO0FBRXZDOztHQUVHO0FBQ0g7SUFNQztRQUxRLFlBQU8sR0FBRyxLQUFLLENBQUM7UUFDaEIsc0JBQWlCLEdBQUcsS0FBSyxDQUFDO1FBS2pDLFFBQVEsQ0FBQyxnQkFBZ0IsQ0FBQyx5QkFBeUIsRUFBRSxHQUFHLEVBQUU7WUFDekQsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO1FBQ3JCLENBQUMsQ0FBQyxDQUFDO1FBRUgsTUFBTSxDQUFDLGdCQUFnQixDQUFDLFNBQVMsRUFBRSxDQUFDLEtBQUssRUFBRSxFQUFFO1lBQzVDLEVBQUUsQ0FBQyxDQUFDLEtBQUssSUFBSSxLQUFLLENBQUMsSUFBSSxJQUFJLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxLQUFLLHNCQUFzQixDQUFDLENBQUMsQ0FBQztnQkFDdkUsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO1lBQ3JCLENBQUM7UUFDRixDQUFDLENBQUMsQ0FBQztJQUNKLENBQUM7SUFFTSxTQUFTLENBQUMsTUFBcUI7UUFDckMsSUFBSSxDQUFDLFNBQVMsR0FBRyxNQUFNLENBQUM7UUFDeEIsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLGlCQUFpQixDQUFDLENBQUMsQ0FBQztZQUM1QixJQUFJLENBQUMsY0FBYyxFQUFFLENBQUM7UUFDdkIsQ0FBQztJQUNGLENBQUM7SUFFTyxZQUFZO1FBQ25CLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxJQUFJLENBQUM7UUFDOUIsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO0lBQ3ZCLENBQUM7SUFFTyxjQUFjO1FBQ3JCLE1BQU0sT0FBTyxHQUFHLG9CQUFVLEVBQUUsQ0FBQztRQUM3QixNQUFNLFFBQVEsR0FBRyxzQkFBVyxFQUFFLENBQUM7UUFFL0IsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLE9BQU8sSUFBSSxRQUFRLENBQUMsdUJBQXVCLElBQUksQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLENBQUMsQ0FBQztZQUN6RSxNQUFNLENBQUM7UUFDUixDQUFDO1FBQ0QsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUM7UUFFcEIsTUFBTSxZQUFZLEdBQUcsUUFBUSxDQUFDLGFBQWEsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNqRCxZQUFZLENBQUMsU0FBUyxHQUFHLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQztRQUNyRCxZQUFZLENBQUMsWUFBWSxDQUFDLElBQUksRUFBRSxrQkFBa0IsQ0FBQyxDQUFDO1FBQ3BELFlBQVksQ0FBQyxZQUFZLENBQUMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1FBRWpFLFlBQVksQ0FBQyxZQUFZLENBQUMsTUFBTSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQzVDLFlBQVksQ0FBQyxZQUFZLENBQUMsWUFBWSxFQUFFLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1FBQ3RFLFlBQVksQ0FBQyxPQUFPLEdBQUcsR0FBRyxFQUFFO1lBQzNCLElBQUksQ0FBQyxTQUFVLENBQUMsV0FBVyxDQUFDLDZCQUE2QixFQUFFLEVBQUUsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO1FBQ3pGLENBQUMsQ0FBQztRQUNGLFFBQVEsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLFlBQVksQ0FBQyxDQUFDO0lBQ3pDLENBQUM7Q0FDRDtBQW5ERCxnQ0FtREM7Ozs7Ozs7Ozs7Ozs7OztBQ3pERDtJQU1DO1FBTFEsbUJBQWMsR0FBYSxFQUFFLENBQUM7UUFDOUIsb0JBQWUsR0FBWSxLQUFLLENBQUM7UUFLeEMsTUFBTSxnQkFBZ0IsR0FBRyxDQUFDLEtBQVUsRUFBRSxFQUFFO1lBQ3ZDLE1BQU0sTUFBTSxHQUFHLEtBQUssQ0FBQyxNQUFNLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQztZQUMzQyxJQUFJLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQztRQUNsQyxDQUFDLENBQUM7UUFFRixNQUFNLENBQUMsZ0JBQWdCLENBQUMsa0JBQWtCLEVBQUUsR0FBRyxFQUFFO1lBQ2hELEdBQUcsQ0FBQyxDQUFDLE1BQU0sSUFBSSxJQUFJLFFBQVEsQ0FBQyxzQkFBc0IsQ0FBQyxpQkFBaUIsQ0FBa0MsQ0FBQyxDQUFDLENBQUM7Z0JBQ3hHLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLENBQUMsQ0FBQztvQkFDekIsSUFBSSxDQUFDLE9BQU8sR0FBRyxnQkFBZ0IsQ0FBQztnQkFDakMsQ0FBQztZQUNGLENBQUM7UUFDRixDQUFDLENBQUMsQ0FBQztRQUVILE1BQU0sQ0FBQyxnQkFBZ0IsQ0FBQyxNQUFNLEVBQUUsR0FBRyxFQUFFO1lBQ3BDLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLGNBQWMsQ0FBQyxNQUFNLENBQUMsQ0FBQyxDQUFDO2dCQUNqQyxNQUFNLENBQUM7WUFDUixDQUFDO1lBQ0QsSUFBSSxDQUFDLGVBQWUsR0FBRyxJQUFJLENBQUM7WUFDNUIsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUM7Z0JBQ2pCLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLHVCQUF1QixFQUFFLEVBQUUsY0FBYyxFQUFFLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQyxDQUFDO1lBQzNGLENBQUM7UUFDRixDQUFDLENBQUMsQ0FBQztJQUNKLENBQUM7SUFFTSxTQUFTLENBQUMsTUFBcUI7UUFDckMsSUFBSSxDQUFDLE1BQU0sR0FBRyxNQUFNLENBQUM7UUFDckIsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLGVBQWUsQ0FBQyxDQUFDLENBQUM7WUFDMUIsTUFBTSxDQUFDLFdBQVcsQ0FBQyx1QkFBdUIsRUFBRSxFQUFFLGNBQWMsRUFBRSxJQUFJLENBQUMsY0FBYyxFQUFFLENBQUMsQ0FBQztRQUN0RixDQUFDO0lBQ0YsQ0FBQztDQUNEO0FBckNELGtEQXFDQzs7Ozs7Ozs7Ozs7Ozs7QUMzQ0Q7OztnR0FHZ0c7O0FBRWhHLHVFQUFtQztBQUNuQyxtRkFBZ0Q7QUFTaEQsTUFBTSxDQUFDLFVBQVUsR0FBRyxJQUFJLGdCQUFVLEVBQUUsQ0FBQztBQUNyQyxNQUFNLENBQUMsbUJBQW1CLEdBQUcsSUFBSSw2QkFBbUIsRUFBRSxDQUFDOzs7Ozs7Ozs7Ozs7OztBQ2hCdkQ7OztnR0FHZ0c7O0FBWWhHLElBQUksY0FBYyxHQUFnQyxTQUFTLENBQUM7QUFFNUQsaUJBQXdCLEdBQVc7SUFDbEMsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDLGNBQWMsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQ3hFLEVBQUUsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7UUFDYixNQUFNLElBQUksR0FBRyxPQUFPLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3ZDLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUM7WUFDVixNQUFNLENBQUMsSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUN6QixDQUFDO0lBQ0YsQ0FBQztJQUVELE1BQU0sSUFBSSxLQUFLLENBQUMsMkJBQTJCLEdBQUcsRUFBRSxDQUFDLENBQUM7QUFDbkQsQ0FBQztBQVZELDBCQVVDO0FBRUQ7SUFDQyxFQUFFLENBQUMsQ0FBQyxjQUFjLENBQUMsQ0FBQyxDQUFDO1FBQ3BCLE1BQU0sQ0FBQyxjQUFjLENBQUM7SUFDdkIsQ0FBQztJQUVELGNBQWMsR0FBRyxPQUFPLENBQUMsZUFBZSxDQUFDLENBQUM7SUFDMUMsRUFBRSxDQUFDLENBQUMsY0FBYyxDQUFDLENBQUMsQ0FBQztRQUNwQixNQUFNLENBQUMsY0FBYyxDQUFDO0lBQ3ZCLENBQUM7SUFFRCxNQUFNLElBQUksS0FBSyxDQUFDLHlCQUF5QixDQUFDLENBQUM7QUFDNUMsQ0FBQztBQVhELGtDQVdDOzs7Ozs7Ozs7Ozs7OztBQ3hDRDs7O2dHQUdnRzs7QUFFaEc7SUFDQyxNQUFNLEtBQUssR0FBRyxRQUFRLENBQUMsY0FBYyxDQUFDLDhCQUE4QixDQUFDLENBQUM7SUFDdEUsRUFBRSxDQUFDLENBQUMsS0FBSyxDQUFDLENBQUMsQ0FBQztRQUNYLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxZQUFZLENBQUMsY0FBYyxDQUFDLENBQUM7UUFDaEQsRUFBRSxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztZQUNWLE1BQU0sQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3pCLENBQUM7SUFDRixDQUFDO0lBQ0QsTUFBTSxJQUFJLEtBQUssQ0FBQyx3QkFBd0IsQ0FBQyxDQUFDO0FBQzNDLENBQUM7QUFURCxnQ0FTQyIsImZpbGUiOiJwcmUuanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSkge1xuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuIFx0XHR9XG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRpOiBtb2R1bGVJZCxcbiBcdFx0XHRsOiBmYWxzZSxcbiBcdFx0XHRleHBvcnRzOiB7fVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBkZWZpbmUgZ2V0dGVyIGZ1bmN0aW9uIGZvciBoYXJtb255IGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uZCA9IGZ1bmN0aW9uKGV4cG9ydHMsIG5hbWUsIGdldHRlcikge1xuIFx0XHRpZighX193ZWJwYWNrX3JlcXVpcmVfXy5vKGV4cG9ydHMsIG5hbWUpKSB7XG4gXHRcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsIG5hbWUsIHtcbiBcdFx0XHRcdGNvbmZpZ3VyYWJsZTogZmFsc2UsXG4gXHRcdFx0XHRlbnVtZXJhYmxlOiB0cnVlLFxuIFx0XHRcdFx0Z2V0OiBnZXR0ZXJcbiBcdFx0XHR9KTtcbiBcdFx0fVxuIFx0fTtcblxuIFx0Ly8gZGVmaW5lIF9fZXNNb2R1bGUgb24gZXhwb3J0c1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5yID0gZnVuY3Rpb24oZXhwb3J0cykge1xuIFx0XHRPYmplY3QuZGVmaW5lUHJvcGVydHkoZXhwb3J0cywgJ19fZXNNb2R1bGUnLCB7IHZhbHVlOiB0cnVlIH0pO1xuIFx0fTtcblxuIFx0Ly8gZ2V0RGVmYXVsdEV4cG9ydCBmdW5jdGlvbiBmb3IgY29tcGF0aWJpbGl0eSB3aXRoIG5vbi1oYXJtb255IG1vZHVsZXNcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubiA9IGZ1bmN0aW9uKG1vZHVsZSkge1xuIFx0XHR2YXIgZ2V0dGVyID0gbW9kdWxlICYmIG1vZHVsZS5fX2VzTW9kdWxlID9cbiBcdFx0XHRmdW5jdGlvbiBnZXREZWZhdWx0KCkgeyByZXR1cm4gbW9kdWxlWydkZWZhdWx0J107IH0gOlxuIFx0XHRcdGZ1bmN0aW9uIGdldE1vZHVsZUV4cG9ydHMoKSB7IHJldHVybiBtb2R1bGU7IH07XG4gXHRcdF9fd2VicGFja19yZXF1aXJlX18uZChnZXR0ZXIsICdhJywgZ2V0dGVyKTtcbiBcdFx0cmV0dXJuIGdldHRlcjtcbiBcdH07XG5cbiBcdC8vIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbFxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5vID0gZnVuY3Rpb24ob2JqZWN0LCBwcm9wZXJ0eSkgeyByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKG9iamVjdCwgcHJvcGVydHkpOyB9O1xuXG4gXHQvLyBfX3dlYnBhY2tfcHVibGljX3BhdGhfX1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5wID0gXCJcIjtcblxuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKF9fd2VicGFja19yZXF1aXJlX18ucyA9IFwiLi9wcmV2aWV3LXNyYy9wcmUudHNcIik7XG4iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuaW1wb3J0IHsgTWVzc2FnZVBvc3RlciB9IGZyb20gJy4vbWVzc2FnaW5nJztcbmltcG9ydCB7IGdldFNldHRpbmdzIH0gZnJvbSAnLi9zZXR0aW5ncyc7XG5pbXBvcnQgeyBnZXRTdHJpbmdzIH0gZnJvbSAnLi9zdHJpbmdzJztcblxuLyoqXG4gKiBTaG93cyBhbiBhbGVydCB3aGVuIHRoZXJlIGlzIGEgY29udGVudCBzZWN1cml0eSBwb2xpY3kgdmlvbGF0aW9uLlxuICovXG5leHBvcnQgY2xhc3MgQ3NwQWxlcnRlciB7XG5cdHByaXZhdGUgZGlkU2hvdyA9IGZhbHNlO1xuXHRwcml2YXRlIGRpZEhhdmVDc3BXYXJuaW5nID0gZmFsc2U7XG5cblx0cHJpdmF0ZSBtZXNzYWdpbmc/OiBNZXNzYWdlUG9zdGVyO1xuXG5cdGNvbnN0cnVjdG9yKCkge1xuXHRcdGRvY3VtZW50LmFkZEV2ZW50TGlzdGVuZXIoJ3NlY3VyaXR5cG9saWN5dmlvbGF0aW9uJywgKCkgPT4ge1xuXHRcdFx0dGhpcy5vbkNzcFdhcm5pbmcoKTtcblx0XHR9KTtcblxuXHRcdHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdtZXNzYWdlJywgKGV2ZW50KSA9PiB7XG5cdFx0XHRpZiAoZXZlbnQgJiYgZXZlbnQuZGF0YSAmJiBldmVudC5kYXRhLm5hbWUgPT09ICd2c2NvZGUtZGlkLWJsb2NrLXN2ZycpIHtcblx0XHRcdFx0dGhpcy5vbkNzcFdhcm5pbmcoKTtcblx0XHRcdH1cblx0XHR9KTtcblx0fVxuXG5cdHB1YmxpYyBzZXRQb3N0ZXIocG9zdGVyOiBNZXNzYWdlUG9zdGVyKSB7XG5cdFx0dGhpcy5tZXNzYWdpbmcgPSBwb3N0ZXI7XG5cdFx0aWYgKHRoaXMuZGlkSGF2ZUNzcFdhcm5pbmcpIHtcblx0XHRcdHRoaXMuc2hvd0NzcFdhcm5pbmcoKTtcblx0XHR9XG5cdH1cblxuXHRwcml2YXRlIG9uQ3NwV2FybmluZygpIHtcblx0XHR0aGlzLmRpZEhhdmVDc3BXYXJuaW5nID0gdHJ1ZTtcblx0XHR0aGlzLnNob3dDc3BXYXJuaW5nKCk7XG5cdH1cblxuXHRwcml2YXRlIHNob3dDc3BXYXJuaW5nKCkge1xuXHRcdGNvbnN0IHN0cmluZ3MgPSBnZXRTdHJpbmdzKCk7XG5cdFx0Y29uc3Qgc2V0dGluZ3MgPSBnZXRTZXR0aW5ncygpO1xuXG5cdFx0aWYgKHRoaXMuZGlkU2hvdyB8fCBzZXR0aW5ncy5kaXNhYmxlU2VjdXJpdHlXYXJuaW5ncyB8fCAhdGhpcy5tZXNzYWdpbmcpIHtcblx0XHRcdHJldHVybjtcblx0XHR9XG5cdFx0dGhpcy5kaWRTaG93ID0gdHJ1ZTtcblxuXHRcdGNvbnN0IG5vdGlmaWNhdGlvbiA9IGRvY3VtZW50LmNyZWF0ZUVsZW1lbnQoJ2EnKTtcblx0XHRub3RpZmljYXRpb24uaW5uZXJUZXh0ID0gc3RyaW5ncy5jc3BBbGVydE1lc3NhZ2VUZXh0O1xuXHRcdG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ2lkJywgJ2NvZGUtY3NwLXdhcm5pbmcnKTtcblx0XHRub3RpZmljYXRpb24uc2V0QXR0cmlidXRlKCd0aXRsZScsIHN0cmluZ3MuY3NwQWxlcnRNZXNzYWdlVGl0bGUpO1xuXG5cdFx0bm90aWZpY2F0aW9uLnNldEF0dHJpYnV0ZSgncm9sZScsICdidXR0b24nKTtcblx0XHRub3RpZmljYXRpb24uc2V0QXR0cmlidXRlKCdhcmlhLWxhYmVsJywgc3RyaW5ncy5jc3BBbGVydE1lc3NhZ2VMYWJlbCk7XG5cdFx0bm90aWZpY2F0aW9uLm9uY2xpY2sgPSAoKSA9PiB7XG5cdFx0XHR0aGlzLm1lc3NhZ2luZyEucG9zdE1lc3NhZ2UoJ3Nob3dQcmV2aWV3U2VjdXJpdHlTZWxlY3RvcicsIHsgc291cmNlOiBzZXR0aW5ncy5zb3VyY2UgfSk7XG5cdFx0fTtcblx0XHRkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKG5vdGlmaWNhdGlvbik7XG5cdH1cbn1cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuaW1wb3J0IHsgTWVzc2FnZVBvc3RlciB9IGZyb20gJy4vbWVzc2FnaW5nJztcblxuZXhwb3J0IGNsYXNzIFN0eWxlTG9hZGluZ01vbml0b3Ige1xuXHRwcml2YXRlIHVubG9hZGVkU3R5bGVzOiBzdHJpbmdbXSA9IFtdO1xuXHRwcml2YXRlIGZpbmlzaGVkTG9hZGluZzogYm9vbGVhbiA9IGZhbHNlO1xuXG5cdHByaXZhdGUgcG9zdGVyPzogTWVzc2FnZVBvc3RlcjtcblxuXHRjb25zdHJ1Y3RvcigpIHtcblx0XHRjb25zdCBvblN0eWxlTG9hZEVycm9yID0gKGV2ZW50OiBhbnkpID0+IHtcblx0XHRcdGNvbnN0IHNvdXJjZSA9IGV2ZW50LnRhcmdldC5kYXRhc2V0LnNvdXJjZTtcblx0XHRcdHRoaXMudW5sb2FkZWRTdHlsZXMucHVzaChzb3VyY2UpO1xuXHRcdH07XG5cblx0XHR3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignRE9NQ29udGVudExvYWRlZCcsICgpID0+IHtcblx0XHRcdGZvciAoY29uc3QgbGluayBvZiBkb2N1bWVudC5nZXRFbGVtZW50c0J5Q2xhc3NOYW1lKCdjb2RlLXVzZXItc3R5bGUnKSBhcyBIVE1MQ29sbGVjdGlvbk9mPEhUTUxFbGVtZW50Pikge1xuXHRcdFx0XHRpZiAobGluay5kYXRhc2V0LnNvdXJjZSkge1xuXHRcdFx0XHRcdGxpbmsub25lcnJvciA9IG9uU3R5bGVMb2FkRXJyb3I7XG5cdFx0XHRcdH1cblx0XHRcdH1cblx0XHR9KTtcblxuXHRcdHdpbmRvdy5hZGRFdmVudExpc3RlbmVyKCdsb2FkJywgKCkgPT4ge1xuXHRcdFx0aWYgKCF0aGlzLnVubG9hZGVkU3R5bGVzLmxlbmd0aCkge1xuXHRcdFx0XHRyZXR1cm47XG5cdFx0XHR9XG5cdFx0XHR0aGlzLmZpbmlzaGVkTG9hZGluZyA9IHRydWU7XG5cdFx0XHRpZiAodGhpcy5wb3N0ZXIpIHtcblx0XHRcdFx0dGhpcy5wb3N0ZXIucG9zdE1lc3NhZ2UoJ3ByZXZpZXdTdHlsZUxvYWRFcnJvcicsIHsgdW5sb2FkZWRTdHlsZXM6IHRoaXMudW5sb2FkZWRTdHlsZXMgfSk7XG5cdFx0XHR9XG5cdFx0fSk7XG5cdH1cblxuXHRwdWJsaWMgc2V0UG9zdGVyKHBvc3RlcjogTWVzc2FnZVBvc3Rlcik6IHZvaWQge1xuXHRcdHRoaXMucG9zdGVyID0gcG9zdGVyO1xuXHRcdGlmICh0aGlzLmZpbmlzaGVkTG9hZGluZykge1xuXHRcdFx0cG9zdGVyLnBvc3RNZXNzYWdlKCdwcmV2aWV3U3R5bGVMb2FkRXJyb3InLCB7IHVubG9hZGVkU3R5bGVzOiB0aGlzLnVubG9hZGVkU3R5bGVzIH0pO1xuXHRcdH1cblx0fVxufSIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5pbXBvcnQgeyBDc3BBbGVydGVyIH0gZnJvbSAnLi9jc3AnO1xuaW1wb3J0IHsgU3R5bGVMb2FkaW5nTW9uaXRvciB9IGZyb20gJy4vbG9hZGluZyc7XG5cbmRlY2xhcmUgZ2xvYmFsIHtcblx0aW50ZXJmYWNlIFdpbmRvdyB7XG5cdFx0Y3NwQWxlcnRlcjogQ3NwQWxlcnRlcjtcblx0XHRzdHlsZUxvYWRpbmdNb25pdG9yOiBTdHlsZUxvYWRpbmdNb25pdG9yO1xuXHR9XG59XG5cbndpbmRvdy5jc3BBbGVydGVyID0gbmV3IENzcEFsZXJ0ZXIoKTtcbndpbmRvdy5zdHlsZUxvYWRpbmdNb25pdG9yID0gbmV3IFN0eWxlTG9hZGluZ01vbml0b3IoKTsiLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuZXhwb3J0IGludGVyZmFjZSBQcmV2aWV3U2V0dGluZ3Mge1xuXHRzb3VyY2U6IHN0cmluZztcblx0bGluZTogbnVtYmVyO1xuXHRsaW5lQ291bnQ6IG51bWJlcjtcblx0c2Nyb2xsUHJldmlld1dpdGhFZGl0b3I/OiBib29sZWFuO1xuXHRzY3JvbGxFZGl0b3JXaXRoUHJldmlldzogYm9vbGVhbjtcblx0ZGlzYWJsZVNlY3VyaXR5V2FybmluZ3M6IGJvb2xlYW47XG5cdGRvdWJsZUNsaWNrVG9Td2l0Y2hUb0VkaXRvcjogYm9vbGVhbjtcbn1cblxubGV0IGNhY2hlZFNldHRpbmdzOiBQcmV2aWV3U2V0dGluZ3MgfCB1bmRlZmluZWQgPSB1bmRlZmluZWQ7XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXREYXRhKGtleTogc3RyaW5nKTogUHJldmlld1NldHRpbmdzIHtcblx0Y29uc3QgZWxlbWVudCA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd2c2NvZGUtbWFya2Rvd24tcHJldmlldy1kYXRhJyk7XG5cdGlmIChlbGVtZW50KSB7XG5cdFx0Y29uc3QgZGF0YSA9IGVsZW1lbnQuZ2V0QXR0cmlidXRlKGtleSk7XG5cdFx0aWYgKGRhdGEpIHtcblx0XHRcdHJldHVybiBKU09OLnBhcnNlKGRhdGEpO1xuXHRcdH1cblx0fVxuXG5cdHRocm93IG5ldyBFcnJvcihgQ291bGQgbm90IGxvYWQgZGF0YSBmb3IgJHtrZXl9YCk7XG59XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRTZXR0aW5ncygpOiBQcmV2aWV3U2V0dGluZ3Mge1xuXHRpZiAoY2FjaGVkU2V0dGluZ3MpIHtcblx0XHRyZXR1cm4gY2FjaGVkU2V0dGluZ3M7XG5cdH1cblxuXHRjYWNoZWRTZXR0aW5ncyA9IGdldERhdGEoJ2RhdGEtc2V0dGluZ3MnKTtcblx0aWYgKGNhY2hlZFNldHRpbmdzKSB7XG5cdFx0cmV0dXJuIGNhY2hlZFNldHRpbmdzO1xuXHR9XG5cblx0dGhyb3cgbmV3IEVycm9yKCdDb3VsZCBub3QgbG9hZCBzZXR0aW5ncycpO1xufVxuIiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRTdHJpbmdzKCk6IHsgW2tleTogc3RyaW5nXTogc3RyaW5nIH0ge1xuXHRjb25zdCBzdG9yZSA9IGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCd2c2NvZGUtbWFya2Rvd24tcHJldmlldy1kYXRhJyk7XG5cdGlmIChzdG9yZSkge1xuXHRcdGNvbnN0IGRhdGEgPSBzdG9yZS5nZXRBdHRyaWJ1dGUoJ2RhdGEtc3RyaW5ncycpO1xuXHRcdGlmIChkYXRhKSB7XG5cdFx0XHRyZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcblx0XHR9XG5cdH1cblx0dGhyb3cgbmV3IEVycm9yKCdDb3VsZCBub3QgbG9hZCBzdHJpbmdzJyk7XG59XG4iXSwic291cmNlUm9vdCI6IiJ9 +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvY3NwLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL2xvYWRpbmcudHMiLCJ3ZWJwYWNrOi8vLy4vcHJldmlldy1zcmMvcHJlLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL3NldHRpbmdzLnRzIiwid2VicGFjazovLy8uL3ByZXZpZXctc3JjL3N0cmluZ3MudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOzs7QUFHQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxhQUFLO0FBQ0w7QUFDQTs7QUFFQTtBQUNBO0FBQ0EseURBQWlELGNBQWM7QUFDL0Q7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsbUNBQTJCLDBCQUEwQixFQUFFO0FBQ3ZELHlDQUFpQyxlQUFlO0FBQ2hEO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLDhEQUFzRCwrREFBK0Q7O0FBRXJIO0FBQ0E7OztBQUdBO0FBQ0E7Ozs7Ozs7Ozs7Ozs7O0FDbkVBOzs7Z0dBR2dHOztBQUdoRyxzRkFBeUM7QUFDekMsbUZBQXVDO0FBRXZDOztHQUVHO0FBQ0gsTUFBYSxVQUFVO0lBTXRCO1FBTFEsWUFBTyxHQUFHLEtBQUssQ0FBQztRQUNoQixzQkFBaUIsR0FBRyxLQUFLLENBQUM7UUFLakMsUUFBUSxDQUFDLGdCQUFnQixDQUFDLHlCQUF5QixFQUFFLEdBQUcsRUFBRTtZQUN6RCxJQUFJLENBQUMsWUFBWSxFQUFFLENBQUM7UUFDckIsQ0FBQyxDQUFDLENBQUM7UUFFSCxNQUFNLENBQUMsZ0JBQWdCLENBQUMsU0FBUyxFQUFFLENBQUMsS0FBSyxFQUFFLEVBQUU7WUFDNUMsSUFBSSxLQUFLLElBQUksS0FBSyxDQUFDLElBQUksSUFBSSxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksS0FBSyxzQkFBc0IsRUFBRTtnQkFDdEUsSUFBSSxDQUFDLFlBQVksRUFBRSxDQUFDO2FBQ3BCO1FBQ0YsQ0FBQyxDQUFDLENBQUM7SUFDSixDQUFDO0lBRU0sU0FBUyxDQUFDLE1BQXFCO1FBQ3JDLElBQUksQ0FBQyxTQUFTLEdBQUcsTUFBTSxDQUFDO1FBQ3hCLElBQUksSUFBSSxDQUFDLGlCQUFpQixFQUFFO1lBQzNCLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQztTQUN0QjtJQUNGLENBQUM7SUFFTyxZQUFZO1FBQ25CLElBQUksQ0FBQyxpQkFBaUIsR0FBRyxJQUFJLENBQUM7UUFDOUIsSUFBSSxDQUFDLGNBQWMsRUFBRSxDQUFDO0lBQ3ZCLENBQUM7SUFFTyxjQUFjO1FBQ3JCLE1BQU0sT0FBTyxHQUFHLG9CQUFVLEVBQUUsQ0FBQztRQUM3QixNQUFNLFFBQVEsR0FBRyxzQkFBVyxFQUFFLENBQUM7UUFFL0IsSUFBSSxJQUFJLENBQUMsT0FBTyxJQUFJLFFBQVEsQ0FBQyx1QkFBdUIsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLEVBQUU7WUFDeEUsT0FBTztTQUNQO1FBQ0QsSUFBSSxDQUFDLE9BQU8sR0FBRyxJQUFJLENBQUM7UUFFcEIsTUFBTSxZQUFZLEdBQUcsUUFBUSxDQUFDLGFBQWEsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNqRCxZQUFZLENBQUMsU0FBUyxHQUFHLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQztRQUNyRCxZQUFZLENBQUMsWUFBWSxDQUFDLElBQUksRUFBRSxrQkFBa0IsQ0FBQyxDQUFDO1FBQ3BELFlBQVksQ0FBQyxZQUFZLENBQUMsT0FBTyxFQUFFLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1FBRWpFLFlBQVksQ0FBQyxZQUFZLENBQUMsTUFBTSxFQUFFLFFBQVEsQ0FBQyxDQUFDO1FBQzVDLFlBQVksQ0FBQyxZQUFZLENBQUMsWUFBWSxFQUFFLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1FBQ3RFLFlBQVksQ0FBQyxPQUFPLEdBQUcsR0FBRyxFQUFFO1lBQzNCLElBQUksQ0FBQyxTQUFVLENBQUMsV0FBVyxDQUFDLDZCQUE2QixFQUFFLEVBQUUsTUFBTSxFQUFFLFFBQVEsQ0FBQyxNQUFNLEVBQUUsQ0FBQyxDQUFDO1FBQ3pGLENBQUMsQ0FBQztRQUNGLFFBQVEsQ0FBQyxJQUFJLENBQUMsV0FBVyxDQUFDLFlBQVksQ0FBQyxDQUFDO0lBQ3pDLENBQUM7Q0FDRDtBQW5ERCxnQ0FtREM7Ozs7Ozs7Ozs7Ozs7OztBQ3pERCxNQUFhLG1CQUFtQjtJQU0vQjtRQUxRLG1CQUFjLEdBQWEsRUFBRSxDQUFDO1FBQzlCLG9CQUFlLEdBQVksS0FBSyxDQUFDO1FBS3hDLE1BQU0sZ0JBQWdCLEdBQUcsQ0FBQyxLQUFVLEVBQUUsRUFBRTtZQUN2QyxNQUFNLE1BQU0sR0FBRyxLQUFLLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUM7WUFDM0MsSUFBSSxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDbEMsQ0FBQyxDQUFDO1FBRUYsTUFBTSxDQUFDLGdCQUFnQixDQUFDLGtCQUFrQixFQUFFLEdBQUcsRUFBRTtZQUNoRCxLQUFLLE1BQU0sSUFBSSxJQUFJLFFBQVEsQ0FBQyxzQkFBc0IsQ0FBQyxpQkFBaUIsQ0FBa0MsRUFBRTtnQkFDdkcsSUFBSSxJQUFJLENBQUMsT0FBTyxDQUFDLE1BQU0sRUFBRTtvQkFDeEIsSUFBSSxDQUFDLE9BQU8sR0FBRyxnQkFBZ0IsQ0FBQztpQkFDaEM7YUFDRDtRQUNGLENBQUMsQ0FBQyxDQUFDO1FBRUgsTUFBTSxDQUFDLGdCQUFnQixDQUFDLE1BQU0sRUFBRSxHQUFHLEVBQUU7WUFDcEMsSUFBSSxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsTUFBTSxFQUFFO2dCQUNoQyxPQUFPO2FBQ1A7WUFDRCxJQUFJLENBQUMsZUFBZSxHQUFHLElBQUksQ0FBQztZQUM1QixJQUFJLElBQUksQ0FBQyxNQUFNLEVBQUU7Z0JBQ2hCLElBQUksQ0FBQyxNQUFNLENBQUMsV0FBVyxDQUFDLHVCQUF1QixFQUFFLEVBQUUsY0FBYyxFQUFFLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQyxDQUFDO2FBQzFGO1FBQ0YsQ0FBQyxDQUFDLENBQUM7SUFDSixDQUFDO0lBRU0sU0FBUyxDQUFDLE1BQXFCO1FBQ3JDLElBQUksQ0FBQyxNQUFNLEdBQUcsTUFBTSxDQUFDO1FBQ3JCLElBQUksSUFBSSxDQUFDLGVBQWUsRUFBRTtZQUN6QixNQUFNLENBQUMsV0FBVyxDQUFDLHVCQUF1QixFQUFFLEVBQUUsY0FBYyxFQUFFLElBQUksQ0FBQyxjQUFjLEVBQUUsQ0FBQyxDQUFDO1NBQ3JGO0lBQ0YsQ0FBQztDQUNEO0FBckNELGtEQXFDQzs7Ozs7Ozs7Ozs7Ozs7QUMzQ0Q7OztnR0FHZ0c7O0FBRWhHLHVFQUFtQztBQUNuQyxtRkFBZ0Q7QUFTaEQsTUFBTSxDQUFDLFVBQVUsR0FBRyxJQUFJLGdCQUFVLEVBQUUsQ0FBQztBQUNyQyxNQUFNLENBQUMsbUJBQW1CLEdBQUcsSUFBSSw2QkFBbUIsRUFBRSxDQUFDOzs7Ozs7Ozs7Ozs7OztBQ2hCdkQ7OztnR0FHZ0c7O0FBWWhHLElBQUksY0FBYyxHQUFnQyxTQUFTLENBQUM7QUFFNUQsU0FBZ0IsT0FBTyxDQUFDLEdBQVc7SUFDbEMsTUFBTSxPQUFPLEdBQUcsUUFBUSxDQUFDLGNBQWMsQ0FBQyw4QkFBOEIsQ0FBQyxDQUFDO0lBQ3hFLElBQUksT0FBTyxFQUFFO1FBQ1osTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLFlBQVksQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN2QyxJQUFJLElBQUksRUFBRTtZQUNULE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxJQUFJLENBQUMsQ0FBQztTQUN4QjtLQUNEO0lBRUQsTUFBTSxJQUFJLEtBQUssQ0FBQywyQkFBMkIsR0FBRyxFQUFFLENBQUMsQ0FBQztBQUNuRCxDQUFDO0FBVkQsMEJBVUM7QUFFRCxTQUFnQixXQUFXO0lBQzFCLElBQUksY0FBYyxFQUFFO1FBQ25CLE9BQU8sY0FBYyxDQUFDO0tBQ3RCO0lBRUQsY0FBYyxHQUFHLE9BQU8sQ0FBQyxlQUFlLENBQUMsQ0FBQztJQUMxQyxJQUFJLGNBQWMsRUFBRTtRQUNuQixPQUFPLGNBQWMsQ0FBQztLQUN0QjtJQUVELE1BQU0sSUFBSSxLQUFLLENBQUMseUJBQXlCLENBQUMsQ0FBQztBQUM1QyxDQUFDO0FBWEQsa0NBV0M7Ozs7Ozs7Ozs7Ozs7O0FDeENEOzs7Z0dBR2dHOztBQUVoRyxTQUFnQixVQUFVO0lBQ3pCLE1BQU0sS0FBSyxHQUFHLFFBQVEsQ0FBQyxjQUFjLENBQUMsOEJBQThCLENBQUMsQ0FBQztJQUN0RSxJQUFJLEtBQUssRUFBRTtRQUNWLE1BQU0sSUFBSSxHQUFHLEtBQUssQ0FBQyxZQUFZLENBQUMsY0FBYyxDQUFDLENBQUM7UUFDaEQsSUFBSSxJQUFJLEVBQUU7WUFDVCxPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7U0FDeEI7S0FDRDtJQUNELE1BQU0sSUFBSSxLQUFLLENBQUMsd0JBQXdCLENBQUMsQ0FBQztBQUMzQyxDQUFDO0FBVEQsZ0NBU0MiLCJmaWxlIjoicHJlLmpzIiwic291cmNlc0NvbnRlbnQiOlsiIFx0Ly8gVGhlIG1vZHVsZSBjYWNoZVxuIFx0dmFyIGluc3RhbGxlZE1vZHVsZXMgPSB7fTtcblxuIFx0Ly8gVGhlIHJlcXVpcmUgZnVuY3Rpb25cbiBcdGZ1bmN0aW9uIF9fd2VicGFja19yZXF1aXJlX18obW9kdWxlSWQpIHtcblxuIFx0XHQvLyBDaGVjayBpZiBtb2R1bGUgaXMgaW4gY2FjaGVcbiBcdFx0aWYoaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0pIHtcbiBcdFx0XHRyZXR1cm4gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0uZXhwb3J0cztcbiBcdFx0fVxuIFx0XHQvLyBDcmVhdGUgYSBuZXcgbW9kdWxlIChhbmQgcHV0IGl0IGludG8gdGhlIGNhY2hlKVxuIFx0XHR2YXIgbW9kdWxlID0gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0gPSB7XG4gXHRcdFx0aTogbW9kdWxlSWQsXG4gXHRcdFx0bDogZmFsc2UsXG4gXHRcdFx0ZXhwb3J0czoge31cbiBcdFx0fTtcblxuIFx0XHQvLyBFeGVjdXRlIHRoZSBtb2R1bGUgZnVuY3Rpb25cbiBcdFx0bW9kdWxlc1ttb2R1bGVJZF0uY2FsbChtb2R1bGUuZXhwb3J0cywgbW9kdWxlLCBtb2R1bGUuZXhwb3J0cywgX193ZWJwYWNrX3JlcXVpcmVfXyk7XG5cbiBcdFx0Ly8gRmxhZyB0aGUgbW9kdWxlIGFzIGxvYWRlZFxuIFx0XHRtb2R1bGUubCA9IHRydWU7XG5cbiBcdFx0Ly8gUmV0dXJuIHRoZSBleHBvcnRzIG9mIHRoZSBtb2R1bGVcbiBcdFx0cmV0dXJuIG1vZHVsZS5leHBvcnRzO1xuIFx0fVxuXG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlcyBvYmplY3QgKF9fd2VicGFja19tb2R1bGVzX18pXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm0gPSBtb2R1bGVzO1xuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZSBjYWNoZVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5jID0gaW5zdGFsbGVkTW9kdWxlcztcblxuIFx0Ly8gZGVmaW5lIGdldHRlciBmdW5jdGlvbiBmb3IgaGFybW9ueSBleHBvcnRzXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmQgPSBmdW5jdGlvbihleHBvcnRzLCBuYW1lLCBnZXR0ZXIpIHtcbiBcdFx0aWYoIV9fd2VicGFja19yZXF1aXJlX18ubyhleHBvcnRzLCBuYW1lKSkge1xuIFx0XHRcdE9iamVjdC5kZWZpbmVQcm9wZXJ0eShleHBvcnRzLCBuYW1lLCB7XG4gXHRcdFx0XHRjb25maWd1cmFibGU6IGZhbHNlLFxuIFx0XHRcdFx0ZW51bWVyYWJsZTogdHJ1ZSxcbiBcdFx0XHRcdGdldDogZ2V0dGVyXG4gXHRcdFx0fSk7XG4gXHRcdH1cbiBcdH07XG5cbiBcdC8vIGRlZmluZSBfX2VzTW9kdWxlIG9uIGV4cG9ydHNcbiBcdF9fd2VicGFja19yZXF1aXJlX18uciA9IGZ1bmN0aW9uKGV4cG9ydHMpIHtcbiBcdFx0T2JqZWN0LmRlZmluZVByb3BlcnR5KGV4cG9ydHMsICdfX2VzTW9kdWxlJywgeyB2YWx1ZTogdHJ1ZSB9KTtcbiBcdH07XG5cbiBcdC8vIGdldERlZmF1bHRFeHBvcnQgZnVuY3Rpb24gZm9yIGNvbXBhdGliaWxpdHkgd2l0aCBub24taGFybW9ueSBtb2R1bGVzXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm4gPSBmdW5jdGlvbihtb2R1bGUpIHtcbiBcdFx0dmFyIGdldHRlciA9IG1vZHVsZSAmJiBtb2R1bGUuX19lc01vZHVsZSA/XG4gXHRcdFx0ZnVuY3Rpb24gZ2V0RGVmYXVsdCgpIHsgcmV0dXJuIG1vZHVsZVsnZGVmYXVsdCddOyB9IDpcbiBcdFx0XHRmdW5jdGlvbiBnZXRNb2R1bGVFeHBvcnRzKCkgeyByZXR1cm4gbW9kdWxlOyB9O1xuIFx0XHRfX3dlYnBhY2tfcmVxdWlyZV9fLmQoZ2V0dGVyLCAnYScsIGdldHRlcik7XG4gXHRcdHJldHVybiBnZXR0ZXI7XG4gXHR9O1xuXG4gXHQvLyBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGxcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubyA9IGZ1bmN0aW9uKG9iamVjdCwgcHJvcGVydHkpIHsgcmV0dXJuIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbChvYmplY3QsIHByb3BlcnR5KTsgfTtcblxuIFx0Ly8gX193ZWJwYWNrX3B1YmxpY19wYXRoX19cbiBcdF9fd2VicGFja19yZXF1aXJlX18ucCA9IFwiXCI7XG5cblxuIFx0Ly8gTG9hZCBlbnRyeSBtb2R1bGUgYW5kIHJldHVybiBleHBvcnRzXG4gXHRyZXR1cm4gX193ZWJwYWNrX3JlcXVpcmVfXyhfX3dlYnBhY2tfcmVxdWlyZV9fLnMgPSBcIi4vcHJldmlldy1zcmMvcHJlLnRzXCIpO1xuIiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmltcG9ydCB7IE1lc3NhZ2VQb3N0ZXIgfSBmcm9tICcuL21lc3NhZ2luZyc7XG5pbXBvcnQgeyBnZXRTZXR0aW5ncyB9IGZyb20gJy4vc2V0dGluZ3MnO1xuaW1wb3J0IHsgZ2V0U3RyaW5ncyB9IGZyb20gJy4vc3RyaW5ncyc7XG5cbi8qKlxuICogU2hvd3MgYW4gYWxlcnQgd2hlbiB0aGVyZSBpcyBhIGNvbnRlbnQgc2VjdXJpdHkgcG9saWN5IHZpb2xhdGlvbi5cbiAqL1xuZXhwb3J0IGNsYXNzIENzcEFsZXJ0ZXIge1xuXHRwcml2YXRlIGRpZFNob3cgPSBmYWxzZTtcblx0cHJpdmF0ZSBkaWRIYXZlQ3NwV2FybmluZyA9IGZhbHNlO1xuXG5cdHByaXZhdGUgbWVzc2FnaW5nPzogTWVzc2FnZVBvc3RlcjtcblxuXHRjb25zdHJ1Y3RvcigpIHtcblx0XHRkb2N1bWVudC5hZGRFdmVudExpc3RlbmVyKCdzZWN1cml0eXBvbGljeXZpb2xhdGlvbicsICgpID0+IHtcblx0XHRcdHRoaXMub25Dc3BXYXJuaW5nKCk7XG5cdFx0fSk7XG5cblx0XHR3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbWVzc2FnZScsIChldmVudCkgPT4ge1xuXHRcdFx0aWYgKGV2ZW50ICYmIGV2ZW50LmRhdGEgJiYgZXZlbnQuZGF0YS5uYW1lID09PSAndnNjb2RlLWRpZC1ibG9jay1zdmcnKSB7XG5cdFx0XHRcdHRoaXMub25Dc3BXYXJuaW5nKCk7XG5cdFx0XHR9XG5cdFx0fSk7XG5cdH1cblxuXHRwdWJsaWMgc2V0UG9zdGVyKHBvc3RlcjogTWVzc2FnZVBvc3Rlcikge1xuXHRcdHRoaXMubWVzc2FnaW5nID0gcG9zdGVyO1xuXHRcdGlmICh0aGlzLmRpZEhhdmVDc3BXYXJuaW5nKSB7XG5cdFx0XHR0aGlzLnNob3dDc3BXYXJuaW5nKCk7XG5cdFx0fVxuXHR9XG5cblx0cHJpdmF0ZSBvbkNzcFdhcm5pbmcoKSB7XG5cdFx0dGhpcy5kaWRIYXZlQ3NwV2FybmluZyA9IHRydWU7XG5cdFx0dGhpcy5zaG93Q3NwV2FybmluZygpO1xuXHR9XG5cblx0cHJpdmF0ZSBzaG93Q3NwV2FybmluZygpIHtcblx0XHRjb25zdCBzdHJpbmdzID0gZ2V0U3RyaW5ncygpO1xuXHRcdGNvbnN0IHNldHRpbmdzID0gZ2V0U2V0dGluZ3MoKTtcblxuXHRcdGlmICh0aGlzLmRpZFNob3cgfHwgc2V0dGluZ3MuZGlzYWJsZVNlY3VyaXR5V2FybmluZ3MgfHwgIXRoaXMubWVzc2FnaW5nKSB7XG5cdFx0XHRyZXR1cm47XG5cdFx0fVxuXHRcdHRoaXMuZGlkU2hvdyA9IHRydWU7XG5cblx0XHRjb25zdCBub3RpZmljYXRpb24gPSBkb2N1bWVudC5jcmVhdGVFbGVtZW50KCdhJyk7XG5cdFx0bm90aWZpY2F0aW9uLmlubmVyVGV4dCA9IHN0cmluZ3MuY3NwQWxlcnRNZXNzYWdlVGV4dDtcblx0XHRub3RpZmljYXRpb24uc2V0QXR0cmlidXRlKCdpZCcsICdjb2RlLWNzcC13YXJuaW5nJyk7XG5cdFx0bm90aWZpY2F0aW9uLnNldEF0dHJpYnV0ZSgndGl0bGUnLCBzdHJpbmdzLmNzcEFsZXJ0TWVzc2FnZVRpdGxlKTtcblxuXHRcdG5vdGlmaWNhdGlvbi5zZXRBdHRyaWJ1dGUoJ3JvbGUnLCAnYnV0dG9uJyk7XG5cdFx0bm90aWZpY2F0aW9uLnNldEF0dHJpYnV0ZSgnYXJpYS1sYWJlbCcsIHN0cmluZ3MuY3NwQWxlcnRNZXNzYWdlTGFiZWwpO1xuXHRcdG5vdGlmaWNhdGlvbi5vbmNsaWNrID0gKCkgPT4ge1xuXHRcdFx0dGhpcy5tZXNzYWdpbmchLnBvc3RNZXNzYWdlKCdzaG93UHJldmlld1NlY3VyaXR5U2VsZWN0b3InLCB7IHNvdXJjZTogc2V0dGluZ3Muc291cmNlIH0pO1xuXHRcdH07XG5cdFx0ZG9jdW1lbnQuYm9keS5hcHBlbmRDaGlsZChub3RpZmljYXRpb24pO1xuXHR9XG59XG4iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cbmltcG9ydCB7IE1lc3NhZ2VQb3N0ZXIgfSBmcm9tICcuL21lc3NhZ2luZyc7XG5cbmV4cG9ydCBjbGFzcyBTdHlsZUxvYWRpbmdNb25pdG9yIHtcblx0cHJpdmF0ZSB1bmxvYWRlZFN0eWxlczogc3RyaW5nW10gPSBbXTtcblx0cHJpdmF0ZSBmaW5pc2hlZExvYWRpbmc6IGJvb2xlYW4gPSBmYWxzZTtcblxuXHRwcml2YXRlIHBvc3Rlcj86IE1lc3NhZ2VQb3N0ZXI7XG5cblx0Y29uc3RydWN0b3IoKSB7XG5cdFx0Y29uc3Qgb25TdHlsZUxvYWRFcnJvciA9IChldmVudDogYW55KSA9PiB7XG5cdFx0XHRjb25zdCBzb3VyY2UgPSBldmVudC50YXJnZXQuZGF0YXNldC5zb3VyY2U7XG5cdFx0XHR0aGlzLnVubG9hZGVkU3R5bGVzLnB1c2goc291cmNlKTtcblx0XHR9O1xuXG5cdFx0d2luZG93LmFkZEV2ZW50TGlzdGVuZXIoJ0RPTUNvbnRlbnRMb2FkZWQnLCAoKSA9PiB7XG5cdFx0XHRmb3IgKGNvbnN0IGxpbmsgb2YgZG9jdW1lbnQuZ2V0RWxlbWVudHNCeUNsYXNzTmFtZSgnY29kZS11c2VyLXN0eWxlJykgYXMgSFRNTENvbGxlY3Rpb25PZjxIVE1MRWxlbWVudD4pIHtcblx0XHRcdFx0aWYgKGxpbmsuZGF0YXNldC5zb3VyY2UpIHtcblx0XHRcdFx0XHRsaW5rLm9uZXJyb3IgPSBvblN0eWxlTG9hZEVycm9yO1xuXHRcdFx0XHR9XG5cdFx0XHR9XG5cdFx0fSk7XG5cblx0XHR3aW5kb3cuYWRkRXZlbnRMaXN0ZW5lcignbG9hZCcsICgpID0+IHtcblx0XHRcdGlmICghdGhpcy51bmxvYWRlZFN0eWxlcy5sZW5ndGgpIHtcblx0XHRcdFx0cmV0dXJuO1xuXHRcdFx0fVxuXHRcdFx0dGhpcy5maW5pc2hlZExvYWRpbmcgPSB0cnVlO1xuXHRcdFx0aWYgKHRoaXMucG9zdGVyKSB7XG5cdFx0XHRcdHRoaXMucG9zdGVyLnBvc3RNZXNzYWdlKCdwcmV2aWV3U3R5bGVMb2FkRXJyb3InLCB7IHVubG9hZGVkU3R5bGVzOiB0aGlzLnVubG9hZGVkU3R5bGVzIH0pO1xuXHRcdFx0fVxuXHRcdH0pO1xuXHR9XG5cblx0cHVibGljIHNldFBvc3Rlcihwb3N0ZXI6IE1lc3NhZ2VQb3N0ZXIpOiB2b2lkIHtcblx0XHR0aGlzLnBvc3RlciA9IHBvc3Rlcjtcblx0XHRpZiAodGhpcy5maW5pc2hlZExvYWRpbmcpIHtcblx0XHRcdHBvc3Rlci5wb3N0TWVzc2FnZSgncHJldmlld1N0eWxlTG9hZEVycm9yJywgeyB1bmxvYWRlZFN0eWxlczogdGhpcy51bmxvYWRlZFN0eWxlcyB9KTtcblx0XHR9XG5cdH1cbn0iLCIvKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuICogIENvcHlyaWdodCAoYykgTWljcm9zb2Z0IENvcnBvcmF0aW9uLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogIExpY2Vuc2VkIHVuZGVyIHRoZSBNSVQgTGljZW5zZS4gU2VlIExpY2Vuc2UudHh0IGluIHRoZSBwcm9qZWN0IHJvb3QgZm9yIGxpY2Vuc2UgaW5mb3JtYXRpb24uXG4gKi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tKi9cblxuaW1wb3J0IHsgQ3NwQWxlcnRlciB9IGZyb20gJy4vY3NwJztcbmltcG9ydCB7IFN0eWxlTG9hZGluZ01vbml0b3IgfSBmcm9tICcuL2xvYWRpbmcnO1xuXG5kZWNsYXJlIGdsb2JhbCB7XG5cdGludGVyZmFjZSBXaW5kb3cge1xuXHRcdGNzcEFsZXJ0ZXI6IENzcEFsZXJ0ZXI7XG5cdFx0c3R5bGVMb2FkaW5nTW9uaXRvcjogU3R5bGVMb2FkaW5nTW9uaXRvcjtcblx0fVxufVxuXG53aW5kb3cuY3NwQWxlcnRlciA9IG5ldyBDc3BBbGVydGVyKCk7XG53aW5kb3cuc3R5bGVMb2FkaW5nTW9uaXRvciA9IG5ldyBTdHlsZUxvYWRpbmdNb25pdG9yKCk7IiwiLyotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbiAqICBDb3B5cmlnaHQgKGMpIE1pY3Jvc29mdCBDb3Jwb3JhdGlvbi4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqICBMaWNlbnNlZCB1bmRlciB0aGUgTUlUIExpY2Vuc2UuIFNlZSBMaWNlbnNlLnR4dCBpbiB0aGUgcHJvamVjdCByb290IGZvciBsaWNlbnNlIGluZm9ybWF0aW9uLlxuICotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLSovXG5cbmV4cG9ydCBpbnRlcmZhY2UgUHJldmlld1NldHRpbmdzIHtcblx0c291cmNlOiBzdHJpbmc7XG5cdGxpbmU6IG51bWJlcjtcblx0bGluZUNvdW50OiBudW1iZXI7XG5cdHNjcm9sbFByZXZpZXdXaXRoRWRpdG9yPzogYm9vbGVhbjtcblx0c2Nyb2xsRWRpdG9yV2l0aFByZXZpZXc6IGJvb2xlYW47XG5cdGRpc2FibGVTZWN1cml0eVdhcm5pbmdzOiBib29sZWFuO1xuXHRkb3VibGVDbGlja1RvU3dpdGNoVG9FZGl0b3I6IGJvb2xlYW47XG59XG5cbmxldCBjYWNoZWRTZXR0aW5nczogUHJldmlld1NldHRpbmdzIHwgdW5kZWZpbmVkID0gdW5kZWZpbmVkO1xuXG5leHBvcnQgZnVuY3Rpb24gZ2V0RGF0YShrZXk6IHN0cmluZyk6IFByZXZpZXdTZXR0aW5ncyB7XG5cdGNvbnN0IGVsZW1lbnQgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndnNjb2RlLW1hcmtkb3duLXByZXZpZXctZGF0YScpO1xuXHRpZiAoZWxlbWVudCkge1xuXHRcdGNvbnN0IGRhdGEgPSBlbGVtZW50LmdldEF0dHJpYnV0ZShrZXkpO1xuXHRcdGlmIChkYXRhKSB7XG5cdFx0XHRyZXR1cm4gSlNPTi5wYXJzZShkYXRhKTtcblx0XHR9XG5cdH1cblxuXHR0aHJvdyBuZXcgRXJyb3IoYENvdWxkIG5vdCBsb2FkIGRhdGEgZm9yICR7a2V5fWApO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0U2V0dGluZ3MoKTogUHJldmlld1NldHRpbmdzIHtcblx0aWYgKGNhY2hlZFNldHRpbmdzKSB7XG5cdFx0cmV0dXJuIGNhY2hlZFNldHRpbmdzO1xuXHR9XG5cblx0Y2FjaGVkU2V0dGluZ3MgPSBnZXREYXRhKCdkYXRhLXNldHRpbmdzJyk7XG5cdGlmIChjYWNoZWRTZXR0aW5ncykge1xuXHRcdHJldHVybiBjYWNoZWRTZXR0aW5ncztcblx0fVxuXG5cdHRocm93IG5ldyBFcnJvcignQ291bGQgbm90IGxvYWQgc2V0dGluZ3MnKTtcbn1cbiIsIi8qLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG4gKiAgQ29weXJpZ2h0IChjKSBNaWNyb3NvZnQgQ29ycG9yYXRpb24uIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiAgTGljZW5zZWQgdW5kZXIgdGhlIE1JVCBMaWNlbnNlLiBTZWUgTGljZW5zZS50eHQgaW4gdGhlIHByb2plY3Qgcm9vdCBmb3IgbGljZW5zZSBpbmZvcm1hdGlvbi5cbiAqLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0qL1xuXG5leHBvcnQgZnVuY3Rpb24gZ2V0U3RyaW5ncygpOiB7IFtrZXk6IHN0cmluZ106IHN0cmluZyB9IHtcblx0Y29uc3Qgc3RvcmUgPSBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndnNjb2RlLW1hcmtkb3duLXByZXZpZXctZGF0YScpO1xuXHRpZiAoc3RvcmUpIHtcblx0XHRjb25zdCBkYXRhID0gc3RvcmUuZ2V0QXR0cmlidXRlKCdkYXRhLXN0cmluZ3MnKTtcblx0XHRpZiAoZGF0YSkge1xuXHRcdFx0cmV0dXJuIEpTT04ucGFyc2UoZGF0YSk7XG5cdFx0fVxuXHR9XG5cdHRocm93IG5ldyBFcnJvcignQ291bGQgbm90IGxvYWQgc3RyaW5ncycpO1xufVxuIl0sInNvdXJjZVJvb3QiOiIifQ== diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 34494ab241..e18099fe67 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -160,6 +160,14 @@ "command": "markdown.preview.toggleLock", "when": "markdownPreviewFocus" }, + { + "command": "markdown.preview.refresh", + "when": "editorLangId == markdown" + }, + { + "command": "markdown.preview.refresh", + "when": "markdownPreviewFocus" + }, { "command": "notebook.showPreview", "when": "false" @@ -315,12 +323,12 @@ "@types/highlight.js": "9.12.3", "@types/lodash.throttle": "^4.1.3", "@types/markdown-it": "0.0.2", - "@types/node": "^8.10.25", + "@types/node": "^10.12.21", "lodash.throttle": "^4.1.1", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", "ts-loader": "^4.0.1", - "typescript": "^2.7.2", + "typescript": "^3.3.1", "vscode": "^1.1.10", "webpack": "^4.1.0", "webpack-cli": "^2.0.10" diff --git a/extensions/markdown-language-features/preview-src/events.ts b/extensions/markdown-language-features/preview-src/events.ts index 919b4863a6..8cb41f6661 100644 --- a/extensions/markdown-language-features/preview-src/events.ts +++ b/extensions/markdown-language-features/preview-src/events.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ export function onceDocumentLoaded(f: () => void) { - if (document.readyState === 'loading' || document.readyState === 'uninitialized') { + if (document.readyState === 'loading' || document.readyState as string === 'uninitialized') { document.addEventListener('DOMContentLoaded', f); } else { f(); diff --git a/extensions/markdown-language-features/preview-src/index.ts b/extensions/markdown-language-features/preview-src/index.ts index 3ae1b9e103..cae89bb56c 100644 --- a/extensions/markdown-language-features/preview-src/index.ts +++ b/extensions/markdown-language-features/preview-src/index.ts @@ -19,7 +19,7 @@ const settings = getSettings(); const vscode = acquireVsCodeApi(); // Set VS Code state -const state = getData('data-state'); +let state = getData('data-state'); vscode.setState(state); const messaging = createPosterForVsCode(vscode); @@ -152,6 +152,8 @@ if (settings.scrollEditorWithPreview) { const line = getEditorLineNumberForPageOffset(window.scrollY); if (typeof line === 'number' && !isNaN(line)) { messaging.postMessage('revealLine', { line }); + state.line = line; + vscode.setState(state); } } }, 50)); diff --git a/extensions/markdown-language-features/preview-src/scroll-sync.ts b/extensions/markdown-language-features/preview-src/scroll-sync.ts index 36c284926e..9ada06a5f9 100644 --- a/extensions/markdown-language-features/preview-src/scroll-sync.ts +++ b/extensions/markdown-language-features/preview-src/scroll-sync.ts @@ -24,13 +24,13 @@ const getCodeLineElements = (() => { let elements: CodeLineElement[]; return () => { if (!elements) { - elements = ([{ element: document.body, line: 0 }]).concat(Array.prototype.map.call( - document.getElementsByClassName('code-line'), - (element: any) => { - const line = +element.getAttribute('data-line'); - return { element, line }; - }) - .filter((x: any) => !isNaN(x.line))); + elements = [{ element: document.body, line: 0 }]; + for (const element of document.getElementsByClassName('code-line')) { + const line = +element.getAttribute('data-line')!; + if (!isNaN(line)) { + elements.push({ element: element as HTMLElement, line }); + } + } } return elements; }; diff --git a/extensions/markdown-language-features/preview-src/tsconfig.json b/extensions/markdown-language-features/preview-src/tsconfig.json index 9684d1ec2d..8a1e8a03fb 100644 --- a/extensions/markdown-language-features/preview-src/tsconfig.json +++ b/extensions/markdown-language-features/preview-src/tsconfig.json @@ -6,6 +6,7 @@ "jsx": "react", "sourceMap": true, "strict": true, + "strictBindCallApply": true, "noImplicitAny": true, "noUnusedLocals": true } diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts index f2fbc17361..104300acf3 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.ts @@ -15,8 +15,8 @@ import MarkdownWorkspaceSymbolProvider from './features/workspaceSymbolProvider' import { Logger } from './logger'; import { MarkdownEngine } from './markdownEngine'; import { getMarkdownExtensionContributions } from './markdownExtensions'; -import { ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector } from './security'; -import { loadDefaultTelemetryReporter } from './telemetryReporter'; +import { ExtensionContentSecurityPolicyArbiter, PreviewSecuritySelector, ContentSecurityPolicyArbiter } from './security'; +import { loadDefaultTelemetryReporter, TelemetryReporter } from './telemetryReporter'; import { githubSlugifier } from './slugify'; @@ -25,33 +25,55 @@ export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(telemetryReporter); const contributions = getMarkdownExtensionContributions(context); + context.subscriptions.push(contributions); const cspArbiter = new ExtensionContentSecurityPolicyArbiter(context.globalState, context.workspaceState); const engine = new MarkdownEngine(contributions, githubSlugifier); const logger = new Logger(); - const selector: vscode.DocumentSelector = [ - { language: 'markdown', scheme: 'file' }, - { language: 'markdown', scheme: 'untitled' } - ]; - const contentProvider = new MarkdownContentProvider(engine, context, cspArbiter, contributions, logger); const symbolProvider = new MDDocumentSymbolProvider(engine); const previewManager = new MarkdownPreviewManager(contentProvider, logger, contributions); context.subscriptions.push(previewManager); - context.subscriptions.push(vscode.languages.setLanguageConfiguration('markdown', { - wordPattern: new RegExp('(\\p{Alphabetic}|\\p{Number})+', 'ug'), - })); - context.subscriptions.push(vscode.languages.registerDocumentSymbolProvider(selector, symbolProvider)); - context.subscriptions.push(vscode.languages.registerDocumentLinkProvider(selector, new LinkProvider())); - context.subscriptions.push(vscode.languages.registerFoldingRangeProvider(selector, new MarkdownFoldingProvider(engine))); - context.subscriptions.push(vscode.languages.registerWorkspaceSymbolProvider(new MarkdownWorkspaceSymbolProvider(symbolProvider))); + context.subscriptions.push(registerMarkdownLanguageFeatures(symbolProvider, engine)); + context.subscriptions.push(registerMarkdownCommands(previewManager, telemetryReporter, cspArbiter, engine)); + context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => { + logger.updateConfiguration(); + previewManager.updateConfiguration(); + })); +} + +function registerMarkdownLanguageFeatures( + symbolProvider: MDDocumentSymbolProvider, + engine: MarkdownEngine +): vscode.Disposable { + const selector: vscode.DocumentSelector = [ + { language: 'markdown', scheme: 'file' }, + { language: 'markdown', scheme: 'untitled' } + ]; + + return vscode.Disposable.from( + vscode.languages.setLanguageConfiguration('markdown', { + wordPattern: new RegExp('(\\p{Alphabetic}|\\p{Number})+', 'ug'), + }), + vscode.languages.registerDocumentSymbolProvider(selector, symbolProvider), + vscode.languages.registerDocumentLinkProvider(selector, new LinkProvider()), + vscode.languages.registerFoldingRangeProvider(selector, new MarkdownFoldingProvider(engine)), + vscode.languages.registerWorkspaceSymbolProvider(new MarkdownWorkspaceSymbolProvider(symbolProvider)) + ); +} + +function registerMarkdownCommands( + previewManager: MarkdownPreviewManager, + telemetryReporter: TelemetryReporter, + cspArbiter: ContentSecurityPolicyArbiter, + engine: MarkdownEngine +): vscode.Disposable { const previewSecuritySelector = new PreviewSecuritySelector(cspArbiter, previewManager); const commandManager = new CommandManager(); - context.subscriptions.push(commandManager); commandManager.register(new commands.ShowPreviewCommand(previewManager, telemetryReporter)); commandManager.register(new commands.ShowPreviewToSideCommand(previewManager, telemetryReporter)); commandManager.register(new commands.ShowLockedPreviewToSideCommand(previewManager, telemetryReporter)); @@ -63,9 +85,5 @@ export function activate(context: vscode.ExtensionContext) { commandManager.register(new commands.ToggleLockCommand(previewManager)); // {{SQL CARBON EDIT}} commandManager.register(new commands.ShowNotebookPreview(engine)); - - context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => { - logger.updateConfiguration(); - previewManager.updateConfiguration(); - })); + return commandManager; } diff --git a/extensions/markdown-language-features/src/features/preview.ts b/extensions/markdown-language-features/src/features/preview.ts index 544d89ec6f..c9c2b86b3d 100644 --- a/extensions/markdown-language-features/src/features/preview.ts +++ b/extensions/markdown-language-features/src/features/preview.ts @@ -8,12 +8,12 @@ import * as path from 'path'; import { Logger } from '../logger'; import { MarkdownContentProvider } from './previewContentProvider'; -import { disposeAll } from '../util/dispose'; +import { Disposable } from '../util/dispose'; import * as nls from 'vscode-nls'; import { getVisibleLine, MarkdownFileTopmostLineMonitor } from '../util/topmostLineMonitor'; import { MarkdownPreviewConfigurationManager } from './previewConfig'; -import { MarkdownContributions } from '../markdownExtensions'; +import { MarkdownContributionProvider, MarkdownContributions } from '../markdownExtensions'; import { isMarkdownFile } from '../util/file'; import { resolveLinkToMarkdownFile } from '../commands/openDocumentLink'; const localize = nls.loadMessageBundle(); @@ -60,7 +60,7 @@ interface PreviewStyleLoadErrorMessage extends WebviewMessage { }; } -export class MarkdownPreview { +export class MarkdownPreview extends Disposable { public static viewType = 'markdown.preview'; @@ -70,7 +70,6 @@ export class MarkdownPreview { private readonly editor: vscode.WebviewPanel; private throttleTimer: any; private line: number | undefined = undefined; - private readonly disposables: vscode.Disposable[] = []; private firstUpdate = true; private currentVersion?: { resource: vscode.Uri, version: number }; private forceUpdate = false; @@ -85,7 +84,7 @@ export class MarkdownPreview { previewConfigurations: MarkdownPreviewConfigurationManager, logger: Logger, topmostLineMonitor: MarkdownFileTopmostLineMonitor, - contributions: MarkdownContributions, + contributionProvider: MarkdownContributionProvider, ): Promise { const resource = vscode.Uri.parse(state.resource); const locked = state.locked; @@ -99,9 +98,9 @@ export class MarkdownPreview { previewConfigurations, logger, topmostLineMonitor, - contributions); + contributionProvider); - preview.editor.webview.options = MarkdownPreview.getWebviewOptions(resource, contributions); + preview.editor.webview.options = MarkdownPreview.getWebviewOptions(resource, contributionProvider.contributions); if (!isNaN(line)) { preview.line = line; @@ -118,14 +117,14 @@ export class MarkdownPreview { previewConfigurations: MarkdownPreviewConfigurationManager, logger: Logger, topmostLineMonitor: MarkdownFileTopmostLineMonitor, - contributions: MarkdownContributions + contributionProvider: MarkdownContributionProvider ): MarkdownPreview { const webview = vscode.window.createWebviewPanel( MarkdownPreview.viewType, MarkdownPreview.getPreviewTitle(resource, locked), previewColumn, { enableFindWidget: true, - ...MarkdownPreview.getWebviewOptions(resource, contributions) + ...MarkdownPreview.getWebviewOptions(resource, contributionProvider.contributions) }); return new MarkdownPreview( @@ -136,7 +135,7 @@ export class MarkdownPreview { previewConfigurations, logger, topmostLineMonitor, - contributions); + contributionProvider); } private constructor( @@ -147,19 +146,24 @@ export class MarkdownPreview { private readonly _previewConfigurations: MarkdownPreviewConfigurationManager, private readonly _logger: Logger, topmostLineMonitor: MarkdownFileTopmostLineMonitor, - private readonly _contributions: MarkdownContributions, + private readonly _contributionProvider: MarkdownContributionProvider, ) { + super(); this._resource = resource; this._locked = locked; this.editor = webview; this.editor.onDidDispose(() => { this.dispose(); - }, null, this.disposables); + }, null, this._disposables); this.editor.onDidChangeViewState(e => { this._onDidChangeViewStateEmitter.fire(e); - }, null, this.disposables); + }, null, this._disposables); + + _contributionProvider.onContributionsChanged(() => { + setImmediate(() => this.refresh()); + }, null, this._disposables); this.editor.webview.onDidReceiveMessage((e: CacheImageSizesMessage | RevealLineMessage | DidClickMessage | ClickLinkMessage | ShowPreviewSecuritySelectorMessage | PreviewStyleLoadErrorMessage) => { if (e.source !== this._resource.toString()) { @@ -191,19 +195,19 @@ export class MarkdownPreview { vscode.window.showWarningMessage(localize('onPreviewStyleLoadError', "Could not load 'markdown.styles': {0}", e.body.unloadedStyles.join(', '))); break; } - }, null, this.disposables); + }, null, this._disposables); vscode.workspace.onDidChangeTextDocument(event => { if (this.isPreviewOf(event.document.uri)) { this.refresh(); } - }, null, this.disposables); + }, null, this._disposables); topmostLineMonitor.onDidChangeTopmostLine(event => { if (this.isPreviewOf(event.resource)) { this.updateForView(event.resource, event.line); } - }, null, this.disposables); + }, null, this._disposables); vscode.window.onDidChangeTextEditorSelection(event => { if (this.isPreviewOf(event.textEditor.document.uri)) { @@ -213,13 +217,13 @@ export class MarkdownPreview { source: this.resource.toString() }); } - }, null, this.disposables); + }, null, this._disposables); vscode.window.onDidChangeActiveTextEditor(editor => { if (editor && isMarkdownFile(editor.document) && !this._locked) { this.update(editor.document.uri); } - }, null, this.disposables); + }, null, this._disposables); } private readonly _onDisposeEmitter = new vscode.EventEmitter(); @@ -242,18 +246,17 @@ export class MarkdownPreview { } public dispose() { + super.dispose(); if (this._disposed) { return; } this._disposed = true; this._onDisposeEmitter.fire(); - this._onDisposeEmitter.dispose(); + this._onDidChangeViewStateEmitter.dispose(); this.editor.dispose(); - - disposeAll(this.disposables); } public update(resource: vscode.Uri) { @@ -328,7 +331,7 @@ export class MarkdownPreview { } private get iconPath() { - const root = path.join(this._contributions.extensionPath, 'media'); + const root = path.join(this._contributionProvider.extensionPath, 'media'); return { light: vscode.Uri.file(path.join(root, 'Preview.svg')), dark: vscode.Uri.file(path.join(root, 'Preview_inverse.svg')) @@ -392,7 +395,7 @@ export class MarkdownPreview { if (this._resource === resource) { this.editor.title = MarkdownPreview.getPreviewTitle(this._resource, this._locked); this.editor.iconPath = this.iconPath; - this.editor.webview.options = MarkdownPreview.getWebviewOptions(resource, this._contributions); + this.editor.webview.options = MarkdownPreview.getWebviewOptions(resource, this._contributionProvider.contributions); this.editor.webview.html = content; } } @@ -410,7 +413,7 @@ export class MarkdownPreview { private static getLocalResourceRoots( resource: vscode.Uri, contributions: MarkdownContributions - ): vscode.Uri[] { + ): ReadonlyArray { const baseRoots = contributions.previewResourceRoots; const folder = vscode.workspace.getWorkspaceFolder(resource); diff --git a/extensions/markdown-language-features/src/features/previewContentProvider.ts b/extensions/markdown-language-features/src/features/previewContentProvider.ts index 6dcebd0115..34a099c829 100644 --- a/extensions/markdown-language-features/src/features/previewContentProvider.ts +++ b/extensions/markdown-language-features/src/features/previewContentProvider.ts @@ -13,7 +13,7 @@ const localize = nls.loadMessageBundle(); import { Logger } from '../logger'; import { ContentSecurityPolicyArbiter, MarkdownPreviewSecurityLevel } from '../security'; import { MarkdownPreviewConfigurationManager, MarkdownPreviewConfiguration } from './previewConfig'; -import { MarkdownContributions } from '../markdownExtensions'; +import { MarkdownContributionProvider } from '../markdownExtensions'; /** * Strings used inside the markdown preview. @@ -40,7 +40,7 @@ export class MarkdownContentProvider { private readonly engine: MarkdownEngine, private readonly context: vscode.ExtensionContext, private readonly cspArbiter: ContentSecurityPolicyArbiter, - private readonly contributions: MarkdownContributions, + private readonly contributionProvider: MarkdownContributionProvider, private readonly logger: Logger ) { } @@ -163,7 +163,7 @@ export class MarkdownContentProvider { } private getStyles(resource: vscode.Uri, nonce: string, config: MarkdownPreviewConfiguration, state?: any): string { - const baseStyles = this.contributions.previewStyles + const baseStyles = this.contributionProvider.contributions.previewStyles .map(resource => ``) .join('\n'); @@ -174,7 +174,7 @@ export class MarkdownContentProvider { } private getScripts(nonce: string): string { - return this.contributions.previewScripts + return this.contributionProvider.contributions.previewScripts .map(resource => ``) .join('\n'); } diff --git a/extensions/markdown-language-features/src/features/previewManager.ts b/extensions/markdown-language-features/src/features/previewManager.ts index b50da474f5..e5ba6dd984 100644 --- a/extensions/markdown-language-features/src/features/previewManager.ts +++ b/extensions/markdown-language-features/src/features/previewManager.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode'; import { Logger } from '../logger'; -import { MarkdownContributions } from '../markdownExtensions'; +import { MarkdownContributionProvider } from '../markdownExtensions'; import { disposeAll } from '../util/dispose'; import { MarkdownFileTopmostLineMonitor } from '../util/topmostLineMonitor'; import { MarkdownPreview, PreviewSettings } from './preview'; @@ -25,7 +25,7 @@ export class MarkdownPreviewManager implements vscode.WebviewPanelSerializer { public constructor( private readonly _contentProvider: MarkdownContentProvider, private readonly _logger: Logger, - private readonly _contributions: MarkdownContributions + private readonly _contributions: MarkdownContributionProvider ) { this._disposables.push(vscode.window.registerWebviewPanelSerializer(MarkdownPreview.viewType, this)); } diff --git a/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts b/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts index 22bd456de0..81258b5a6d 100644 --- a/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts +++ b/extensions/markdown-language-features/src/features/workspaceSymbolProvider.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { disposeAll } from '../util/dispose'; +import { Disposable } from '../util/dispose'; import { isMarkdownFile } from '../util/file'; import { Lazy, lazy } from '../util/lazy'; import MDDocumentSymbolProvider from './documentSymbolProvider'; @@ -18,25 +18,13 @@ export interface WorkspaceMarkdownDocumentProvider { readonly onDidDeleteMarkdownDocument: vscode.Event; } -class VSCodeWorkspaceMarkdownDocumentProvider implements WorkspaceMarkdownDocumentProvider { +class VSCodeWorkspaceMarkdownDocumentProvider extends Disposable implements WorkspaceMarkdownDocumentProvider { - private readonly _onDidChangeMarkdownDocumentEmitter = new vscode.EventEmitter(); - private readonly _onDidCreateMarkdownDocumentEmitter = new vscode.EventEmitter(); - private readonly _onDidDeleteMarkdownDocumentEmitter = new vscode.EventEmitter(); + private readonly _onDidChangeMarkdownDocumentEmitter = this._register(new vscode.EventEmitter()); + private readonly _onDidCreateMarkdownDocumentEmitter = this._register(new vscode.EventEmitter()); + private readonly _onDidDeleteMarkdownDocumentEmitter = this._register(new vscode.EventEmitter()); private _watcher: vscode.FileSystemWatcher | undefined; - private _disposables: vscode.Disposable[] = []; - - public dispose() { - this._onDidChangeMarkdownDocumentEmitter.dispose(); - this._onDidDeleteMarkdownDocumentEmitter.dispose(); - - if (this._watcher) { - this._watcher.dispose(); - } - - disposeAll(this._disposables); - } async getAllMarkdownDocuments() { const resources = await vscode.workspace.findFiles('**/*.md', '**/node_modules/**'); @@ -64,7 +52,7 @@ class VSCodeWorkspaceMarkdownDocumentProvider implements WorkspaceMarkdownDocume return; } - this._watcher = vscode.workspace.createFileSystemWatcher('**/*.md'); + this._watcher = this._register(vscode.workspace.createFileSystemWatcher('**/*.md')); this._watcher.onDidChange(async resource => { const document = await this.getMarkdownDocument(resource); @@ -98,15 +86,16 @@ class VSCodeWorkspaceMarkdownDocumentProvider implements WorkspaceMarkdownDocume } -export default class MarkdownWorkspaceSymbolProvider implements vscode.WorkspaceSymbolProvider { +export default class MarkdownWorkspaceSymbolProvider extends Disposable implements vscode.WorkspaceSymbolProvider { private _symbolCache = new Map>>(); private _symbolCachePopulated: boolean = false; - private _disposables: vscode.Disposable[] = []; public constructor( private _symbolProvider: MDDocumentSymbolProvider, private _workspaceMarkdownDocumentProvider: WorkspaceMarkdownDocumentProvider = new VSCodeWorkspaceMarkdownDocumentProvider() - ) { } + ) { + super(); + } public async provideWorkspaceSymbols(query: string): Promise { if (!this._symbolCachePopulated) { @@ -130,10 +119,6 @@ export default class MarkdownWorkspaceSymbolProvider implements vscode.Workspace } } - public dispose(): void { - disposeAll(this._disposables); - } - private getSymbols(document: SkinnyTextDocument): Lazy> { return lazy(async () => { return this._symbolProvider.provideDocumentSymbolInformation(document); diff --git a/extensions/markdown-language-features/src/markdownEngine.ts b/extensions/markdown-language-features/src/markdownEngine.ts index 53f117779e..aafabfa4cf 100644 --- a/extensions/markdown-language-features/src/markdownEngine.ts +++ b/extensions/markdown-language-features/src/markdownEngine.ts @@ -7,7 +7,7 @@ import * as crypto from 'crypto'; import { MarkdownIt, Token } from 'markdown-it'; import * as path from 'path'; import * as vscode from 'vscode'; -import { MarkdownContributions } from './markdownExtensions'; +import { MarkdownContributionProvider as MarkdownContributionProvider } from './markdownExtensions'; import { Slugifier } from './slugify'; import { SkinnyTextDocument } from './tableOfContentsProvider'; import { getUriForLinkWithKnownExternalScheme } from './util/links'; @@ -57,16 +57,21 @@ export class MarkdownEngine { private _tokenCache = new TokenCache(); public constructor( - private readonly extensionPreviewResourceProvider: MarkdownContributions, + private readonly contributionProvider: MarkdownContributionProvider, private readonly slugifier: Slugifier, - ) { } + ) { + contributionProvider.onContributionsChanged(() => { + // Markdown plugin contributions may have changed + this.md = undefined; + }); + } private async getEngine(config: MarkdownItConfig): Promise { if (!this.md) { this.md = import('markdown-it').then(async markdownIt => { let md: MarkdownIt = markdownIt(await getMarkdownOptions(() => md)); - for (const plugin of this.extensionPreviewResourceProvider.markdownItPlugins) { + for (const plugin of this.contributionProvider.contributions.markdownItPlugins.values()) { try { md = (await plugin)(md); } catch { diff --git a/extensions/markdown-language-features/src/markdownExtensions.ts b/extensions/markdown-language-features/src/markdownExtensions.ts index e712fc13c8..bc78d859d4 100644 --- a/extensions/markdown-language-features/src/markdownExtensions.ts +++ b/extensions/markdown-language-features/src/markdownExtensions.ts @@ -5,13 +5,15 @@ import * as vscode from 'vscode'; import * as path from 'path'; +import { Disposable } from './util/dispose'; +import * as arrays from './util/arrays'; const resolveExtensionResource = (extension: vscode.Extension, resourcePath: string): vscode.Uri => { return vscode.Uri.file(path.join(extension.extensionPath, resourcePath)) .with({ scheme: 'vscode-resource' }); }; -const resolveExtensionResources = (extension: vscode.Extension, resourcePaths: any): vscode.Uri[] => { +const resolveExtensionResources = (extension: vscode.Extension, resourcePaths: unknown): vscode.Uri[] => { const result: vscode.Uri[] = []; if (Array.isArray(resourcePaths)) { for (const resource of resourcePaths) { @@ -26,96 +28,135 @@ const resolveExtensionResources = (extension: vscode.Extension, resourcePat }; export interface MarkdownContributions { - readonly extensionPath: string; - readonly previewScripts: vscode.Uri[]; - readonly previewStyles: vscode.Uri[]; - readonly markdownItPlugins: Thenable<(md: any) => any>[]; - readonly previewResourceRoots: vscode.Uri[]; + readonly previewScripts: ReadonlyArray; + readonly previewStyles: ReadonlyArray; + readonly previewResourceRoots: ReadonlyArray; + readonly markdownItPlugins: Map any>>; } -class MarkdownExtensionContributions implements MarkdownContributions { - private readonly _scripts: vscode.Uri[] = []; - private readonly _styles: vscode.Uri[] = []; - private readonly _previewResourceRoots: vscode.Uri[] = []; - private readonly _plugins: Thenable<(md: any) => any>[] = []; +export namespace MarkdownContributions { + export const Empty: MarkdownContributions = { + previewScripts: [], + previewStyles: [], + previewResourceRoots: [], + markdownItPlugins: new Map() + }; - private _loaded = false; - - public constructor( - public readonly extensionPath: string, - ) { } - - public get previewScripts(): vscode.Uri[] { - this.ensureLoaded(); - return this._scripts; + export function merge(a: MarkdownContributions, b: MarkdownContributions): MarkdownContributions { + return { + previewScripts: [...a.previewScripts, ...b.previewScripts], + previewStyles: [...a.previewStyles, ...b.previewStyles], + previewResourceRoots: [...a.previewResourceRoots, ...b.previewResourceRoots], + markdownItPlugins: new Map([...a.markdownItPlugins.entries(), ...b.markdownItPlugins.entries()]), + }; } - public get previewStyles(): vscode.Uri[] { - this.ensureLoaded(); - return this._styles; + function uriEqual(a: vscode.Uri, b: vscode.Uri): boolean { + return a.toString() === b.toString(); } - public get previewResourceRoots(): vscode.Uri[] { - this.ensureLoaded(); - return this._previewResourceRoots; + export function equal(a: MarkdownContributions, b: MarkdownContributions): boolean { + return arrays.equals(a.previewScripts, b.previewScripts, uriEqual) + && arrays.equals(a.previewStyles, b.previewStyles, uriEqual) + && arrays.equals(a.previewResourceRoots, b.previewResourceRoots, uriEqual) + && arrays.equals(Array.from(a.markdownItPlugins.keys()), Array.from(b.markdownItPlugins.keys())); } - public get markdownItPlugins(): Thenable<(md: any) => any>[] { - this.ensureLoaded(); - return this._plugins; - } - - private ensureLoaded() { - if (this._loaded) { - return; + export function fromExtension( + extension: vscode.Extension + ): MarkdownContributions { + const contributions = extension.packageJSON && extension.packageJSON.contributes; + if (!contributions) { + return MarkdownContributions.Empty; } - this._loaded = true; - for (const extension of vscode.extensions.all) { - const contributes = extension.packageJSON && extension.packageJSON.contributes; - if (!contributes) { - continue; - } + const previewStyles = getContributedStyles(contributions, extension); + const previewScripts = getContributedScripts(contributions, extension); + const previewResourceRoots = previewStyles.length || previewScripts.length ? [vscode.Uri.file(extension.extensionPath)] : []; + const markdownItPlugins = getContributedMarkdownItPlugins(contributions, extension); - this.tryLoadPreviewStyles(contributes, extension); - this.tryLoadPreviewScripts(contributes, extension); - this.tryLoadMarkdownItPlugins(contributes, extension); - - if (contributes['markdown.previewScripts'] || contributes['markdown.previewStyles']) { - this._previewResourceRoots.push(vscode.Uri.file(extension.extensionPath)); - } - } + return { + previewScripts, + previewStyles, + previewResourceRoots, + markdownItPlugins + }; } - private tryLoadMarkdownItPlugins( + function getContributedMarkdownItPlugins( contributes: any, extension: vscode.Extension - ) { + ): Map any>> { + const map = new Map any>>(); if (contributes['markdown.markdownItPlugins']) { - this._plugins.push(extension.activate().then(() => { + map.set(extension.id, extension.activate().then(() => { if (extension.exports && extension.exports.extendMarkdownIt) { return (md: any) => extension.exports.extendMarkdownIt(md); } return (md: any) => md; })); } + return map; } - private tryLoadPreviewScripts( + function getContributedScripts( contributes: any, extension: vscode.Extension ) { - this._scripts.push(...resolveExtensionResources(extension, contributes['markdown.previewScripts'])); + return resolveExtensionResources(extension, contributes['markdown.previewScripts']); } - private tryLoadPreviewStyles( + function getContributedStyles( contributes: any, extension: vscode.Extension ) { - this._styles.push(...resolveExtensionResources(extension, contributes['markdown.previewStyles'])); + return resolveExtensionResources(extension, contributes['markdown.previewStyles']); } } -export function getMarkdownExtensionContributions(context: vscode.ExtensionContext): MarkdownContributions { - return new MarkdownExtensionContributions(context.extensionPath); +export interface MarkdownContributionProvider { + readonly extensionPath: string; + readonly contributions: MarkdownContributions; + readonly onContributionsChanged: vscode.Event; + + dispose(): void; +} + +class VSCodeExtensionMarkdownContributionProvider extends Disposable implements MarkdownContributionProvider { + private _contributions?: MarkdownContributions; + + public constructor( + public readonly extensionPath: string, + ) { + super(); + + vscode.extensions.onDidChange(() => { + const currentContributions = this.getCurrentContributions(); + const existingContributions = this._contributions || MarkdownContributions.Empty; + if (!MarkdownContributions.equal(existingContributions, currentContributions)) { + this._contributions = currentContributions; + this._onContributionsChanged.fire(this); + } + }, undefined, this._disposables); + } + + private readonly _onContributionsChanged = this._register(new vscode.EventEmitter()); + public readonly onContributionsChanged = this._onContributionsChanged.event; + + public get contributions(): MarkdownContributions { + if (!this._contributions) { + this._contributions = this.getCurrentContributions(); + } + return this._contributions; + } + + private getCurrentContributions(): MarkdownContributions { + return vscode.extensions.all + .map(MarkdownContributions.fromExtension) + .reduce(MarkdownContributions.merge, MarkdownContributions.Empty); + } +} + +export function getMarkdownExtensionContributions(context: vscode.ExtensionContext): MarkdownContributionProvider { + return new VSCodeExtensionMarkdownContributionProvider(context.extensionPath); } \ No newline at end of file diff --git a/extensions/markdown-language-features/src/test/engine.ts b/extensions/markdown-language-features/src/test/engine.ts index 1ff4505648..bff44db40e 100644 --- a/extensions/markdown-language-features/src/test/engine.ts +++ b/extensions/markdown-language-features/src/test/engine.ts @@ -5,15 +5,14 @@ import * as vscode from 'vscode'; import { MarkdownEngine } from '../markdownEngine'; -import { MarkdownContributions } from '../markdownExtensions'; +import { MarkdownContributionProvider, MarkdownContributions } from '../markdownExtensions'; import { githubSlugifier } from '../slugify'; +import { Disposable } from '../util/dispose'; -const emptyContributions = new class implements MarkdownContributions { +const emptyContributions = new class extends Disposable implements MarkdownContributionProvider { readonly extensionPath = ''; - readonly previewScripts: vscode.Uri[] = []; - readonly previewStyles: vscode.Uri[] = []; - readonly previewResourceRoots: vscode.Uri[] = []; - readonly markdownItPlugins: Promise<(md: any) => any>[] = []; + readonly contributions = MarkdownContributions.Empty; + readonly onContributionsChanged = this._register(new vscode.EventEmitter()).event; }; export function createNewMarkdownEngine(): MarkdownEngine { diff --git a/extensions/markdown-language-features/src/test/workspaceSymbolProvider.test.ts b/extensions/markdown-language-features/src/test/workspaceSymbolProvider.test.ts index b1f8e531a3..8be325abfd 100644 --- a/extensions/markdown-language-features/src/test/workspaceSymbolProvider.test.ts +++ b/extensions/markdown-language-features/src/test/workspaceSymbolProvider.test.ts @@ -38,7 +38,7 @@ suite('markdown.WorkspaceSymbolProvider', () => { const fileNameCount = 10; const files: vscode.TextDocument[] = []; for (let i = 0; i < fileNameCount; ++i) { - const testFileName = vscode.Uri.parse(`test${i}.md`); + const testFileName = vscode.Uri.file(`test${i}.md`); files.push(new InMemoryDocument(testFileName, `# common\nabc\n## header${i}`)); } diff --git a/extensions/markdown-language-features/src/util/arrays.ts b/extensions/markdown-language-features/src/util/arrays.ts new file mode 100644 index 0000000000..bf5524c901 --- /dev/null +++ b/extensions/markdown-language-features/src/util/arrays.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function equals(one: ReadonlyArray, other: ReadonlyArray, itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean { + if (one.length !== other.length) { + return false; + } + + for (let i = 0, len = one.length; i < len; i++) { + if (!itemEquals(one[i], other[i])) { + return false; + } + } + + return true; +} diff --git a/extensions/markdown-language-features/src/util/dispose.ts b/extensions/markdown-language-features/src/util/dispose.ts index fbce45d605..83ac3bf543 100644 --- a/extensions/markdown-language-features/src/util/dispose.ts +++ b/extensions/markdown-language-features/src/util/dispose.ts @@ -14,3 +14,29 @@ export function disposeAll(disposables: vscode.Disposable[]) { } } +export abstract class Disposable { + private _isDisposed = false; + + protected _disposables: vscode.Disposable[] = []; + + public dispose(): any { + if (this._isDisposed) { + return; + } + this._isDisposed = true; + disposeAll(this._disposables); + } + + protected _register(value: T): T { + if (this._isDisposed) { + value.dispose(); + } else { + this._disposables.push(value); + } + return value; + } + + protected get isDisposed() { + return this._isDisposed; + } +} \ No newline at end of file diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index e63c5f4da9..e85ae950b3 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -29,10 +29,10 @@ resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.2.tgz#5d9ad19e6e6508cdd2f2596df86fd0aade598660" integrity sha1-XZrRnm5lCM3S8llt+G/Qqt5ZhmA= -"@types/node@^8.10.25": - version "8.10.25" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.25.tgz#801fe4e39372cef18f268db880a5fbfcf71adc7e" - integrity sha512-WXvAXaknB0c2cJ7N44e1kUrVu5K90mSfPPaT5XxfuSMxEWva86EYIwxUZM3jNZ2P1CIC9e2z4WJqpAF69PQxeA== +"@types/node@^10.12.21": + version "10.12.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" + integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== abbrev@1: version "1.1.1" @@ -6010,10 +6010,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^2.7.2: - version "2.7.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-2.7.2.tgz#2d615a1ef4aee4f574425cdff7026edf81919836" - integrity sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw== +typescript@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.1.tgz#6de14e1db4b8a006ac535e482c8ba018c55f750b" + integrity sha512-cTmIDFW7O0IHbn1DPYjkiebHxwtCMU+eTy30ZtJNBPF9j2O1ITu5XH2YnBeVRKWHqF+3JQwWJv0Q0aUgX8W7IA== uc.micro@^1.0.1: version "1.0.3" diff --git a/extensions/mssql/.gitignore b/extensions/mssql/.gitignore index 3b8c6a5a24..e58344bcee 100644 --- a/extensions/mssql/.gitignore +++ b/extensions/mssql/.gitignore @@ -1 +1 @@ -sqltoolsservice +sqltoolsservice diff --git a/extensions/mssql/package.json b/extensions/mssql/package.json index 894b284554..e3f2959af9 100644 --- a/extensions/mssql/package.json +++ b/extensions/mssql/package.json @@ -24,6 +24,7 @@ "dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.16", "error-ex": "^1.3.2", "figures": "^2.0.0", + "find-remove": "1.2.1", "opener": "^1.4.3", "request": "^2.88.0", "request-promise": "^4.2.2", diff --git a/extensions/mssql/yarn.lock b/extensions/mssql/yarn.lock index 5bc8b382ec..a431c42788 100644 --- a/extensions/mssql/yarn.lock +++ b/extensions/mssql/yarn.lock @@ -60,6 +60,11 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + base64-js@^1.0.2: version "1.3.0" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" @@ -85,6 +90,14 @@ bluebird@^3.5.0: resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + buffer-alloc-unsafe@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" @@ -153,6 +166,11 @@ commander@~2.8.1: dependencies: graceful-readlink ">= 1.0.0" +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" @@ -375,6 +393,19 @@ file-type@^6.1.0: resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919" integrity sha512-YPcTBDV+2Tm0VqjybVd32MHdlEGAtuxS3VAYsumFokDSMG+ROT5wawGlnHDoz7bfMcMDt9hxuXvXwoKUx2fkOg== +find-remove@1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/find-remove/-/find-remove-1.2.1.tgz#afd93400d23890e018ea197591e9d850d3d049a2" + integrity sha512-zcspBi9mWAyM9YTcVJLkI/x6rbjSDqHijjPa0vTwEmVZnYSmvYMtixDkUnSnuv2xAAkc9fblpkCg91paBIJaLw== + dependencies: + fmerge "1.2.0" + rimraf "2.6.2" + +fmerge@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/fmerge/-/fmerge-1.2.0.tgz#36e99d2ae255e3ee1af666b4df780553671cf692" + integrity sha1-NumdKuJV4+4a9ma033gFU2cc9pI= + forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" @@ -394,6 +425,11 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + get-stream@^2.2.0: version "2.3.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" @@ -414,6 +450,18 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +glob@^7.0.5: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + graceful-fs@^4.1.10: version "4.1.15" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" @@ -467,7 +515,15 @@ ieee754@^1.1.4: resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA== -inherits@~2.0.3: +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= @@ -574,6 +630,13 @@ mime-types@^2.1.12, mime-types@~2.1.19: dependencies: mime-db "~1.38.0" +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -613,7 +676,7 @@ object-assign@^4.0.1: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= -once@^1.4.0: +once@^1.3.0, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= @@ -635,6 +698,11 @@ p-finally@^1.0.0: resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + path-key@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" @@ -758,6 +826,13 @@ request@^2.88.0: tunnel-agent "^0.6.0" uuid "^3.3.2" +rimraf@2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== + dependencies: + glob "^7.0.5" + safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" diff --git a/extensions/package.json b/extensions/package.json index 0f226217b7..d18865ad4d 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.3.1" + "typescript": "3.4.0-rc" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/python/package.json b/extensions/python/package.json index c81942af76..53ca25bd88 100644 --- a/extensions/python/package.json +++ b/extensions/python/package.json @@ -10,7 +10,7 @@ "contributes": { "languages": [{ "id": "python", - "extensions": [ ".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi", ".snakefile", ".smk", ".pyi"], + "extensions": [ ".py", ".rpy", ".pyw", ".cpy", ".gyp", ".gypi", ".snakefile", ".smk", ".pyi", ".ipy"], "aliases": [ "Python", "py" ], "firstLine": "^#!\\s*/.*\\bpython[0-9.-]*\\b", "configuration": "./language-configuration.json" diff --git a/extensions/shared.tsconfig.json b/extensions/shared.tsconfig.json index 1cbda1bf9f..bb42dd479c 100644 --- a/extensions/shared.tsconfig.json +++ b/extensions/shared.tsconfig.json @@ -1,11 +1,7 @@ { "compilerOptions": { - "target": "es2017", + "target": "es2018", "module": "commonjs", - "lib": [ - "es6", - "es2015.promise" - ], "strict": true, "alwaysStrict": true, "noImplicitAny": true, diff --git a/extensions/shared.webpack.config.js b/extensions/shared.webpack.config.js index 135c719fd8..6d69001b2b 100644 --- a/extensions/shared.webpack.config.js +++ b/extensions/shared.webpack.config.js @@ -51,8 +51,6 @@ module.exports = function withDefaults(/**@type WebpackConfig*/extConfig) { }, externals: { 'vscode': 'commonjs vscode', // ignored because it doesn't exist - - // "vscode-extension-telemetry": 'commonjs vscode-extension-telemetry', // commonly used }, output: { // all output goes into `dist`. diff --git a/extensions/sql/cgmanifest.json b/extensions/sql/cgmanifest.json index db3a5db2b0..4cdccf12d9 100644 --- a/extensions/sql/cgmanifest.json +++ b/extensions/sql/cgmanifest.json @@ -6,7 +6,7 @@ "git": { "name": "Microsoft/vscode-mssql", "repositoryUrl": "https://github.com/Microsoft/vscode-mssql", - "commitHash": "3aa44d04b04d219ad5fa8f411ca9dd32294a7a06" + "commitHash": "cd754662e5607c62ecdc51d2a2dc844546a0bbb6" } }, "license": "MIT", diff --git a/extensions/sql/syntaxes/sql.tmLanguage.json b/extensions/sql/syntaxes/sql.tmLanguage.json index 5706724bb1..046bdfa564 100644 --- a/extensions/sql/syntaxes/sql.tmLanguage.json +++ b/extensions/sql/syntaxes/sql.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/vscode-mssql/commit/3aa44d04b04d219ad5fa8f411ca9dd32294a7a06", + "version": "https://github.com/Microsoft/vscode-mssql/commit/cd754662e5607c62ecdc51d2a2dc844546a0bbb6", "name": "SQL", "scopeName": "source.sql", "patterns": [ @@ -516,4 +516,4 @@ ] } } -} \ No newline at end of file +} diff --git a/extensions/vscode-colorize-tests/package.json b/extensions/vscode-colorize-tests/package.json index 9b43cfa2a6..763513a590 100644 --- a/extensions/vscode-colorize-tests/package.json +++ b/extensions/vscode-colorize-tests/package.json @@ -12,7 +12,7 @@ "postinstall": "node ./node_modules/vscode/bin/install" }, "devDependencies": { - "@types/node": "^8.10.25", + "@types/node": "^10.12.21", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", "vscode": "1.1.5" diff --git a/extensions/vscode-colorize-tests/yarn.lock b/extensions/vscode-colorize-tests/yarn.lock index b17b8b1954..46684d06b5 100644 --- a/extensions/vscode-colorize-tests/yarn.lock +++ b/extensions/vscode-colorize-tests/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@types/node@^8.10.25": - version "8.10.25" - resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.25.tgz#801fe4e39372cef18f268db880a5fbfcf71adc7e" - integrity sha512-WXvAXaknB0c2cJ7N44e1kUrVu5K90mSfPPaT5XxfuSMxEWva86EYIwxUZM3jNZ2P1CIC9e2z4WJqpAF69PQxeA== +"@types/node@^10.12.21": + version "10.12.21" + resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.21.tgz#7e8a0c34cf29f4e17a36e9bd0ea72d45ba03908e" + integrity sha512-CBgLNk4o3XMnqMc0rhb6lc77IwShMEglz05deDcn2lQxyXEZivfwgYJu7SMha9V5XcrP6qZuevTHV/QrN2vjKQ== ajv@^5.1.0: version "5.3.0" diff --git a/extensions/yaml/cgmanifest.json b/extensions/yaml/cgmanifest.json index 80ba8ae522..e6c3ca158b 100644 --- a/extensions/yaml/cgmanifest.json +++ b/extensions/yaml/cgmanifest.json @@ -34,4 +34,4 @@ } ], "version": 1 -} +} \ No newline at end of file diff --git a/extensions/yarn.lock b/extensions/yarn.lock index faf73e8790..0c1d37b29a 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,7 +2,7 @@ # yarn lockfile v1 -typescript@3.3.1: - version "3.3.1" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.1.tgz#6de14e1db4b8a006ac535e482c8ba018c55f750b" - integrity sha512-cTmIDFW7O0IHbn1DPYjkiebHxwtCMU+eTy30ZtJNBPF9j2O1ITu5XH2YnBeVRKWHqF+3JQwWJv0Q0aUgX8W7IA== +typescript@3.4.0-rc: + version "3.4.0-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.0-rc.tgz#bc4026c3c517a30fa514b896f2684c6ba92caadb" + integrity sha512-wtm2GwuV0Yy2zvmpFaMDtWHnRbzfERi9FLpL+K+JqGkHoclgEQ6TmD1AMxMIjs80NWRIq0Xp1veQyuZbGsUYsg== diff --git a/extensionsGallery.json b/extensionsGallery.json index c9bb8b76dd..eeb6dd1f92 100644 --- a/extensionsGallery.json +++ b/extensionsGallery.json @@ -1,13 +1,13 @@ -{ -"results": [{ - "extensions": [ - ], - "resultMetadata": [{ - "metadataType": "ResultCount", - "metadataItems": [{ - "name": "TotalCount", - "count": 0 - }] - }] -}] -} +{ +"results": [{ + "extensions": [ + ], + "resultMetadata": [{ + "metadataType": "ResultCount", + "metadataItems": [{ + "name": "TotalCount", + "count": 0 + }] + }] +}] +} diff --git a/gulpfile.js b/gulpfile.js index 658d6017c8..19c7be05c2 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -10,31 +10,27 @@ require('events').EventEmitter.defaultMaxListeners = 100; const gulp = require('gulp'); const util = require('./build/lib/util'); +const task = require('./build/lib/task'); const path = require('path'); const compilation = require('./build/lib/compilation'); +/*const { monacoTypecheckTask , monacoTypecheckWatchTask } = require('./build/gulpfile.editor');*/ +const { compileExtensionsTask, watchExtensionsTask } = require('./build/gulpfile.extensions'); // Fast compile for development time -gulp.task('clean-client', util.rimraf('out')); -gulp.task('compile-client', ['clean-client'], compilation.compileTask('src', 'out', false)); -gulp.task('watch-client', ['clean-client'], compilation.watchTask('out', false)); +const compileClientTask = task.define('compile-client', task.series(util.rimraf('out'), compilation.compileTask('src', 'out', false))); +gulp.task(compileClientTask); -// Full compile, including nls and inline sources in sourcemaps, for build -gulp.task('clean-client-build', util.rimraf('out-build')); -gulp.task('compile-client-build', ['clean-client-build'], compilation.compileTask('src', 'out-build', true)); -gulp.task('watch-client-build', ['clean-client-build'], compilation.watchTask('out-build', true)); - -// Default -gulp.task('default', ['compile']); +const watchClientTask = task.define('watch-client', task.series(util.rimraf('out'), compilation.watchTask('out', false))); +gulp.task(watchClientTask); // All -gulp.task('clean', ['clean-client', 'clean-extensions']); -gulp.task('compile', ['monaco-typecheck', 'compile-client', 'compile-extensions']); -gulp.task('watch', [/* 'monaco-typecheck-watch', */ 'watch-client', 'watch-extensions']); +const compileTask = task.define('compile', task.parallel(/*monacoTypecheckTask, */compileClientTask, compileExtensionsTask)); +gulp.task(compileTask); -// All Build -gulp.task('clean-build', ['clean-client-build', 'clean-extensions-build']); -gulp.task('compile-build', ['compile-client-build', 'compile-extensions-build']); -gulp.task('watch-build', ['watch-client-build', 'watch-extensions-build']); +gulp.task(task.define('watch', task.parallel(/* monacoTypecheckWatchTask, */ watchClientTask, watchExtensionsTask))); + +// Default +gulp.task('default', compileTask); process.on('unhandledRejection', (reason, p) => { console.log('Unhandled Rejection at: Promise', p, 'reason:', reason); diff --git a/package.json b/package.json index 8ce303dcb1..b53295d4fb 100644 --- a/package.json +++ b/package.json @@ -39,17 +39,13 @@ "@angular/platform-browser-dynamic": "~4.1.3", "@angular/router": "~4.1.3", "@angular/upgrade": "~4.1.3", - "@types/chart.js": "^2.7.31", - "@types/htmlparser2": "^3.7.31", "angular2-grid": "2.0.6", "angular2-slickgrid": "github:Microsoft/angular2-slickgrid#1.4.6", "ansi_up": "^3.0.0", "applicationinsights": "1.0.8", "chart.js": "^2.6.0", - "fast-plist": "0.1.2", - "find-remove": "1.2.1", - "fs-extra": "^3.0.1", - "gc-signals": "^0.0.1", + "color-convert": "^0.5.3", + "gc-signals": "^0.0.2", "getmac": "1.4.1", "graceful-fs": "4.1.11", "html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.6", @@ -77,20 +73,21 @@ "svg.js": "^2.2.5", "v8-inspect-profiler": "^0.0.20", "vscode-chokidar": "1.6.5", - "vscode-debugprotocol": "1.33.0", + "vscode-debugprotocol": "1.34.0", "vscode-nsfw": "1.1.1", - "vscode-proxy-agent": "0.3.0", + "vscode-proxy-agent": "0.4.0", "vscode-ripgrep": "^1.2.5", "vscode-sqlite3": "4.0.7", "vscode-textmate": "^4.0.1", - "vscode-xterm": "3.11.0-beta4", - "winreg": "^1.2.4", + "vscode-xterm": "3.13.0-beta1", "yauzl": "^2.9.1", "yazl": "^2.4.3", "zone.js": "^0.8.4" }, "devDependencies": { "7zip": "0.0.6", + "@types/chart.js": "^2.7.31", + "@types/htmlparser2": "^3.7.31", "@types/keytar": "^4.0.1", "@types/minimist": "^1.2.0", "@types/mocha": "2.2.39", @@ -101,6 +98,7 @@ "@types/sinon": "^1.16.36", "@types/webpack": "^4.4.10", "@types/winreg": "^1.2.30", + "ansi-colors": "^3.2.3", "asar": "^0.14.0", "azure-storage": "^0.3.1", "chromium-pickle-js": "^0.2.0", @@ -114,29 +112,27 @@ "eslint": "^3.4.0", "event-stream": "3.3.4", "express": "^4.13.1", + "fancy-log": "^1.3.3", + "fast-plist": "0.1.2", "glob": "^5.0.13", - "gulp": "^3.9.1", - "gulp-atom-electron": "^1.19.2", - "gulp-azure-storage": "^0.8.2", - "gulp-bom": "^1.0.0", + "gulp": "^4.0.0", + "gulp-atom-electron": "^1.20.0", + "gulp-azure-storage": "^0.10.0", "gulp-buffer": "0.0.2", - "gulp-cli": "^2.0.1", - "gulp-concat": "^2.6.0", - "gulp-cssnano": "^2.1.0", - "gulp-eslint": "^3.0.1", - "gulp-filter": "^3.0.0", - "gulp-flatmap": "^1.0.0", - "gulp-json-editor": "^2.2.1", - "gulp-mocha": "^2.1.3", + "gulp-concat": "^2.6.1", + "gulp-cssnano": "^2.1.3", + "gulp-eslint": "^5.0.0", + "gulp-filter": "^5.1.0", + "gulp-flatmap": "^1.0.2", + "gulp-json-editor": "^2.5.0", "gulp-plumber": "^1.2.0", "gulp-remote-src": "^0.4.4", "gulp-rename": "^1.2.0", "gulp-replace": "^0.5.4", - "gulp-shell": "^0.5.2", - "gulp-tsb": "2.0.5", - "gulp-tslint": "^8.1.2", + "gulp-shell": "^0.6.5", + "gulp-tsb": "2.0.7", + "gulp-tslint": "^8.1.3", "gulp-uglify": "^3.0.0", - "gulp-util": "^3.0.6", "gulp-vinyl-zip": "^2.1.2", "husky": "^0.13.1", "innosetup-compiler": "^5.5.60", @@ -155,7 +151,7 @@ "pump": "^1.0.1", "queue": "3.0.6", "rcedit": "^1.1.0", - "remap-istanbul": "^0.6.4", + "remap-istanbul": "^0.13.0", "rimraf": "^2.2.8", "should": "^13.2.3", "sinon": "^1.17.2", @@ -165,15 +161,15 @@ "tslint": "^5.11.0", "tslint-microsoft-contrib": "^6.0.0", "typemoq": "^0.3.2", - "typescript": "3.2.2", + "typescript": "3.3.1", "typescript-formatter": "7.1.0", "typescript-tslint-plugin": "^0.0.7", "uglify-es": "^3.0.18", - "underscore": "^1.8.3", - "vinyl": "^0.4.5", - "vinyl-fs": "^2.4.3", + "underscore": "^1.8.2", + "vinyl": "^2.0.0", + "vinyl-fs": "^3.0.0", "vsce": "1.48.0", - "vscode-nls-dev": "3.2.2", + "vscode-nls-dev": "3.2.5", "webpack": "^4.16.5", "webpack-cli": "^3.1.0", "webpack-stream": "^5.1.1" @@ -186,6 +182,8 @@ "url": "https://github.com/Microsoft/azuredatastudio/issues" }, "optionalDependencies": { + "vscode-windows-registry": "1.0.1", + "win-ca-lib": "https://github.com/chrmarti/win-ca/releases/download/v2.4.1-lib-test/win-ca-lib-2.4.1.tgz", "windows-foreground-love": "0.1.0", "windows-mutex": "0.2.1", "windows-process-tree": "0.2.3" diff --git a/product.json b/product.json index 06e0b6e350..9870802e13 100644 --- a/product.json +++ b/product.json @@ -16,6 +16,7 @@ "win32AppUserModelId": "Microsoft.azuredatastudio", "win32ShellNameShort": "Azure Data Studio", "darwinBundleIdentifier": "com.azuredatastudio.oss", + "linuxIconName": "com.azuredatastudio.oss", "reportIssueUrl": "https://github.com/Microsoft/azuredatastudio/issues/new?labels=customer%20reported%20issue", "requestFeatureUrl": "https://github.com/Microsoft/azuredatastudio/issues/new?labels=feature-request", "privacyStatementUrl": "https://privacy.microsoft.com/en-us/privacystatement", diff --git a/resources/linux/rpm/code.spec.template b/resources/linux/rpm/code.spec.template index 0e1e582b23..c942d65a74 100644 --- a/resources/linux/rpm/code.spec.template +++ b/resources/linux/rpm/code.spec.template @@ -22,7 +22,8 @@ mkdir -p %{buildroot}/usr/share/pixmaps #mkdir -p %{buildroot}/usr/share/zsh/site-functions cp -r usr/share/@@NAME@@/* %{buildroot}/usr/share/@@NAME@@ cp -r usr/share/applications/@@NAME@@.desktop %{buildroot}/usr/share/applications -cp -r usr/share/pixmaps/@@NAME@@.png %{buildroot}/usr/share/pixmaps +cp -r usr/share/applications/@@NAME@@-url-handler.desktop %{buildroot}/usr/share/applications +cp -r usr/share/pixmaps/@@ICON@@.png %{buildroot}/usr/share/pixmaps #cp usr/share/bash-completion/completions/code %{buildroot}/usr/share/bash-completion/completions/code #cp usr/share/zsh/site-functions/_code %{buildroot}/usr/share/zsh/site-functions/_code @@ -55,6 +56,7 @@ fi /usr/share/@@NAME@@/ /usr/share/applications/@@NAME@@.desktop -/usr/share/pixmaps/@@NAME@@.png +/usr/share/applications/@@NAME@@-url-handler.desktop +/usr/share/pixmaps/@@ICON@@.png #/usr/share/bash-completion/completions/code #/usr/share/zsh/site-functions/_code diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 68e37c364c..17d4519d8a 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -11,11 +11,11 @@ set VSCODEUSERDATADIR=%TMP%\vscodeuserfolder-%RANDOM%-%TIME:~6,5% :: if %errorlevel% neq 0 exit /b %errorlevel% :: Tests in the extension host -REM call .\scripts\code.bat %~dp0\..\extensions\vscode-api-tests\testWorkspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests --disableExtensions --user-data-dir=%VSCODEUSERDATADIR% -REM if %errorlevel% neq 0 exit /b %errorlevel% +call .\scripts\code.bat %~dp0\..\extensions\vscode-api-tests\testWorkspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests --disableExtensions --user-data-dir=%VSCODEUSERDATADIR% +if %errorlevel% neq 0 exit /b %errorlevel% -REM call .\scripts\code.bat %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests --disableExtensions --user-data-dir=%VSCODEUSERDATADIR% -REM if %errorlevel% neq 0 exit /b %errorlevel% +call .\scripts\code.bat %~dp0\..\extensions\vscode-api-tests\testworkspace.code-workspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\workspace-tests --disableExtensions --user-data-dir=%VSCODEUSERDATADIR% +if %errorlevel% neq 0 exit /b %errorlevel% call .\scripts\code.bat %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disableExtensions --user-data-dir=%VSCODEUSERDATADIR% call .\scripts\code.bat %~dp0\..\extensions\markdown-language-features\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\markdown-language-features --extensionTestsPath=%~dp0\..\extensions\markdown-language-features\out\test --disableExtensions --user-data-dir=%VSCODEUSERDATADIR% diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index 4bf48cb0c4..61cd8f69e5 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -32,6 +32,12 @@ exports.load = function (modulePaths, resultCallback, options) { const args = parseURLQueryArgs(); const configuration = JSON.parse(args['config'] || '{}') || {}; + // Apply zoom level early to avoid glitches + const zoomLevel = configuration.zoomLevel; + if (typeof zoomLevel === 'number' && zoomLevel !== 0) { + webFrame.setZoomLevel(zoomLevel); + } + // Error handler // @ts-ignore process.on('uncaughtException', function (error) { @@ -51,37 +57,6 @@ exports.load = function (modulePaths, resultCallback, options) { // Enable ASAR support bootstrap.enableASARSupport(path.join(configuration.appRoot, 'node_modules')); - // Apply zoom level early to avoid glitches - const zoomLevel = configuration.zoomLevel; - if (typeof zoomLevel === 'number' && zoomLevel !== 0) { - webFrame.setZoomLevel(zoomLevel); - } - - // {{SQL CARBON EDIT}} - // Load the loader and start loading the workbench - function createScript(src, onload) { - const script = document.createElement('script'); - script.src = src; - script.addEventListener('load', onload); - - const head = document.getElementsByTagName('head')[0]; - head.insertBefore(script, head.lastChild); - } - - function uriFromPath(_path) { - var pathName = path.resolve(_path).replace(/\\/g, '/'); - if (pathName.length > 0 && pathName.charAt(0) !== '/') { - pathName = '/' + pathName; - } - - return encodeURI('file://' + pathName); - } - - const appRoot = uriFromPath(configuration.appRoot); - - createScript(appRoot + '/node_modules/chart.js/dist/Chart.js', undefined); - // {{SQL CARBON EDIT}} - End - if (options && typeof options.canModifyDOM === 'function') { options.canModifyDOM(configuration); } @@ -120,6 +95,7 @@ exports.load = function (modulePaths, resultCallback, options) { // {{SQL CARBON EDIT}} require('reflect-metadata'); + require('chart.js'); loaderConfig.nodeModules = loaderConfig.nodeModules.concat([ '@angular/common', '@angular/core', diff --git a/src/bootstrap.js b/src/bootstrap.js index ff6752de50..844960605c 100644 --- a/src/bootstrap.js +++ b/src/bootstrap.js @@ -118,6 +118,36 @@ exports.writeFile = function (file, content) { }); }); }; + +/** + * @param {string} dir + * @returns {Promise} + */ +function mkdir(dir) { + const fs = require('fs'); + + return new Promise((c, e) => fs.mkdir(dir, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir))); +} + +/** + * @param {string} dir + * @returns {Promise} + */ +exports.mkdirp = function mkdirp(dir) { + const path = require('path'); + + return mkdir(dir).then(null, err => { + if (err && err.code === 'ENOENT') { + const parent = path.dirname(dir); + + if (parent !== dir) { // if not arrived at root + return mkdirp(parent).then(() => mkdir(dir)); + } + } + + throw err; + }); +}; //#endregion //#region NLS helpers diff --git a/src/buildfile.js b/src/buildfile.js index 2410d797b1..507ef3f818 100644 --- a/src/buildfile.js +++ b/src/buildfile.js @@ -5,13 +5,15 @@ exports.base = [{ name: 'vs/base/common/worker/simpleWorker', - include: [ 'vs/editor/common/services/editorSimpleWorker' ], - prepend: [ 'vs/loader.js' ], - append: [ 'vs/base/worker/workerMain' ], + include: ['vs/editor/common/services/editorSimpleWorker'], + prepend: ['vs/loader.js'], + append: ['vs/base/worker/workerMain'], dest: 'vs/base/worker/workerMain.js' }]; -//@ts-ignore review + exports.workbench = require('./vs/workbench/buildfile').collectModules(['vs/workbench/workbench.main']); +exports.workbenchNodeless = require('./vs/workbench/buildfile').collectModules(['vs/workbench/workbench.nodeless.main']); + exports.code = require('./vs/code/buildfile').collectModules(); exports.entrypoint = function (name) { diff --git a/src/main.js b/src/main.js index e73736670b..0bad7713e4 100644 --- a/src/main.js +++ b/src/main.js @@ -7,6 +7,8 @@ 'use strict'; const perf = require('./vs/base/common/performance'); +const lp = require('./vs/base/node/languagePacks'); + perf.mark('main:started'); const fs = require('fs'); @@ -57,9 +59,11 @@ registerListeners(); */ let nlsConfiguration = undefined; const userDefinedLocale = getUserDefinedLocale(); -userDefinedLocale.then((locale) => { +const metaDataFile = path.join(__dirname, 'nls.metadata.json'); + +userDefinedLocale.then(locale => { if (locale && !nlsConfiguration) { - nlsConfiguration = getNLSConfiguration(locale); + nlsConfiguration = lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale); } }); @@ -89,7 +93,7 @@ function onReady() { Promise.all([nodeCachedDataDir.ensureExists(), userDefinedLocale]).then(([cachedDataDir, locale]) => { if (locale && !nlsConfiguration) { - nlsConfiguration = getNLSConfiguration(locale); + nlsConfiguration = lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, locale); } if (!nlsConfiguration) { @@ -98,7 +102,7 @@ function onReady() { // First, we need to test a user defined locale. If it fails we try the app locale. // If that fails we fall back to English. - nlsConfiguration.then((nlsConfig) => { + nlsConfiguration.then(nlsConfig => { const startup = nlsConfig => { nlsConfig._languagePackSupport = true; @@ -129,7 +133,7 @@ function onReady() { // See above the comment about the loader and case sensitiviness appLocale = appLocale.toLowerCase(); - getNLSConfiguration(appLocale).then((nlsConfig) => { + lp.getNLSConfiguration(product.commit, userDataPath, metaDataFile, appLocale).then(nlsConfig => { if (!nlsConfig) { nlsConfig = { locale: appLocale, availableLanguages: {} }; } @@ -151,7 +155,7 @@ function onReady() { function configureCommandlineSwitches(cliArgs, nodeCachedDataDir) { // Force pre-Chrome-60 color profile handling (for https://github.com/Microsoft/vscode/issues/51791) - app.commandLine.appendSwitch('disable-features', 'ColorCorrectRendering'); + app.commandLine.appendSwitch('disable-color-correct-rendering'); // Support JS Flags const jsFlags = resolveJSFlags(cliArgs, nodeCachedDataDir.jsFlags()); @@ -275,7 +279,7 @@ function getNodeCachedDir() { } ensureExists() { - return mkdirp(this.value).then(() => this.value, () => { /*ignore*/ }); + return bootstrap.mkdirp(this.value).then(() => this.value, () => { /*ignore*/ }); } _compute() { @@ -305,7 +309,7 @@ function getNodeCachedDir() { * @returns {string} */ function stripComments(content) { - const regexp = /("(?:[^\\\"]*(?:\\.)?)*")|('(?:[^\\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g; + const regexp = /("(?:[^\\"]*(?:\\.)?)*")|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g; return content.replace(regexp, function (match, m1, m2, m3, m4) { // Only one of m1, m2, m3, m4 matches @@ -328,101 +332,6 @@ function stripComments(content) { }); } -/** - * @param {string} dir - * @returns {Promise} - */ -function mkdir(dir) { - return new Promise((c, e) => fs.mkdir(dir, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir))); -} - -/** - * @param {string} file - * @returns {Promise} - */ -function exists(file) { - return new Promise(c => fs.exists(file, c)); -} - -/** - * @param {string} file - * @returns {Promise} - */ -function touch(file) { - return new Promise((c, e) => { const d = new Date(); fs.utimes(file, d, d, err => err ? e(err) : c()); }); -} - -/** - * @param {string} file - * @returns {Promise} - */ -function lstat(file) { - return new Promise((c, e) => fs.lstat(file, (err, stats) => err ? e(err) : c(stats))); -} - -/** - * @param {string} dir - * @returns {Promise} - */ -function readdir(dir) { - return new Promise((c, e) => fs.readdir(dir, (err, files) => err ? e(err) : c(files))); -} - -/** - * @param {string} dir - * @returns {Promise} - */ -function rmdir(dir) { - return new Promise((c, e) => fs.rmdir(dir, err => err ? e(err) : c(undefined))); -} - -/** - * @param {string} file - * @returns {Promise} - */ -function unlink(file) { - return new Promise((c, e) => fs.unlink(file, err => err ? e(err) : c(undefined))); -} - -/** - * @param {string} dir - * @returns {Promise} - */ -function mkdirp(dir) { - return mkdir(dir).then(null, err => { - if (err && err.code === 'ENOENT') { - const parent = path.dirname(dir); - - if (parent !== dir) { // if not arrived at root - return mkdirp(parent).then(() => mkdir(dir)); - } - } - - throw err; - }); -} - -/** - * @param {string} location - * @returns {Promise} - */ -function rimraf(location) { - return lstat(location).then(stat => { - if (stat.isDirectory() && !stat.isSymbolicLink()) { - return readdir(location) - .then(children => Promise.all(children.map(child => rimraf(path.join(location, child))))) - .then(() => rmdir(location)); - } else { - return unlink(location); - } - }, err => { - if (err.code === 'ENOENT') { - return undefined; - } - throw err; - }); -} - // Language tags are case insensitive however an amd loader is case sensitive // To make this work on case preserving & insensitive FS we do the following: // the language bundles have lower case language tags and we always lower case @@ -437,7 +346,7 @@ function getUserDefinedLocale() { } const localeConfig = path.join(userDataPath, 'User', 'locale.json'); - return bootstrap.readFile(localeConfig).then((content) => { + return bootstrap.readFile(localeConfig).then(content => { content = stripComments(content); try { const value = JSON.parse(content).locale; @@ -449,175 +358,4 @@ function getUserDefinedLocale() { return undefined; }); } - -/** - * @returns {object} - */ -function getLanguagePackConfigurations() { - const configFile = path.join(userDataPath, 'languagepacks.json'); - try { - return require(configFile); - } catch (err) { - // Do nothing. If we can't read the file we have no - // language pack config. - } - return undefined; -} - -/** - * @param {object} config - * @param {string} locale - */ -function resolveLanguagePackLocale(config, locale) { - try { - while (locale) { - if (config[locale]) { - return locale; - } else { - const index = locale.lastIndexOf('-'); - if (index > 0) { - locale = locale.substring(0, index); - } else { - return undefined; - } - } - } - } catch (err) { - console.error('Resolving language pack configuration failed.', err); - } - return undefined; -} - -/** - * @param {string} locale - */ -function getNLSConfiguration(locale) { - if (locale === 'pseudo') { - return Promise.resolve({ locale: locale, availableLanguages: {}, pseudo: true }); - } - - if (process.env['VSCODE_DEV']) { - return Promise.resolve({ locale: locale, availableLanguages: {} }); - } - - // We have a built version so we have extracted nls file. Try to find - // the right file to use. - - // Check if we have an English or English US locale. If so fall to default since that is our - // English translation (we don't ship *.nls.en.json files) - if (locale && (locale === 'en' || locale === 'en-us')) { - return Promise.resolve({ locale: locale, availableLanguages: {} }); - } - - const initialLocale = locale; - - perf.mark('nlsGeneration:start'); - - const defaultResult = function (locale) { - perf.mark('nlsGeneration:end'); - return Promise.resolve({ locale: locale, availableLanguages: {} }); - }; - try { - const commit = product.commit; - if (!commit) { - return defaultResult(initialLocale); - } - const configs = getLanguagePackConfigurations(); - if (!configs) { - return defaultResult(initialLocale); - } - locale = resolveLanguagePackLocale(configs, locale); - if (!locale) { - return defaultResult(initialLocale); - } - const packConfig = configs[locale]; - let mainPack; - if (!packConfig || typeof packConfig.hash !== 'string' || !packConfig.translations || typeof (mainPack = packConfig.translations['vscode']) !== 'string') { - return defaultResult(initialLocale); - } - return exists(mainPack).then((fileExists) => { - if (!fileExists) { - return defaultResult(initialLocale); - } - const packId = packConfig.hash + '.' + locale; - const cacheRoot = path.join(userDataPath, 'clp', packId); - const coreLocation = path.join(cacheRoot, commit); - const translationsConfigFile = path.join(cacheRoot, 'tcf.json'); - const corruptedFile = path.join(cacheRoot, 'corrupted.info'); - const result = { - locale: initialLocale, - availableLanguages: { '*': locale }, - _languagePackId: packId, - _translationsConfigFile: translationsConfigFile, - _cacheRoot: cacheRoot, - _resolvedLanguagePackCoreLocation: coreLocation, - _corruptedFile: corruptedFile - }; - return exists(corruptedFile).then((corrupted) => { - // The nls cache directory is corrupted. - let toDelete; - if (corrupted) { - toDelete = rimraf(cacheRoot); - } else { - toDelete = Promise.resolve(undefined); - } - return toDelete.then(() => { - return exists(coreLocation).then((fileExists) => { - if (fileExists) { - // We don't wait for this. No big harm if we can't touch - touch(coreLocation).catch(() => { }); - perf.mark('nlsGeneration:end'); - return result; - } - return mkdirp(coreLocation).then(() => { - return Promise.all([bootstrap.readFile(path.join(__dirname, 'nls.metadata.json')), bootstrap.readFile(mainPack)]); - }).then((values) => { - const metadata = JSON.parse(values[0]); - const packData = JSON.parse(values[1]).contents; - const bundles = Object.keys(metadata.bundles); - const writes = []; - for (let bundle of bundles) { - const modules = metadata.bundles[bundle]; - const target = Object.create(null); - for (let module of modules) { - const keys = metadata.keys[module]; - const defaultMessages = metadata.messages[module]; - const translations = packData[module]; - let targetStrings; - if (translations) { - targetStrings = []; - for (let i = 0; i < keys.length; i++) { - const elem = keys[i]; - const key = typeof elem === 'string' ? elem : elem.key; - let translatedMessage = translations[key]; - if (translatedMessage === undefined) { - translatedMessage = defaultMessages[i]; - } - targetStrings.push(translatedMessage); - } - } else { - targetStrings = defaultMessages; - } - target[module] = targetStrings; - } - writes.push(bootstrap.writeFile(path.join(coreLocation, bundle.replace(/\//g, '!') + '.nls.json'), JSON.stringify(target))); - } - writes.push(bootstrap.writeFile(translationsConfigFile, JSON.stringify(packConfig.translations))); - return Promise.all(writes); - }).then(() => { - perf.mark('nlsGeneration:end'); - return result; - }).catch((err) => { - console.error('Generating translation files failed.', err); - return defaultResult(locale); - }); - }); - }); - }); - }); - } catch (err) { - console.error('Generating translation files failed.', err); - return defaultResult(locale); - } -} -//#endregion +//#endregion \ No newline at end of file diff --git a/src/sql/azdata.proposed.d.ts b/src/sql/azdata.proposed.d.ts index d6830c6de4..5de4c3c172 100644 --- a/src/sql/azdata.proposed.d.ts +++ b/src/sql/azdata.proposed.d.ts @@ -5,7 +5,6 @@ // This is the place for API experiments and proposal. -import * as core from 'azdata'; import * as vscode from 'vscode'; declare module 'azdata' { @@ -216,6 +215,44 @@ declare module 'azdata' { options: { [name: string]: any }; } + // Object Explorer interfaces ----------------------------------------------------------------------- + export interface ObjectExplorerSession { + success: boolean; + sessionId: string; + rootNode: NodeInfo; + errorMessage: string; + } + + /** + * A NodeInfo object represents an element in the Object Explorer tree under + * a connection. + */ + export interface NodeInfo { + nodePath: string; + nodeType: string; + nodeSubType: string; + nodeStatus: string; + label: string; + isLeaf: boolean; + metadata: ObjectMetadata; + errorMessage: string; + /** + * Optional iconType for the object in the tree. Currently this only supports + * an icon name or SqlThemeIcon name, rather than a path to an icon. + * If not defined, the nodeType + nodeStatus / nodeSubType values + * will be used instead. + */ + iconType?: string | SqlThemeIcon; + /** + * Informs who provides the children to a node, used by data explorer tree view api + */ + childProvider?: string; + /** + * Holds the connection profile for nodes, used by data explorer tree view api + */ + payload?: any; + } + export interface IConnectionProfile extends ConnectionInfo { connectionName: string; serverName: string; @@ -1011,36 +1048,6 @@ declare module 'azdata' { subset: EditRow[]; } - /** - * A NodeInfo object represents an element in the Object Explorer tree under - * a connection. - */ - export interface NodeInfo { - nodePath: string; - nodeType: string; - nodeSubType: string; - nodeStatus: string; - label: string; - isLeaf: boolean; - metadata: ObjectMetadata; - errorMessage: string; - /** - * Optional iconType for the object in the tree. Currently this only supports - * an icon name or SqlThemeIcon name, rather than a path to an icon. - * If not defined, the nodeType + nodeStatus / nodeSubType values - * will be used instead. - */ - iconType?: string | SqlThemeIcon; - /** - * Informs who provides the children to a node, used by data explorer tree view api - */ - childProvider?: string; - /** - * Holds the connection profile for nodes, used by data explorer tree view api - */ - payload?: any; - } - /** * A reference to a named icon. Currently only a subset of the SQL icons are available. * Using a theme icon is preferred over a custom icon as it gives theme authors the possibility to change the icons. @@ -1144,14 +1151,6 @@ declare module 'azdata' { public readonly id: string; } - // Object Explorer interfaces ----------------------------------------------------------------------- - export interface ObjectExplorerSession { - success: boolean; - sessionId: string; - rootNode: NodeInfo; - errorMessage: string; - } - export interface ObjectExplorerSessionResponse { sessionId: string; } diff --git a/src/sql/azdata.test.d.ts b/src/sql/azdata.test.d.ts index f34748b9b1..ad4f048eed 100644 --- a/src/sql/azdata.test.d.ts +++ b/src/sql/azdata.test.d.ts @@ -5,7 +5,6 @@ // This is the place for APIs used for testing -import * as core from 'azdata'; import * as vscode from 'vscode'; declare module 'azdata' { diff --git a/src/sql/base/browser/builder.ts b/src/sql/base/browser/builder.ts index 6a7fd3357d..9aa571d2c2 100644 --- a/src/sql/base/browser/builder.ts +++ b/src/sql/base/browser/builder.ts @@ -1377,7 +1377,7 @@ export const $: QuickBuilder = function (arg?: any): Builder { if (arg[0] === '<') { let element: Node; let container = document.createElement('div'); - container.innerHTML = strings.format.apply(strings, arguments); + container.innerHTML = strings.format.apply(strings, <[string, ...any[]]>(arguments)); if (container.children.length === 0) { throw new Error('Bad use of $'); @@ -1426,10 +1426,10 @@ export const $: QuickBuilder = function (arg?: any): Builder { // Use the arguments as the arguments to Builder#element(...) else { let result = offDOM(); - result.element.apply(result, arguments); + result.element.apply(result, <[string, any?, ((builder: Builder) => void)?]>(arguments)); return result; } } else { throw new Error('Bad use of $'); } -}; \ No newline at end of file +}; diff --git a/src/sql/base/browser/ui/panel/panel.ts b/src/sql/base/browser/ui/panel/panel.ts index 95f23b4c09..bf3f07e79a 100644 --- a/src/sql/base/browser/ui/panel/panel.ts +++ b/src/sql/base/browser/ui/panel/panel.ts @@ -199,11 +199,6 @@ export class TabbedPanel extends Disposable implements IThemable { this.showTab(lastTab); } } - - // this shouldn't happen but just in case - if (this._shownTab === undefined && this._tabMap.size > 0) { - this.showTab(this._tabMap.keys().next().value); - } } if (!this.options.showHeaderWhenSingleView && this._tabMap.size === 1 && this._headerVisible) { diff --git a/src/sql/base/common/objects.ts b/src/sql/base/common/objects.ts index f0c1cb5593..8f06d6815a 100644 --- a/src/sql/base/common/objects.ts +++ b/src/sql/base/common/objects.ts @@ -53,9 +53,9 @@ export function mixin(destination: any, source: any, overwrite: boolean = true, } export function entries(o: { [key: string]: T }): [string, T][] { - return Object.entries(o); + return Object.keys(o).map(k => [k, o[k]] as [string, T]); } export function values(o: { [key: string]: T }): T[] { - return Object.values(o); + return Object.keys(o).map(k => o[k]); } diff --git a/src/sql/parts/accountManagement/accountDialog/accountDialog.ts b/src/sql/parts/accountManagement/accountDialog/accountDialog.ts index c5c8952e5a..c3801a4c2d 100644 --- a/src/sql/parts/accountManagement/accountDialog/accountDialog.ts +++ b/src/sql/parts/accountManagement/accountDialog/accountDialog.ts @@ -9,7 +9,6 @@ import 'vs/css!./media/accountDialog'; import 'vs/css!sql/parts/accountManagement/common/media/accountActions'; import * as DOM from 'vs/base/browser/dom'; import { List } from 'vs/base/browser/ui/list/listWidget'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { Event, Emitter } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -36,6 +35,7 @@ import { AccountListRenderer, AccountListDelegate } from 'sql/parts/accountManag import { AccountProviderAddedEventParams, UpdateAccountListEventParams } from 'sql/platform/accountManagement/common/eventTypes'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; import * as TelemetryKeys from 'sql/common/telemetryKeys'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; class AccountPanel extends ViewletPanel { public index: number; @@ -115,7 +115,7 @@ export class AccountDialog extends Modal { public get onCloseEvent(): Event { return this._onCloseEmitter.event; } constructor( - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IThemeService themeService: IThemeService, @IInstantiationService private _instantiationService: IInstantiationService, @IContextMenuService private _contextMenuService: IContextMenuService, @@ -128,8 +128,8 @@ export class AccountDialog extends Modal { super( localize('linkedAccounts', 'Linked accounts'), TelemetryKeys.Accounts, - partService, telemetryService, + layoutService, clipboardService, themeService, contextKeyService, diff --git a/src/sql/parts/accountManagement/autoOAuthDialog/autoOAuthDialog.ts b/src/sql/parts/accountManagement/autoOAuthDialog/autoOAuthDialog.ts index 4d8434946f..5889ab59b9 100644 --- a/src/sql/parts/accountManagement/autoOAuthDialog/autoOAuthDialog.ts +++ b/src/sql/parts/accountManagement/autoOAuthDialog/autoOAuthDialog.ts @@ -8,7 +8,6 @@ import 'vs/css!./media/autoOAuthDialog'; import { Builder, $ } from 'sql/base/browser/builder'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { Event, Emitter } from 'vs/base/common/event'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { localize } from 'vs/nls'; @@ -22,6 +21,7 @@ import { attachModalDialogStyler, attachButtonStyler } from 'sql/platform/theme/ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import * as TelemetryKeys from 'sql/common/telemetryKeys'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export class AutoOAuthDialog extends Modal { private _copyAndOpenButton: Button; @@ -42,7 +42,7 @@ export class AutoOAuthDialog extends Modal { public get onCloseEvent(): Event { return this._onCloseEvent.event; } constructor( - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IThemeService themeService: IThemeService, @IContextViewService private _contextViewService: IContextViewService, @ITelemetryService telemetryService: ITelemetryService, @@ -52,8 +52,8 @@ export class AutoOAuthDialog extends Modal { super( '', TelemetryKeys.AutoOAuth, - partService, telemetryService, + layoutService, clipboardService, themeService, contextKeyService, diff --git a/src/sql/parts/accountManagement/firewallRuleDialog/firewallRuleDialog.ts b/src/sql/parts/accountManagement/firewallRuleDialog/firewallRuleDialog.ts index 6cc1c45bad..806adeb43a 100644 --- a/src/sql/parts/accountManagement/firewallRuleDialog/firewallRuleDialog.ts +++ b/src/sql/parts/accountManagement/firewallRuleDialog/firewallRuleDialog.ts @@ -8,7 +8,6 @@ import 'vs/css!./media/firewallRuleDialog'; import { Builder, $ } from 'sql/base/browser/builder'; import * as DOM from 'vs/base/browser/dom'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { Event, Emitter } from 'vs/base/common/event'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { localize } from 'vs/nls'; @@ -30,6 +29,7 @@ import { attachModalDialogStyler, attachButtonStyler } from 'sql/platform/theme/ import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox'; import { IAccountPickerService } from 'sql/platform/accountManagement/common/accountPicker'; import * as TelemetryKeys from 'sql/common/telemetryKeys'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; // TODO: Make the help link 1) extensible (01/08/2018, https://github.com/Microsoft/azuredatastudio/issues/450) // in case that other non-Azure sign in is to be used @@ -64,7 +64,7 @@ export class FirewallRuleDialog extends Modal { constructor( @IAccountPickerService private _accountPickerService: IAccountPickerService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IThemeService themeService: IThemeService, @IInstantiationService private _instantiationService: IInstantiationService, @IContextViewService private _contextViewService: IContextViewService, @@ -76,8 +76,8 @@ export class FirewallRuleDialog extends Modal { super( localize('createNewFirewallRule', 'Create new firewall rule'), TelemetryKeys.FireWallRule, - partService, telemetryService, + layoutService, clipboardService, themeService, contextKeyService, diff --git a/src/sql/parts/common/customInputConverter.ts b/src/sql/parts/common/customInputConverter.ts index 5a4ef297c7..cf130c5df8 100644 --- a/src/sql/parts/common/customInputConverter.ts +++ b/src/sql/parts/common/customInputConverter.ts @@ -6,7 +6,6 @@ import { EditorInput, IEditorInput } from 'vs/workbench/common/editor'; import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; -import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; import { URI } from 'vs/base/common/uri'; import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput'; @@ -17,6 +16,7 @@ import { NotebookInput } from 'sql/parts/notebook/notebookInput'; import { INotebookService } from 'sql/workbench/services/notebook/common/notebookService'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { notebookModeId } from 'sql/common/constants'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; const fs = require('fs'); diff --git a/src/sql/parts/connection/common/connection.contribution.ts b/src/sql/parts/connection/common/connection.contribution.ts index f82196cbbc..fac71e16b6 100644 --- a/src/sql/parts/connection/common/connection.contribution.ts +++ b/src/sql/parts/connection/common/connection.contribution.ts @@ -5,7 +5,6 @@ import { IExtensionGalleryService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; -import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -16,11 +15,12 @@ import { AddServerGroupAction, AddServerAction } from 'sql/parts/objectExplorer/ import { ClearRecentConnectionsAction, GetCurrentConnectionStringAction } from 'sql/parts/connection/common/connectionActions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; -import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService'; -import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { localize } from 'vs/nls'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; +import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/node/extensionsWorkbenchService'; // Singletons registerSingleton(IExtensionGalleryService, ExtensionGalleryService); diff --git a/src/sql/parts/connection/common/connectionStatus.ts b/src/sql/parts/connection/common/connectionStatus.ts index 0991847c19..1f28ae6540 100644 --- a/src/sql/parts/connection/common/connectionStatus.ts +++ b/src/sql/parts/connection/common/connectionStatus.ts @@ -6,7 +6,6 @@ import { $, append, show, hide } from 'vs/base/browser/dom'; import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; diff --git a/src/sql/parts/dashboard/contents/webviewContent.component.ts b/src/sql/parts/dashboard/contents/webviewContent.component.ts index aa44fca2a0..1fc4fa47d4 100644 --- a/src/sql/parts/dashboard/contents/webviewContent.component.ts +++ b/src/sql/parts/dashboard/contents/webviewContent.component.ts @@ -7,14 +7,12 @@ import 'vs/css!./webviewContent'; import { Component, forwardRef, Input, OnInit, Inject, ElementRef } from '@angular/core'; import { Event, Emitter } from 'vs/base/common/event'; -import { Parts, IPartService } from 'vs/workbench/services/part/common/partService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { addDisposableListener, EventType } from 'vs/base/browser/dom'; import { memoize } from 'vs/base/common/decorators'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { WebviewElement } from 'vs/workbench/parts/webview/electron-browser/webviewElement'; import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service'; import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; import { IDashboardWebview, IDashboardViewService } from 'sql/platform/dashboard/common/dashboardViewService'; @@ -23,6 +21,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import * as azdata from 'azdata'; import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; @Component({ template: '', @@ -45,7 +45,7 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo @Inject(forwardRef(() => ElementRef)) private _el: ElementRef, @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService, @Inject(IDashboardViewService) private dashboardViewService: IDashboardViewService, - @Inject(IPartService) private partService: IPartService, + @Inject(IWorkbenchLayoutService) private layoutService: IWorkbenchLayoutService, @Inject(IInstantiationService) private instantiationService: IInstantiationService ) { super(); @@ -107,9 +107,9 @@ export class WebviewContent extends AngularDisposable implements OnInit, IDashbo } this._webview = this.instantiationService.createInstance(WebviewElement, - this.partService.getContainer(Parts.EDITOR_PART), + this.layoutService.getContainer(Parts.EDITOR_PART), + {}, { - enableWrappedPostMessage: true, allowScripts: true }); diff --git a/src/sql/parts/dashboard/dashboard.module.ts b/src/sql/parts/dashboard/dashboard.module.ts index 9faa746a54..82603a8e3e 100644 --- a/src/sql/parts/dashboard/dashboard.module.ts +++ b/src/sql/parts/dashboard/dashboard.module.ts @@ -9,7 +9,7 @@ import { BrowserModule } from '@angular/platform-browser'; import { RouterModule, Routes, UrlSerializer, Router, NavigationEnd } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { NgGridModule } from 'angular2-grid'; -import { ChartsModule } from 'ng2-charts/ng2-charts'; +import { ChartsModule } from 'ng2-charts'; import CustomUrlSerializer from 'sql/base/node/urlSerializer'; import { Extensions, IInsightRegistry } from 'sql/platform/dashboard/common/insightRegistry'; diff --git a/src/sql/parts/dashboard/pages/serverDashboardPage.component.ts b/src/sql/parts/dashboard/pages/serverDashboardPage.component.ts index da0e911a0f..5ff606a26c 100644 --- a/src/sql/parts/dashboard/pages/serverDashboardPage.component.ts +++ b/src/sql/parts/dashboard/pages/serverDashboardPage.component.ts @@ -35,7 +35,7 @@ export class ServerDashboardPage extends DashboardPage implements OnInit { }; protected readonly context = 'server'; - private _letDashboardPromise: Thenable; + private _letDashboardPromise: Promise; constructor( @Inject(forwardRef(() => IBreadcrumbService)) private breadcrumbService: IBreadcrumbService, @@ -53,7 +53,7 @@ export class ServerDashboardPage extends DashboardPage implements OnInit { let connInfo = this.dashboardService.connectionManagementService.connectionInfo; if (connInfo && connInfo.providerId === 'MSSQL') { // revert back to default database - this._letDashboardPromise = this.dashboardService.connectionManagementService.changeDatabase('master'); + this._letDashboardPromise = this.dashboardService.connectionManagementService.changeDatabase('master').then(); } else { this._letDashboardPromise = Promise.resolve(); } diff --git a/src/sql/parts/dashboard/widgets/insights/insightsWidget.component.ts b/src/sql/parts/dashboard/widgets/insights/insightsWidget.component.ts index f0f0b99749..ff023b46b2 100644 --- a/src/sql/parts/dashboard/widgets/insights/insightsWidget.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/insightsWidget.component.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Component, Inject, ViewContainerRef, forwardRef, AfterContentInit, - ComponentFactoryResolver, ViewChild, ChangeDetectorRef + ComponentFactoryResolver, ViewChild, ChangeDetectorRef, Injector } from '@angular/core'; import { Observable } from 'rxjs/Observable'; @@ -68,8 +68,8 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget, @Inject(forwardRef(() => ComponentFactoryResolver)) private _componentFactoryResolver: ComponentFactoryResolver, @Inject(forwardRef(() => CommonServiceInterface)) private dashboardService: CommonServiceInterface, @Inject(WIDGET_CONFIG) protected _config: WidgetConfig, - @Inject(forwardRef(() => ViewContainerRef)) private viewContainerRef: ViewContainerRef, @Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef, + @Inject(forwardRef(() => Injector)) private _injector: Injector, @Inject(IInstantiationService) private instantiationService: IInstantiationService, @Inject(IStorageService) private storageService: IStorageService, @Inject(IWorkspaceContextService) private workspaceContextService: IWorkspaceContextService, @@ -233,8 +233,9 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget, let componentFactory = this._componentFactoryResolver.resolveComponentFactory(insightRegistry.getCtorFromId(this._typeKey)); - let componentRef = this.componentHost.viewContainerRef.createComponent(componentFactory); + let componentRef = this.componentHost.viewContainerRef.createComponent(componentFactory, 0, this._injector); let componentInstance = componentRef.instance; + // check if the setter is defined if (componentInstance.setConfig) { componentInstance.setConfig(this.insightConfig.type[this._typeKey]); diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component.ts index 3bcea19ef2..e6bc75638f 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Component, Input, Inject, ChangeDetectorRef, forwardRef, ElementRef, ViewChild } from '@angular/core'; -import { BaseChartDirective } from 'ng2-charts/ng2-charts'; +import { BaseChartDirective } from 'ng2-charts'; import * as TelemetryKeys from 'sql/common/telemetryKeys'; import * as TelemetryUtils from 'sql/common/telemetryUtilities'; @@ -297,4 +297,4 @@ Chart.pluginService.register({ ctx.fillRect(0, 0, chart.chart.width, chart.chart.height); } } -}); \ No newline at end of file +}); diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/types/barChart.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/types/barChart.component.ts index 7216581573..dd93385d0a 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/types/barChart.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/types/barChart.component.ts @@ -7,9 +7,11 @@ import { ChartInsight } from 'sql/parts/dashboard/widgets/insights/views/charts/ import { mixin } from 'sql/base/common/objects'; import { ChartType, IChartConfig, customMixin } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; -import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as colors from 'vs/platform/theme/common/colorRegistry'; import { editorLineNumbers } from 'vs/editor/common/view/editorColorRegistry'; +import { ChangeDetectorRef, Inject, ElementRef, forwardRef } from '@angular/core'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export interface IBarChartConfig extends IChartConfig { yAxisMin: number; @@ -23,6 +25,15 @@ export interface IBarChartConfig extends IChartConfig { export default class BarChart extends ChartInsight { protected readonly chartType: ChartType = ChartType.Bar; + constructor( + @Inject(forwardRef(() => ChangeDetectorRef)) _changeRef: ChangeDetectorRef, + @Inject(forwardRef(() => ElementRef)) _el: ElementRef, + @Inject(IWorkbenchThemeService) themeService: IWorkbenchThemeService, + @Inject(ITelemetryService) telemetryService: ITelemetryService + ) { + super(_changeRef, _el, themeService, telemetryService); + } + public setConfig(config: IBarChartConfig): void { let options = {}; if (config.xAxisMax) { diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/types/doughnutChart.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/types/doughnutChart.component.ts index 91d85fd7b4..39a55ad43d 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/types/doughnutChart.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/types/doughnutChart.component.ts @@ -5,7 +5,19 @@ import PieChart from './pieChart.component'; import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; +import { ChangeDetectorRef, Inject, forwardRef, ElementRef } from '@angular/core'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export default class DoughnutChart extends PieChart { protected readonly chartType: ChartType = ChartType.Doughnut; + + constructor( + @Inject(forwardRef(() => ChangeDetectorRef)) _changeRef: ChangeDetectorRef, + @Inject(forwardRef(() => ElementRef)) _el: ElementRef, + @Inject(IWorkbenchThemeService) themeService: IWorkbenchThemeService, + @Inject(ITelemetryService) telemetryService: ITelemetryService + ) { + super(_changeRef, _el, themeService, telemetryService); + } } diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/types/horizontalBarChart.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/types/horizontalBarChart.component.ts index 381b78d5a2..40158748c9 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/types/horizontalBarChart.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/types/horizontalBarChart.component.ts @@ -5,7 +5,19 @@ import BarChart from './barChart.component'; import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; +import { forwardRef, Inject, ChangeDetectorRef, ElementRef } from '@angular/core'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export default class HorizontalBarChart extends BarChart { protected readonly chartType: ChartType = ChartType.HorizontalBar; + + constructor( + @Inject(forwardRef(() => ChangeDetectorRef)) _changeRef: ChangeDetectorRef, + @Inject(forwardRef(() => ElementRef)) _el: ElementRef, + @Inject(IWorkbenchThemeService) themeService: IWorkbenchThemeService, + @Inject(ITelemetryService) telemetryService: ITelemetryService + ) { + super(_changeRef, _el, themeService, telemetryService); + } } diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component.ts index 6ac32ae81b..f2b14e8704 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/types/lineChart.component.ts @@ -9,6 +9,9 @@ import BarChart, { IBarChartConfig } from './barChart.component'; import { memoize, unmemoize } from 'sql/base/common/decorators'; import { clone } from 'sql/base/common/objects'; import { ChartType, DataType, defaultChartConfig, IDataSet, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; +import { ChangeDetectorRef, Inject, forwardRef, ElementRef } from '@angular/core'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export interface ILineConfig extends IBarChartConfig { dataType?: DataType; @@ -21,6 +24,15 @@ export default class LineChart extends BarChart { protected _config: ILineConfig; protected _defaultConfig = defaultLineConfig; + constructor( + @Inject(forwardRef(() => ChangeDetectorRef)) _changeRef: ChangeDetectorRef, + @Inject(forwardRef(() => ElementRef)) _el: ElementRef, + @Inject(IWorkbenchThemeService) themeService: IWorkbenchThemeService, + @Inject(ITelemetryService) telemetryService: ITelemetryService + ) { + super(_changeRef, _el, themeService, telemetryService); + } + public init() { if (this._config.dataType === DataType.Point) { this.addAxisLabels(); diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/types/pieChart.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/types/pieChart.component.ts index 0e7b63e71f..1c619054ad 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/types/pieChart.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/types/pieChart.component.ts @@ -5,7 +5,19 @@ import { ChartInsight } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; +import { ChangeDetectorRef, Inject, forwardRef, ElementRef } from '@angular/core'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export default class PieChart extends ChartInsight { protected readonly chartType: ChartType = ChartType.Pie; + + constructor( + @Inject(forwardRef(() => ChangeDetectorRef)) _changeRef: ChangeDetectorRef, + @Inject(forwardRef(() => ElementRef)) _el: ElementRef, + @Inject(IWorkbenchThemeService) themeService: IWorkbenchThemeService, + @Inject(ITelemetryService) telemetryService: ITelemetryService + ) { + super(_changeRef, _el, themeService, telemetryService); + } } diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/types/scatterChart.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/types/scatterChart.component.ts index a5da493af6..716a574964 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/types/scatterChart.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/types/scatterChart.component.ts @@ -8,10 +8,22 @@ import { clone } from 'sql/base/common/objects'; import { ChartType, defaultChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { mixin } from 'vs/base/common/objects'; +import { ChangeDetectorRef, Inject, forwardRef, ElementRef } from '@angular/core'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const defaultScatterConfig = mixin(clone(defaultChartConfig), { dataType: 'point', dataDirection: 'horizontal' }) as ILineConfig; export default class ScatterChart extends LineChart { protected readonly chartType: ChartType = ChartType.Scatter; protected _defaultConfig = defaultScatterConfig; + + constructor( + @Inject(forwardRef(() => ChangeDetectorRef)) _changeRef: ChangeDetectorRef, + @Inject(forwardRef(() => ElementRef)) _el: ElementRef, + @Inject(IWorkbenchThemeService) themeService: IWorkbenchThemeService, + @Inject(ITelemetryService) telemetryService: ITelemetryService + ) { + super(_changeRef, _el, themeService, telemetryService); + } } diff --git a/src/sql/parts/dashboard/widgets/insights/views/charts/types/timeSeriesChart.component.ts b/src/sql/parts/dashboard/widgets/insights/views/charts/types/timeSeriesChart.component.ts index a9e1ce9d72..dc191e6359 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/charts/types/timeSeriesChart.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/charts/types/timeSeriesChart.component.ts @@ -9,12 +9,24 @@ import { ChartType, defaultChartConfig, IPointDataSet } from 'sql/parts/dashboar import { mixin } from 'vs/base/common/objects'; import { Color } from 'vs/base/common/color'; +import { ChangeDetectorRef, Inject, forwardRef, ElementRef } from '@angular/core'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; const defaultTimeSeriesConfig = mixin(clone(defaultChartConfig), { dataType: 'point', dataDirection: 'horizontal' }) as ILineConfig; export default class TimeSeriesChart extends LineChart { protected _defaultConfig = defaultTimeSeriesConfig; + constructor( + @Inject(forwardRef(() => ChangeDetectorRef)) _changeRef: ChangeDetectorRef, + @Inject(forwardRef(() => ElementRef)) _el: ElementRef, + @Inject(IWorkbenchThemeService) themeService: IWorkbenchThemeService, + @Inject(ITelemetryService) telemetryService: ITelemetryService + ) { + super(_changeRef, _el, themeService, telemetryService); + } + protected addAxisLabels(): void { let xLabel = this._config.xAxisLabel || this.getLabels()[1] || 'x'; let yLabel = this._config.yAxisLabel || this.getLabels()[2] || 'y'; diff --git a/src/sql/parts/dashboard/widgets/insights/views/imageInsight.component.ts b/src/sql/parts/dashboard/widgets/insights/views/imageInsight.component.ts index e52059bae1..e059831dc3 100644 --- a/src/sql/parts/dashboard/widgets/insights/views/imageInsight.component.ts +++ b/src/sql/parts/dashboard/widgets/insights/views/imageInsight.component.ts @@ -74,6 +74,6 @@ export default class ImageInsight implements IInsightsView, OnInit { hexVal = hexVal.slice(2); } // should be able to be replaced with new Buffer(hexVal, 'hex').toString('base64') - return btoa(String.fromCharCode.apply(null, hexVal.replace(/\r|\n/g, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' '))); + return btoa(String.fromCharCode.apply(null, hexVal.replace(/\r|\n/g, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ').map(v => Number(v)))); } } diff --git a/src/sql/parts/dashboard/widgets/webview/webviewWidget.component.ts b/src/sql/parts/dashboard/widgets/webview/webviewWidget.component.ts index b7c5d57f44..3efb5b3a60 100644 --- a/src/sql/parts/dashboard/widgets/webview/webviewWidget.component.ts +++ b/src/sql/parts/dashboard/widgets/webview/webviewWidget.component.ts @@ -5,7 +5,6 @@ import { Component, Inject, forwardRef, ChangeDetectorRef, OnInit, ViewChild, ElementRef } from '@angular/core'; -import { Parts, IPartService } from 'vs/workbench/services/part/common/partService'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { memoize } from 'vs/base/common/decorators'; @@ -17,12 +16,9 @@ import { IDashboardWebview, IDashboardViewService } from 'sql/platform/dashboard import * as azdata from 'azdata'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { WebviewElement } from 'vs/workbench/parts/webview/electron-browser/webviewElement'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { truncate } from 'fs'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; interface IWebviewWidgetConfig { id: string; @@ -47,9 +43,9 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget, @Inject(forwardRef(() => CommonServiceInterface)) private _dashboardService: DashboardServiceInterface, @Inject(WIDGET_CONFIG) protected _config: WidgetConfig, @Inject(forwardRef(() => ElementRef)) private _el: ElementRef, + @Inject(IWorkbenchLayoutService) private layoutService: IWorkbenchLayoutService, @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService, @Inject(IDashboardViewService) private dashboardViewService: IDashboardViewService, - @Inject(IPartService) private partService: IPartService, @Inject(IInstantiationService) private instantiationService: IInstantiationService, ) { super(); @@ -108,10 +104,10 @@ export class WebviewWidget extends DashboardWidget implements IDashboardWidget, } this._webview = this.instantiationService.createInstance(WebviewElement, - this.partService.getContainer(Parts.EDITOR_PART), + this.layoutService.getContainer(Parts.EDITOR_PART), + {}, { allowScripts: true, - enableWrappedPostMessage: true }); this._webview.mountTo(this._el.nativeElement); diff --git a/src/sql/parts/dataExplorer/common/dataExplorer.contribution.ts b/src/sql/parts/dataExplorer/common/dataExplorer.contribution.ts index 3344fbfb4e..f744b09bf9 100644 --- a/src/sql/parts/dataExplorer/common/dataExplorer.contribution.ts +++ b/src/sql/parts/dataExplorer/common/dataExplorer.contribution.ts @@ -17,7 +17,7 @@ import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ToggleViewletAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; // Viewlet Action export class OpenDataExplorerViewletAction extends ToggleViewletAction { @@ -28,9 +28,9 @@ export class OpenDataExplorerViewletAction extends ToggleViewletAction { id: string, label: string, @IViewletService viewletService: IViewletService, - @IPartService partService: IPartService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(viewletDescriptor, partService, viewletService); + super(viewletDescriptor, layoutService, viewletService); } } @@ -88,4 +88,4 @@ if (process.env.NODE_ENV === 'development') { } } }); -} \ No newline at end of file +} diff --git a/src/sql/parts/dataExplorer/common/dataExplorerExtensionPoint.ts b/src/sql/parts/dataExplorer/common/dataExplorerExtensionPoint.ts index ed7c4fbb40..ac3096cb09 100644 --- a/src/sql/parts/dataExplorer/common/dataExplorerExtensionPoint.ts +++ b/src/sql/parts/dataExplorer/common/dataExplorerExtensionPoint.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { forEach } from 'vs/base/common/collections'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExtensions, ViewsRegistry, ITreeViewDescriptor } from 'vs/workbench/common/views'; +import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExtensions, ITreeViewDescriptor, IViewsRegistry } from 'vs/workbench/common/views'; import { IExtensionPoint, ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -102,7 +102,7 @@ class DataExplorerContainerExtensionHandler implements IWorkbenchContribution { collector.warn(localize('ViewsContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Data Explorer'.", entry.key)); container = this.viewContainersRegistry.get(VIEWLET_ID); } - const registeredViews = ViewsRegistry.getViews(container); + const registeredViews = Registry.as(ViewContainerExtensions.ViewsRegistry).getViews(container); const viewIds = []; const viewDescriptors = coalesce(entry.value.map(item => { // validate @@ -118,7 +118,7 @@ class DataExplorerContainerExtensionHandler implements IWorkbenchContribution { const viewDescriptor = { id: item.id, name: item.name, - ctor: CustomTreeViewPanel, + ctorDescriptor: { ctor: CustomTreeViewPanel }, when: ContextKeyExpr.deserialize(item.when), canToggleVisibility: true, collapsed: this.showCollapsed(container), @@ -128,7 +128,7 @@ class DataExplorerContainerExtensionHandler implements IWorkbenchContribution { viewIds.push(viewDescriptor.id); return viewDescriptor; })); - ViewsRegistry.registerViews(viewDescriptors, container); + Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews(viewDescriptors, container); }); } }); diff --git a/src/sql/parts/dataExplorer/objectExplorer/connectionViewlet/connectionViewletPanel.ts b/src/sql/parts/dataExplorer/objectExplorer/connectionViewlet/connectionViewletPanel.ts index 2b914cb280..ba66d48218 100644 --- a/src/sql/parts/dataExplorer/objectExplorer/connectionViewlet/connectionViewletPanel.ts +++ b/src/sql/parts/dataExplorer/objectExplorer/connectionViewlet/connectionViewletPanel.ts @@ -11,7 +11,6 @@ import { IExtensionTipsService, IExtensionManagementServerService } from 'vs/pla import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; @@ -26,6 +25,7 @@ import { ServerTreeView } from 'sql/parts/objectExplorer/viewlet/serverTreeView' import { ClearSearchAction, ActiveConnectionsFilterAction, AddServerAction, AddServerGroupAction } from 'sql/parts/objectExplorer/viewlet/connectionTreeAction'; import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; export class ConnectionViewletPanel extends ViewletPanel { diff --git a/src/sql/parts/dataExplorer/viewlet/dataExplorerViewlet.ts b/src/sql/parts/dataExplorer/viewlet/dataExplorerViewlet.ts index 374dad0521..21a4331779 100644 --- a/src/sql/parts/dataExplorer/viewlet/dataExplorerViewlet.ts +++ b/src/sql/parts/dataExplorer/viewlet/dataExplorerViewlet.ts @@ -20,13 +20,14 @@ import { ViewContainerViewlet, IViewletViewOptions } from 'vs/workbench/browser/ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { VIEWLET_ID, VIEW_CONTAINER } from 'sql/parts/dataExplorer/common/dataExplorerExtensionPoint'; import { ConnectionViewletPanel } from 'sql/parts/dataExplorer/objectExplorer/connectionViewlet/connectionViewletPanel'; -import { Extensions as ViewContainerExtensions, ViewsRegistry, IViewDescriptor } from 'vs/workbench/common/views'; +import { Extensions as ViewContainerExtensions, IViewDescriptor, IViewsRegistry } from 'vs/workbench/common/views'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { Registry } from 'vs/platform/registry/common/platform'; export class DataExplorerViewletViewsContribution implements IWorkbenchContribution { @@ -39,14 +40,14 @@ export class DataExplorerViewletViewsContribution implements IWorkbenchContribut private registerViews(): void { let viewDescriptors = []; viewDescriptors.push(this.createObjectExplorerViewDescriptor()); - ViewsRegistry.registerViews(viewDescriptors, VIEW_CONTAINER); + Registry.as(ViewContainerExtensions.ViewsRegistry).registerViews(viewDescriptors, VIEW_CONTAINER); } private createObjectExplorerViewDescriptor(): IViewDescriptor { return { id: 'dataExplorer.servers', name: localize('dataExplorer.servers', "Servers"), - ctor: ConnectionViewletPanel, + ctorDescriptor: { ctor: ConnectionViewletPanel }, weight: 100, canToggleVisibility: true, order: 0 @@ -62,7 +63,7 @@ export class DataExplorerViewlet extends ViewContainerViewlet { private disposables: IDisposable[] = []; constructor( - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IViewletService private viewletService: IViewletService, @@ -73,7 +74,7 @@ export class DataExplorerViewlet extends ViewContainerViewlet { @IExtensionService extensionService: IExtensionService, @IConfigurationService configurationService: IConfigurationService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, partService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); this.disposables.push(this.viewletService.onDidViewletOpen(this.onViewletOpen, this, this.disposables)); } @@ -120,7 +121,7 @@ export class DataExplorerViewlet extends ViewContainerViewlet { } protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPanel { - return this.instantiationService.createInstance(viewDescriptor.ctor, options) as ViewletPanel; + return this.instantiationService.createInstance(viewDescriptor.ctorDescriptor.ctor, options) as ViewletPanel; } private onViewletOpen(viewlet: IViewlet): void { @@ -133,4 +134,4 @@ export class DataExplorerViewlet extends ViewContainerViewlet { this.disposables = dispose(this.disposables); super.dispose(); } -} \ No newline at end of file +} diff --git a/src/sql/parts/disasterRecovery/backup/backupDialog.ts b/src/sql/parts/disasterRecovery/backup/backupDialog.ts index 7a0d789e29..d664830bb8 100644 --- a/src/sql/parts/disasterRecovery/backup/backupDialog.ts +++ b/src/sql/parts/disasterRecovery/backup/backupDialog.ts @@ -13,12 +13,12 @@ import * as TelemetryKeys from 'sql/common/telemetryKeys'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { Builder } from 'sql/base/browser/builder'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export class BackupDialog extends Modal { private _bodyBuilder: Builder; @@ -28,14 +28,14 @@ export class BackupDialog extends Modal { constructor( @IThemeService themeService: IThemeService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService private _instantiationService: IInstantiationService, @IClipboardService clipboardService: IClipboardService ) { - super('', TelemetryKeys.Backup, partService, telemetryService, clipboardService, themeService, contextKeyService, { isAngular: true, hasErrors: true }); + super('', TelemetryKeys.Backup, telemetryService, layoutService, clipboardService, themeService, contextKeyService, { isAngular: true, hasErrors: true }); } protected renderBody(container: HTMLElement) { @@ -105,4 +105,4 @@ export class BackupDialog extends Modal { // Nothing currently laid out in this class } -} \ No newline at end of file +} diff --git a/src/sql/parts/disasterRecovery/restore/restoreDialog.ts b/src/sql/parts/disasterRecovery/restore/restoreDialog.ts index add6c3a2b5..850c412f0e 100644 --- a/src/sql/parts/disasterRecovery/restore/restoreDialog.ts +++ b/src/sql/parts/disasterRecovery/restore/restoreDialog.ts @@ -14,7 +14,6 @@ import { MessageType, IInputOptions } from 'vs/base/browser/ui/inputbox/inputBox import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { localize } from 'vs/nls'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { mixin } from 'vs/base/common/objects'; @@ -42,6 +41,7 @@ import { TabbedPanel, PanelTabIdentifier } from 'sql/base/browser/ui/panel/panel import { ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; import { IFileBrowserDialogController } from 'sql/workbench/services/fileBrowser/common/fileBrowserDialogController'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; interface FileListElement { logicalFileName: string; @@ -128,7 +128,7 @@ export class RestoreDialog extends Modal { constructor( optionsMetadata: azdata.ServiceOption[], - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IThemeService themeService: IThemeService, @IContextViewService private _contextViewService: IContextViewService, @ITelemetryService telemetryService: ITelemetryService, @@ -136,7 +136,7 @@ export class RestoreDialog extends Modal { @IFileBrowserDialogController private fileBrowserDialogService: IFileBrowserDialogController, @IClipboardService clipboardService: IClipboardService ) { - super(localize('RestoreDialogTitle', 'Restore database'), TelemetryKeys.Restore, partService, telemetryService, clipboardService, themeService, contextKeyService, { hasErrors: true, isWide: true, hasSpinner: true }); + super(localize('RestoreDialogTitle', 'Restore database'), TelemetryKeys.Restore, telemetryService, layoutService, clipboardService, themeService, contextKeyService, { hasErrors: true, isWide: true, hasSpinner: true }); this._restoreTitle = localize('restoreDialog.restoreTitle', 'Restore database'); this._databaseTitle = localize('restoreDialog.database', 'Database'); this._backupFileTitle = localize('restoreDialog.backupFile', 'Backup file'); diff --git a/src/sql/parts/editData/editor/editDataEditor.ts b/src/sql/parts/editData/editor/editDataEditor.ts index c3f56a58b6..44552a571f 100644 --- a/src/sql/parts/editData/editor/editDataEditor.ts +++ b/src/sql/parts/editData/editor/editDataEditor.ts @@ -33,12 +33,12 @@ import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResour import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; -import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; import { IFlexibleSash, HorizontalFlexibleSash } from 'sql/parts/query/views/flexibleSash'; import { EditDataResultsEditor } from 'sql/parts/editData/editor/editDataResultsEditor'; import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; /** * Editor that hosts an action bar and a resultSetInput for an edit data session diff --git a/src/sql/parts/extensions/sqlExtensionsHelper.ts b/src/sql/parts/extensions/sqlExtensionsHelper.ts index 439516ee8f..24bd72a39b 100644 --- a/src/sql/parts/extensions/sqlExtensionsHelper.ts +++ b/src/sql/parts/extensions/sqlExtensionsHelper.ts @@ -5,9 +5,6 @@ 'use strict'; -import 'vs/css!vs/workbench/parts/extensions/electron-browser/media/extensionEditor'; - - import { append, $ } from 'vs/base/browser/dom'; import { IInsightTypeContrib } from 'sql/parts/dashboard/widgets/insights/interfaces'; import { IDashboardTabContrib } from 'sql/parts/dashboard/common/dashboardTab.contribution'; @@ -106,4 +103,3 @@ function renderDashboardInsights(onDetailsToggle: Function, contributionReader: append(container, details); return true; } - diff --git a/src/sql/parts/modelComponents/modelEditor/modelViewInput.ts b/src/sql/parts/modelComponents/modelEditor/modelViewInput.ts index f9cc7c15b7..3491adfbeb 100644 --- a/src/sql/parts/modelComponents/modelEditor/modelViewInput.ts +++ b/src/sql/parts/modelComponents/modelEditor/modelViewInput.ts @@ -9,10 +9,10 @@ import { IEditorModel } from 'vs/platform/editor/common/editor'; import { EditorInput, EditorModel, ConfirmResult } from 'vs/workbench/common/editor'; import * as DOM from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; import { DialogPane } from 'sql/platform/dialog/dialogPane'; import { Emitter, Event } from 'vs/base/common/event'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; export type ModeViewSaveHandler = (handle: number) => Thenable; @@ -56,13 +56,13 @@ export class ModelViewInput extends EditorInput { constructor(private _title: string, private _model: ModelViewInputModel, private _options: azdata.ModelViewEditorOptions, @IInstantiationService private _instantiationService: IInstantiationService, - @IPartService private readonly _partService: IPartService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(); this._model.onDidChangeDirty(() => this._onDidChangeDirty.fire()); this._container = document.createElement('div'); this._container.id = `modelView-${_model.modelViewId}`; - this._partService.getContainer(Parts.EDITOR_PART).appendChild(this._container); + this.layoutService.getContainer(Parts.EDITOR_PART).appendChild(this._container); } diff --git a/src/sql/parts/modelComponents/modelStore.ts b/src/sql/parts/modelComponents/modelStore.ts index 7430bc4b32..739391aae2 100644 --- a/src/sql/parts/modelComponents/modelStore.ts +++ b/src/sql/parts/modelComponents/modelStore.ts @@ -11,6 +11,7 @@ import * as azdata from 'azdata'; import { IModelStore, IComponentDescriptor, IComponent } from './interfaces'; import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry'; import { Deferred } from 'sql/base/common/promise'; +import { entries } from 'sql/base/common/objects'; const componentRegistry = Registry.as(Extensions.ComponentContribution); @@ -73,7 +74,7 @@ export class ModelStore implements IModelStore { } validate(component: IComponent): Thenable { - let componentId = Object.entries(this._componentMappings).find(([id, mappedComponent]) => component === mappedComponent)[0]; + let componentId = entries(this._componentMappings).find(([id, mappedComponent]) => component === mappedComponent)[0]; return Promise.all(this._validationCallbacks.map(callback => callback(componentId))).then(validations => validations.every(validation => validation === true)); } diff --git a/src/sql/parts/modelComponents/queryTextEditor.ts b/src/sql/parts/modelComponents/queryTextEditor.ts index 99abcc7930..7ff5a493c7 100644 --- a/src/sql/parts/modelComponents/queryTextEditor.ts +++ b/src/sql/parts/modelComponents/queryTextEditor.ts @@ -17,7 +17,6 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { EditorOptions } from 'vs/workbench/common/editor'; import { StandaloneCodeEditor } from 'vs/editor/standalone/browser/standaloneCodeEditor'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -25,7 +24,9 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Configuration } from 'vs/editor/browser/config/configuration'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; /** * Extension of TextResourceEditor that is always readonly rather than only with non UntitledInputs @@ -52,7 +53,8 @@ export class QueryTextEditor extends BaseTextEditor { @IEditorGroupsService editorGroupService: IEditorGroupsService, @IEditorService protected editorService: IEditorService, @IWindowService windowService: IWindowService, - @IWorkspaceConfigurationService private workspaceConfigurationService: IWorkspaceConfigurationService + @IConfigurationService private workspaceConfigurationService: IConfigurationService, + @IAccessibilityService private accessibilityService: IAccessibilityService ) { super( @@ -131,7 +133,7 @@ export class QueryTextEditor extends BaseTextEditor { public setHeightToScrollHeight(configChanged?: boolean): void { let editorWidget = this.getControl() as ICodeEditor; if (!this._config) { - this._config = new Configuration(undefined, editorWidget.getDomNode()); + this._config = new Configuration(undefined, editorWidget.getDomNode(), this.accessibilityService); this._scrollbarHeight = this._config.editor.viewInfo.scrollbar.horizontalScrollbarSize; } let editorWidgetModel = editorWidget.getModel(); diff --git a/src/sql/parts/modelComponents/webview.component.ts b/src/sql/parts/modelComponents/webview.component.ts index 1c675b4142..5013c54b31 100644 --- a/src/sql/parts/modelComponents/webview.component.ts +++ b/src/sql/parts/modelComponents/webview.component.ts @@ -11,12 +11,10 @@ import { import * as azdata from 'azdata'; import * as vscode from 'vscode'; import { addDisposableListener, EventType } from 'vs/base/browser/dom'; -import { Parts, IPartService } from 'vs/workbench/services/part/common/partService'; import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { WebviewElement, WebviewOptions } from 'vs/workbench/parts/webview/electron-browser/webviewElement'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -25,6 +23,8 @@ import { ComponentBase } from 'sql/parts/modelComponents/componentBase'; import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { WebviewElement, WebviewOptions, WebviewContentOptions } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; function reviveWebviewOptions(options: vscode.WebviewOptions): vscode.WebviewOptions { return { @@ -54,7 +54,7 @@ export default class WebViewComponent extends ComponentBase implements IComponen @Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface, @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef, @Inject(forwardRef(() => ElementRef)) el: ElementRef, - @Inject(IPartService) private partService: IPartService, + @Inject(IWorkbenchLayoutService) private layoutService: IWorkbenchLayoutService, @Inject(IThemeService) private themeService: IThemeService, @Inject(IEnvironmentService) private environmentService: IEnvironmentService, @Inject(IContextViewService) private contextViewService: IContextViewService, @@ -76,10 +76,12 @@ export default class WebViewComponent extends ComponentBase implements IComponen private _createWebview(): void { this._webview = this.instantiationService.createInstance(WebviewElement, - this.partService.getContainer(Parts.EDITOR_PART), + this.layoutService.getContainer(Parts.EDITOR_PART), { - allowScripts: true, - enableWrappedPostMessage: true + allowSvgs: true + }, + { + allowScripts: true }); this._webview.mountTo(this._el.nativeElement); @@ -195,14 +197,11 @@ export default class WebViewComponent extends ComponentBase implements IComponen return this._extensionLocationUri; } - private getExtendedOptions(): WebviewOptions { + private getExtendedOptions(): WebviewContentOptions { let options = this.options || { enableScripts: true }; options = reviveWebviewOptions(options); return { allowScripts: options.enableScripts, - allowSvgs: true, - enableWrappedPostMessage: true, - useSameOriginForRoot: false, localResourceRoots: options!.localResourceRoots || this.getDefaultLocalResourceRoots() }; } diff --git a/src/sql/parts/notebook/models/notebookModel.ts b/src/sql/parts/notebook/models/notebookModel.ts index 8a3e521070..17d7e40df5 100644 --- a/src/sql/parts/notebook/models/notebookModel.ts +++ b/src/sql/parts/notebook/models/notebookModel.ts @@ -23,6 +23,7 @@ import { URI } from 'vs/base/common/uri'; import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHostTypes'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { uriPrefixes } from 'sql/platform/connection/common/utils'; +import { keys } from 'vs/base/common/map'; /* * Used to control whether a message in a dialog/wizard is displayed as an error, @@ -205,7 +206,7 @@ export class NotebookModel extends Disposable implements INotebookModel { } public standardKernelsDisplayName(): string[] { - return Array.from(this._kernelDisplayNameToNotebookProviderIds.keys()); + return Array.from(keys(this._kernelDisplayNameToNotebookProviderIds)); } public get inErrorState(): boolean { diff --git a/src/sql/parts/notebook/notebook.component.ts b/src/sql/parts/notebook/notebook.component.ts index 3b43ce1ecc..0f70df95b6 100644 --- a/src/sql/parts/notebook/notebook.component.ts +++ b/src/sql/parts/notebook/notebook.component.ts @@ -19,7 +19,6 @@ import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { fillInActions, LabeledMenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/parts/extensions/common/extensions'; import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; import { AngularDisposable } from 'sql/base/node/lifecycle'; @@ -41,6 +40,7 @@ import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHos import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import { CellMagicMapper } from 'sql/parts/notebook/models/cellMagicMapper'; +import { IExtensionsViewlet, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; export const NOTEBOOK_SELECTOR: string = 'notebook-component'; diff --git a/src/sql/parts/notebook/notebookUtils.ts b/src/sql/parts/notebook/notebookUtils.ts index 8008c1b94f..5cab9e9496 100644 --- a/src/sql/parts/notebook/notebookUtils.ts +++ b/src/sql/parts/notebook/notebookUtils.ts @@ -10,9 +10,9 @@ import { nb } from 'azdata'; import * as os from 'os'; import * as pfs from 'vs/base/node/pfs'; import { localize } from 'vs/nls'; -import { IOutputChannel } from 'vs/workbench/parts/output/common/output'; import { DEFAULT_NOTEBOOK_PROVIDER, DEFAULT_NOTEBOOK_FILETYPE, INotebookService } from 'sql/workbench/services/notebook/common/notebookService'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; +import { IOutputChannel } from 'vs/workbench/contrib/output/common/output'; /** @@ -122,4 +122,4 @@ export async function asyncForEach(array: any, callback: any): Promise { for (let index = 0; index < array.length; index++) { await callback(array[index], index, array); } -} \ No newline at end of file +} diff --git a/src/sql/parts/objectExplorer/common/registeredServer.contribution.ts b/src/sql/parts/objectExplorer/common/registeredServer.contribution.ts index cc1a54fb58..2119139c35 100644 --- a/src/sql/parts/objectExplorer/common/registeredServer.contribution.ts +++ b/src/sql/parts/objectExplorer/common/registeredServer.contribution.ts @@ -16,9 +16,8 @@ import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/co import { VIEWLET_ID } from 'sql/platform/connection/common/connectionManagement'; import { ConnectionViewlet } from 'sql/workbench/parts/connection/electron-browser/connectionViewlet'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { ToggleViewletAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; // Viewlet Action export class OpenConnectionsViewletAction extends ToggleViewletAction { @@ -29,9 +28,9 @@ export class OpenConnectionsViewletAction extends ToggleViewletAction { id: string, label: string, @IViewletService viewletService: IViewletService, - @IPartService partService: IPartService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(viewletDescriptor, partService, viewletService); + super(viewletDescriptor, layoutService, viewletService); } } @@ -98,4 +97,4 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { title: localize({ key: 'miViewRegisteredServers', comment: ['&& denotes a mnemonic'] }, "&&Servers") }, order: 1 -}); \ No newline at end of file +}); diff --git a/src/sql/parts/objectExplorer/common/treeNode.ts b/src/sql/parts/objectExplorer/common/treeNode.ts index 9da3bc6c3f..550090d881 100644 --- a/src/sql/parts/objectExplorer/common/treeNode.ts +++ b/src/sql/parts/objectExplorer/common/treeNode.ts @@ -7,7 +7,7 @@ import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { NodeType, SqlThemeIcon } from 'sql/parts/objectExplorer/common/nodeType'; -import * as azdata from 'azdata'; +import * as azdata from 'sqlops'; import * as UUID from 'vs/base/common/uuid'; diff --git a/src/sql/parts/objectExplorer/serverGroupDialog/serverGroupDialog.ts b/src/sql/parts/objectExplorer/serverGroupDialog/serverGroupDialog.ts index 42e4ae0ef1..0bb874545a 100644 --- a/src/sql/parts/objectExplorer/serverGroupDialog/serverGroupDialog.ts +++ b/src/sql/parts/objectExplorer/serverGroupDialog/serverGroupDialog.ts @@ -13,7 +13,6 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { Event, Emitter } from 'vs/base/common/event'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { localize } from 'vs/nls'; @@ -27,6 +26,7 @@ import { attachButtonStyler, attachModalDialogStyler } from 'sql/platform/theme/ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import * as TelemetryKeys from 'sql/common/telemetryKeys'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export class ServerGroupDialog extends Modal { private _bodyBuilder: Builder; @@ -50,14 +50,14 @@ export class ServerGroupDialog extends Modal { public onCloseEvent: Event = this._onCloseEvent.event; constructor( - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IThemeService themeService: IThemeService, @IContextViewService private _contextViewService: IContextViewService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @IClipboardService clipboardService: IClipboardService ) { - super(localize('ServerGroupsDialogTitle', 'Server Groups'), TelemetryKeys.ServerGroups, partService, telemetryService, clipboardService, themeService, contextKeyService); + super(localize('ServerGroupsDialogTitle', 'Server Groups'), TelemetryKeys.ServerGroups, telemetryService, layoutService, clipboardService, themeService, contextKeyService); } public render() { @@ -361,4 +361,4 @@ export class ServerGroupDialog extends Modal { this.show(); this._groupNameInputBox.focus(); } -} \ No newline at end of file +} diff --git a/src/sql/parts/profiler/dialog/profilerColumnEditorDialog.ts b/src/sql/parts/profiler/dialog/profilerColumnEditorDialog.ts index 18f8cccab5..575a6f0107 100644 --- a/src/sql/parts/profiler/dialog/profilerColumnEditorDialog.ts +++ b/src/sql/parts/profiler/dialog/profilerColumnEditorDialog.ts @@ -11,7 +11,6 @@ import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput'; import * as TelemetryKeys from 'sql/common/telemetryKeys'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import * as nls from 'vs/nls'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Builder } from 'sql/base/browser/builder'; @@ -26,6 +25,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; class EventItem { @@ -312,14 +312,14 @@ export class ProfilerColumnEditorDialog extends Modal { private _treeContainer: HTMLElement; constructor( - @IPartService _partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @IContextViewService private _contextViewService: IContextViewService, @IClipboardService clipboardService: IClipboardService ) { - super(nls.localize('profilerColumnDialog.profiler', 'Profiler'), TelemetryKeys.Profiler, _partService, telemetryService, clipboardService, themeService, contextKeyService); + super(nls.localize('profilerColumnDialog.profiler', 'Profiler'), TelemetryKeys.Profiler, telemetryService, layoutService, clipboardService, themeService, contextKeyService); } public render(): void { diff --git a/src/sql/parts/profiler/dialog/profilerFilterDialog.ts b/src/sql/parts/profiler/dialog/profilerFilterDialog.ts index 37eead496e..86283aacc4 100644 --- a/src/sql/parts/profiler/dialog/profilerFilterDialog.ts +++ b/src/sql/parts/profiler/dialog/profilerFilterDialog.ts @@ -13,7 +13,6 @@ import { attachButtonStyler, attachModalDialogStyler, attachInputBoxStyler } fro import { KeyCode } from 'vs/base/common/keyCodes'; import { Builder } from 'sql/base/browser/builder'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -27,6 +26,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ProfilerFilter, ProfilerFilterClause, ProfilerFilterClauseOperator } from 'sql/workbench/services/profiler/common/interfaces'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; const ClearText: string = localize('profilerFilterDialog.clear', 'Clear All'); @@ -73,12 +73,12 @@ export class ProfilerFilterDialog extends Modal { constructor( @IThemeService themeService: IThemeService, @IClipboardService clipboardService: IClipboardService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @IContextViewService private contextViewService: IContextViewService ) { - super('', TelemetryKeys.ProfilerFilter, partService, telemetryService, clipboardService, themeService, contextKeyService, { isFlyout: false, hasTitleIcon: true }); + super('', TelemetryKeys.ProfilerFilter, telemetryService, layoutService, clipboardService, themeService, contextKeyService, { isFlyout: false, hasTitleIcon: true }); } public open(input: ProfilerInput) { @@ -326,4 +326,4 @@ interface ClauseRowUI { field: SelectBox; operator: SelectBox; value: InputBox; -} \ No newline at end of file +} diff --git a/src/sql/parts/profiler/editor/profilerEditor.ts b/src/sql/parts/profiler/editor/profilerEditor.ts index 1d44efe26a..b0c04d487c 100644 --- a/src/sql/parts/profiler/editor/profilerEditor.ts +++ b/src/sql/parts/profiler/editor/profilerEditor.ts @@ -35,7 +35,6 @@ import { CommonFindController, FindStartFocusAction } from 'vs/editor/contrib/fi import * as types from 'vs/base/common/types'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IView, SplitView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; diff --git a/src/sql/parts/profiler/editor/profilerResourceEditor.ts b/src/sql/parts/profiler/editor/profilerResourceEditor.ts index e977012211..38fce45df4 100644 --- a/src/sql/parts/profiler/editor/profilerResourceEditor.ts +++ b/src/sql/parts/profiler/editor/profilerResourceEditor.ts @@ -17,12 +17,12 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { EditorOptions } from 'vs/workbench/common/editor'; import { StandaloneCodeEditor } from 'vs/editor/standalone/browser/standaloneCodeEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; class ProfilerResourceCodeEditor extends StandaloneCodeEditor { diff --git a/src/sql/parts/query/common/flavorStatus.ts b/src/sql/parts/query/common/flavorStatus.ts index bad50bd84a..5997715ec8 100644 --- a/src/sql/parts/query/common/flavorStatus.ts +++ b/src/sql/parts/query/common/flavorStatus.ts @@ -9,7 +9,6 @@ import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { IEditorCloseEvent } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Action } from 'vs/base/common/actions'; import errors = require('vs/base/common/errors'); @@ -69,7 +68,6 @@ export class SqlFlavorStatusbarItem implements IStatusbarItem { constructor( @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IEditorService private _editorService: EditorServiceImpl, - @IEditorGroupsService private _editorGroupService: IEditorGroupsService, @IQuickInputService private _quickInputService: IQuickInputService, @IInstantiationService private _instantiationService: IInstantiationService, ) { diff --git a/src/sql/parts/query/editor/charting/actions.ts b/src/sql/parts/query/editor/charting/actions.ts index 34968ede46..e0a79fa4d1 100644 --- a/src/sql/parts/query/editor/charting/actions.ts +++ b/src/sql/parts/query/editor/charting/actions.ts @@ -14,7 +14,7 @@ import { resolveCurrentDirectory, getRootPath } from 'sql/platform/node/pathUtil import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { join, normalize } from 'vs/base/common/paths'; +import { join, normalize } from 'vs/base/common/path'; import { writeFile } from 'vs/base/node/pfs'; import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; @@ -193,7 +193,7 @@ export class SaveImageAction extends Action { filepathPlaceHolder = join(filepathPlaceHolder, 'chart.png'); return this.windowService.showSaveDialog({ title: localize('chartViewer.saveAsFileTitle', 'Choose Results File'), - defaultPath: normalize(filepathPlaceHolder, true) + defaultPath: normalize(filepathPlaceHolder) }); } diff --git a/src/sql/parts/query/editor/charting/insights/imageInsight.ts b/src/sql/parts/query/editor/charting/insights/imageInsight.ts index abbf1065cb..e295759ed6 100644 --- a/src/sql/parts/query/editor/charting/insights/imageInsight.ts +++ b/src/sql/parts/query/editor/charting/insights/imageInsight.ts @@ -67,6 +67,6 @@ export class ImageInsight implements IInsight { hexVal = hexVal.slice(2); } // should be able to be replaced with new Buffer(hexVal, 'hex').toString('base64') - return btoa(String.fromCharCode.apply(null, hexVal.replace(/\r|\n/g, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' '))); + return btoa(String.fromCharCode.apply(null, hexVal.replace(/\r|\n/g, '').replace(/([\da-fA-F]{2}) ?/g, '0x$1 ').replace(/ +$/, '').split(' ').map(v => Number(v)))); } } diff --git a/src/sql/parts/query/editor/queryEditor.ts b/src/sql/parts/query/editor/queryEditor.ts index 3ca68a6240..91386b4736 100644 --- a/src/sql/parts/query/editor/queryEditor.ts +++ b/src/sql/parts/query/editor/queryEditor.ts @@ -27,7 +27,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { ISelectionData } from 'azdata'; -import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IRange } from 'vs/editor/common/core/range'; @@ -48,6 +47,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { CancellationToken } from 'vs/base/common/cancellation'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; /** * Editor that hosts 2 sub-editors: A TextResourceEditor for SQL file editing, and a QueryResultsEditor diff --git a/src/sql/parts/query/execution/keyboardQueryActions.ts b/src/sql/parts/query/execution/keyboardQueryActions.ts index 525d5f542b..6f132eda50 100644 --- a/src/sql/parts/query/execution/keyboardQueryActions.ts +++ b/src/sql/parts/query/execution/keyboardQueryActions.ts @@ -7,7 +7,6 @@ import nls = require('vs/nls'); import { Action } from 'vs/base/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import * as azdata from 'azdata'; @@ -20,6 +19,7 @@ import * as Constants from 'sql/parts/query/common/constants'; import * as ConnectionConstants from 'sql/platform/connection/common/constants'; import { EditDataEditor } from 'sql/parts/editData/editor/editDataEditor'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const singleQuote = '\''; @@ -247,7 +247,7 @@ export class RunQueryShortcutAction extends Action { @IQueryModelService protected _queryModelService: IQueryModelService, @IQueryManagementService private _queryManagementService: IQueryManagementService, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, - @IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService + @IConfigurationService private _workspaceConfigurationService: IConfigurationService ) { super(RunQueryShortcutAction.ID); } @@ -454,4 +454,4 @@ export class ParseSyntaxAction extends Action { } return this._connectionManagementService.isConnected(editor.currentQueryInput.uri); } -} \ No newline at end of file +} diff --git a/src/sql/parts/query/execution/queryStatus.ts b/src/sql/parts/query/execution/queryStatus.ts index e519fd0c1f..ff44e8a3ca 100644 --- a/src/sql/parts/query/execution/queryStatus.ts +++ b/src/sql/parts/query/execution/queryStatus.ts @@ -8,7 +8,6 @@ import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { IEditorCloseEvent } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { IQueryModelService } from 'sql/platform/query/common/queryModel'; import LocalizedConstants = require('sql/parts/query/common/localizedConstants'); import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils'; @@ -30,8 +29,7 @@ export class QueryStatusbarItem implements IStatusbarItem { constructor( @IQueryModelService private _queryModelService: IQueryModelService, - @IEditorService private _editorService: EditorServiceImpl, - @IEditorGroupsService private _editorGroupService: IEditorGroupsService, + @IEditorService private _editorService: EditorServiceImpl ) { this._queryStatusEditors = {}; } diff --git a/src/sql/parts/taskHistory/common/taskHistory.contribution.ts b/src/sql/parts/taskHistory/common/taskHistory.contribution.ts index 8b18c5eca3..8fd7de7a4c 100644 --- a/src/sql/parts/taskHistory/common/taskHistory.contribution.ts +++ b/src/sql/parts/taskHistory/common/taskHistory.contribution.ts @@ -21,9 +21,8 @@ import ext = require('vs/workbench/common/contributions'); import { ITaskService } from 'sql/platform/taskHistory/common/taskService'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { ToggleViewletAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export class StatusUpdater implements ext.IWorkbenchContribution { static ID = 'data.taskhistory.statusUpdater'; @@ -83,9 +82,9 @@ export class TaskHistoryViewletAction extends ToggleViewletAction { id: string, label: string, @IViewletService viewletService: IViewletService, - @IPartService partService: IPartService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(viewletDescriptor, partService, viewletService); + super(viewletDescriptor, layoutService, viewletService); } } @@ -134,4 +133,4 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { title: localize({ key: 'miViewTasks', comment: ['&& denotes a mnemonic'] }, "&&Tasks") }, order: 2 -}); \ No newline at end of file +}); diff --git a/src/sql/parts/taskHistory/viewlet/taskHistoryViewlet.ts b/src/sql/parts/taskHistory/viewlet/taskHistoryViewlet.ts index f3b2803a9b..d9fefdfe3d 100644 --- a/src/sql/parts/taskHistory/viewlet/taskHistoryViewlet.ts +++ b/src/sql/parts/taskHistory/viewlet/taskHistoryViewlet.ts @@ -17,10 +17,10 @@ import Severity from 'vs/base/common/severity'; import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; import { TaskHistoryView } from 'sql/parts/taskHistory/viewlet/taskHistoryView'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export const VIEWLET_ID = 'workbench.view.taskHistory'; @@ -35,11 +35,11 @@ export class TaskHistoryViewlet extends Viewlet { @IThemeService themeService: IThemeService, @IInstantiationService private _instantiationService: IInstantiationService, @INotificationService private _notificationService: INotificationService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IConfigurationService configurationService: IConfigurationService, @IStorageService storageService: IStorageService ) { - super(VIEWLET_ID, configurationService, partService, telemetryService, themeService, storageService); + super(VIEWLET_ID, configurationService, layoutService, telemetryService, themeService, storageService); } private onError(err: any): void { diff --git a/src/sql/platform/capabilities/common/capabilitiesService.ts b/src/sql/platform/capabilities/common/capabilitiesService.ts index 0e2a557e6d..4683212bb0 100644 --- a/src/sql/platform/capabilities/common/capabilitiesService.ts +++ b/src/sql/platform/capabilities/common/capabilitiesService.ts @@ -10,7 +10,7 @@ import * as Constants from 'sql/common/constants'; import { ConnectionProviderProperties, IConnectionProviderRegistry, Extensions as ConnectionExtensions } from 'sql/workbench/parts/connection/common/connectionProviderExtension'; import { toObject } from 'sql/base/common/map'; -import * as azdata from 'azdata'; +import * as azdata from 'sqlops'; import { Event, Emitter } from 'vs/base/common/event'; import { IAction } from 'vs/base/common/actions'; @@ -21,6 +21,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { entries } from 'sql/base/common/objects'; export const SERVICE_ID = 'capabilitiesService'; export const HOST_NAME = 'azdata'; @@ -115,14 +116,14 @@ export class CapabilitiesService extends Disposable implements ICapabilitiesServ } // handle in case some extensions have already registered (unlikley) - Object.entries(connectionRegistry.providers).map(v => { + entries(connectionRegistry.providers).map(v => { this.handleConnectionProvider({ id: v[0], properties: v[1] }); }); // register for when new extensions are added this._register(connectionRegistry.onNewProvider(this.handleConnectionProvider, this)); // handle adding already known capabilities (could have caching problems) - Object.entries(this.capabilities.connectionProviderCache).map(v => { + entries(this.capabilities.connectionProviderCache).map(v => { this.handleConnectionProvider({ id: v[0], properties: v[1] }, false); }); diff --git a/src/sql/platform/connection/common/connectionConfig.ts b/src/sql/platform/connection/common/connectionConfig.ts index 76d8159692..44db17dd38 100644 --- a/src/sql/platform/connection/common/connectionConfig.ts +++ b/src/sql/platform/connection/common/connectionConfig.ts @@ -9,15 +9,14 @@ import * as Utils from './utils'; import { IConnectionProfile, IConnectionProfileStore } from './interfaces'; import { IConnectionConfig } from './iconnectionConfig'; import { ConnectionProfileGroup, IConnectionProfileGroup } from './connectionProfileGroup'; -import { ConfigurationEditingService, IConfigurationValue } from 'vs/workbench/services/configuration/node/configurationEditingService'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; -import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConnectionProfile } from './connectionProfile'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import * as azdata from 'azdata'; import * as nls from 'vs/nls'; import { generateUuid } from 'vs/base/common/uuid'; +import { ConfigurationEditingService, IConfigurationValue } from 'vs/workbench/services/configuration/common/configurationEditingService'; export interface ISaveGroupResult { groups: IConnectionProfileGroup[]; @@ -34,7 +33,7 @@ export class ConnectionConfig implements IConnectionConfig { */ public constructor( private _configurationEditService: ConfigurationEditingService, - private _workspaceConfigurationService: IWorkspaceConfigurationService, + private _workspaceConfigurationService: IConfigurationService, private _capabilitiesService: ICapabilitiesService ) { } diff --git a/src/sql/platform/connection/common/connectionManagementService.ts b/src/sql/platform/connection/common/connectionManagementService.ts index 927a2269d6..4bc9457431 100644 --- a/src/sql/platform/connection/common/connectionManagementService.ts +++ b/src/sql/platform/connection/common/connectionManagementService.ts @@ -31,7 +31,7 @@ import { IAngularEventingService, AngularEventType } from 'sql/platform/angularE import * as QueryConstants from 'sql/parts/query/common/constants'; import { Deferred } from 'sql/base/common/promise'; import { ConnectionOptionSpecialType } from 'sql/workbench/api/common/sqlExtHostTypes'; -import { values } from 'sql/base/common/objects'; +import { values, entries } from 'sql/base/common/objects'; import { ConnectionProviderProperties, IConnectionProviderRegistry, Extensions as ConnectionProviderExtensions } from 'sql/workbench/parts/connection/common/connectionProviderExtension'; import { IAccountManagementService, AzureResource } from 'sql/platform/accountManagement/common/interfaces'; import { IServerGroupController, IServerGroupDialogCallbacks } from 'sql/platform/serverGroup/common/serverGroupController'; @@ -48,15 +48,15 @@ import { Memento } from 'vs/workbench/common/memento'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; -import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import * as statusbar from 'vs/workbench/browser/parts/statusbar/statusbar'; import { IStatusbarService, StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditingService'; export class ConnectionManagementService extends Disposable implements IConnectionManagementService { @@ -88,7 +88,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti @IInstantiationService private _instantiationService: IInstantiationService, @IEditorService private _editorService: IEditorService, @ITelemetryService private _telemetryService: ITelemetryService, - @IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService, + @IConfigurationService private _configurationService: IConfigurationService, @ICredentialsService private _credentialsService: ICredentialsService, @ICapabilitiesService private _capabilitiesService: ICapabilitiesService, @IQuickInputService private _quickInputService: IQuickInputService, @@ -109,7 +109,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti } if (!this._connectionStore) { this._connectionStore = new ConnectionStore(_storageService, this._connectionMemento, - this._configurationEditService, this._workspaceConfigurationService, this._credentialsService, this._capabilitiesService); + this._configurationEditService, this._configurationService, this._credentialsService, this._capabilitiesService); } // Register Statusbar item @@ -130,7 +130,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti }; registry.onNewProvider(providerRegistration, this); - Object.entries(registry.providers).map(v => { + entries(registry.providers).map(v => { providerRegistration({ id: v[0], properties: v[1] }); }); @@ -750,7 +750,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti public ensureDefaultLanguageFlavor(uri: string): void { if (!this.getProviderIdFromUri(uri)) { // Lookup the default settings and use this - let defaultProvider = WorkbenchUtils.getSqlConfigValue(this._workspaceConfigurationService, Constants.defaultEngine); + let defaultProvider = WorkbenchUtils.getSqlConfigValue(this._configurationService, Constants.defaultEngine); if (defaultProvider && this._providers.has(defaultProvider)) { // Only set a default if it's in the list of registered providers this.doChangeLanguageFlavor(uri, 'sql', defaultProvider); @@ -1324,7 +1324,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti } public getTabColorForUri(uri: string): string { - if (WorkbenchUtils.getSqlConfigValue(this._workspaceConfigurationService, 'tabColorMode') === QueryConstants.tabColorModeOff) { + if (WorkbenchUtils.getSqlConfigValue(this._configurationService, 'tabColorMode') === QueryConstants.tabColorModeOff) { return undefined; } let connectionProfile = this.getConnectionProfile(uri); diff --git a/src/sql/platform/connection/common/connectionStore.ts b/src/sql/platform/connection/common/connectionStore.ts index 8fae8094af..7bf8d9bb4e 100644 --- a/src/sql/platform/connection/common/connectionStore.ts +++ b/src/sql/platform/connection/common/connectionStore.ts @@ -15,9 +15,9 @@ import { ConnectionConfig } from './connectionConfig'; import { Memento } from 'vs/workbench/common/memento'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ConnectionProfileGroup, IConnectionProfileGroup } from './connectionProfileGroup'; -import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditingService'; const MAX_CONNECTIONS_DEFAULT = 25; @@ -36,7 +36,7 @@ export class ConnectionStore { private _storageService: IStorageService, private _context: Memento, private _configurationEditService: ConfigurationEditingService, - private _workspaceConfigurationService: IWorkspaceConfigurationService, + private _configurationService: IConfigurationService, private _credentialService: ICredentialsService, private _capabilitiesService: ICapabilitiesService, private _connectionConfig?: IConnectionConfig @@ -48,7 +48,7 @@ export class ConnectionStore { this._groupFullNameToIdMap = {}; if (!this._connectionConfig) { this._connectionConfig = new ConnectionConfig(this._configurationEditService, - this._workspaceConfigurationService, this._capabilitiesService); + this._configurationService, this._capabilitiesService); } } @@ -488,7 +488,7 @@ export class ConnectionStore { } private getMaxRecentConnectionsCount(): number { - let config = this._workspaceConfigurationService.getValue(Constants.sqlConfigSectionName); + let config = this._configurationService.getValue(Constants.sqlConfigSectionName); let maxConnections: number = config[Constants.configMaxRecentConnections]; if (typeof (maxConnections) !== 'number' || maxConnections <= 0) { @@ -568,4 +568,4 @@ export class ConnectionStore { } return result; } -} \ No newline at end of file +} diff --git a/src/sql/platform/dialog/dialogModal.ts b/src/sql/platform/dialog/dialogModal.ts index abbb9cf469..ae08523537 100644 --- a/src/sql/platform/dialog/dialogModal.ts +++ b/src/sql/platform/dialog/dialogModal.ts @@ -12,7 +12,6 @@ import { Dialog, DialogButton } from 'sql/platform/dialog/dialogTypes'; import { DialogPane } from 'sql/platform/dialog/dialogPane'; import { Builder } from 'sql/base/browser/builder'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -26,6 +25,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { DialogMessage, MessageLevel } from '../../workbench/api/common/sqlExtHostTypes'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export class DialogModal extends Modal { private _dialogPane: DialogPane; @@ -40,14 +40,14 @@ export class DialogModal extends Modal { private _dialog: Dialog, name: string, options: IModalOptions, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IWorkbenchThemeService themeService: IWorkbenchThemeService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @IClipboardService clipboardService: IClipboardService, @IInstantiationService private _instantiationService: IInstantiationService ) { - super(_dialog.title, name, partService, telemetryService, clipboardService, themeService, contextKeyService, options); + super(_dialog.title, name, telemetryService, layoutService, clipboardService, themeService, contextKeyService, options); } public layout(): void { diff --git a/src/sql/platform/dialog/wizardModal.ts b/src/sql/platform/dialog/wizardModal.ts index 84ba2f197d..a252fa814d 100644 --- a/src/sql/platform/dialog/wizardModal.ts +++ b/src/sql/platform/dialog/wizardModal.ts @@ -16,7 +16,6 @@ import { DialogModule } from 'sql/platform/dialog/dialog.module'; import { Button } from 'vs/base/browser/ui/button/button'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { Builder } from 'sql/base/browser/builder'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -25,6 +24,7 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Emitter } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export class WizardModal extends Modal { private _dialogPanes = new Map(); @@ -48,14 +48,14 @@ export class WizardModal extends Modal { private _wizard: Wizard, name: string, options: IModalOptions, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IWorkbenchThemeService themeService: IWorkbenchThemeService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService private _instantiationService: IInstantiationService, @IClipboardService clipboardService: IClipboardService ) { - super(_wizard.title, name, partService, telemetryService, clipboardService, themeService, contextKeyService, options); + super(_wizard.title, name, telemetryService, layoutService, clipboardService, themeService, contextKeyService, options); this._useDefaultMessageBoxLocation = false; } @@ -305,4 +305,4 @@ export class WizardModal extends Modal { super.dispose(); this._dialogPanes.forEach(dialogPane => dialogPane.dispose()); } -} \ No newline at end of file +} diff --git a/src/sql/platform/node/pathUtilities.ts b/src/sql/platform/node/pathUtilities.ts index 36a10cd2c9..0c161b46f0 100644 --- a/src/sql/platform/node/pathUtilities.ts +++ b/src/sql/platform/node/pathUtilities.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'vs/base/common/paths'; +import { normalize, join, dirname } from 'vs/base/common/path'; import * as os from 'os'; import { URI } from 'vs/base/common/uri'; @@ -18,7 +18,7 @@ export function resolveCurrentDirectory(uri: string, rootPath: string): string { // use current directory of the sql file if sql file is saved if (sqlUri.scheme === FILE_SCHEMA) { - currentDirectory = path.dirname(sqlUri.fsPath); + currentDirectory = dirname(sqlUri.fsPath); } else if (sqlUri.scheme === Schemas.untitled) { // if sql file is unsaved/untitled but a workspace is open use workspace root let root = rootPath; @@ -29,14 +29,14 @@ export function resolveCurrentDirectory(uri: string, rootPath: string): string { currentDirectory = os.tmpdir(); } } else { - currentDirectory = path.dirname(sqlUri.path); + currentDirectory = dirname(sqlUri.path); } return currentDirectory; } export function resolveFilePath(uri: string, filePath: string, rootPath: string): string { let currentDirectory = resolveCurrentDirectory(uri, rootPath); - return path.normalize(path.join(currentDirectory, filePath)); + return normalize(join(currentDirectory, filePath)); } export function getRootPath(contextService: IWorkspaceContextService): string { diff --git a/src/sql/platform/node/resultSerializer.ts b/src/sql/platform/node/resultSerializer.ts index 812205a767..11b52fcf01 100644 --- a/src/sql/platform/node/resultSerializer.ts +++ b/src/sql/platform/node/resultSerializer.ts @@ -12,15 +12,13 @@ import { IQueryManagementService } from 'sql/platform/query/common/queryManageme import { ISaveRequest, SaveFormat } from 'sql/parts/grid/common/interfaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IOutputService, IOutputChannel, IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/parts/output/common/output'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IWindowsService, IWindowService, FileFilter } from 'vs/platform/windows/common/windows'; import { Registry } from 'vs/platform/registry/common/platform'; import { URI } from 'vs/base/common/uri'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { Schemas } from 'vs/base/common/network'; -import * as paths from 'vs/base/common/paths'; +import * as path from 'vs/base/common/path'; import * as nls from 'vs/nls'; import * as pretty from 'pretty-data'; @@ -30,6 +28,8 @@ import { getBaseLabel } from 'vs/base/common/labels'; import { ShowFileInFolderAction, OpenFileInFolderAction } from 'sql/workbench/common/workspaceActions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { getRootPath, resolveCurrentDirectory, resolveFilePath } from 'sql/platform/node/pathUtilities'; +import { IOutputService, IOutputChannelRegistry, IOutputChannel, Extensions as OutputExtensions } from 'vs/workbench/contrib/output/common/output'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; let prevSavePath: string; @@ -47,7 +47,7 @@ export class ResultSerializer { @IInstantiationService private _instantiationService: IInstantiationService, @IOutputService private _outputService: IOutputService, @IQueryManagementService private _queryManagementService: IQueryManagementService, - @IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService, + @IConfigurationService private _workspaceConfigurationService: IConfigurationService, @IEditorService private _editorService: IEditorService, @IWorkspaceContextService private _contextService: IWorkspaceContextService, @IWindowsService private _windowsService: IWindowsService, @@ -148,11 +148,11 @@ export class ResultSerializer { } private promptForFilepath(saveRequest: ISaveRequest): Thenable { - let filepathPlaceHolder = (prevSavePath) ? paths.dirname(prevSavePath) : resolveCurrentDirectory(this._uri, this.rootPath); - filepathPlaceHolder = paths.join(filepathPlaceHolder, this.getResultsDefaultFilename(saveRequest)); + let filepathPlaceHolder = (prevSavePath) ? path.dirname(prevSavePath) : resolveCurrentDirectory(this._uri, this.rootPath); + filepathPlaceHolder = path.join(filepathPlaceHolder, this.getResultsDefaultFilename(saveRequest)); return this._windowService.showSaveDialog({ title: nls.localize('resultsSerializer.saveAsFileTitle', 'Choose Results File'), - defaultPath: paths.normalize(filepathPlaceHolder, true), + defaultPath: path.normalize(filepathPlaceHolder), filters: this.getResultsFileExtension(saveRequest) }).then(filePath => { prevSavePath = filePath; @@ -277,7 +277,7 @@ export class ResultSerializer { private getParameters(filePath: string, batchIndex: number, resultSetNo: number, format: string, selection: Slick.Range): SaveResultsRequestParams { let saveResultsParams: SaveResultsRequestParams; - if (!paths.isAbsolute(filePath)) { + if (!path.isAbsolute(filePath)) { this._filePath = resolveFilePath(this._uri, filePath, this.rootPath); } else { this._filePath = filePath; @@ -315,7 +315,7 @@ export class ResultSerializer { private promptFileSavedNotification(savedFilePath: string) { - let label = getBaseLabel(paths.dirname(savedFilePath)); + let label = getBaseLabel(path.dirname(savedFilePath)); this._notificationService.prompt( Severity.Info, @@ -323,14 +323,14 @@ export class ResultSerializer { [{ label: nls.localize('openLocation', "Open file location"), run: () => { - let action = new ShowFileInFolderAction(savedFilePath, label || paths.sep, this._windowsService); + let action = new ShowFileInFolderAction(savedFilePath, label || path.sep, this._windowsService); action.run(); action.dispose(); } }, { label: nls.localize('openFile', "Open file"), run: () => { - let action = new OpenFileInFolderAction(savedFilePath, label || paths.sep, this._windowsService); + let action = new OpenFileInFolderAction(savedFilePath, label || path.sep, this._windowsService); action.run(); action.dispose(); } diff --git a/src/sql/platform/query/common/queryManagement.ts b/src/sql/platform/query/common/queryManagement.ts index 93b4b90d55..c6ebdafded 100644 --- a/src/sql/platform/query/common/queryManagement.ts +++ b/src/sql/platform/query/common/queryManagement.ts @@ -12,6 +12,7 @@ import * as TelemetryKeys from 'sql/common/telemetryKeys'; import * as TelemetryUtils from 'sql/common/telemetryUtilities'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Event, Emitter } from 'vs/base/common/event'; +import { keys } from 'vs/base/common/map'; export const SERVICE_ID = 'queryManagementService'; @@ -157,7 +158,7 @@ export class QueryManagementService implements IQueryManagementService { } public getRegisteredProviders(): string[] { - return Array.from(this._requestHandlers.keys()); + return Array.from(keys(this._requestHandlers)); } private addTelemetry(eventName: string, ownerUri: string, runOptions?: azdata.ExecutionPlanOptions): void { diff --git a/src/sql/services/bootstrap/bootstrapService.ts b/src/sql/services/bootstrap/bootstrapService.ts index 8c6c7279d5..1507e47738 100644 --- a/src/sql/services/bootstrap/bootstrapService.ts +++ b/src/sql/services/bootstrap/bootstrapService.ts @@ -8,15 +8,18 @@ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { IInstantiationService, _util } from 'vs/platform/instantiation/common/instantiation'; import { IEditorInput } from 'vs/workbench/common/editor'; import { Trace } from 'vs/platform/instantiation/common/instantiationService'; +import { values } from 'vs/base/common/map'; const selectorCounter = new Map(); export function providerIterator(service: IInstantiationService): Provider[] { - return Array.from(_util.serviceIds.values()).map(v => { + return Array.from(values(_util.serviceIds)).map(v => { + let factory = () => { + return (service)._getOrCreateServiceInstance(v, Trace.traceCreation(v)); + }; + factory.prototype = factory; return { - provide: v, useFactory: () => { - return (service)._getOrCreateServiceInstance(v, Trace.traceCreation(v)); - } + provide: v, useFactory: factory }; }); } diff --git a/src/sql/workbench/api/node/extHostModelView.ts b/src/sql/workbench/api/node/extHostModelView.ts index b0412782b2..fbbf7e7776 100644 --- a/src/sql/workbench/api/node/extHostModelView.ts +++ b/src/sql/workbench/api/node/extHostModelView.ts @@ -15,7 +15,7 @@ import * as azdata from 'azdata'; import { SqlMainContext, ExtHostModelViewShape, MainThreadModelViewShape, ExtHostModelViewTreeViewsShape } from 'sql/workbench/api/node/sqlExtHost.protocol'; import { IItemConfig, ModelComponentTypes, IComponentShape, IComponentEventArgs, ComponentEventType } from 'sql/workbench/api/common/sqlExtHostTypes'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; class ModelBuilderImpl implements azdata.ModelBuilder { private nextComponentId: number; diff --git a/src/sql/workbench/api/node/extHostModelViewDialog.ts b/src/sql/workbench/api/node/extHostModelViewDialog.ts index eb4d8a80d3..5cb37b297d 100644 --- a/src/sql/workbench/api/node/extHostModelViewDialog.ts +++ b/src/sql/workbench/api/node/extHostModelViewDialog.ts @@ -18,7 +18,7 @@ import { SqlMainContext, ExtHostModelViewDialogShape, MainThreadModelViewDialogS import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Inject } from '@angular/core'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; const DONE_LABEL = nls.localize('dialogDoneLabel', 'Done'); const CANCEL_LABEL = nls.localize('dialogCancelLabel', 'Cancel'); @@ -687,4 +687,4 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape { let handle = this.getHandle(wizard); return this._proxy.$closeWizard(handle); } -} \ No newline at end of file +} diff --git a/src/sql/workbench/api/node/extHostModelViewTree.ts b/src/sql/workbench/api/node/extHostModelViewTree.ts index effa95086c..dc50ffa30c 100644 --- a/src/sql/workbench/api/node/extHostModelViewTree.ts +++ b/src/sql/workbench/api/node/extHostModelViewTree.ts @@ -13,7 +13,7 @@ import { IMainContext } from 'vs/workbench/api/node/extHost.protocol'; import * as azdata from 'azdata'; import * as vsTreeExt from 'vs/workbench/api/node/extHostTreeViews'; import { Emitter } from 'vs/base/common/event'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export class ExtHostModelViewTreeViews implements ExtHostModelViewTreeViewsShape { private _proxy: MainThreadModelViewShape; @@ -166,4 +166,4 @@ export class ExtHostTreeView extends vsTreeExt.ExtHostTreeView { item = Object.assign({}, item, { checked: extensionTreeItem.checked, enabled: extensionTreeItem.enabled }); return item; } -} \ No newline at end of file +} diff --git a/src/sql/workbench/api/node/extHostObjectExplorer.ts b/src/sql/workbench/api/node/extHostObjectExplorer.ts index ec714f8a62..5593954951 100644 --- a/src/sql/workbench/api/node/extHostObjectExplorer.ts +++ b/src/sql/workbench/api/node/extHostObjectExplorer.ts @@ -8,6 +8,7 @@ import { IMainContext } from 'vs/workbench/api/node/extHost.protocol'; import { ExtHostObjectExplorerShape, SqlMainContext, MainThreadObjectExplorerShape } from 'sql/workbench/api/node/sqlExtHost.protocol'; import * as azdata from 'azdata'; import * as vscode from 'vscode'; +import { entries } from 'sql/base/common/objects'; export class ExtHostObjectExplorer implements ExtHostObjectExplorerShape { @@ -85,6 +86,13 @@ class ExtHostObjectExplorerNode implements azdata.objectexplorer.ObjectExplorerN } private getDetailsFromInfo(nodeInfo: azdata.NodeInfo): void { - Object.entries(nodeInfo).forEach(([key, value]) => this[key] = value); + this.nodePath = nodeInfo.nodePath; + this.nodeType = nodeInfo.nodeType; + this.nodeSubType = nodeInfo.nodeSubType; + this.nodeStatus = nodeInfo.nodeStatus; + this.label = nodeInfo.label; + this.isLeaf = nodeInfo.isLeaf; + this.metadata = nodeInfo.metadata; + this.errorMessage = nodeInfo.errorMessage; } } diff --git a/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts b/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts index 7eff11a4ce..8e3c91f3c5 100644 --- a/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts +++ b/src/sql/workbench/api/node/mainThreadNotebookDocumentsAndEditors.ts @@ -12,7 +12,6 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { IExtHostContext, IUndoStopOptions } from 'vs/workbench/api/node/extHost.protocol'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { viewColumnToEditorGroup } from 'vs/workbench/api/shared/editor'; @@ -32,6 +31,7 @@ import { NotebookChangeType, CellTypes } from 'sql/parts/notebook/models/contrac import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { notebookModeId } from 'sql/common/constants'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; class MainThreadNotebookEditor extends Disposable { private _contentChangedEmitter = new Emitter(); diff --git a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts index 1365d0b8ca..9c5cebb5c8 100644 --- a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts @@ -7,7 +7,6 @@ import * as extHostApi from 'vs/workbench/api/node/extHost.api.impl'; import { IInitData, IMainContext } from 'vs/workbench/api/node/extHost.protocol'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import * as extHostTypes from 'vs/workbench/api/node/extHostTypes'; import { URI } from 'vs/base/common/uri'; @@ -40,7 +39,7 @@ import { ExtHostNotebookDocumentsAndEditors } from 'sql/workbench/api/node/extHo import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry'; import { ExtHostExtensionManagement } from 'sql/workbench/api/node/extHostExtensionManagement'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { TernarySearchTree } from 'vs/base/common/map'; export interface ISqlExtensionApiFactory { diff --git a/src/sql/workbench/api/node/sqlExtHost.protocol.ts b/src/sql/workbench/api/node/sqlExtHost.protocol.ts index 64f95984bd..6c96229c19 100644 --- a/src/sql/workbench/api/node/sqlExtHost.protocol.ts +++ b/src/sql/workbench/api/node/sqlExtHost.protocol.ts @@ -24,7 +24,7 @@ import { } from 'sql/workbench/api/common/sqlExtHostTypes'; import { EditorViewColumn } from 'vs/workbench/api/shared/editor'; import { IUndoStopOptions } from 'vs/workbench/api/node/extHost.protocol'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export abstract class ExtHostAccountManagementShape { $autoOAuthCancelled(handle: number): Thenable { throw ni(); } @@ -871,4 +871,4 @@ export interface ExtHostExtensionManagementShape { export interface MainThreadExtensionManagementShape extends IDisposable { $install(vsixPath: string): Thenable; -} \ No newline at end of file +} diff --git a/src/sql/workbench/browser/modal/modal.ts b/src/sql/workbench/browser/modal/modal.ts index f0e6c8eacb..34c104ec94 100644 --- a/src/sql/workbench/browser/modal/modal.ts +++ b/src/sql/workbench/browser/modal/modal.ts @@ -5,7 +5,6 @@ import 'vs/css!./media/modal'; import { IThemable, attachButtonStyler } from 'vs/platform/theme/common/styler'; import { Color } from 'vs/base/common/color'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { mixin } from 'vs/base/common/objects'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; @@ -24,6 +23,7 @@ import { localize } from 'vs/nls'; import { MessageLevel } from 'sql/workbench/api/common/sqlExtHostTypes'; import * as os from 'os'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export const MODAL_SHOWING_KEY = 'modalShowing'; export const MODAL_SHOWING_CONTEXT = new RawContextKey>(MODAL_SHOWING_KEY, []); @@ -151,8 +151,8 @@ export abstract class Modal extends Disposable implements IThemable { constructor( private _title: string, private _name: string, - private _partService: IPartService, private _telemetryService: ITelemetryService, + protected layoutService: IWorkbenchLayoutService, protected _clipboardService: IClipboardService, protected _themeService: IThemeService, _contextKeyService: IContextKeyService, @@ -394,7 +394,7 @@ export abstract class Modal extends Disposable implements IThemable { */ protected show() { this._modalShowingContext.get().push(this._staticKey); - this._builder.appendTo(this._partService.getWorkbenchElement()); + this._builder.appendTo(this.layoutService.getWorkbenchElement()); this.setFocusableElements(); diff --git a/src/sql/workbench/browser/modal/optionsDialog.ts b/src/sql/workbench/browser/modal/optionsDialog.ts index 35edf2b7cf..1eb7405c18 100644 --- a/src/sql/workbench/browser/modal/optionsDialog.ts +++ b/src/sql/workbench/browser/modal/optionsDialog.ts @@ -16,7 +16,6 @@ import { ScrollableSplitView } from 'sql/base/browser/ui/scrollableSplitview/scr import * as azdata from 'azdata'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { Event, Emitter } from 'vs/base/common/event'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -34,6 +33,7 @@ import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/v import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export class CategoryView extends ViewletPanel { @@ -88,7 +88,7 @@ export class OptionsDialog extends Modal { title: string, name: string, options: IOptionsDialogOptions, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IWorkbenchThemeService private _workbenchThemeService: IWorkbenchThemeService, @IContextViewService private _contextViewService: IContextViewService, @IInstantiationService private _instantiationService: IInstantiationService, @@ -96,7 +96,7 @@ export class OptionsDialog extends Modal { @IContextKeyService contextKeyService: IContextKeyService, @IClipboardService clipboardService: IClipboardService ) { - super(title, name, partService, telemetryService, clipboardService, _workbenchThemeService, contextKeyService, options); + super(title, name, telemetryService, layoutService, clipboardService, _workbenchThemeService, contextKeyService, options); } public render() { @@ -274,4 +274,4 @@ export class OptionsDialog extends Modal { delete this._optionElements[optionName]; } } -} \ No newline at end of file +} diff --git a/src/sql/workbench/browser/parts/views/customView.ts b/src/sql/workbench/browser/parts/views/customView.ts index d012a5d2b2..3f6f09adf1 100644 --- a/src/sql/workbench/browser/parts/views/customView.ts +++ b/src/sql/workbench/browser/parts/views/customView.ts @@ -24,7 +24,7 @@ import { IDataSource, ITree, IRenderer, ContextMenuEvent } from 'vs/base/parts/t import { ResourceLabel } from 'vs/workbench/browser/labels'; import { ActionBar, IActionItemProvider, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { URI } from 'vs/base/common/uri'; -import { basename } from 'vs/base/common/paths'; +import { basename } from 'vs/base/common/path'; import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { FileKind } from 'vs/platform/files/common/files'; import { WorkbenchTreeController } from 'vs/platform/list/browser/listService'; @@ -393,7 +393,7 @@ export class CustomTreeView extends Disposable implements ITreeView { getOptimalWidth(): number { if (this.tree) { const parentNode = this.tree.getHTMLElement(); - const childNodes = ([] as Element[]).slice.call(parentNode.querySelectorAll('.outline-item-label > a')); + const childNodes = ([] as HTMLElement[]).slice.call(parentNode.querySelectorAll('.outline-item-label > a')); return DOM.getLargestChildWidth(parentNode, childNodes); } return 0; diff --git a/src/sql/workbench/common/actions.ts b/src/sql/workbench/common/actions.ts index 4be05406dc..11c414f2bd 100644 --- a/src/sql/workbench/common/actions.ts +++ b/src/sql/workbench/common/actions.ts @@ -26,8 +26,8 @@ import { IWindowsService } from 'vs/platform/windows/common/windows'; import * as nls from 'vs/nls'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export interface BaseActionContext { object?: ObjectMetadata; @@ -307,7 +307,7 @@ export class BackupAction extends Task { let workbenchEditorService = accessor.get(IEditorService); profile = TaskUtilities.getCurrentGlobalConnection(objectExplorerService, connectionManagementService, workbenchEditorService); } - let configurationService = accessor.get(IWorkspaceConfigurationService); + let configurationService = accessor.get(IConfigurationService); let previewFeaturesEnabled: boolean = configurationService.getValue('workbench')['enablePreviewFeatures']; if (!previewFeaturesEnabled) { return new Promise((resolve, reject) => { @@ -346,7 +346,7 @@ export class RestoreAction extends Task { } runTask(accessor: ServicesAccessor, profile: IConnectionProfile): Promise { - let configurationService = accessor.get(IWorkspaceConfigurationService); + let configurationService = accessor.get(IConfigurationService); let previewFeaturesEnabled: boolean = configurationService.getValue('workbench')['enablePreviewFeatures']; if (!previewFeaturesEnabled) { return new Promise((resolve, reject) => { diff --git a/src/sql/workbench/common/taskUtilities.ts b/src/sql/workbench/common/taskUtilities.ts index d42530eb4c..54212bb0f4 100644 --- a/src/sql/workbench/common/taskUtilities.ts +++ b/src/sql/workbench/common/taskUtilities.ts @@ -31,7 +31,7 @@ import * as azdata from 'azdata'; import Severity from 'vs/base/common/severity'; import * as nls from 'vs/nls'; -import * as path from 'vs/base/common/paths'; +import * as path from 'vs/base/common/path'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; // map for the version of SQL Server (default is 140) @@ -463,4 +463,4 @@ function getFilePath(metadata: azdata.ObjectMetadata): string { function getServerInfo(connectionService: IConnectionManagementService, ownerUri: string): azdata.ServerInfo { let connection: ConnectionManagementInfo = connectionService.getConnectionInfo(ownerUri); return connection.serverInfo; -} \ No newline at end of file +} diff --git a/src/sql/workbench/common/workspaceActions.ts b/src/sql/workbench/common/workspaceActions.ts index 0a401aea40..8a7b452092 100644 --- a/src/sql/workbench/common/workspaceActions.ts +++ b/src/sql/workbench/common/workspaceActions.ts @@ -1,5 +1,6 @@ import { Action } from 'vs/base/common/actions'; import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { URI } from 'vs/base/common/uri'; export class ShowFileInFolderAction extends Action { @@ -8,7 +9,7 @@ export class ShowFileInFolderAction extends Action { } run(): Promise { - return this.windowsService.showItemInFolder(this.path); + return this.windowsService.showItemInFolder(URI.parse(this.path)); } } @@ -21,4 +22,4 @@ export class OpenFileInFolderAction extends Action { run() { return this.windowsService.openExternal(this.path); } -} \ No newline at end of file +} diff --git a/src/sql/workbench/parts/connection/electron-browser/connectionViewlet.ts b/src/sql/workbench/parts/connection/electron-browser/connectionViewlet.ts index 279f5be87b..5b2e0b5bbb 100644 --- a/src/sql/workbench/parts/connection/electron-browser/connectionViewlet.ts +++ b/src/sql/workbench/parts/connection/electron-browser/connectionViewlet.ts @@ -23,12 +23,12 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ClearSearchAction, AddServerAction, AddServerGroupAction, ActiveConnectionsFilterAction } from 'sql/parts/objectExplorer/viewlet/connectionTreeAction'; import { warn } from 'sql/base/common/log'; import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IConnectionsViewlet } from 'sql/workbench/parts/connection/common/connectionViewlet'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet { @@ -47,12 +47,12 @@ export class ConnectionViewlet extends Viewlet implements IConnectionsViewlet { @IInstantiationService private _instantiationService: IInstantiationService, @INotificationService private _notificationService: INotificationService, @IObjectExplorerService private objectExplorerService: IObjectExplorerService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IConfigurationService configurationService: IConfigurationService, @IStorageService storageService: IStorageService ) { - super(VIEWLET_ID, configurationService, partService, telemetryService, _themeService, storageService); + super(VIEWLET_ID, configurationService, layoutService, telemetryService, _themeService, storageService); this._clearSearchAction = this._instantiationService.createInstance(ClearSearchAction, ClearSearchAction.ID, ClearSearchAction.LABEL, this); this._addServerAction = this._instantiationService.createInstance(AddServerAction, diff --git a/src/sql/workbench/parts/dataExplorer/electron-browser/nodeCommands.ts b/src/sql/workbench/parts/dataExplorer/electron-browser/nodeCommands.ts index 26034cc6b2..64e5da6fee 100644 --- a/src/sql/workbench/parts/dataExplorer/electron-browser/nodeCommands.ts +++ b/src/sql/workbench/parts/dataExplorer/electron-browser/nodeCommands.ts @@ -11,8 +11,9 @@ import { generateUri } from 'sql/platform/connection/common/utils'; import { ICustomViewDescriptor, TreeViewItemHandleArg } from 'sql/workbench/common/views'; import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { ViewsRegistry } from 'vs/workbench/common/views'; +import { IViewsRegistry, Extensions } from 'vs/workbench/common/views'; import { IProgressService2 } from 'vs/platform/progress/common/progress'; +import { Registry } from 'vs/platform/registry/common/platform'; export const DISCONNECT_COMMAND_ID = 'dataExplorer.disconnect'; export const MANAGE_COMMAND_ID = 'dataExplorer.manage'; @@ -25,7 +26,7 @@ CommandsRegistry.registerCommand({ if (args.$treeItem) { const oeService = accessor.get(IOEShimService); return oeService.disconnectNode(args.$treeViewId, args.$treeItem).then(() => { - const { treeView } = (ViewsRegistry.getView(args.$treeViewId)); + const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(args.$treeViewId)); // we need to collapse it then refresh it so that the tree doesn't try and use it's cache next time the user expands the node return treeView.collapse(args.$treeItem).then(() => treeView.refresh([args.$treeItem]).then(() => true)); }); @@ -83,7 +84,7 @@ CommandsRegistry.registerCommand({ handler: (accessor, args: TreeViewItemHandleArg) => { const progressSerivce = accessor.get(IProgressService2); if (args.$treeItem) { - const { treeView } = (ViewsRegistry.getView(args.$treeViewId)); + const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(args.$treeViewId)); if (args.$treeContainerId) { return progressSerivce.withProgress({ location: args.$treeContainerId }, () => treeView.refresh([args.$treeItem]).then(() => true)); } else { diff --git a/src/sql/workbench/parts/webview/electron-browser/webViewDialog.ts b/src/sql/workbench/parts/webview/electron-browser/webViewDialog.ts index ec68342fbe..6a639ac328 100644 --- a/src/sql/workbench/parts/webview/electron-browser/webViewDialog.ts +++ b/src/sql/workbench/parts/webview/electron-browser/webViewDialog.ts @@ -12,15 +12,15 @@ import * as TelemetryKeys from 'sql/common/telemetryKeys'; import { attachButtonStyler, attachModalDialogStyler } from 'sql/platform/theme/common/styler'; import { Builder } from 'sql/base/browser/builder'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; import { Event, Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { localize } from 'vs/nls'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { WebviewElement } from 'vs/workbench/parts/webview/electron-browser/webviewElement'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; export class WebViewDialog extends Modal { @@ -42,12 +42,12 @@ export class WebViewDialog extends Modal { constructor( @IThemeService themeService: IThemeService, @IClipboardService clipboardService: IClipboardService, - @IPartService private _webViewPartService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService private _instantiationService: IInstantiationService ) { - super('', TelemetryKeys.WebView, _webViewPartService, telemetryService, clipboardService, themeService, contextKeyService, { isFlyout: false, hasTitleIcon: true }); + super('', TelemetryKeys.WebView, telemetryService, layoutService, clipboardService, themeService, contextKeyService, { isFlyout: false, hasTitleIcon: true }); this._okLabel = localize('webViewDialog.ok', 'OK'); this._closeLabel = localize('webViewDialog.close', 'Close'); } @@ -89,9 +89,9 @@ export class WebViewDialog extends Modal { this._body = bodyBuilder.getHTMLElement(); this._webview = this._instantiationService.createInstance(WebviewElement, - this._webViewPartService.getContainer(Parts.EDITOR_PART), + this.layoutService.getContainer(Parts.EDITOR_PART), + {}, { - enableWrappedPostMessage: true, allowScripts: true }); @@ -166,4 +166,4 @@ export class WebViewDialog extends Modal { element.dispose(); }); } -} \ No newline at end of file +} diff --git a/src/sql/workbench/services/commandLine/common/commandLineService.ts b/src/sql/workbench/services/commandLine/common/commandLineService.ts index 456a036521..d406f6f2ca 100644 --- a/src/sql/workbench/services/commandLine/common/commandLineService.ts +++ b/src/sql/workbench/services/commandLine/common/commandLineService.ts @@ -16,11 +16,11 @@ import { IConnectionProviderRegistry, Extensions as ConnectionProviderExtensions import * as TaskUtilities from 'sql/workbench/common/taskUtilities'; import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { warn } from 'sql/base/common/log'; import { ipcRenderer as ipc} from 'electron'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export class CommandLineService implements ICommandLineProcessing { public _serviceBrand: any; @@ -33,7 +33,7 @@ export class CommandLineService implements ICommandLineProcessing { @IObjectExplorerService private _objectExplorerService: IObjectExplorerService, @IEditorService private _editorService: IEditorService, @ICommandService private _commandService: ICommandService, - @IWorkspaceConfigurationService private _configurationService: IWorkspaceConfigurationService + @IConfigurationService private _configurationService: IConfigurationService ) { if (ipc) { ipc.on('ads:processCommandLine', (event: any, args: ParsedArgs) => this.onLaunched(args)); @@ -128,4 +128,4 @@ export class CommandLineService implements ICommandLineProcessing { profile.setOptionValue('groupId', profile.groupId); return profile; } -} \ No newline at end of file +} diff --git a/src/sql/workbench/services/connection/browser/connectionDialogService.ts b/src/sql/workbench/services/connection/browser/connectionDialogService.ts index bc5ff6d179..c301a3e5e0 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogService.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogService.ts @@ -21,17 +21,17 @@ import { Deferred } from 'sql/base/common/promise'; import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService'; import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import * as platform from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { Action, IAction } from 'vs/base/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import * as types from 'vs/base/common/types'; import { trim } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export interface IConnectionValidateResult { isValid: boolean; @@ -82,11 +82,11 @@ export class ConnectionDialogService implements IConnectionDialogService { private _connectionManagementService: IConnectionManagementService; constructor( - @IPartService private _partService: IPartService, + @IWorkbenchLayoutService private layoutService: IWorkbenchLayoutService, @IInstantiationService private _instantiationService: IInstantiationService, @ICapabilitiesService private _capabilitiesService: ICapabilitiesService, @IErrorMessageService private _errorMessageService: IErrorMessageService, - @IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService, + @IConfigurationService private _configurationService: IConfigurationService, @IClipboardService private _clipboardService: IClipboardService, @ICommandService private _commandService: ICommandService ) { @@ -148,8 +148,8 @@ export class ConnectionDialogService implements IConnectionDialogService { } } } - if (!defaultProvider && this._workspaceConfigurationService) { - defaultProvider = WorkbenchUtils.getSqlConfigValue(this._workspaceConfigurationService, Constants.defaultEngine); + if (!defaultProvider && this._configurationService) { + defaultProvider = WorkbenchUtils.getSqlConfigValue(this._configurationService, Constants.defaultEngine); } // as a fallback, default to MSSQL if the value from settings is not available return defaultProvider || Constants.mssqlProviderName; @@ -398,7 +398,7 @@ export class ConnectionDialogService implements IConnectionDialogService { private doShowDialog(params: INewConnectionParams): Promise { if (!this._connectionDialog) { - let container = this._partService.getWorkbenchElement().parentElement; + let container = this.layoutService.getWorkbenchElement().parentElement; this._container = container; this._connectionDialog = this._instantiationService.createInstance(ConnectionDialogWidget, this._providerTypes, this._providerNameToDisplayNameMap[this._model.providerName], this._providerNameToDisplayNameMap); this._connectionDialog.onCancel(() => { diff --git a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts index 49ecda8818..77d1328f74 100644 --- a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts +++ b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts @@ -23,7 +23,6 @@ import { ClearRecentConnectionsAction } from 'sql/parts/connection/common/connec import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { Event, Emitter } from 'vs/base/common/event'; import { Builder, $ } from 'sql/base/browser/builder'; import { ICancelableEvent } from 'vs/base/parts/tree/browser/treeDefaults'; @@ -38,6 +37,7 @@ import * as DOM from 'vs/base/browser/dom'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export interface OnShowUIResponse { selectedProviderType: string; @@ -92,14 +92,14 @@ export class ConnectionDialogWidget extends Modal { @IInstantiationService private _instantiationService: IInstantiationService, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IWorkbenchThemeService private _workbenchThemeService: IWorkbenchThemeService, - @IPartService _partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService private _contextMenuService: IContextMenuService, @IContextViewService private _contextViewService: IContextViewService, @IClipboardService clipboardService: IClipboardService ) { - super(localize('connection', 'Connection'), TelemetryKeys.Connection, _partService, telemetryService, clipboardService, _workbenchThemeService, contextKeyService, { hasSpinner: true, hasErrors: true }); + super(localize('connection', 'Connection'), TelemetryKeys.Connection, telemetryService, layoutService, clipboardService, _workbenchThemeService, contextKeyService, { hasSpinner: true, hasErrors: true }); } /** @@ -467,4 +467,4 @@ export class ConnectionDialogWidget extends Modal { public get databaseDropdownExpanded(): boolean { return this._databaseDropdownExpanded; } -} \ No newline at end of file +} diff --git a/src/sql/workbench/services/dashboard/browser/newDashboardTabDialog.ts b/src/sql/workbench/services/dashboard/browser/newDashboardTabDialog.ts index c488116a96..5543eff159 100644 --- a/src/sql/workbench/services/dashboard/browser/newDashboardTabDialog.ts +++ b/src/sql/workbench/services/dashboard/browser/newDashboardTabDialog.ts @@ -10,7 +10,6 @@ import 'vs/css!./media/newDashboardTabDialog'; import * as DOM from 'vs/base/browser/dom'; import { List } from 'vs/base/browser/ui/list/listWidget'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { Event, Emitter } from 'vs/base/common/event'; import { localize } from 'vs/nls'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -29,6 +28,7 @@ import { NewDashboardTabViewModel, IDashboardUITab } from 'sql/workbench/service import { IDashboardTab } from 'sql/platform/dashboard/common/dashboardRegistry'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; import { Orientation } from 'vs/base/browser/ui/sash/sash'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; class ExtensionListDelegate implements IListVirtualDelegate { @@ -113,7 +113,7 @@ export class NewDashboardTabDialog extends Modal { public get onCancel(): Event { return this._onCancel.event; } constructor( - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IThemeService themeService: IThemeService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @@ -122,8 +122,8 @@ export class NewDashboardTabDialog extends Modal { super( localize('newDashboardTab.openDashboardExtensions', 'Open dashboard extensions'), TelemetryKeys.AddNewDashboardTab, - partService, telemetryService, + layoutService, clipboardService, themeService, contextKeyService, diff --git a/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts b/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts index ae5c38a882..7b1588adfb 100644 --- a/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts +++ b/src/sql/workbench/services/errorMessage/browser/errorMessageDialog.ts @@ -15,13 +15,13 @@ import { Builder } from 'sql/base/browser/builder'; import Severity from 'vs/base/common/severity'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import { Event, Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { localize } from 'vs/nls'; import { IAction } from 'vs/base/common/actions'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; const maxActions = 1; @@ -44,11 +44,11 @@ export class ErrorMessageDialog extends Modal { constructor( @IThemeService themeService: IThemeService, @IClipboardService clipboardService: IClipboardService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService ) { - super('', TelemetryKeys.ErrorMessage, partService, telemetryService, clipboardService, themeService, contextKeyService, { isFlyout: false, hasTitleIcon: true }); + super('', TelemetryKeys.ErrorMessage, telemetryService, layoutService, clipboardService, themeService, contextKeyService, { isFlyout: false, hasTitleIcon: true }); this._okLabel = localize('errorMessageDialog.ok', 'OK'); this._closeLabel = localize('errorMessageDialog.close', 'Close'); } @@ -175,4 +175,4 @@ export class ErrorMessageDialog extends Modal { public dispose(): void { } -} \ No newline at end of file +} diff --git a/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts b/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts index 3e0f94c0e0..528e061fa9 100644 --- a/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts +++ b/src/sql/workbench/services/fileBrowser/browser/fileBrowserDialog.ts @@ -29,10 +29,10 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import * as DOM from 'vs/base/browser/dom'; import * as strings from 'vs/base/common/strings'; import { IClipboardService } from 'sql/platform/clipboard/common/clipboardService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export class FileBrowserDialog extends Modal { private _viewModel: FileBrowserViewModel; @@ -50,7 +50,7 @@ export class FileBrowserDialog extends Modal { private _isFolderSelected: boolean; constructor(title: string, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IWorkbenchThemeService private _workbenchthemeService: IWorkbenchThemeService, @IInstantiationService private _instantiationService: IInstantiationService, @IContextViewService private _contextViewService: IContextViewService, @@ -58,7 +58,7 @@ export class FileBrowserDialog extends Modal { @IContextKeyService contextKeyService: IContextKeyService, @IClipboardService clipboardService: IClipboardService ) { - super(title, TelemetryKeys.Backup, partService, telemetryService, clipboardService, _workbenchthemeService, contextKeyService, { isFlyout: true, hasTitleIcon: false, hasBackButton: true, hasSpinner: true }); + super(title, TelemetryKeys.Backup, telemetryService, layoutService, clipboardService, _workbenchthemeService, contextKeyService, { isFlyout: true, hasTitleIcon: false, hasBackButton: true, hasSpinner: true }); this._viewModel = this._instantiationService.createInstance(FileBrowserViewModel); this._viewModel.onAddFileTree(args => this.handleOnAddFileTree(args.rootNode, args.selectedNode, args.expandedNodes)); this._viewModel.onPathValidate(args => this.handleOnValidate(args.succeeded, args.message)); @@ -245,4 +245,4 @@ export class FileBrowserDialog extends Modal { this._treeContainer.style('background-color', this.headerAndFooterBackground); } } -} \ No newline at end of file +} diff --git a/src/sql/workbench/services/fileBrowser/browser/fileBrowserRenderer.ts b/src/sql/workbench/services/fileBrowser/browser/fileBrowserRenderer.ts index fbc3a54067..cf033b0846 100644 --- a/src/sql/workbench/services/fileBrowser/browser/fileBrowserRenderer.ts +++ b/src/sql/workbench/services/fileBrowser/browser/fileBrowserRenderer.ts @@ -9,9 +9,9 @@ import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree'; import { FileKind } from 'vs/platform/files/common/files'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IFileTemplateData } from 'vs/workbench/parts/files/electron-browser/views/explorerViewer'; import { toDisposable } from 'vs/base/common/lifecycle'; import { ResourceLabels, DEFAULT_LABELS_CONTAINER } from 'vs/workbench/browser/labels'; +import { IFileTemplateData } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; const EmptyDisposable = toDisposable(() => null); @@ -78,4 +78,4 @@ export class FileBrowserRenderer implements IRenderer { public disposeTemplate(tree: ITree, templateId: string, templateData: IFileTemplateData): void { templateData.label.dispose(); } -} \ No newline at end of file +} diff --git a/src/sql/workbench/services/insights/browser/insightsDialogView.ts b/src/sql/workbench/services/insights/browser/insightsDialogView.ts index 427bde12ae..37f72e2387 100644 --- a/src/sql/workbench/services/insights/browser/insightsDialogView.ts +++ b/src/sql/workbench/services/insights/browser/insightsDialogView.ts @@ -23,7 +23,6 @@ import { IClipboardService } from 'sql/platform/clipboard/common/clipboardServic import { IDisposableDataProvider } from 'sql/base/browser/ui/table/interfaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; import * as DOM from 'vs/base/browser/dom'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -41,6 +40,7 @@ import { SplitView, Orientation, Sizing } from 'vs/base/browser/ui/splitview/spl import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; const labelDisplay = nls.localize("insights.item", "Item"); const valueDisplay = nls.localize("insights.value", "Value"); @@ -158,7 +158,7 @@ export class InsightsDialogView extends Modal { private _model: IInsightsDialogModel, @IInstantiationService private _instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextMenuService private _contextMenuService: IContextMenuService, @ITelemetryService telemetryService: ITelemetryService, @IContextKeyService contextKeyService: IContextKeyService, @@ -166,7 +166,7 @@ export class InsightsDialogView extends Modal { @ICapabilitiesService private _capabilitiesService: ICapabilitiesService, @IClipboardService clipboardService: IClipboardService ) { - super(nls.localize("InsightsDialogTitle", "Insights"), TelemetryKeys.Insights, partService, telemetryService, clipboardService, themeService, contextKeyService); + super(nls.localize("InsightsDialogTitle", "Insights"), TelemetryKeys.Insights, telemetryService, layoutService, clipboardService, themeService, contextKeyService); this._model.onDataChange(e => this.build()); } diff --git a/src/sql/workbench/services/notebook/common/notebookServiceImpl.ts b/src/sql/workbench/services/notebook/common/notebookServiceImpl.ts index 0539f6959a..15b45ddfb5 100644 --- a/src/sql/workbench/services/notebook/common/notebookServiceImpl.ts +++ b/src/sql/workbench/services/notebook/common/notebookServiceImpl.ts @@ -29,13 +29,14 @@ import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/c import { NotebookEditorVisibleContext } from 'sql/workbench/services/notebook/common/notebookContext'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { NotebookEditor } from 'sql/parts/notebook/notebookEditor'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerNotebookThemes } from 'sql/parts/notebook/notebookStyles'; import { IQueryManagementService } from 'sql/platform/query/common/queryManagement'; import { ILanguageMagic, notebookConstants } from 'sql/parts/notebook/models/modelInterfaces'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { SqlNotebookProvider } from 'sql/workbench/services/notebook/sql/sqlNotebookProvider'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { keys } from 'vs/base/common/map'; export interface NotebookProviderProperties { provider: string; @@ -271,7 +272,7 @@ export class NotebookService extends Disposable implements INotebookService { } getSupportedFileExtensions(): string[] { - return Array.from(this._fileToProviders.keys()); + return Array.from(keys(this._fileToProviders)); } getProvidersForFileType(fileType: string): string[] { diff --git a/src/sql/workbench/services/objectExplorer/common/objectExplorerService.ts b/src/sql/workbench/services/objectExplorer/common/objectExplorerService.ts index 2a7aa91c47..4746118ffb 100644 --- a/src/sql/workbench/services/objectExplorer/common/objectExplorerService.ts +++ b/src/sql/workbench/services/objectExplorer/common/objectExplorerService.ts @@ -21,6 +21,7 @@ import { warn, error } from 'sql/base/common/log'; import { ServerTreeView } from 'sql/parts/objectExplorer/viewlet/serverTreeView'; import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; import * as Utils from 'sql/platform/connection/common/utils'; +import { entries } from 'sql/base/common/objects'; export const SERVICE_ID = 'ObjectExplorerService'; @@ -490,7 +491,7 @@ export class ObjectExplorerService implements IObjectExplorerService { // Complete any requests that are still open for the session let sessionStatus = this._sessions[session.sessionId]; if (sessionStatus && sessionStatus.nodes) { - Object.entries(sessionStatus.nodes).forEach(([nodePath, nodeStatus]: [string, NodeStatus]) => { + entries(sessionStatus.nodes).forEach(([nodePath, nodeStatus]: [string, NodeStatus]) => { if (nodeStatus.expandEmitter) { nodeStatus.expandEmitter.fire({ sessionId: session.sessionId, @@ -800,4 +801,4 @@ export class ObjectExplorerService implements IObjectExplorerService { } return currentNode; } -} \ No newline at end of file +} diff --git a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts index 28332fff55..f8842c2f0d 100644 --- a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts +++ b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts @@ -7,7 +7,6 @@ import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput'; import { QueryInput } from 'sql/parts/query/common/queryInput'; import { EditDataInput } from 'sql/parts/editData/common/editDataInput'; import { IConnectableInput, IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; -import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; import { IQueryEditorService, IQueryEditorOptions } from 'sql/workbench/services/queryEditor/common/queryEditorService'; import { QueryPlanInput } from 'sql/parts/queryPlan/queryPlanInput'; import { sqlModeId, untitledFilePrefix, getSupportedInputResource } from 'sql/parts/common/customInputConverter'; @@ -18,11 +17,10 @@ import { ITextModel } from 'vs/editor/common/model'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; import Severity from 'vs/base/common/severity'; import nls = require('vs/nls'); import { URI } from 'vs/base/common/uri'; -import paths = require('vs/base/common/paths'); +import paths = require('vs/base/common/extpath'); import { isLinux } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -31,6 +29,8 @@ import { IEditorInput, IEditor } from 'vs/workbench/common/editor'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ILanguageSelection } from 'vs/editor/common/services/modeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; const fs = require('fs'); diff --git a/src/sql/workbench/update/releaseNotes.ts b/src/sql/workbench/update/releaseNotes.ts index 76c60b731b..5b9ac97e14 100644 --- a/src/sql/workbench/update/releaseNotes.ts +++ b/src/sql/workbench/update/releaseNotes.ts @@ -7,13 +7,13 @@ import nls = require('vs/nls'); import { Action } from 'vs/base/common/actions'; -import pkg from 'vs/platform/node/package'; -import product from 'vs/platform/node/product'; +import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/node/product'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { AbstractShowReleaseNotesAction } from 'vs/workbench/parts/update/electron-browser/update'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { AbstractShowReleaseNotesAction } from 'vs/workbench/contrib/update/electron-browser/update'; export class OpenGettingStartedInBrowserAction extends Action { diff --git a/src/sqltest/parts/commandLine/commandLineService.test.ts b/src/sqltest/parts/commandLine/commandLineService.test.ts index 8fc15a7bf3..a8dba8db58 100644 --- a/src/sqltest/parts/commandLine/commandLineService.test.ts +++ b/src/sqltest/parts/commandLine/commandLineService.test.ts @@ -22,9 +22,9 @@ import { TestConnectionManagementService } from 'sqltest/stubs/connectionManagem import { ICommandService } from 'vs/platform/commands/common/commands'; import { TestCommandService } from 'vs/editor/test/browser/editorTestServices'; import { WorkspaceConfigurationTestService } from 'sqltest/stubs/workspaceConfigurationTestService'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IConnectionProfile } from 'sql/platform/connection/common/interfaces'; import { assertThrowsAsync } from 'sqltest/utils/testUtils'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; class TestParsedArgs implements ParsedArgs { [arg: string]: any; @@ -63,7 +63,7 @@ class TestParsedArgs implements ParsedArgs { locale?: string; log?: string; logExtensionHostCommunication?: boolean; - 'max-memory'?: number; + 'max-memory'?: string; 'new-window'?: boolean; 'open-url'?: boolean; performance?: boolean; @@ -104,7 +104,7 @@ suite('commandLineService tests', () => { }); function getCommandLineService(connectionManagementService: IConnectionManagementService, - configurationService: IWorkspaceConfigurationService, + configurationService: IConfigurationService, capabilitiesService?: ICapabilitiesService, commandService?: ICommandService ): CommandLineService { @@ -121,8 +121,8 @@ suite('commandLineService tests', () => { return service; } - function getConfigurationServiceMock(showConnectDialogOnStartup: boolean): TypeMoq.Mock { - let configurationService = TypeMoq.Mock.ofType(WorkspaceConfigurationTestService); + function getConfigurationServiceMock(showConnectDialogOnStartup: boolean): TypeMoq.Mock { + let configurationService = TypeMoq.Mock.ofType(WorkspaceConfigurationTestService); configurationService.setup((c) => c.getValue(TypeMoq.It.isAnyString())).returns((config: string) => showConnectDialogOnStartup); return configurationService; } @@ -283,4 +283,3 @@ suite('commandLineService tests', () => { assertThrowsAsync(async () => await service.processCommandLine(args)); }); }); - diff --git a/src/sqltest/parts/connection/connectionConfig.test.ts b/src/sqltest/parts/connection/connectionConfig.test.ts index 144671e3dc..eea6f93b5c 100644 --- a/src/sqltest/parts/connection/connectionConfig.test.ts +++ b/src/sqltest/parts/connection/connectionConfig.test.ts @@ -12,7 +12,6 @@ import { IConnectionProfile, IConnectionProfileStore } from 'sql/platform/connec import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { WorkspaceConfigurationTestService } from 'sqltest/stubs/workspaceConfigurationTestService'; -import { IConfigurationValue, ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService'; import * as Constants from 'sql/platform/connection/common/constants'; import { IConnectionProfileGroup, ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; import * as assert from 'assert'; @@ -21,6 +20,7 @@ import * as azdata from 'azdata'; import { Emitter } from 'vs/base/common/event'; import { ConnectionOptionSpecialType, ServiceOptionType } from 'sql/workbench/api/common/sqlExtHostTypes'; import { CapabilitiesTestService } from 'sqltest/stubs/capabilitiesTestService'; +import { ConfigurationEditingService, IConfigurationValue } from 'vs/workbench/services/configuration/common/configurationEditingService'; suite('SQL ConnectionConfig tests', () => { let capabilitiesService: TypeMoq.Mock; @@ -952,4 +952,3 @@ suite('SQL ConnectionConfig tests', () => { }); }); - diff --git a/src/sqltest/parts/query/editor/queryEditor.test.ts b/src/sqltest/parts/query/editor/queryEditor.test.ts index 832840b58d..dc693681f9 100644 --- a/src/sqltest/parts/query/editor/queryEditor.test.ts +++ b/src/sqltest/parts/query/editor/queryEditor.test.ts @@ -122,14 +122,14 @@ suite('SQL QueryEditor Tests', () => { // Create a QueryInput let filePath = 'file://someFile.sql'; let uri: URI = URI.parse(filePath); - let fileInput = new UntitledEditorInput(uri, false, '', '', '', instantiationService.object, undefined, undefined, undefined); + let fileInput = new UntitledEditorInput(uri, false, '', '', '', instantiationService.object, undefined, undefined); let queryResultsInput: QueryResultsInput = new QueryResultsInput(uri.fsPath, configurationService.object); queryInput = new QueryInput('first', fileInput, queryResultsInput, undefined, undefined, undefined, undefined, undefined); // Create a QueryInput to compare to the previous one let filePath2 = 'file://someFile2.sql'; let uri2: URI = URI.parse(filePath2); - let fileInput2 = new UntitledEditorInput(uri2, false, '', '', '', instantiationService.object, undefined, undefined, undefined); + let fileInput2 = new UntitledEditorInput(uri2, false, '', '', '', instantiationService.object, undefined, undefined); let queryResultsInput2: QueryResultsInput = new QueryResultsInput(uri2.fsPath, configurationService.object); queryInput2 = new QueryInput('second', fileInput2, queryResultsInput2, undefined, undefined, undefined, undefined, undefined); @@ -357,7 +357,7 @@ suite('SQL QueryEditor Tests', () => { return new RunQueryAction(undefined, undefined, undefined); }); - let fileInput = new UntitledEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService.object, undefined, undefined, undefined); + let fileInput = new UntitledEditorInput(URI.parse('file://testUri'), false, '', '', '', instantiationService.object, undefined, undefined); queryModelService = TypeMoq.Mock.ofType(QueryModelService, TypeMoq.MockBehavior.Loose, undefined, undefined); queryModelService.callBase = true; queryModelService.setup(x => x.disposeQuery(TypeMoq.It.isAny())).returns(() => void 0); diff --git a/src/sqltest/stubs/editorGroupService.ts b/src/sqltest/stubs/editorGroupService.ts index 578c01e564..f19e81e1ee 100644 --- a/src/sqltest/stubs/editorGroupService.ts +++ b/src/sqltest/stubs/editorGroupService.ts @@ -7,12 +7,26 @@ import { ServiceIdentifier, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; -import { IEditorGroupsService, GroupOrientation, IEditorGroup, GroupDirection, GroupsArrangement, IMoveEditorOptions, GroupsOrder, EditorGroupLayout, IFindGroupScope, IAddGroupOptions, IMergeGroupOptions } from 'vs/workbench/services/group/common/editorGroupsService'; import { IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor'; -import { IEditorInput, EditorInput, GroupIdentifier } from 'vs/workbench/common/editor'; +import { IEditorInput, EditorInput, GroupIdentifier, IEditorPartOptions } from 'vs/workbench/common/editor'; import { EditorGroup } from 'vs/workbench/common/editor/editorGroup'; +import { IEditorGroupsService, IFindGroupScope, IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupOrientation, GroupsOrder, GroupsArrangement, IMoveEditorOptions, EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IDimension } from 'vs/editor/common/editorCommon'; +import { IDisposable } from 'vs/base/common/lifecycle'; export class EditorGroupTestService implements IEditorGroupsService { + dimension: IDimension; + whenRestored: Promise; + centerLayout(active: boolean): void { + throw new Error('Method not implemented.'); + } + isLayoutCentered(): boolean { + throw new Error('Method not implemented.'); + } + partOptions: IEditorPartOptions; + enforcePartOptions(options: IEditorPartOptions): IDisposable { + throw new Error('Method not implemented.'); + } _serviceBrand: ServiceIdentifier; findGroup(scope: IFindGroupScope, source?: IEditorGroup | GroupIdentifier, wrap?: boolean): IEditorGroup { @@ -43,12 +57,16 @@ export class EditorGroupTestService implements IEditorGroupsService { onEditorGroupMoved: Event; + onDidLayout: Event; + /** * An event for when the active editor group changes. The active editor * group is the default location for new editors to open. */ readonly onDidActiveGroupChange: Event; + onDidActivateGroup: Event; + /** * An event for when a new group was added. */ @@ -214,4 +232,4 @@ export class EditorGroupTestService implements IEditorGroupsService { applyLayout(layout: EditorGroupLayout): void { } -} \ No newline at end of file +} diff --git a/src/sqltest/stubs/storageTestService.ts b/src/sqltest/stubs/storageTestService.ts index 27824e2cf9..76ab3699b8 100644 --- a/src/sqltest/stubs/storageTestService.ts +++ b/src/sqltest/stubs/storageTestService.ts @@ -65,6 +65,12 @@ export class StorageTestService implements IStorageService { return undefined; } + getNumber(key: string, scope: StorageScope, fallbackValue: number): number; + getNumber(key: string, scope: StorageScope, fallbackValue?: number): number; + getNumber(key: any, scope: any, fallbackValue?: any): number { + return undefined; + } + /** * Retrieve an element stored with the given key from local storage. Use * the provided defaultValue if the element is null or undefined. The element @@ -86,4 +92,4 @@ export class StorageTestService implements IStorageService { getBoolean(key: string, scope?: StorageScope, defaultValue?: boolean): boolean { return true; } -} \ No newline at end of file +} diff --git a/src/sqltest/stubs/workbenchEditorTestService.ts b/src/sqltest/stubs/workbenchEditorTestService.ts index 9991e14a9f..f26ba30268 100644 --- a/src/sqltest/stubs/workbenchEditorTestService.ts +++ b/src/sqltest/stubs/workbenchEditorTestService.ts @@ -9,9 +9,9 @@ import { IEditorOptions, IResourceInput, ITextEditorOptions } from 'vs/platform import { IEditor, IEditorInput, IResourceDiffInput, IResourceSideBySideInput, GroupIdentifier, ITextEditor, IUntitledResourceInput, ITextDiffEditor, ITextSideBySideEditor, IEditorInputWithOptions } from 'vs/workbench/common/editor'; import { Event } from 'vs/base/common/event'; import { Position } from 'vs/editor/common/core/position'; -import { IEditorGroup, IEditorReplacement } from 'vs/workbench/services/group/common/editorGroupsService'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { IEditorGroup, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; export class WorkbenchEditorTestService implements IEditorService { _serviceBrand: ServiceIdentifier; @@ -109,4 +109,4 @@ export class WorkbenchEditorTestService implements IEditorService { getOpened(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier): IEditorInput { return undefined; } -} \ No newline at end of file +} diff --git a/src/sqltest/stubs/workspaceConfigurationTestService.ts b/src/sqltest/stubs/workspaceConfigurationTestService.ts index 533abdd76b..f70a806f8a 100644 --- a/src/sqltest/stubs/workspaceConfigurationTestService.ts +++ b/src/sqltest/stubs/workspaceConfigurationTestService.ts @@ -4,13 +4,12 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; -import { IConfigurationData, IConfigurationOverrides, ConfigurationTarget, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationData, IConfigurationOverrides, ConfigurationTarget, IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Event } from 'vs/base/common/event'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -export class WorkspaceConfigurationTestService implements IWorkspaceConfigurationService { +export class WorkspaceConfigurationTestService implements IConfigurationService { _serviceBrand: any; getValue(): T; diff --git a/src/sqltest/workbench/common/taskUtilities.test.ts b/src/sqltest/workbench/common/taskUtilities.test.ts index c946e82b1e..58b958246f 100644 --- a/src/sqltest/workbench/common/taskUtilities.test.ts +++ b/src/sqltest/workbench/common/taskUtilities.test.ts @@ -77,7 +77,7 @@ suite('TaskUtilities', function () { // Mock the workbench service to return the active tab connection let tabConnectionUri = 'file://test_uri'; - let editorInput = new UntitledEditorInput(URI.parse(tabConnectionUri), false, undefined, undefined, undefined, undefined, undefined, undefined, undefined); + let editorInput = new UntitledEditorInput(URI.parse(tabConnectionUri), false, undefined, undefined, undefined, undefined, undefined, undefined); let queryInput = new QueryInput(undefined, editorInput, undefined, undefined, undefined, undefined, undefined, undefined); mockConnectionManagementService.setup(x => x.getConnectionProfile(tabConnectionUri)).returns(() => tabProfile); @@ -106,4 +106,4 @@ suite('TaskUtilities', function () { let actualProfile = TaskUtilities.getCurrentGlobalConnection(mockObjectExplorerService.object, mockConnectionManagementService.object, mockWorkbenchEditorService.object); assert.equal(actualProfile, oeProfile); }); -}); \ No newline at end of file +}); diff --git a/src/tsconfig.base.json b/src/tsconfig.base.json index 9c55727cb7..cd4c623357 100644 --- a/src/tsconfig.base.json +++ b/src/tsconfig.base.json @@ -1,12 +1,12 @@ { "compilerOptions": { "module": "amd", - "moduleResolution": "classic", + "moduleResolution": "node", "noImplicitAny": false, "target": "es5", "experimentalDecorators": true, "noImplicitReturns": true, - "noUnusedLocals": true, + "noUnusedLocals": false, "noImplicitThis": true, "alwaysStrict": true, "strictBindCallApply": true, @@ -14,6 +14,12 @@ "paths": { "vs/*": [ "./vs/*" + ], + "sql/*": [ + "./sql/*" + ], + "sqltest/*": [ + "./sqltest/*" ] }, "types": [ @@ -25,4 +31,4 @@ "winreg" ] } -} \ No newline at end of file +} diff --git a/src/tsconfig.json b/src/tsconfig.json index 13257d3968..5a3597bf75 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -1,30 +1,24 @@ { + "extends": "./tsconfig.base.json", "compilerOptions": { - "module": "amd", - "moduleResolution": "classic", - "noImplicitAny": false, "removeComments": false, "preserveConstEnums": true, - "target": "es5", "sourceMap": false, - "experimentalDecorators": true, - "declaration": true, - "noImplicitReturns": true, - "noUnusedLocals": false, - "noImplicitThis": true, - "alwaysStrict": true, - "baseUrl": ".", "outDir": "../out", - "typeRoots": [ - "typings" - ], - "types": [ - "mocha" + "target": "es6", + "lib": [ + "dom", + "es5", + "es2015.iterable" ] }, + "include": [ + "./typings", + "./vs", + "./sql", + "./sqltest" + ], "exclude": [ - "../out", - "../out-build", - "../out-vscode" + "./typings/require-monaco.d.ts" ] -} \ No newline at end of file +} diff --git a/src/tsconfig.strictNullChecks.json b/src/tsconfig.strictNullChecks.json index 0a62155bf7..9bec4ee930 100644 --- a/src/tsconfig.strictNullChecks.json +++ b/src/tsconfig.strictNullChecks.json @@ -6,901 +6,469 @@ }, "include": [ "./typings", - "./vs/base/browser/**/*.ts", - "./vs/base/common/**/*.ts", - "./vs/base/node/**/*.ts", - "./vs/editor/browser/**/*.ts", - "./vs/editor/common/**/*.ts", - "./vs/editor/contrib/codeAction/**/*.ts", - "./vs/editor/contrib/format/**/*.ts", - "./vs/editor/contrib/gotoError/**/*.ts", - "./vs/editor/contrib/inPlaceReplace/**/*.ts", - "./vs/editor/contrib/smartSelect/**/*.ts", - "./vs/editor/contrib/snippet/**/*.ts", - "./vs/editor/contrib/suggest/**/*.ts", - "./vs/editor/test/**/*.ts", - // Begin SQL files - "./sql/base/common/**/*ts", - // End SQL files + "./vs/base/**/*.ts", + "./vs/code/**/*.ts", + "./vs/editor/**/*.ts", + "./vs/platform/**/*.ts", + "./vs/workbench/common/**/*", + "./vs/workbench/browser/**/*", + "./vs/workbench/electron-browser/**/*", + "./vs/workbench/contrib/emmet/**/*", + "./vs/workbench/contrib/externalTerminal/**/*", + "./vs/workbench/contrib/scm/**/*.ts", + "./vs/workbench/contrib/snippets/**/*.ts", + "./vs/workbench/contrib/outline/**/*.ts", + "./vs/workbench/contrib/performance/**/*.ts", + "./vs/workbench/contrib/welcome/**/*.ts", + "./vs/workbench/contrib/issue/**/*", + "./vs/workbench/contrib/splash/**/*.ts", + "./vs/workbench/contrib/tasks/**/*.ts", + "./vs/workbench/services/commands/**/*", + "./vs/workbench/services/configuration/common/**/*", + "./vs/workbench/services/files/node/watcher/**/*", + "./vs/workbench/services/themes/**/*.ts", + "./vs/workbench/services/bulkEdit/**/*.ts", + "./vs/workbench/services/output/**/*.ts", + "./vs/workbench/services/preferences/**/*.ts", + "./vs/workbench/services/timer/**/*.ts", + "./vs/workbench/contrib/debug/**/*.ts", + "./vs/workbench/contrib/files/**/*.ts", + "./vs/workbench/contrib/webview/**/*.ts", + "./vs/workbench/contrib/preferences/common/**/*.ts", + "./vs/workbench/contrib/preferences/**/settings*.ts", + "./vs/workbench/contrib/terminal/**/*" ], "files": [ - // "./vs/base/parts/contextmenu/common/contextmenu.ts", - // "./vs/base/parts/contextmenu/electron-browser/contextmenu.ts", - // "./vs/base/parts/contextmenu/electron-main/contextmenu.ts", - // "./vs/base/parts/ipc/electron-browser/ipc.electron-browser.ts", - // "./vs/base/parts/ipc/electron-main/ipc.electron-main.ts", - // "./vs/base/parts/ipc/node/ipc.cp.ts", - // "./vs/base/parts/ipc/node/ipc.electron.ts", - // "./vs/base/parts/ipc/node/ipc.net.ts", - // "./vs/base/parts/ipc/node/ipc.ts", - // "./vs/base/parts/ipc/test/node/ipc.cp.test.ts", - // "./vs/base/parts/ipc/test/node/ipc.net.test.ts", - // "./vs/base/parts/ipc/test/node/ipc.test.ts", - // "./vs/base/parts/ipc/test/node/testApp.ts", - // "./vs/base/parts/ipc/test/node/testService.ts", - // "./vs/base/parts/quickopen/common/quickOpen.ts", - // "./vs/base/parts/quickopen/common/quickOpenScorer.ts", - // "./vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts", - // "./vs/base/parts/tree/browser/tree.ts", - // "./vs/base/parts/tree/browser/treeDefaults.ts", - // "./vs/base/parts/tree/browser/treeDnd.ts", - // "./vs/base/parts/tree/browser/treeImpl.ts", - // "./vs/base/parts/tree/browser/treeModel.ts", - // "./vs/base/parts/tree/browser/treeUtils.ts", - // "./vs/base/parts/tree/browser/treeView.ts", - // "./vs/base/parts/tree/browser/treeViewModel.ts", - // "./vs/base/parts/tree/test/browser/treeModel.test.ts", - // "./vs/base/parts/tree/test/browser/treeViewModel.test.ts", - // "./vs/base/test/browser/browser.test.ts", - // "./vs/base/test/browser/comparers.test.ts", - // "./vs/base/test/browser/dom.test.ts", - // "./vs/base/test/browser/highlightedLabel.test.ts", - // "./vs/base/test/browser/htmlContent.test.ts", - // "./vs/base/test/browser/progressBar.test.ts", - // "./vs/base/test/browser/ui/contextview/contextview.test.ts", - // "./vs/base/test/browser/ui/grid/grid.test.ts", - // "./vs/base/test/browser/ui/grid/gridview.test.ts", - // "./vs/base/test/browser/ui/grid/util.ts", - // "./vs/base/test/browser/ui/list/listView.test.ts", - // "./vs/base/test/browser/ui/list/rangeMap.test.ts", - // "./vs/base/test/browser/ui/scrollbar/scrollableElement.test.ts", - // "./vs/base/test/browser/ui/scrollbar/scrollbarState.test.ts", - // "./vs/base/test/browser/ui/splitview/splitview.test.ts", - // "./vs/base/test/browser/ui/tree/asyncDataTree.test.ts", - // "./vs/base/test/browser/ui/tree/dataTree.test.ts", - // "./vs/base/test/browser/ui/tree/indexTreeModel.test.ts", - // "./vs/base/test/browser/ui/tree/objectTree.test.ts", - // "./vs/base/test/browser/ui/tree/objectTreeModel.test.ts", - // "./vs/base/test/common/arrays.test.ts", - // "./vs/base/test/common/assert.test.ts", - // "./vs/base/test/common/async.test.ts", - // "./vs/base/test/common/cache.test.ts", - // "./vs/base/test/common/cancellation.test.ts", - // "./vs/base/test/common/charCode.test.ts", - // "./vs/base/test/common/collections.test.ts", - // "./vs/base/test/common/color.test.ts", - // "./vs/base/test/common/decorators.test.ts", - // "./vs/base/test/common/diff/diff.test.ts", - // "./vs/base/test/common/errors.test.ts", - // "./vs/base/test/common/event.test.ts", - // "./vs/base/test/common/filters.perf.test.ts", - // "./vs/base/test/common/filters.test.ts", - // "./vs/base/test/common/hash.test.ts", - // "./vs/base/test/common/history.test.ts", - // "./vs/base/test/common/json.test.ts", - // "./vs/base/test/common/jsonEdit.test.ts", - // "./vs/base/test/common/jsonFormatter.test.ts", - // "./vs/base/test/common/keyCodes.test.ts", - // "./vs/base/test/common/labels.test.ts", - // "./vs/base/test/common/lifecycle.test.ts", - // "./vs/base/test/common/linkedList.test.ts", - // "./vs/base/test/common/map.test.ts", - // "./vs/base/test/common/marshalling.test.ts", - // "./vs/base/test/common/mime.test.ts", - // "./vs/base/test/common/objects.test.ts", - // "./vs/base/test/common/octicon.test.ts", - // "./vs/base/test/common/paging.test.ts", - // "./vs/base/test/common/paths.test.ts", - // "./vs/base/test/common/resources.test.ts", - // "./vs/base/test/common/scrollable.test.ts", - // "./vs/base/test/common/strings.test.ts", - // "./vs/base/test/common/types.test.ts", - // "./vs/base/test/common/uri.test.ts", - // "./vs/base/test/common/utils.ts", - // "./vs/base/test/common/uuid.test.ts", - // "./vs/base/test/node/config.test.ts", - // "./vs/base/test/node/console.test.ts", - // "./vs/base/test/node/decoder.test.ts", - // "./vs/base/test/node/encoding/encoding.test.ts", - // "./vs/base/test/node/extfs/extfs.test.ts", - // "./vs/base/test/node/flow.test.ts", - // "./vs/base/test/node/glob.test.ts", - // "./vs/base/test/node/id.test.ts", - // "./vs/base/test/node/pfs.test.ts", - // "./vs/base/test/node/port.test.ts", - // "./vs/base/test/node/processes/fixtures/fork.ts", - // "./vs/base/test/node/processes/fixtures/fork_large.ts", - // "./vs/base/test/node/processes/processes.test.ts", - // "./vs/base/test/node/storage/storage.test.ts", - // "./vs/base/test/node/stream/stream.test.ts", - // "./vs/base/test/node/uri.test.perf.ts", - // "./vs/base/test/node/utils.ts", - // "./vs/base/worker/defaultWorkerFactory.ts", - // "./vs/base/worker/workerMain.ts", - // "./vs/code/code.main.ts", - // "./vs/code/electron-browser/issue/issueReporterMain.ts", - // "./vs/code/electron-browser/issue/issueReporterModel.ts", - // "./vs/code/electron-browser/issue/issueReporterPage.ts", - // "./vs/code/electron-browser/issue/issueReporterUtil.ts", - // "./vs/code/electron-browser/issue/test/testReporterModel.test.ts", - // "./vs/code/electron-browser/processExplorer/processExplorerMain.ts", - // "./vs/code/electron-browser/sharedProcess/contrib/contributions.ts", - // "./vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts", - // "./vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts", - // "./vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts", - // "./vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts", - // "./vs/code/electron-browser/sharedProcess/sharedProcessMain.ts", - // "./vs/code/electron-main/auth.ts", - // "./vs/code/electron-main/keyboard.ts", - // "./vs/code/electron-main/logUploader.ts", - // "./vs/code/electron-main/sharedProcess.ts", - // "./vs/code/electron-main/theme.ts", - // "./vs/code/node/cli.ts", - // "./vs/code/node/cliProcessMain.ts", - // "./vs/code/node/paths.ts", - // "./vs/code/node/shellEnv.ts", - // "./vs/code/node/wait.ts", - // "./vs/code/node/windowsFinder.ts", - // "./vs/code/test/node/argv.test.ts", - // "./vs/code/test/node/windowsFinder.test.ts", - // "./vs/editor/contrib/bracketMatching/bracketMatching.ts", - // "./vs/editor/contrib/bracketMatching/test/bracketMatching.test.ts", - // "./vs/editor/contrib/caretOperations/caretOperations.ts", - // "./vs/editor/contrib/caretOperations/moveCaretCommand.ts", - // "./vs/editor/contrib/caretOperations/test/moveCarretCommand.test.ts", - // "./vs/editor/contrib/caretOperations/transpose.ts", - // "./vs/editor/contrib/clipboard/clipboard.ts", - // "./vs/editor/contrib/codelens/codelens.ts", - // "./vs/editor/contrib/codelens/codelensController.ts", - // "./vs/editor/contrib/codelens/codelensWidget.ts", - // "./vs/editor/contrib/colorPicker/color.ts", - // "./vs/editor/contrib/colorPicker/colorDetector.ts", - // "./vs/editor/contrib/colorPicker/colorPickerModel.ts", - // "./vs/editor/contrib/colorPicker/colorPickerWidget.ts", - // "./vs/editor/contrib/comment/blockCommentCommand.ts", - // "./vs/editor/contrib/comment/comment.ts", - // "./vs/editor/contrib/comment/lineCommentCommand.ts", - // "./vs/editor/contrib/comment/test/blockCommentCommand.test.ts", - // "./vs/editor/contrib/comment/test/lineCommentCommand.test.ts", - // "./vs/editor/contrib/contextmenu/contextmenu.ts", - // "./vs/editor/contrib/cursorUndo/cursorUndo.ts", - // "./vs/editor/contrib/documentSymbols/outlineModel.ts", - // "./vs/editor/contrib/dnd/dnd.ts", - // "./vs/editor/contrib/dnd/dragAndDropCommand.ts", - // "./vs/editor/contrib/find/findController.ts", - // "./vs/editor/contrib/find/findDecorations.ts", - // "./vs/editor/contrib/find/findModel.ts", - // "./vs/editor/contrib/find/findOptionsWidget.ts", - // "./vs/editor/contrib/find/findState.ts", - // "./vs/editor/contrib/find/findWidget.ts", - // "./vs/editor/contrib/find/replaceAllCommand.ts", - // "./vs/editor/contrib/find/replacePattern.ts", - // "./vs/editor/contrib/find/simpleFindWidget.ts", - // "./vs/editor/contrib/find/test/find.test.ts", - // "./vs/editor/contrib/find/test/findController.test.ts", - // "./vs/editor/contrib/find/test/findModel.test.ts", - // "./vs/editor/contrib/find/test/replacePattern.test.ts", - // "./vs/editor/contrib/folding/folding.ts", - // "./vs/editor/contrib/folding/foldingDecorations.ts", - // "./vs/editor/contrib/folding/foldingModel.ts", - // "./vs/editor/contrib/folding/foldingRanges.ts", - // "./vs/editor/contrib/folding/hiddenRangeModel.ts", - // "./vs/editor/contrib/folding/indentRangeProvider.ts", - // "./vs/editor/contrib/folding/intializingRangeProvider.ts", - // "./vs/editor/contrib/folding/syntaxRangeProvider.ts", - // "./vs/editor/contrib/folding/test/foldingModel.test.ts", - // "./vs/editor/contrib/folding/test/foldingRanges.test.ts", - // "./vs/editor/contrib/folding/test/hiddenRangeModel.test.ts", - // "./vs/editor/contrib/folding/test/indentFold.test.ts", - // "./vs/editor/contrib/folding/test/indentRangeProvider.test.ts", - // "./vs/editor/contrib/folding/test/syntaxFold.test.ts", - // "./vs/editor/contrib/fontZoom/fontZoom.ts", - // "./vs/editor/contrib/goToDefinition/clickLinkGesture.ts", - // "./vs/editor/contrib/goToDefinition/goToDefinition.ts", - // "./vs/editor/contrib/hover/getHover.ts", - // "./vs/editor/contrib/hover/hover.ts", - // "./vs/editor/contrib/hover/hoverOperation.ts", - // "./vs/editor/contrib/hover/hoverWidgets.ts", - // "./vs/editor/contrib/hover/modesContentHover.ts", - // "./vs/editor/contrib/hover/modesGlyphHover.ts", - // "./vs/editor/contrib/indentation/indentUtils.ts", - // "./vs/editor/contrib/indentation/indentation.ts", - // "./vs/editor/contrib/indentation/test/indentation.test.ts", - // "./vs/editor/contrib/linesOperations/copyLinesCommand.ts", - // "./vs/editor/contrib/linesOperations/deleteLinesCommand.ts", - // "./vs/editor/contrib/linesOperations/linesOperations.ts", - // "./vs/editor/contrib/linesOperations/moveLinesCommand.ts", - // "./vs/editor/contrib/linesOperations/sortLinesCommand.ts", - // "./vs/editor/contrib/linesOperations/test/copyLinesCommand.test.ts", - // "./vs/editor/contrib/linesOperations/test/deleteLinesCommand.test.ts", - // "./vs/editor/contrib/linesOperations/test/linesOperations.test.ts", - // "./vs/editor/contrib/linesOperations/test/moveLinesCommand.test.ts", - // "./vs/editor/contrib/linesOperations/test/sortLinesCommand.test.ts", - // "./vs/editor/contrib/links/getLinks.ts", - // "./vs/editor/contrib/links/links.ts", - // "./vs/editor/contrib/markdown/markdownRenderer.ts", - // "./vs/editor/contrib/message/messageController.ts", - // "./vs/editor/contrib/multicursor/multicursor.ts", - // "./vs/editor/contrib/multicursor/test/multicursor.test.ts", - // "./vs/editor/contrib/parameterHints/parameterHints.ts", - // "./vs/editor/contrib/parameterHints/parameterHintsModel.ts", - // "./vs/editor/contrib/parameterHints/parameterHintsWidget.ts", - // "./vs/editor/contrib/parameterHints/provideSignatureHelp.ts", - // "./vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts", - // "./vs/editor/contrib/quickOpen/quickOpen.ts", - // "./vs/editor/contrib/referenceSearch/peekViewWidget.ts", - // "./vs/editor/contrib/referenceSearch/referencesModel.ts", - // "./vs/editor/contrib/referenceSearch/referencesTree.ts", - // "./vs/editor/contrib/referenceSearch/referencesWidget.ts", - // "./vs/editor/contrib/referenceSearch/test/referencesModel.test.ts", - // "./vs/editor/contrib/rename/rename.ts", - // "./vs/editor/contrib/rename/renameInputField.ts", - // "./vs/editor/contrib/suggest/test/suggestMemory.test.ts", - // "./vs/editor/contrib/toggleTabFocusMode/toggleTabFocusMode.ts", - // "./vs/editor/contrib/tokenization/tokenization.ts", - // "./vs/editor/contrib/wordHighlighter/wordHighlighter.ts", - // "./vs/editor/contrib/wordOperations/test/wordOperations.test.ts", - // "./vs/editor/contrib/wordOperations/test/wordTestUtils.ts", - // "./vs/editor/contrib/wordOperations/wordOperations.ts", - // "./vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts", - // "./vs/editor/contrib/wordPartOperations/wordPartOperations.ts", - // "./vs/editor/contrib/zoneWidget/zoneWidget.ts", - // "./vs/editor/editor.api.ts", - // "./vs/editor/editor.worker.ts", - // "./vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts", - // "./vs/editor/standalone/browser/colorizer.ts", - // "./vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts", - // "./vs/editor/standalone/browser/inspectTokens/inspectTokens.ts", - // "./vs/editor/standalone/browser/simpleServices.ts", - // "./vs/editor/standalone/browser/standaloneCodeEditor.ts", - // "./vs/editor/standalone/browser/standaloneCodeServiceImpl.ts", - // "./vs/editor/standalone/browser/standaloneEditor.ts", - // "./vs/editor/standalone/browser/standaloneLanguages.ts", - // "./vs/editor/standalone/browser/standaloneServices.ts", - // "./vs/editor/standalone/browser/standaloneThemeServiceImpl.ts", - // "./vs/editor/standalone/browser/toggleHighContrast/toggleHighContrast.ts", - // "./vs/editor/standalone/common/monarch/monarchCommon.ts", - // "./vs/editor/standalone/common/monarch/monarchCompile.ts", - // "./vs/editor/standalone/common/monarch/monarchLexer.ts", - // "./vs/editor/standalone/common/monarch/monarchTypes.ts", - // "./vs/editor/standalone/common/standaloneThemeService.ts", - // "./vs/editor/standalone/common/themes.ts", - // "./vs/editor/standalone/test/browser/simpleServices.test.ts", - // "./vs/editor/standalone/test/browser/standaloneLanguages.test.ts", - // "./vs/editor/test/browser/controller/imeTester.ts", - // "./vs/editor/test/browser/editorTestServices.ts", - // "./vs/editor/test/browser/testCodeEditor.ts", - // "./vs/editor/test/browser/testCommand.ts", - // "./vs/editor/test/browser/view/minimapFontCreator.ts", - // "./vs/editor/test/browser/view/viewLayer.test.ts", - // "./vs/editor/test/common/commentMode.ts", - // "./vs/editor/test/common/config/commonEditorConfig.test.ts", - // "./vs/editor/test/common/controller/cursorMoveHelper.test.ts", - // "./vs/editor/test/common/core/characterClassifier.test.ts", - // "./vs/editor/test/common/core/lineTokens.test.ts", - // "./vs/editor/test/common/core/range.test.ts", - // "./vs/editor/test/common/core/viewLineToken.ts", - // "./vs/editor/test/common/editorTestUtils.ts", - // "./vs/editor/test/common/mocks/mockMode.ts", - // "./vs/editor/test/common/mocks/testConfiguration.ts", - // "./vs/editor/test/common/model/benchmark/benchmarkUtils.ts", - // "./vs/editor/test/common/model/benchmark/entry.ts", - // "./vs/editor/test/common/model/benchmark/modelbuilder.benchmark.ts", - // "./vs/editor/test/common/model/benchmark/operations.benchmark.ts", - // "./vs/editor/test/common/model/benchmark/searchNReplace.benchmark.ts", - // "./vs/editor/test/common/model/editableTextModelAuto.test.ts", - // "./vs/editor/test/common/model/editableTextModelTestUtils.ts", - // "./vs/editor/test/common/model/linesTextBuffer/linesTextBufferBuilder.test.ts", - // "./vs/editor/test/common/model/linesTextBuffer/textBufferAutoTestUtils.ts", - // "./vs/editor/test/common/model/model.test.ts", - // "./vs/editor/test/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.test.ts", - // "./vs/editor/test/common/modes/languageConfiguration.test.ts", - // "./vs/editor/test/common/modes/supports/characterPair.test.ts", - // "./vs/editor/test/common/modes/supports/javascriptOnEnterRules.ts", - // "./vs/editor/test/common/modes/supports/tokenization.test.ts", - // "./vs/editor/test/common/modesTestUtils.ts", - // "./vs/editor/test/common/services/languagesRegistry.test.ts", - // "./vs/editor/test/common/view/minimapCharRendererFactory.ts", - // "./vs/editor/test/common/view/overviewZoneManager.test.ts", - // "./vs/editor/test/common/viewLayout/editorLayoutProvider.test.ts", - // "./vs/editor/test/common/viewLayout/lineDecorations.test.ts", - // "./vs/editor/test/common/viewLayout/viewLineRenderer.test.ts", - // "./vs/editor/test/common/viewLayout/whitespaceComputer.test.ts", - // "./vs/editor/test/common/viewModel/characterHardWrappingLineMapper.test.ts", - // "./vs/editor/test/common/viewModel/prefixSumComputer.test.ts", - // "./vs/editor/test/common/viewModel/testViewModel.ts", - // "./vs/editor/test/common/viewModel/viewModelDecorations.test.ts", - // "./vs/editor/test/common/viewModel/viewModelImpl.test.ts", - // "./vs/monaco.d.ts", - // "./vs/nls.d.ts", - // "./vs/nls.mock.ts", - // "./vs/platform/actions/browser/menuItemActionItem.ts", - // "./vs/platform/actions/common/actions.ts", - // "./vs/platform/actions/common/menuService.ts", - // "./vs/platform/actions/test/common/menuService.test.ts", - // "./vs/platform/backup/common/backup.ts", - // "./vs/platform/backup/electron-main/backupMainService.ts", - // "./vs/platform/broadcast/electron-browser/broadcastService.ts", - // "./vs/platform/clipboard/common/clipboardService.ts", - // "./vs/platform/clipboard/electron-browser/clipboardService.ts", - // "./vs/platform/commands/common/commands.ts", - // "./vs/platform/commands/test/commands.test.ts", - // "./vs/platform/configuration/common/configuration.ts", - // "./vs/platform/configuration/common/configurationModels.ts", - // "./vs/platform/configuration/common/configurationRegistry.ts", - // "./vs/platform/configuration/node/configuration.ts", - // "./vs/platform/configuration/node/configurationService.ts", - // "./vs/platform/configuration/test/common/configuration.test.ts", - // "./vs/platform/configuration/test/common/configurationModels.test.ts", - // "./vs/platform/configuration/test/common/testConfigurationService.ts", - // "./vs/platform/configuration/test/node/configurationService.test.ts", - // "./vs/platform/contextkey/browser/contextKeyService.ts", - // "./vs/platform/contextkey/common/contextkey.ts", - // "./vs/platform/contextkey/test/common/contextkey.test.ts", - // "./vs/platform/contextview/browser/contextMenuHandler.ts", - // "./vs/platform/contextview/browser/contextMenuService.ts", - // "./vs/platform/contextview/browser/contextView.ts", - // "./vs/platform/contextview/browser/contextViewService.ts", - // "./vs/platform/credentials/test/node/keytar.test.ts", - // "./vs/platform/diagnostics/electron-main/diagnosticsService.ts", - // "./vs/platform/dialogs/common/dialogs.ts", - // "./vs/platform/dialogs/node/dialogIpc.ts", - // "./vs/platform/download/common/download.ts", - // "./vs/platform/download/node/downloadIpc.ts", - // "./vs/platform/download/node/downloadService.ts", - // "./vs/platform/driver/electron-browser/driver.ts", - // "./vs/platform/driver/electron-main/driver.ts", - // "./vs/platform/driver/node/driver.ts", - // "./vs/platform/editor/common/editor.ts", - // "./vs/platform/environment/common/environment.ts", - // "./vs/platform/environment/node/argv.ts", - // "./vs/platform/environment/node/environmentService.ts", - // "./vs/platform/environment/test/node/environmentService.test.ts", - // "./vs/platform/extensionManagement/common/extensionEnablementService.ts", - // "./vs/platform/extensionManagement/common/extensionManagement.ts", - // "./vs/platform/extensionManagement/common/extensionManagementUtil.ts", - // "./vs/platform/extensionManagement/common/extensionNls.ts", - // "./vs/platform/extensionManagement/node/extensionGalleryService.ts", - // "./vs/platform/extensionManagement/node/extensionLifecycle.ts", - // "./vs/platform/extensionManagement/node/extensionManagementIpc.ts", - // "./vs/platform/extensionManagement/node/extensionManagementService.ts", - // "./vs/platform/extensionManagement/node/extensionManagementUtil.ts", - // "./vs/platform/extensionManagement/node/extensionsManifestCache.ts", - // "./vs/platform/extensionManagement/node/multiExtensionManagement.ts", - // "./vs/platform/extensionManagement/test/electron-browser/extensionManagement.test.ts", - // "./vs/platform/extensions/common/extensionHost.ts", - // "./vs/platform/extensions/common/extensions.ts", - // "./vs/platform/extensions/node/extensionValidator.ts", - // "./vs/platform/extensions/test/node/extensionValidator.test.ts", - // "./vs/platform/files/common/files.ts", - // "./vs/platform/files/node/files.ts", - // "./vs/platform/files/test/files.test.ts", - // "./vs/platform/history/common/history.ts", - // "./vs/platform/history/electron-main/historyMainService.ts", - // "./vs/platform/instantiation/common/descriptors.ts", - // "./vs/platform/instantiation/common/extensions.ts", - // "./vs/platform/instantiation/common/graph.ts", - // "./vs/platform/instantiation/common/instantiation.ts", - // "./vs/platform/instantiation/common/instantiationService.ts", - // "./vs/platform/instantiation/common/serviceCollection.ts", - // "./vs/platform/instantiation/node/instantiationService.ts", - // "./vs/platform/instantiation/test/common/graph.test.ts", - // "./vs/platform/instantiation/test/common/instantiationService.test.ts", - // "./vs/platform/instantiation/test/common/instantiationServiceMock.ts", - // "./vs/platform/integrity/common/integrity.ts", - // "./vs/platform/integrity/node/integrityServiceImpl.ts", - // "./vs/platform/issue/common/issue.ts", - // "./vs/platform/issue/electron-main/issueService.ts", - // "./vs/platform/issue/node/issueIpc.ts", - // "./vs/platform/jsonschemas/common/jsonContributionRegistry.ts", - // "./vs/platform/keybinding/common/abstractKeybindingService.ts", - // "./vs/platform/keybinding/common/keybinding.ts", - // "./vs/platform/keybinding/common/keybindingResolver.ts", - // "./vs/platform/keybinding/common/keybindingsRegistry.ts", - // "./vs/platform/keybinding/common/resolvedKeybindingItem.ts", - // "./vs/platform/keybinding/common/usLayoutResolvedKeybinding.ts", - // "./vs/platform/keybinding/test/common/abstractKeybindingService.test.ts", - // "./vs/platform/keybinding/test/common/keybindingLabels.test.ts", - // "./vs/platform/keybinding/test/common/keybindingResolver.test.ts", - // "./vs/platform/keybinding/test/common/mockKeybindingService.ts", - // "./vs/platform/label/common/label.ts", - // "./vs/platform/launch/electron-main/launchService.ts", - // "./vs/platform/lifecycle/common/lifecycle.ts", - // "./vs/platform/lifecycle/electron-browser/lifecycleService.ts", - // "./vs/platform/lifecycle/electron-main/lifecycleMain.ts", - // "./vs/platform/list/browser/listService.ts", - // "./vs/platform/localizations/common/localizations.ts", - // "./vs/platform/localizations/node/localizations.ts", - // "./vs/platform/localizations/node/localizationsIpc.ts", - // "./vs/platform/log/common/bufferLog.ts", - // "./vs/platform/log/common/log.ts", - // "./vs/platform/log/node/logIpc.ts", - // "./vs/platform/log/node/spdlogService.ts", - // "./vs/platform/markers/common/markerService.ts", - // "./vs/platform/markers/common/markers.ts", - // "./vs/platform/markers/test/common/markerService.test.ts", - // "./vs/platform/menubar/common/menubar.ts", - // "./vs/platform/menubar/electron-main/menubar.ts", - // "./vs/platform/menubar/electron-main/menubarService.ts", - // "./vs/platform/menubar/node/menubarIpc.ts", - // "./vs/platform/node/minimalTranslations.ts", - // "./vs/platform/node/package.ts", - // "./vs/platform/node/product.ts", - // "./vs/platform/node/test/zip.test.ts", - // "./vs/platform/node/zip.ts", - // "./vs/platform/notification/common/notification.ts", - // "./vs/platform/notification/test/common/testNotificationService.ts", - // "./vs/platform/opener/common/opener.ts", - // "./vs/platform/output/node/outputAppender.ts", - // "./vs/platform/progress/common/progress.ts", - // "./vs/platform/quickOpen/common/quickOpen.ts", - // "./vs/platform/quickinput/common/quickInput.ts", - // "./vs/platform/registry/common/platform.ts", - // "./vs/platform/registry/test/common/platform.test.ts", - // "./vs/platform/remote/common/remoteAuthorityResolver.ts", - // "./vs/platform/remote/common/remoteHosts.ts", - // "./vs/platform/remote/electron-browser/remoteAuthorityResolverService.ts", - // "./vs/platform/remote/node/remoteAgentConnection.ts", - // "./vs/platform/remote/node/remoteAgentFileSystemChannel.ts", - // "./vs/platform/request/electron-browser/requestService.ts", - // "./vs/platform/request/electron-main/requestService.ts", - // "./vs/platform/request/node/request.ts", - // "./vs/platform/request/node/requestService.ts", - // "./vs/platform/search/common/replace.ts", - // "./vs/platform/search/common/search.ts", - // "./vs/platform/search/test/common/replace.test.ts", - // "./vs/platform/search/test/common/search.test.ts", - // "./vs/platform/state/common/state.ts", - // "./vs/platform/state/node/stateService.ts", - // "./vs/platform/statusbar/common/statusbar.ts", - // "./vs/platform/storage/common/storage.ts", - // "./vs/platform/storage/node/storageIpc.ts", - // "./vs/platform/storage/node/storageMainService.ts", - // "./vs/platform/storage/node/storageService.ts", - // "./vs/platform/telemetry/browser/errorTelemetry.ts", - // "./vs/platform/telemetry/common/telemetry.ts", - // "./vs/platform/telemetry/common/telemetryService.ts", - // "./vs/platform/telemetry/common/telemetryUtils.ts", - // "./vs/platform/telemetry/node/appInsightsAppender.ts", - // "./vs/platform/telemetry/node/commonProperties.ts", - // "./vs/platform/telemetry/node/telemetryIpc.ts", - // "./vs/platform/telemetry/node/telemetryNodeUtils.ts", - // "./vs/platform/telemetry/node/workbenchCommonProperties.ts", - // "./vs/platform/telemetry/test/electron-browser/appInsightsAppender.test.ts", - // "./vs/platform/telemetry/test/electron-browser/telemetryService.test.ts", - // "./vs/platform/theme/common/colorRegistry.ts", - // "./vs/platform/theme/common/styler.ts", - // "./vs/platform/theme/common/themeService.ts", - // "./vs/platform/theme/test/common/testThemeService.ts", - // "./vs/platform/update/common/update.ts", - // "./vs/platform/update/electron-main/abstractUpdateService.ts", - // "./vs/platform/update/electron-main/updateService.darwin.ts", - // "./vs/platform/update/electron-main/updateService.linux.ts", - // "./vs/platform/update/electron-main/updateService.snap.ts", - // "./vs/platform/update/electron-main/updateService.win32.ts", - // "./vs/platform/update/node/update.config.contribution.ts", - // "./vs/platform/update/node/updateIpc.ts", - // "./vs/platform/url/common/url.ts", - // "./vs/platform/url/common/urlService.ts", - // "./vs/platform/url/electron-main/electronUrlListener.ts", - // "./vs/platform/url/node/urlIpc.ts", - // "./vs/platform/widget/browser/contextScopedHistoryWidget.ts", - // "./vs/platform/widget/common/contextScopedWidget.ts", - // "./vs/platform/windows/common/windows.ts", - // "./vs/platform/windows/electron-browser/windowService.ts", - // "./vs/platform/windows/electron-main/windows.ts", - // "./vs/platform/windows/electron-main/windowsService.ts", - // "./vs/platform/windows/node/windowsIpc.ts", - // "./vs/platform/workbench/common/contextkeys.ts", - // "./vs/platform/workspace/common/workspace.ts", - // "./vs/platform/workspace/test/common/testWorkspace.ts", - // "./vs/platform/workspace/test/common/workspace.test.ts", - // "./vs/platform/workspaces/common/workspaces.ts", - // "./vs/platform/workspaces/electron-main/workspacesMainService.ts", - // "./vs/platform/workspaces/node/workspaces.ts", - // "./vs/platform/workspaces/node/workspacesIpc.ts", - // "./vs/vscode.d.ts", - // "./vs/vscode.proposed.d.ts", - // "./vs/workbench/api/node/extHostExtensionActivator.ts", - // "./vs/workbench/api/node/extHostSearch.fileIndex.ts", - // "./vs/workbench/api/node/extHostTypes.ts", - // "./vs/workbench/api/shared/editor.ts", - // "./vs/workbench/api/shared/tasks.ts", - // "./vs/workbench/browser/actions.ts", - // "./vs/workbench/browser/actions/layoutActions.ts", - // "./vs/workbench/browser/actions/navigationActions.ts", - // "./vs/workbench/browser/actions/workspaceActions.ts", - // "./vs/workbench/browser/actions/workspaceCommands.ts", - // "./vs/workbench/browser/composite.ts", - // "./vs/workbench/browser/editor.ts", - // "./vs/workbench/browser/panel.ts", - // "./vs/workbench/browser/part.ts", - // "./vs/workbench/browser/parts/compositePart.ts", - // "./vs/workbench/browser/parts/editor/baseEditor.ts", - // "./vs/workbench/browser/parts/editor/binaryDiffEditor.ts", - // "./vs/workbench/browser/parts/editor/binaryEditor.ts", - // "./vs/workbench/browser/parts/editor/breadcrumbs.ts", - // "./vs/workbench/browser/parts/editor/breadcrumbsModel.ts", - // "./vs/workbench/browser/parts/editor/editor.ts", - // "./vs/workbench/browser/parts/editor/editorControl.ts", - // "./vs/workbench/browser/parts/editor/editorWidgets.ts", - // "./vs/workbench/browser/parts/editor/rangeDecorations.ts", - // "./vs/workbench/browser/parts/editor/resourceViewer.ts", - // "./vs/workbench/browser/parts/editor/sideBySideEditor.ts", - // "./vs/workbench/browser/parts/editor/textEditor.ts", - // "./vs/workbench/browser/parts/notifications/notificationsActions.ts", - // "./vs/workbench/browser/parts/notifications/notificationsAlerts.ts", - // "./vs/workbench/browser/parts/notifications/notificationsCenter.ts", - // "./vs/workbench/browser/parts/notifications/notificationsCommands.ts", - // "./vs/workbench/browser/parts/notifications/notificationsList.ts", - // "./vs/workbench/browser/parts/notifications/notificationsStatus.ts", - // "./vs/workbench/browser/parts/notifications/notificationsToasts.ts", - // "./vs/workbench/browser/parts/notifications/notificationsViewer.ts", - // "./vs/workbench/browser/parts/quickinput/quickInputBox.ts", - // "./vs/workbench/browser/parts/quickinput/quickInputList.ts", - // "./vs/workbench/browser/parts/quickinput/quickInputUtils.ts", - // "./vs/workbench/browser/parts/quickopen/quickopen.ts", - // "./vs/workbench/browser/parts/sidebar/sidebarPart.ts", - // "./vs/workbench/browser/parts/statusbar/statusbar.ts", - // "./vs/workbench/browser/parts/statusbar/statusbarPart.ts", - // "./vs/workbench/browser/parts/views/panelViewlet.ts", - // "./vs/workbench/browser/parts/views/views.ts", - // "./vs/workbench/browser/parts/views/viewsViewlet.ts", - // "./vs/workbench/browser/viewlet.ts", - // "./vs/workbench/common/actions.ts", - // "./vs/workbench/common/activity.ts", - // "./vs/workbench/common/component.ts", - // "./vs/workbench/common/composite.ts", - // "./vs/workbench/common/contributions.ts", - // "./vs/workbench/common/editor.ts", - // "./vs/workbench/common/editor/binaryEditorModel.ts", - // "./vs/workbench/common/editor/dataUriEditorInput.ts", - // "./vs/workbench/common/editor/diffEditorModel.ts", - // "./vs/workbench/common/editor/editorGroup.ts", - // "./vs/workbench/common/extensionHostProtocol.ts", - // "./vs/workbench/common/memento.ts", - // "./vs/workbench/common/notifications.ts", - // "./vs/workbench/common/panel.ts", - // "./vs/workbench/common/resources.ts", - // "./vs/workbench/common/theme.ts", - // "./vs/workbench/common/viewlet.ts", - // "./vs/workbench/common/views.ts", - // "./vs/workbench/electron-browser/actions/helpActions", - // "./vs/workbench/electron-browser/actions/developerActions", - // "./vs/workbench/electron-browser/actions/windowActions", - // "./vs/workbench/electron-browser/resources.ts", - // "./vs/workbench/electron-browser/window.ts", - // "./vs/workbench/parts/backup/common/backupRestorer.ts", - // "./vs/workbench/parts/cli/electron-browser/cli.contribution.ts", - // "./vs/workbench/parts/codeEditor/browser/menuPreventer.ts", - // "./vs/workbench/parts/codeEditor/browser/simpleEditorOptions.ts", - // "./vs/workbench/parts/codeEditor/electron-browser/accessibility.ts", - // "./vs/workbench/parts/codeEditor/electron-browser/inspectKeybindings.ts", - // "./vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint.ts", - // "./vs/workbench/parts/codeEditor/electron-browser/largeFileOptimizations.ts", - // "./vs/workbench/parts/codeEditor/electron-browser/selectionClipboard.ts", - // "./vs/workbench/parts/codeEditor/electron-browser/simpleEditorOptions.ts", - // "./vs/workbench/parts/codeEditor/electron-browser/sleepResumeRepaintMinimap.ts", - // "./vs/workbench/parts/codeEditor/electron-browser/suggestEnabledInput.ts", - // "./vs/workbench/parts/codeEditor/electron-browser/textMate/inspectTMScopes.ts", - // "./vs/workbench/parts/codeEditor/electron-browser/toggleMinimap.ts", - // "./vs/workbench/parts/codeEditor/electron-browser/toggleMultiCursorModifier.ts", - // "./vs/workbench/parts/codeEditor/electron-browser/toggleRenderControlCharacter.ts", - // "./vs/workbench/parts/codeEditor/electron-browser/toggleRenderWhitespace.ts", - // "./vs/workbench/parts/codeEditor/electron-browser/toggleWordWrap.ts", - // "./vs/workbench/parts/comments/common/commentModel.ts", - // "./vs/workbench/parts/comments/electron-browser/commentGlyphWidget.ts", - // "./vs/workbench/parts/comments/electron-browser/simpleCommentEditor.ts", - // "./vs/workbench/parts/debug/browser/debugANSIHandling.ts", - // "./vs/workbench/parts/debug/browser/linkDetector.ts", - // "./vs/workbench/parts/debug/node/telemetryApp.ts", - // "./vs/workbench/parts/emmet/browser/actions/showEmmetCommands.ts", - // "./vs/workbench/parts/emmet/browser/emmet.browser.contribution.ts", - // "./vs/workbench/parts/emmet/electron-browser/actions/expandAbbreviation.ts", - // "./vs/workbench/parts/emmet/electron-browser/emmet.contribution.ts", - // "./vs/workbench/parts/emmet/electron-browser/emmetActions.ts", - // "./vs/workbench/parts/emmet/test/electron-browser/emmetAction.test.ts", - // "./vs/workbench/parts/execution/common/execution.ts", - // "./vs/workbench/parts/execution/electron-browser/execution.contribution.ts", - // "./vs/workbench/parts/execution/electron-browser/terminal.ts", - // "./vs/workbench/parts/execution/electron-browser/terminalService.ts", - // "./vs/workbench/parts/execution/test/electron-browser/terminalService.test.ts", - // "./vs/workbench/parts/experiments/electron-browser/experimentalPrompt.ts", - // "./vs/workbench/parts/experiments/electron-browser/experiments.contribution.ts", - // "./vs/workbench/parts/experiments/node/experimentService.ts", - // "./vs/workbench/parts/extensions/browser/extensionsViewer.ts", - // "./vs/workbench/parts/extensions/common/extensionQuery.ts", - // "./vs/workbench/parts/extensions/common/extensions.ts", - // "./vs/workbench/parts/extensions/common/extensionsFileTemplate.ts", - // "./vs/workbench/parts/extensions/common/extensionsInput.ts", - // "./vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts", - // "./vs/workbench/parts/extensions/electron-browser/extensionsActions.ts", - // "./vs/workbench/parts/extensions/electron-browser/extensionsActivationProgress.ts", - // "./vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts", - // "./vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts", - // "./vs/workbench/parts/extensions/test/common/extensionQuery.test.ts", - // "./vs/workbench/parts/feedback/electron-browser/feedback.contribution.ts", - // "./vs/workbench/parts/feedback/electron-browser/feedback.ts", - // "./vs/workbench/parts/feedback/electron-browser/feedbackStatusbarItem.ts", - // "./vs/workbench/parts/files/browser/files.ts", - // "./vs/workbench/parts/files/common/explorerModel.ts", - // "./vs/workbench/parts/files/common/files.ts", - // "./vs/workbench/parts/files/electron-browser/explorerService.ts", - // "./vs/workbench/parts/files/electron-browser/views/explorerDecorationsProvider.ts", - // "./vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts", - // "./vs/workbench/parts/localizations/electron-browser/localizationsActions.ts", - // "./vs/workbench/parts/logs/common/logConstants.ts", - // "./vs/workbench/parts/logs/electron-browser/logs.contribution.ts", - // "./vs/workbench/parts/logs/electron-browser/logsActions.ts", - // "./vs/workbench/parts/markers/electron-browser/constants.ts", - // "./vs/workbench/parts/markers/electron-browser/markers.ts", - // "./vs/workbench/parts/markers/electron-browser/markersFileDecorations.ts", - // "./vs/workbench/parts/markers/electron-browser/markersFilterOptions.ts", - // "./vs/workbench/parts/markers/electron-browser/markersModel.ts", - // "./vs/workbench/parts/markers/electron-browser/markersPanelActions.ts", - // "./vs/workbench/parts/markers/electron-browser/messages.ts", - // "./vs/workbench/parts/markers/test/electron-browser/markersModel.test.ts", - // "./vs/workbench/parts/outline/electron-browser/outline.ts", - // "./vs/workbench/parts/output/common/output.ts", - // "./vs/workbench/parts/output/common/outputLinkComputer.ts", - // "./vs/workbench/parts/output/common/outputLinkProvider.ts", - // "./vs/workbench/parts/performance/electron-browser/startupTimings.ts", - // "./vs/workbench/parts/preferences/browser/settingsWidgets.ts", - // "./vs/workbench/parts/preferences/common/smartSnippetInserter.ts", - // "./vs/workbench/parts/preferences/test/common/smartSnippetInserter.test.ts", - // "./vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts", - // "./vs/workbench/parts/scm/common/scm.ts", - // "./vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts", - // "./vs/workbench/parts/scm/electron-browser/scmActivity.ts", - // "./vs/workbench/parts/scm/electron-browser/scmMenus.ts", - // "./vs/workbench/parts/scm/electron-browser/scmUtil.ts", - // "./vs/workbench/parts/search/browser/patternInputWidget.ts", - // "./vs/workbench/parts/search/browser/replaceContributions.ts", - // "./vs/workbench/parts/search/browser/replaceService.ts", - // "./vs/workbench/parts/search/common/constants.ts", - // "./vs/workbench/parts/search/common/queryBuilder.ts", - // "./vs/workbench/parts/search/common/replace.ts", - // "./vs/workbench/parts/search/common/search.ts", - // "./vs/workbench/parts/search/common/searchModel.ts", - // "./vs/workbench/parts/search/test/browser/mockSearchTree.ts", - // "./vs/workbench/parts/search/test/common/searchModel.test.ts", - // "./vs/workbench/parts/search/test/common/searchResult.test.ts", - // "./vs/workbench/parts/snippets/electron-browser/configureSnippets.ts", - // "./vs/workbench/parts/snippets/electron-browser/insertSnippet.ts", - // "./vs/workbench/parts/snippets/electron-browser/snippetCompletionProvider.ts", - // "./vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts", - // "./vs/workbench/parts/snippets/electron-browser/snippetsFile.ts", - // "./vs/workbench/parts/snippets/electron-browser/snippetsService.ts", - // "./vs/workbench/parts/snippets/electron-browser/tabCompletion.ts", - // "./vs/workbench/parts/snippets/test/electron-browser/snippetFile.test.ts", - // "./vs/workbench/parts/snippets/test/electron-browser/snippetsRegistry.test.ts", - // "./vs/workbench/parts/snippets/test/electron-browser/snippetsRewrite.test.ts", - // "./vs/workbench/parts/snippets/test/electron-browser/snippetsService.test.ts", - // "./vs/workbench/parts/splash/electron-browser/partsSplash.contribution.ts", - // "./vs/workbench/parts/stats/node/stats.contribution.ts", - // "./vs/workbench/parts/stats/node/workspaceStats.ts", - // "./vs/workbench/parts/stats/test/workspaceStats.test.ts", - // "./vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution.ts", - // "./vs/workbench/parts/surveys/electron-browser/nps.contribution.ts", - // "./vs/workbench/parts/tasks/common/problemCollectors.ts", - // "./vs/workbench/parts/tasks/common/problemMatcher.ts", - // "./vs/workbench/parts/tasks/common/taskDefinitionRegistry.ts", - // "./vs/workbench/parts/tasks/common/taskService.ts", - // "./vs/workbench/parts/tasks/common/taskSystem.ts", - // "./vs/workbench/parts/tasks/common/taskTemplates.ts", - // "./vs/workbench/parts/tasks/common/tasks.ts", - // "./vs/workbench/parts/tasks/electron-browser/jsonSchemaCommon.ts", - // "./vs/workbench/parts/tasks/electron-browser/jsonSchema_v1.ts", - // "./vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts", - // "./vs/workbench/parts/tasks/electron-browser/runAutomaticTasks.ts", - // "./vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts", - // "./vs/workbench/parts/tasks/node/processRunnerDetector.ts", - // "./vs/workbench/parts/tasks/node/processTaskSystem.ts", - // "./vs/workbench/parts/tasks/node/taskConfiguration.ts", - // "./vs/workbench/parts/tasks/node/tasks.ts", - // "./vs/workbench/parts/tasks/test/common/problemMatcher.test.ts", - // "./vs/workbench/parts/tasks/test/electron-browser/configuration.test.ts", - // "./vs/workbench/parts/terminal/browser/terminalFindWidget.ts", - // "./vs/workbench/parts/terminal/browser/terminalTab.ts", - // "./vs/workbench/parts/terminal/browser/terminalWidgetManager.ts", - // "./vs/workbench/parts/terminal/common/terminal.ts", - // "./vs/workbench/parts/terminal/common/terminalColorRegistry.ts", - // "./vs/workbench/parts/terminal/common/terminalCommands.ts", - // "./vs/workbench/parts/terminal/common/terminalMenu.ts", - // "./vs/workbench/parts/terminal/common/terminalService.ts", - // "./vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts", - // "./vs/workbench/parts/terminal/electron-browser/terminalInstance.ts", - // "./vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts", - // "./vs/workbench/parts/terminal/electron-browser/terminalProcessManager.ts", - // "./vs/workbench/parts/terminal/node/terminal.ts", - // "./vs/workbench/parts/terminal/node/terminalCommandTracker.ts", - // "./vs/workbench/parts/terminal/node/terminalEnvironment.ts", - // "./vs/workbench/parts/terminal/node/terminalProcess.ts", - // "./vs/workbench/parts/terminal/node/terminalProcessExtHostProxy.ts", - // "./vs/workbench/parts/terminal/node/windowsShellHelper.ts", - // "./vs/workbench/parts/terminal/test/electron-browser/terminalColorRegistry.test.ts", - // "./vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts", - // "./vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts", - // "./vs/workbench/parts/terminal/test/node/terminalCommandTracker.test.ts", - // "./vs/workbench/parts/terminal/test/node/terminalEnvironment.test.ts", - // "./vs/workbench/parts/themes/electron-browser/themes.contribution.ts", - // "./vs/workbench/parts/url/electron-browser/url.contribution.ts", - // "./vs/workbench/parts/webview/electron-browser/webviewProtocols.ts", - // "./vs/workbench/parts/welcome/gettingStarted/electron-browser/gettingStarted.contribution.ts", - // "./vs/workbench/parts/welcome/gettingStarted/electron-browser/gettingStarted.ts", - // "./vs/workbench/parts/welcome/gettingStarted/electron-browser/telemetryOptOut.ts", - // "./vs/workbench/parts/welcome/gettingStarted/test/common/gettingStarted.test.ts", - // "./vs/workbench/parts/welcome/page/electron-browser/vs_code_welcome_page.ts", - // "./vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution.ts", - // "./vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts", - // "./vs/workbench/parts/welcome/walkThrough/electron-browser/editor/editorWalkThrough.ts", - // "./vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution.ts", - // "./vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughActions.ts", - // "./vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts", - // "./vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts", - // "./vs/workbench/parts/welcome/walkThrough/node/walkThroughInput.ts", - // "./vs/workbench/parts/welcome/walkThrough/node/walkThroughUtils.ts", - // "./vs/workbench/api/common/menusExtensionPoint.ts", - // "./vs/workbench/api/common/configurationExtensionPoint.ts", - // "./vs/workbench/services/activity/common/activity.ts", - // "./vs/workbench/services/backup/common/backup.ts", - // "./vs/workbench/services/backup/node/backupFileService.ts", - // "./vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts", - // "./vs/workbench/services/codeEditor/browser/codeEditorService.ts", - // "./vs/workbench/services/commands/common/commandService.ts", - // "./vs/workbench/services/commands/test/common/commandService.test.ts", - // "./vs/workbench/services/configuration/common/configuration.ts", - // "./vs/workbench/services/configuration/common/configurationModels.ts", - // "./vs/workbench/services/configuration/common/jsonEditing.ts", - // "./vs/workbench/services/configuration/node/jsonEditingService.ts", - // "./vs/workbench/services/configuration/test/common/configurationModels.test.ts", - // "./vs/workbench/services/configurationResolver/common/configurationResolver.ts", - // "./vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts", - // "./vs/workbench/services/configurationResolver/common/configurationResolverUtils.ts", - // "./vs/workbench/services/contextview/electron-browser/contextmenuService.ts", - // "./vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts", - // "./vs/workbench/services/decorations/browser/decorations.ts", - // "./vs/workbench/services/decorations/browser/decorationsService.ts", - // "./vs/workbench/services/decorations/test/browser/decorationsService.test.ts", - // "./vs/workbench/services/dialogs/electron-browser/dialogService.ts", - // "./vs/workbench/services/editor/common/editorService.ts", - // "./vs/workbench/services/extensions/common/extensions.ts", - // "./vs/workbench/services/extensions/common/extensionsRegistry.ts", - // "./vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts", - // "./vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts", - // "./vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler.ts", - // "./vs/workbench/services/extensions/electron-browser/runtimeExtensionsInput.ts", - // "./vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts", - // "./vs/workbench/services/extensions/node/extensionManagementServerService.ts", - // "./vs/workbench/services/extensions/node/extensionPoints.ts", - // "./vs/workbench/services/extensions/node/lazyPromise.ts", - // "./vs/workbench/services/extensions/node/proxyIdentifier.ts", - // "./vs/workbench/services/extensions/node/rpcProtocol.ts", - // "./vs/workbench/services/extensions/test/node/rpcProtocol.test.ts", - // "./vs/workbench/services/files/electron-browser/encoding.ts", - // "./vs/workbench/services/files/node/watcher/common.ts", - // "./vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts", - // "./vs/workbench/services/files/node/watcher/nsfw/test/nsfwWatcherService.test.ts", - // "./vs/workbench/services/files/node/watcher/nsfw/watcher.ts", - // "./vs/workbench/services/files/node/watcher/nsfw/watcherApp.ts", - // "./vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts", - // "./vs/workbench/services/files/node/watcher/nsfw/watcherService.ts", - // "./vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts", - // "./vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts", - // "./vs/workbench/services/files/node/watcher/unix/watcher.ts", - // "./vs/workbench/services/files/node/watcher/unix/watcherApp.ts", - // "./vs/workbench/services/files/node/watcher/unix/watcherIpc.ts", - // "./vs/workbench/services/files/node/watcher/unix/watcherService.ts", - // "./vs/workbench/services/files/node/watcher/win32/csharpWatcherService.ts", - // "./vs/workbench/services/files/node/watcher/win32/watcherService.ts", - // "./vs/workbench/services/files/test/electron-browser/utils.ts", - // "./vs/workbench/services/files/test/electron-browser/watcher.test.ts", - // "./vs/workbench/services/group/common/editorGroupsService.ts", - // "./vs/workbench/services/hash/common/hashService.ts", - // "./vs/workbench/services/hash/node/hashService.ts", - // "./vs/workbench/services/history/common/history.ts", - // "./vs/workbench/services/history/electron-browser/history.ts", - // "./vs/workbench/services/issue/common/issue.ts", - // "./vs/workbench/services/issue/electron-browser/workbenchIssueService.ts", - // "./vs/workbench/services/jsonschemas/common/jsonValidationExtensionPoint.ts", - // "./vs/workbench/services/keybinding/common/keybindingIO.ts", - // "./vs/workbench/services/keybinding/common/keyboardMapper.ts", - // "./vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts", - // "./vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts", - // "./vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts", - // "./vs/workbench/services/keybinding/electron-browser/keybindingService.ts", - // "./vs/workbench/services/keybinding/test/keybindingIO.test.ts", - // "./vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts", - // "./vs/workbench/services/keybinding/test/macLinuxFallbackKeyboardMapper.test.ts", - // "./vs/workbench/services/keybinding/test/macLinuxKeyboardMapper.test.ts", - // "./vs/workbench/services/keybinding/test/windowsKeyboardMapper.test.ts", - // "./vs/workbench/services/label/common/labelService.ts", - // "./vs/workbench/services/mode/common/workbenchModeService.ts", - // "./vs/workbench/services/notification/common/notificationService.ts", - // "./vs/workbench/services/panel/common/panelService.ts", - // "./vs/workbench/services/part/common/partService.ts", - // "./vs/workbench/services/preferences/common/keybindingsEditorModel.ts", - // "./vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts", - // "./vs/workbench/services/progress/browser/progressService.ts", - // "./vs/workbench/services/progress/test/progressService.test.ts", - // "./vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts", - // "./vs/workbench/services/remote/node/remoteAgentEnvironmentChannel.ts", - // "./vs/workbench/services/remote/node/remoteAgentService.ts", - // "./vs/workbench/services/scm/common/scm.ts", - // "./vs/workbench/services/scm/common/scmService.ts", - // "./vs/workbench/services/search/common/searchHelpers.ts", - // "./vs/workbench/services/search/node/fileSearch.ts", - // "./vs/workbench/services/search/node/fileSearchManager.ts", - // "./vs/workbench/services/search/node/rawSearchService.ts", - // "./vs/workbench/services/search/node/ripgrepFileSearch.ts", - // "./vs/workbench/services/search/node/ripgrepSearchProvider.ts", - // "./vs/workbench/services/search/node/ripgrepSearchUtils.ts", - // "./vs/workbench/services/search/node/ripgrepTextSearchEngine.ts", - // "./vs/workbench/services/search/node/search.ts", - // "./vs/workbench/services/search/node/searchApp.ts", - // "./vs/workbench/services/search/node/searchHistoryService.ts", - // "./vs/workbench/services/search/node/searchIpc.ts", - // "./vs/workbench/services/search/node/textSearchAdapter.ts", - // "./vs/workbench/services/search/node/textSearchManager.ts", - // "./vs/workbench/services/search/test/common/searchHelpers.test.ts", - // "./vs/workbench/services/search/test/node/ripgrepFileSearch.test.ts", - // "./vs/workbench/services/search/test/node/ripgrepTextSearchEngine.test.ts", - // "./vs/workbench/services/search/test/node/search.test.ts", - // "./vs/workbench/services/search/test/node/textSearch.integrationTest.ts", - // "./vs/workbench/services/search/test/node/textSearchManager.test.ts", - // "./vs/workbench/services/textMate/electron-browser/TMGrammars.ts", - // "./vs/workbench/services/textMate/electron-browser/TMHelper.ts", - // "./vs/workbench/services/textMate/electron-browser/TMSyntax.ts", - // "./vs/workbench/services/textMate/electron-browser/textMateService.ts", - // "./vs/workbench/services/textfile/common/textfiles.ts", - // "./vs/workbench/services/textfile/electron-browser/textResourcePropertiesService.ts", - // "./vs/workbench/services/themes/common/colorExtensionPoint.ts", - // "./vs/workbench/services/themes/common/colorThemeSchema.ts", - // "./vs/workbench/services/themes/common/fileIconThemeSchema.ts", - // "./vs/workbench/services/themes/common/workbenchThemeService.ts", - // "./vs/workbench/services/themes/electron-browser/colorThemeData.ts", - // "./vs/workbench/services/themes/electron-browser/colorThemeStore.ts", - // "./vs/workbench/services/themes/electron-browser/fileIconThemeData.ts", - // "./vs/workbench/services/themes/electron-browser/fileIconThemeStore.ts", - // "./vs/workbench/services/themes/electron-browser/workbenchThemeService.ts", - // "./vs/workbench/services/themes/electron-browser/themeCompatibility.ts", - // "./vs/workbench/services/timer/electron-browser/timerService.ts", - // "./vs/workbench/services/title/common/titleService.ts", - // "./vs/workbench/services/viewlet/browser/viewlet.ts", - // "./vs/workbench/services/workspace/common/workspaceEditing.ts", - // "./vs/workbench/test/browser/actionRegistry.test.ts", - // "./vs/workbench/test/browser/viewlet.test.ts", - // "./vs/workbench/test/common/editor/editorOptions.test.ts", - // "./vs/workbench/test/common/notifications.test.ts", - // "./vs/workbench/test/electron-browser/api/extHostTypes.test.ts", - // "./vs/workbench/test/electron-browser/api/mock.ts", - // Begin SQL files - // "./sql/base/browser/ui/breadcrumb/breadcrumb.component.ts", // pulls in angular which gives extrenous errors - // "./sql/base/browser/ui/breadcrumb/interfaces.ts", // pulls in angular which gives extrenous errors - "./sql/base/browser/ui/button/button.ts", - "./sql/base/browser/ui/checkbox/checkbox.ts", - // "./sql/base/browser/ui/checkbox/checkbox.component.ts", // pulls in angular which gives extrenous errors - // "./sql/base/browser/ui/dropdownList/dropdownList.ts", // skipping since vscode hasn't checked their dropdown yet - // "./sql/base/browser/ui/editableDropdown/actions.ts", // skipping since vscode hasn't checked their dropdown yet and it isn't in the right place anyways - // "./sql/base/browser/ui/editableDropdown/dropdown.ts", // skipping since vscode hasn't checked their dropdown yet and it isn't in the right place anyways - // "./sql/base/browser/ui/editableDropdown/dropdownTree.ts", // skipping since vscode hasn't checked their dropdown yet and it isn't in the right place anyways - // "./sql/base/browser/ui/editableDropdown/editableDropdown.component.ts", // skipping since vscode hasn't checked their dropdown yet and it isn't in the right place anyways - // "./sql/base/browser/ui/inputBox/inputBox.component.ts", // pulls in angular which gives extrenous errors - // "./sql/base/browser/ui/inputBox/inputBox.ts", // skipping since vscode hasn't checked their inputbox yet - // "./sql/base/browser/ui/listBox/listBox.ts", - // End SQL files + "./vs/monaco.d.ts", + "./vs/nls.d.ts", + "./vs/nls.mock.ts", + "./vs/vscode.d.ts", + "./vs/vscode.proposed.d.ts", + "./vs/workbench/api/browser/viewsExtensionPoint.ts", + "./vs/workbench/api/common/configurationExtensionPoint.ts", + "./vs/workbench/api/common/jsonValidationExtensionPoint.ts", + "./vs/workbench/api/common/menusExtensionPoint.ts", + "./vs/workbench/api/electron-browser/extHostCustomers.ts", + "./vs/workbench/api/electron-browser/mainThreadClipboard.ts", + "./vs/workbench/api/electron-browser/mainThreadCommands.ts", + "./vs/workbench/api/electron-browser/mainThreadConfiguration.ts", + "./vs/workbench/api/electron-browser/mainThreadConsole.ts", + "./vs/workbench/api/electron-browser/mainThreadDebugService.ts", + "./vs/workbench/api/electron-browser/mainThreadDecorations.ts", + "./vs/workbench/api/electron-browser/mainThreadDiagnostics.ts", + "./vs/workbench/api/electron-browser/mainThreadDialogs.ts", + "./vs/workbench/api/electron-browser/mainThreadDocumentContentProviders.ts", + "./vs/workbench/api/electron-browser/mainThreadDocuments.ts", + "./vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors.ts", + "./vs/workbench/api/electron-browser/mainThreadEditor.ts", + "./vs/workbench/api/electron-browser/mainThreadEditors.ts", + "./vs/workbench/api/electron-browser/mainThreadErrors.ts", + "./vs/workbench/api/electron-browser/mainThreadExtensionService.ts", + "./vs/workbench/api/electron-browser/mainThreadFileSystem.ts", + "./vs/workbench/api/electron-browser/mainThreadFileSystemEventService.ts", + "./vs/workbench/api/electron-browser/mainThreadHeapService.ts", + "./vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts", + "./vs/workbench/api/electron-browser/mainThreadLanguages.ts", + "./vs/workbench/api/electron-browser/mainThreadLogService.ts", + "./vs/workbench/api/electron-browser/mainThreadMessageService.ts", + "./vs/workbench/api/electron-browser/mainThreadOutputService.ts", + "./vs/workbench/api/electron-browser/mainThreadProgress.ts", + "./vs/workbench/api/electron-browser/mainThreadQuickOpen.ts", + "./vs/workbench/api/electron-browser/mainThreadSCM.ts", + "./vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts", + "./vs/workbench/api/electron-browser/mainThreadSearch.ts", + "./vs/workbench/api/electron-browser/mainThreadStatusBar.ts", + "./vs/workbench/api/electron-browser/mainThreadStorage.ts", + "./vs/workbench/api/electron-browser/mainThreadTask.ts", + "./vs/workbench/api/electron-browser/mainThreadTelemetry.ts", + "./vs/workbench/api/electron-browser/mainThreadTerminalService.ts", + "./vs/workbench/api/electron-browser/mainThreadTreeViews.ts", + "./vs/workbench/api/electron-browser/mainThreadUrls.ts", + "./vs/workbench/api/electron-browser/mainThreadWebview.ts", + "./vs/workbench/api/electron-browser/mainThreadWindow.ts", + "./vs/workbench/api/electron-browser/mainThreadWorkspace.ts", + "./vs/workbench/api/node/apiCommands.ts", + "./vs/workbench/api/node/extHost.protocol.ts", + "./vs/workbench/api/node/extHostApiCommands.ts", + "./vs/workbench/api/node/extHostCLIServer.ts", + "./vs/workbench/api/node/extHostClipboard.ts", + "./vs/workbench/api/node/extHostCommands.ts", + "./vs/workbench/api/node/extHostComments.ts", + "./vs/workbench/api/node/extHostConfiguration.ts", + // "./vs/workbench/api/node/extHostDebugService.ts", + "./vs/workbench/api/node/extHostDecorations.ts", + "./vs/workbench/api/node/extHostDiagnostics.ts", + "./vs/workbench/api/node/extHostDialogs.ts", + "./vs/workbench/api/node/extHostDocumentContentProviders.ts", + "./vs/workbench/api/node/extHostDocumentData.ts", + "./vs/workbench/api/node/extHostDocumentSaveParticipant.ts", + "./vs/workbench/api/node/extHostDocuments.ts", + "./vs/workbench/api/node/extHostDocumentsAndEditors.ts", + "./vs/workbench/api/node/extHostExtensionActivator.ts", + "./vs/workbench/api/node/extHostFileSystem.ts", + "./vs/workbench/api/node/extHostFileSystemEventService.ts", + "./vs/workbench/api/node/extHostHeapService.ts", + "./vs/workbench/api/node/extHostLanguageFeatures.ts", + "./vs/workbench/api/node/extHostLanguages.ts", + "./vs/workbench/api/node/extHostLogService.ts", + "./vs/workbench/api/node/extHostMessageService.ts", + "./vs/workbench/api/node/extHostOutputService.ts", + "./vs/workbench/api/node/extHostProgress.ts", + "./vs/workbench/api/node/extHostQuickOpen.ts", + "./vs/workbench/api/node/extHostSCM.ts", + "./vs/workbench/api/node/extHostSearch.ts", + "./vs/workbench/api/node/extHostStatusBar.ts", + "./vs/workbench/api/node/extHostStorage.ts", + // "./vs/workbench/api/node/extHostTask.ts", + "./vs/workbench/api/node/extHostTerminalService.ts", + "./vs/workbench/api/node/extHostTextEditor.ts", + "./vs/workbench/api/node/extHostTextEditors.ts", + "./vs/workbench/api/node/extHostTreeViews.ts", + "./vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts", + "./vs/workbench/api/node/extHostTypeConverters.ts", + "./vs/workbench/api/node/extHostTypes.ts", + "./vs/workbench/api/node/extHostUrls.ts", + "./vs/workbench/api/node/extHostWebview.ts", + "./vs/workbench/api/node/extHostWindow.ts", + "./vs/workbench/api/node/extHostWorkspace.ts", + "./vs/workbench/api/shared/editor.ts", + "./vs/workbench/api/shared/tasks.ts", + "./vs/workbench/contrib/backup/common/backup.contribution.ts", + "./vs/workbench/contrib/backup/common/backupModelTracker.ts", + "./vs/workbench/contrib/backup/common/backupRestorer.ts", + "./vs/workbench/contrib/cli/node/cli.contribution.ts", + "./vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts", + "./vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts", + "./vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts", + "./vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts", + "./vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts", + "./vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts", + "./vs/workbench/contrib/codeEditor/browser/menuPreventer.ts", + "./vs/workbench/contrib/codeEditor/browser/selectionClipboard.ts", + "./vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts", + "./vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts", + "./vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts", + "./vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts", + "./vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts", + "./vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts", + "./vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts", + "./vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch.ts", + "./vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts", + "./vs/workbench/contrib/codeEditor/electron-browser/sleepResumeRepaintMinimap.ts", + "./vs/workbench/contrib/codeinset/common/codeInset.ts", + "./vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution.ts", + "./vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.ts", + "./vs/workbench/contrib/comments/common/commentModel.ts", + "./vs/workbench/contrib/comments/common/commentThreadWidget.ts", + "./vs/workbench/contrib/comments/electron-browser/commentGlyphWidget.ts", + "./vs/workbench/contrib/comments/electron-browser/commentsTreeViewer.ts", + "./vs/workbench/contrib/comments/electron-browser/reactionsAction.ts", + "./vs/workbench/contrib/comments/electron-browser/simpleCommentEditor.ts", + "./vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts", + "./vs/workbench/contrib/experiments/electron-browser/experiments.contribution.ts", + "./vs/workbench/contrib/experiments/node/experimentService.ts", + "./vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts", + "./vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts", + "./vs/workbench/contrib/extensions/browser/extensionsQuickOpen.ts", + "./vs/workbench/contrib/extensions/browser/extensionsViewer.ts", + "./vs/workbench/contrib/extensions/common/extensionQuery.ts", + "./vs/workbench/contrib/extensions/common/extensions.ts", + "./vs/workbench/contrib/extensions/common/extensionsFileTemplate.ts", + "./vs/workbench/contrib/extensions/common/extensionsInput.ts", + "./vs/workbench/contrib/extensions/common/extensionsUtils.ts", + "./vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts", + "./vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts", + "./vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts", + "./vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts", + "./vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts", + "./vs/workbench/contrib/extensions/electron-browser/extensionsActivationProgress.ts", + "./vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts", + "./vs/workbench/contrib/extensions/electron-browser/extensionsList.ts", + "./vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts", + "./vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts", + "./vs/workbench/contrib/extensions/electron-browser/extensionsWidgets.ts", + "./vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts", + "./vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput.ts", + "./vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts", + "./vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts", + "./vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts", + "./vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts", + "./vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts", + "./vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts", + "./vs/workbench/contrib/feedback/electron-browser/feedback.contribution.ts", + "./vs/workbench/contrib/feedback/electron-browser/feedback.ts", + "./vs/workbench/contrib/feedback/electron-browser/feedbackStatusbarItem.ts", + "./vs/workbench/contrib/format/browser/format.contribution.ts", + "./vs/workbench/contrib/localizations/browser/localizations.contribution.ts", + "./vs/workbench/contrib/localizations/browser/localizationsActions.ts", + "./vs/workbench/contrib/localizations/browser/minimalTranslations.ts", + "./vs/workbench/contrib/logs/common/logConstants.ts", + "./vs/workbench/contrib/logs/common/logs.contribution.ts", + "./vs/workbench/contrib/logs/common/logsActions.ts", + "./vs/workbench/contrib/markers/browser/constants.ts", + "./vs/workbench/contrib/markers/browser/markers.contribution.ts", + "./vs/workbench/contrib/markers/browser/markers.ts", + "./vs/workbench/contrib/markers/browser/markersFileDecorations.ts", + "./vs/workbench/contrib/markers/browser/markersFilterOptions.ts", + "./vs/workbench/contrib/markers/browser/markersModel.ts", + "./vs/workbench/contrib/markers/browser/markersPanel.ts", + "./vs/workbench/contrib/markers/browser/markersPanelActions.ts", + "./vs/workbench/contrib/markers/browser/markersTreeViewer.ts", + "./vs/workbench/contrib/markers/browser/messages.ts", + "./vs/workbench/contrib/markers/test/electron-browser/markersModel.test.ts", + "./vs/workbench/contrib/output/browser/logViewer.ts", + "./vs/workbench/contrib/output/browser/output.contribution.ts", + "./vs/workbench/contrib/output/browser/outputActions.ts", + "./vs/workbench/contrib/output/browser/outputPanel.ts", + "./vs/workbench/contrib/output/browser/outputServices.ts", + "./vs/workbench/contrib/output/common/output.ts", + "./vs/workbench/contrib/output/common/outputLinkComputer.ts", + "./vs/workbench/contrib/output/common/outputLinkProvider.ts", + "./vs/workbench/contrib/output/test/outputLinkProvider.test.ts", + "./vs/workbench/contrib/preferences/browser/preferencesEditor.ts", + "./vs/workbench/contrib/preferences/browser/preferencesRenderers.ts", + "./vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts", + "./vs/workbench/contrib/preferences/browser/keybindingWidgets.ts", + "./vs/workbench/contrib/preferences/browser/preferencesActions.ts", + "./vs/workbench/contrib/preferences/browser/preferencesWidgets.ts", + "./vs/workbench/contrib/preferences/browser/settingsLayout.ts", + "./vs/workbench/contrib/preferences/browser/settingsWidgets.ts", + "./vs/workbench/contrib/preferences/browser/tocTree.ts", + "./vs/workbench/contrib/preferences/common/preferences.ts", + "./vs/workbench/contrib/preferences/common/preferencesContribution.ts", + "./vs/workbench/contrib/preferences/common/smartSnippetInserter.ts", + "./vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts", + "./vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts", + "./vs/workbench/contrib/quickopen/browser/commandsHandler.ts", + "./vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts", + "./vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts", + "./vs/workbench/contrib/quickopen/browser/helpHandler.ts", + "./vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts", + "./vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts", + "./vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution.ts", + "./vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts", + "./vs/workbench/contrib/scm/browser/scmActivity.ts", + "./vs/workbench/contrib/scm/browser/scmMenus.ts", + "./vs/workbench/contrib/scm/browser/scmUtil.ts", + "./vs/workbench/contrib/scm/common/scm.ts", + "./vs/workbench/contrib/scm/common/scmService.ts", + "./vs/workbench/contrib/search/browser/openAnythingHandler.ts", + "./vs/workbench/contrib/search/browser/openFileHandler.ts", + "./vs/workbench/contrib/search/browser/openSymbolHandler.ts", + "./vs/workbench/contrib/search/browser/patternInputWidget.ts", + "./vs/workbench/contrib/search/browser/replaceContributions.ts", + "./vs/workbench/contrib/search/browser/replaceService.ts", + "./vs/workbench/contrib/search/common/constants.ts", + "./vs/workbench/contrib/search/common/queryBuilder.ts", + "./vs/workbench/contrib/search/common/replace.ts", + "./vs/workbench/contrib/search/common/search.ts", + "./vs/workbench/contrib/search/common/searchHistoryService.ts", + "./vs/workbench/contrib/search/common/searchModel.ts", + "./vs/workbench/contrib/search/test/browser/mockSearchTree.ts", + "./vs/workbench/contrib/search/test/browser/openFileHandler.test.ts", + "./vs/workbench/contrib/search/test/browser/searchViewlet.test.ts", + "./vs/workbench/contrib/search/test/common/queryBuilder.test.ts", + "./vs/workbench/contrib/search/test/common/searchModel.test.ts", + "./vs/workbench/contrib/search/test/common/searchResult.test.ts", + "./vs/workbench/contrib/stats/node/workspaceStats.ts", + "./vs/workbench/contrib/stats/test/workspaceStats.test.ts", + "./vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution.ts", + "./vs/workbench/contrib/surveys/electron-browser/nps.contribution.ts", + "./vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts", + "./vs/workbench/contrib/themes/browser/themes.contribution.ts", + "./vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts", + "./vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts", + "./vs/workbench/contrib/update/electron-browser/update.contribution.ts", + "./vs/workbench/contrib/update/electron-browser/update.ts", + "./vs/workbench/contrib/url/common/url.contribution.ts", + "./vs/workbench/contrib/watermark/browser/watermark.ts", + "./vs/workbench/services/activity/browser/activityService.ts", + "./vs/workbench/services/activity/common/activity.ts", + "./vs/workbench/services/activityBar/browser/activityBarService.ts", + "./vs/workbench/services/backup/common/backup.ts", + "./vs/workbench/services/backup/node/backupFileService.ts", + "./vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts", + "./vs/workbench/services/broadcast/electron-browser/broadcastService.ts", + "./vs/workbench/services/configuration/common/configurationEditingService.ts", + "./vs/workbench/services/configuration/common/jsonEditingService.ts", + "./vs/workbench/services/configuration/node/configuration.ts", + "./vs/workbench/services/configuration/node/configurationService.ts", + "./vs/workbench/services/configuration/test/common/configurationModels.test.ts", + "./vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts", + "./vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts", + "./vs/workbench/services/configurationResolver/common/configurationResolver.ts", + "./vs/workbench/services/configurationResolver/common/configurationResolverSchema.ts", + "./vs/workbench/services/configurationResolver/common/configurationResolverUtils.ts", + "./vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts", + "./vs/workbench/services/decorations/browser/decorations.ts", + "./vs/workbench/services/decorations/browser/decorationsService.ts", + "./vs/workbench/services/decorations/test/browser/decorationsService.test.ts", + "./vs/workbench/services/dialogs/browser/remoteFileDialog.ts", + "./vs/workbench/services/dialogs/browser/fileDialogService.ts", + "./vs/workbench/services/dialogs/electron-browser/dialogService.ts", + "./vs/workbench/services/editor/browser/codeEditorService.ts", + "./vs/workbench/services/editor/browser/editorService.ts", + "./vs/workbench/services/editor/common/editorGroupsService.ts", + "./vs/workbench/services/editor/common/editorService.ts", + "./vs/workbench/services/editor/test/browser/editorGroupsService.test.ts", + "./vs/workbench/services/editor/test/browser/editorService.test.ts", + "./vs/workbench/services/extensionManagement/node/multiExtensionManagement.ts", + "./vs/workbench/services/extensions/common/extensions.ts", + "./vs/workbench/services/extensions/common/extensionsRegistry.ts", + "./vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts", + "./vs/workbench/services/extensions/electron-browser/extensionHost.ts", + "./vs/workbench/services/extensions/electron-browser/extensionHostProcessManager.ts", + "./vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts", + "./vs/workbench/services/extensions/electron-browser/extensionManagementServerService.ts", + "./vs/workbench/services/extensions/electron-browser/extensionService.ts", + "./vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler.ts", + "./vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts", + "./vs/workbench/services/extensions/node/extensionHostProtocol.ts", + "./vs/workbench/services/extensions/node/extensionPoints.ts", + "./vs/workbench/services/extensions/node/extensionsUtil.ts", + "./vs/workbench/services/extensions/node/lazyPromise.ts", + "./vs/workbench/services/extensions/node/proxyIdentifier.ts", + "./vs/workbench/services/extensions/node/rpcProtocol.ts", + "./vs/workbench/services/extensions/test/node/rpcProtocol.test.ts", + "./vs/workbench/services/files/node/encoding.ts", + "./vs/workbench/services/files/node/fileService.ts", + "./vs/workbench/services/files/node/remoteFileService.ts", + "./vs/workbench/services/files/node/streams.ts", + "./vs/workbench/services/files/test/electron-browser/fileService.test.ts", + "./vs/workbench/services/files/test/electron-browser/resolver.test.ts", + "./vs/workbench/services/files/test/electron-browser/utils.ts", + "./vs/workbench/services/files/test/electron-browser/watcher.test.ts", + "./vs/workbench/services/hash/common/hashService.ts", + "./vs/workbench/services/hash/node/hashService.ts", + "./vs/workbench/services/hash/test/hashService.test.ts", + "./vs/workbench/services/history/browser/history.ts", + "./vs/workbench/services/history/common/history.ts", + "./vs/workbench/services/integrity/common/integrity.ts", + "./vs/workbench/services/integrity/node/integrityService.ts", + "./vs/workbench/services/keybinding/common/keybindingEditing.ts", + "./vs/workbench/services/keybinding/common/keybindingIO.ts", + "./vs/workbench/services/keybinding/common/keyboardMapper.ts", + "./vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts", + "./vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts", + "./vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts", + "./vs/workbench/services/keybinding/electron-browser/keybindingService.ts", + "./vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts", + "./vs/workbench/services/keybinding/test/keybindingIO.test.ts", + "./vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts", + "./vs/workbench/services/keybinding/test/macLinuxFallbackKeyboardMapper.test.ts", + "./vs/workbench/services/keybinding/test/macLinuxKeyboardMapper.test.ts", + "./vs/workbench/services/keybinding/test/windowsKeyboardMapper.test.ts", + "./vs/workbench/services/label/common/labelService.ts", + "./vs/workbench/services/label/test/label.test.ts", + "./vs/workbench/services/layout/browser/layoutService.ts", + "./vs/workbench/services/mode/common/workbenchModeService.ts", + "./vs/workbench/services/notification/common/notificationService.ts", + "./vs/workbench/services/panel/common/panelService.ts", + "./vs/workbench/services/preferences/common/preferencesModels.ts", + "./vs/workbench/services/preferences/common/keybindingsEditorModel.ts", + "./vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts", + "./vs/workbench/services/progress/browser/progressService.ts", + "./vs/workbench/services/progress/browser/progressService2.ts", + "./vs/workbench/services/progress/test/progressService.test.ts", + "./vs/workbench/services/remote/common/remoteAgentService.ts", + "./vs/workbench/services/remote/common/remoteEnvironmentService.ts", + "./vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts", + "./vs/workbench/services/remote/node/remoteAgentEnvironmentChannel.ts", + "./vs/workbench/services/search/common/replace.ts", + "./vs/workbench/services/search/common/search.ts", + "./vs/workbench/services/search/common/searchHelpers.ts", + "./vs/workbench/services/search/node/fileSearch.ts", + "./vs/workbench/services/search/node/fileSearchManager.ts", + "./vs/workbench/services/search/node/rawSearchService.ts", + "./vs/workbench/services/search/node/ripgrepFileSearch.ts", + "./vs/workbench/services/search/node/ripgrepSearchProvider.ts", + "./vs/workbench/services/search/node/ripgrepSearchUtils.ts", + "./vs/workbench/services/search/node/ripgrepTextSearchEngine.ts", + "./vs/workbench/services/search/node/searchApp.ts", + "./vs/workbench/services/search/node/searchIpc.ts", + "./vs/workbench/services/search/node/textSearchAdapter.ts", + "./vs/workbench/services/search/node/textSearchManager.ts", + "./vs/workbench/services/search/test/common/replace.test.ts", + "./vs/workbench/services/search/test/common/search.test.ts", + "./vs/workbench/services/search/test/common/searchHelpers.test.ts", + "./vs/workbench/services/search/test/node/ripgrepFileSearch.test.ts", + "./vs/workbench/services/search/test/node/ripgrepTextSearchEngine.test.ts", + "./vs/workbench/services/search/test/node/search.test.ts", + "./vs/workbench/services/search/test/node/textSearch.integrationTest.ts", + "./vs/workbench/services/search/test/node/textSearchManager.test.ts", + "./vs/workbench/services/textMate/common/TMGrammars.ts", + "./vs/workbench/services/textMate/common/TMHelper.ts", + "./vs/workbench/services/textMate/common/textMateService.ts", + "./vs/workbench/services/textMate/electron-browser/textMateService.ts", + "./vs/workbench/services/textfile/common/textFileEditorModel.ts", + "./vs/workbench/services/textfile/common/textFileEditorModelManager.ts", + "./vs/workbench/services/textfile/common/textFileService.ts", + "./vs/workbench/services/textfile/common/textfiles.ts", + "./vs/workbench/services/textfile/node/textResourcePropertiesService.ts", + "./vs/workbench/services/textfile/test/textFileEditorModel.test.ts", + "./vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts", + "./vs/workbench/services/textfile/test/textFileService.test.ts", + "./vs/workbench/services/textmodelResolver/common/textModelResolverService.ts", + "./vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts", + "./vs/workbench/services/timer/electron-browser/timerService.ts", + "./vs/workbench/services/title/common/titleService.ts", + "./vs/workbench/services/untitled/common/untitledEditorService.ts", + "./vs/workbench/services/viewlet/browser/viewlet.ts", + "./vs/workbench/services/workspace/common/workspaceEditing.ts", + "./vs/workbench/services/configurationResolver/common/variableResolver.ts", + "./vs/workbench/services/workspace/node/workspaceEditingService.ts", + "./vs/workbench/test/browser/actionRegistry.test.ts", + "./vs/workbench/test/browser/part.test.ts", + "./vs/workbench/test/browser/parts/editor/baseEditor.test.ts", + "./vs/workbench/test/browser/parts/editor/breadcrumbModel.test.ts", + "./vs/workbench/test/browser/parts/editor/rangeDecorations.test.ts", + "./vs/workbench/test/browser/parts/views/views.test.ts", + "./vs/workbench/test/browser/quickopen.test.ts", + "./vs/workbench/test/browser/viewlet.test.ts", + "./vs/workbench/test/common/editor/dataUriEditorInput.test.ts", + "./vs/workbench/test/common/editor/editor.test.ts", + "./vs/workbench/test/common/editor/editorDiffModel.test.ts", + "./vs/workbench/test/common/editor/editorGroups.test.ts", + "./vs/workbench/test/common/editor/editorInput.test.ts", + "./vs/workbench/test/common/editor/editorModel.test.ts", + "./vs/workbench/test/common/editor/editorOptions.test.ts", + "./vs/workbench/test/common/editor/resourceEditorInput.test.ts", + "./vs/workbench/test/common/editor/untitledEditor.test.ts", + "./vs/workbench/test/common/memento.test.ts", + "./vs/workbench/test/common/notifications.test.ts", + "./vs/workbench/test/electron-browser/api/extHost.api.impl.test.ts", + "./vs/workbench/test/electron-browser/api/extHostCommands.test.ts", + "./vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts", + "./vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts", + "./vs/workbench/test/electron-browser/api/extHostDocumentData.test.ts", + "./vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts", + "./vs/workbench/test/electron-browser/api/extHostDocumentsAndEditors.test.ts", + "./vs/workbench/test/electron-browser/api/extHostFileSystemEventService.test.ts", + "./vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts", + "./vs/workbench/test/electron-browser/api/extHostMessagerService.test.ts", + "./vs/workbench/test/electron-browser/api/extHostSearch.test.ts", + "./vs/workbench/test/electron-browser/api/extHostTextEditor.test.ts", + "./vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts", + "./vs/workbench/test/electron-browser/api/extHostTypeConverter.test.ts", + "./vs/workbench/test/electron-browser/api/extHostTypes.test.ts", + "./vs/workbench/test/electron-browser/api/extHostWebview.test.ts", + "./vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts", + "./vs/workbench/test/electron-browser/api/mainThreadCommands.test.ts", + "./vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts", + "./vs/workbench/test/electron-browser/api/mainThreadDiagnostics.test.ts", + "./vs/workbench/test/electron-browser/api/mainThreadDocumentContentProviders.test.ts", + "./vs/workbench/test/electron-browser/api/mainThreadDocuments.test.ts", + "./vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts", + "./vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts", + "./vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts", + "./vs/workbench/test/electron-browser/api/mainThreadWorkspace.test.ts", + "./vs/workbench/test/electron-browser/api/mock.ts", + "./vs/workbench/test/electron-browser/api/testRPCProtocol.ts", + "./vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts", + "./vs/workbench/test/workbenchTestServices.ts" ], "exclude": [ - "./typings/require-monaco.d.ts" + "./typings/require-monaco.d.ts", + "./vs/workbench/contrib/comments/electron-browser/commentThreadWidget.ts" ] } \ No newline at end of file diff --git a/src/tsconfig.strictNullChecks.sql.json b/src/tsconfig.strictNullChecks.sql.json new file mode 100644 index 0000000000..76154b77b8 --- /dev/null +++ b/src/tsconfig.strictNullChecks.sql.json @@ -0,0 +1,35 @@ +{ + "extends": "./tsconfig.strictNullChecks.json", + "include": [ + "./typings", + "./vs/base/browser/**/*.ts", + "./vs/base/common/**/*.ts", + "./vs/base/node/**/*.ts", + "./vs/editor/browser/**/*.ts", + "./vs/editor/common/**/*.ts", + "./vs/editor/contrib/codeAction/**/*.ts", + "./vs/editor/contrib/format/**/*.ts", + "./vs/editor/contrib/gotoError/**/*.ts", + "./vs/editor/contrib/inPlaceReplace/**/*.ts", + "./vs/editor/contrib/smartSelect/**/*.ts", + "./vs/editor/contrib/snippet/**/*.ts", + "./vs/editor/contrib/suggest/**/*.ts", + "./vs/editor/test/**/*.ts", + "./sql/base/common/**/*ts", + ], + "files": [ + // "./sql/base/browser/ui/breadcrumb/breadcrumb.component.ts", // pulls in angular which gives extrenous errors + // "./sql/base/browser/ui/breadcrumb/interfaces.ts", // pulls in angular which gives extrenous errors + "./sql/base/browser/ui/button/button.ts", + "./sql/base/browser/ui/checkbox/checkbox.ts", + // "./sql/base/browser/ui/checkbox/checkbox.component.ts", // pulls in angular which gives extrenous errors + // "./sql/base/browser/ui/dropdownList/dropdownList.ts", // skipping since vscode hasn't checked their dropdown yet + // "./sql/base/browser/ui/editableDropdown/actions.ts", // skipping since vscode hasn't checked their dropdown yet and it isn't in the right place anyways + // "./sql/base/browser/ui/editableDropdown/dropdown.ts", // skipping since vscode hasn't checked their dropdown yet and it isn't in the right place anyways + // "./sql/base/browser/ui/editableDropdown/dropdownTree.ts", // skipping since vscode hasn't checked their dropdown yet and it isn't in the right place anyways + // "./sql/base/browser/ui/editableDropdown/editableDropdown.component.ts", // skipping since vscode hasn't checked their dropdown yet and it isn't in the right place anyways + // "./sql/base/browser/ui/inputBox/inputBox.component.ts", // pulls in angular which gives extrenous errors + // "./sql/base/browser/ui/inputBox/inputBox.ts", // skipping since vscode hasn't checked their inputbox yet + // "./sql/base/browser/ui/listBox/listBox.ts", + ] +} diff --git a/src/typings/bootstrap.d.ts b/src/typings/bootstrap.d.ts deleted file mode 100644 index e7d8b9c21e..0000000000 --- a/src/typings/bootstrap.d.ts +++ /dev/null @@ -1,124 +0,0 @@ -// Generated by typings -// Source: https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/e94e9a86308b7306bb74a973c4e18f37895f7298/bootstrap/index.d.ts -interface ModalOptions { - backdrop?: boolean|string; - keyboard?: boolean; - show?: boolean; - remote?: string; -} - -interface ModalOptionsBackdropString { - backdrop?: string; // for "static" - keyboard?: boolean; - show?: boolean; - remote?: string; -} - -interface ScrollSpyOptions { - offset?: number; - target?: string; -} - -interface TooltipOptions { - animation?: boolean; - html?: boolean; - placement?: string | Function; - selector?: string; - title?: string | Function; - trigger?: string; - template?: string; - delay?: number | Object; - container?: string | boolean; - viewport?: string | Function | Object; -} - -interface PopoverOptions { - animation?: boolean; - html?: boolean; - placement?: string | Function; - selector?: string; - trigger?: string; - title?: string | Function; - template?: string; - content?: any; - delay?: number | Object; - container?: string | boolean; - viewport?: string | Function | Object; -} - -interface CollapseOptions { - parent?: any; - toggle?: boolean; -} - -interface CarouselOptions { - interval?: number; - pause?: string; - wrap?: boolean; - keybord?: boolean; -} - -interface TypeaheadOptions { - source?: any; - items?: number; - minLength?: number; - matcher?: (item: any) => boolean; - sorter?: (items: any[]) => any[]; - updater?: (item: any) => any; - highlighter?: (item: any) => string; -} - -interface AffixOptions { - offset?: number | Function | Object; - target?: any; -} - -interface TransitionEventNames { - end: string; -} - -interface JQuery { - modal(options?: ModalOptions): JQuery; - modal(options?: ModalOptionsBackdropString): JQuery; - modal(command: string): JQuery; - - dropdown(): JQuery; - dropdown(command: string): JQuery; - - scrollspy(command: string): JQuery; - scrollspy(options?: ScrollSpyOptions): JQuery; - - tab(): JQuery; - tab(command: string): JQuery; - - tooltip(options?: TooltipOptions): JQuery; - tooltip(command: string): JQuery; - - popover(options?: PopoverOptions): JQuery; - popover(command: string): JQuery; - - alert(): JQuery; - alert(command: string): JQuery; - - button(): JQuery; - button(command: string): JQuery; - - collapse(options?: CollapseOptions): JQuery; - collapse(command: string): JQuery; - - carousel(options?: CarouselOptions): JQuery; - carousel(command: string): JQuery; - - typeahead(options?: TypeaheadOptions): JQuery; - - affix(options?: AffixOptions): JQuery; - - emulateTransitionEnd(duration: number): JQuery; -} - -interface JQuerySupport { - transition: boolean | TransitionEventNames; -} - -declare module "bootstrap" { -} diff --git a/src/typings/comment-json.d.ts b/src/typings/comment-json.d.ts deleted file mode 100644 index c3a4b2203c..0000000000 --- a/src/typings/comment-json.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -// Type definitions for comment-json 1.1 -// Project: https://github.com/kaelzhang/node-comment-json -// Definitions by: Jason Dent -// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - -declare module "comment-json" { -export type Reviver = (k: number | string, v: any) => any; -export function parse(json: string, reviver?: Reviver, removes_comments?: boolean): any; -export function stringify(value: any, replacer?: any, space?: string | number): string; -} \ No newline at end of file diff --git a/src/typings/electron.d.ts b/src/typings/electron.d.ts index 8f72a1bc56..f680249a66 100644 --- a/src/typings/electron.d.ts +++ b/src/typings/electron.d.ts @@ -1,4 +1,4 @@ -// Type definitions for Electron 3.1.2 +// Type definitions for Electron 3.1.6 // Project: http://electronjs.org/ // Definitions by: The Electron Team // Definitions: https://github.com/electron/electron-typescript-definitions @@ -7869,6 +7869,11 @@ declare namespace Electron { * Should only be specified for checkbox or radio type menu items. */ checked?: boolean; + /** + * If false, the accelerator won't be registered with the system, but it will still + * be displayed. Defaults to true. + */ + registerAccelerator?: boolean; /** * Should be specified for submenu type menu items. If submenu is specified, the * type: 'submenu' can be omitted. If the value is not a then it will be diff --git a/src/typings/es6-promise.d.ts b/src/typings/es6-promise.d.ts index a7d45d336d..2d3271e284 100644 --- a/src/typings/es6-promise.d.ts +++ b/src/typings/es6-promise.d.ts @@ -44,8 +44,12 @@ declare namespace Promise { * Make a new promise from the thenable. * A thenable is promise-like in as far as it has a "then" method. */ - function resolve(): Promise; - function resolve(value?: T | Thenable): Promise; + function resolve(value: T | Thenable): Promise; + + /** + * + */ + function resolve(): Promise; /** * Make a promise that rejects to obj. For consistency and debugging (eg stack traces), obj should be an instanceof Error diff --git a/src/typings/fast-plist.d.ts b/src/typings/fast-plist.d.ts deleted file mode 100644 index 537e7c2e8e..0000000000 --- a/src/typings/fast-plist.d.ts +++ /dev/null @@ -1,7 +0,0 @@ - -declare module "fast-plist" { - /** - * A very fast plist parser - */ - export function parse(content: string): any; -} diff --git a/src/typings/globals/core-js/index.d.ts b/src/typings/globals/core-js/index.d.ts index fa429b4bc1..8ce32ed9d9 100644 --- a/src/typings/globals/core-js/index.d.ts +++ b/src/typings/globals/core-js/index.d.ts @@ -443,7 +443,7 @@ interface SymbolConstructor { /** * A reference to the prototype. */ - prototype: Symbol; + readonly prototype: Symbol; /** * Returns a new unique Symbol value. @@ -600,56 +600,6 @@ interface JSON { // Modules: es6.map, es6.set, es6.weak-map, and es6.weak-set // ############################################################################################# -interface Map { - clear(): void; - delete(key: K): boolean; - forEach(callbackfn: (value: V, index: K, map: Map) => void, thisArg?: any): void; - get(key: K): V; - has(key: K): boolean; - set(key: K, value?: V): Map; - size: number; -} - -interface MapConstructor { - new (): Map; - new (iterable: Iterable<[K, V]>): Map; - prototype: Map; -} - -declare var Map: MapConstructor; - -interface Set { - add(value: T): Set; - clear(): void; - delete(value: T): boolean; - forEach(callbackfn: (value: T, index: T, set: Set) => void, thisArg?: any): void; - has(value: T): boolean; - size: number; -} - -interface SetConstructor { - new (): Set; - new (iterable: Iterable): Set; - prototype: Set; -} - -declare var Set: SetConstructor; - -interface WeakMap { - delete(key: K): boolean; - get(key: K): V; - has(key: K): boolean; - set(key: K, value?: V): WeakMap; -} - -interface WeakMapConstructor { - new (): WeakMap; - new (iterable: Iterable<[K, V]>): WeakMap; - prototype: WeakMap; -} - -declare var WeakMap: WeakMapConstructor; - interface WeakSet { add(value: T): WeakSet; delete(value: T): boolean; @@ -657,9 +607,9 @@ interface WeakSet { } interface WeakSetConstructor { - new (): WeakSet; - new (iterable: Iterable): WeakSet; - prototype: WeakSet; + new (): WeakSet; + new (iterable: Iterable): WeakSet; + readonly prototype: WeakSet; } declare var WeakSet: WeakSetConstructor; @@ -671,7 +621,7 @@ declare var WeakSet: WeakSetConstructor; interface IteratorResult { done: boolean; - value?: T; + value: T; } interface Iterator { @@ -713,20 +663,6 @@ interface Array { values(): IterableIterator; } -interface Map { - entries(): IterableIterator<[K, V]>; - keys(): IterableIterator; - values(): IterableIterator; - [Symbol.iterator](): IterableIterator<[K, V]>; -} - -interface Set { - entries(): IterableIterator<[T, T]>; - keys(): IterableIterator; - values(): IterableIterator; - [Symbol.iterator](): IterableIterator; -} - interface NodeList { [Symbol.iterator](): IterableIterator; } @@ -897,14 +833,6 @@ interface RegExpConstructor { escape(str: string): string; } -interface Map { - toJSON(): any; -} - -interface Set { - toJSON(): any; -} - // ############################################################################################# // Mozilla JavaScript: Array generics // Modules: js.array.statics diff --git a/src/typings/globals/jqueryui/typings.json b/src/typings/globals/jqueryui/typings.json deleted file mode 100644 index f615a72907..0000000000 --- a/src/typings/globals/jqueryui/typings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "resolution": "main", - "tree": { - "src": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/ead8376ca80553332af7872f9fe723c6fbb4e412/jqueryui/index.d.ts", - "raw": "registry:dt/jqueryui#1.11.0+20161214061125", - "typings": "https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/ead8376ca80553332af7872f9fe723c6fbb4e412/jqueryui/index.d.ts" - } -} diff --git a/src/typings/lib.ie11_safe_es6.d.ts b/src/typings/lib.ie11_safe_es6.d.ts new file mode 100644 index 0000000000..43bde6f7c6 --- /dev/null +++ b/src/typings/lib.ie11_safe_es6.d.ts @@ -0,0 +1,821 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Defined a subset of ES6 built ins that run in IE11 +// CHECK WITH http://kangax.github.io/compat-table/es6/#ie11 + +interface Map { + clear(): void; + delete(key: K): boolean; + forEach(callbackfn: (value: V, index: K, map: Map) => void, thisArg?: any): void; + get(key: K): V | undefined; + has(key: K): boolean; + set(key: K, value?: V): Map; + readonly size: number; + + // not supported on IE11: + // entries(): IterableIterator<[K, V]>; + // keys(): IterableIterator; + // values(): IterableIterator; + // [Symbol.iterator]():IterableIterator<[K,V]>; + // [Symbol.toStringTag]: string; +} + +interface MapConstructor { + new (): Map; + prototype: Map; + + // not supported on IE11: + // new (iterable: Iterable<[K, V]>): Map; +} +declare var Map: MapConstructor; + + +interface Set { + add(value: T): Set; + clear(): void; + delete(value: T): boolean; + forEach(callbackfn: (value: T, index: T, set: Set) => void, thisArg?: any): void; + has(value: T): boolean; + readonly size: number; + + // not supported on IE11: + // entries(): IterableIterator<[T, T]>; + // keys(): IterableIterator; + // values(): IterableIterator; + // [Symbol.iterator]():IterableIterator; + // [Symbol.toStringTag]: string; +} + +interface SetConstructor { + new (): Set; + prototype: Set; + + // not supported on IE11: + // new (iterable: Iterable): Set; +} +declare var Set: SetConstructor; + + +interface WeakMap { + delete(key: K): boolean; + get(key: K): V | undefined; + has(key: K): boolean; + // IE11 doesn't return this + // set(key: K, value?: V): this; + set(key: K, value?: V): undefined; +} + +interface WeakMapConstructor { + new(): WeakMap; + new (): WeakMap; + // new (entries?: [K, V][]): WeakMap; + readonly prototype: WeakMap; +} +declare var WeakMap: WeakMapConstructor; + + +// /** +// * Represents a raw buffer of binary data, which is used to store data for the +// * different typed arrays. ArrayBuffers cannot be read from or written to directly, +// * but can be passed to a typed array or DataView Object to interpret the raw +// * buffer as needed. +// */ +// interface ArrayBuffer { +// /** +// * Read-only. The length of the ArrayBuffer (in bytes). +// */ +// readonly byteLength: number; + +// /** +// * Returns a section of an ArrayBuffer. +// */ +// slice(begin: number, end?: number): ArrayBuffer; +// } + +// interface ArrayBufferConstructor { +// readonly prototype: ArrayBuffer; +// new (byteLength: number): ArrayBuffer; +// isView(arg: any): arg is ArrayBufferView; +// } +// declare const ArrayBuffer: ArrayBufferConstructor; + +// interface ArrayBufferView { +// /** +// * The ArrayBuffer instance referenced by the array. +// */ +// buffer: ArrayBuffer; + +// /** +// * The length in bytes of the array. +// */ +// byteLength: number; + +// /** +// * The offset in bytes of the array. +// */ +// byteOffset: number; +// } + +// interface DataView { +// readonly buffer: ArrayBuffer; +// readonly byteLength: number; +// readonly byteOffset: number; +// /** +// * Gets the Float32 value at the specified byte offset from the start of the view. There is +// * no alignment constraint; multi-byte values may be fetched from any offset. +// * @param byteOffset The place in the buffer at which the value should be retrieved. +// */ +// getFloat32(byteOffset: number, littleEndian?: boolean): number; + +// /** +// * Gets the Float64 value at the specified byte offset from the start of the view. There is +// * no alignment constraint; multi-byte values may be fetched from any offset. +// * @param byteOffset The place in the buffer at which the value should be retrieved. +// */ +// getFloat64(byteOffset: number, littleEndian?: boolean): number; + +// /** +// * Gets the Int8 value at the specified byte offset from the start of the view. There is +// * no alignment constraint; multi-byte values may be fetched from any offset. +// * @param byteOffset The place in the buffer at which the value should be retrieved. +// */ +// getInt8(byteOffset: number): number; + +// /** +// * Gets the Int16 value at the specified byte offset from the start of the view. There is +// * no alignment constraint; multi-byte values may be fetched from any offset. +// * @param byteOffset The place in the buffer at which the value should be retrieved. +// */ +// getInt16(byteOffset: number, littleEndian?: boolean): number; +// /** +// * Gets the Int32 value at the specified byte offset from the start of the view. There is +// * no alignment constraint; multi-byte values may be fetched from any offset. +// * @param byteOffset The place in the buffer at which the value should be retrieved. +// */ +// getInt32(byteOffset: number, littleEndian?: boolean): number; + +// /** +// * Gets the Uint8 value at the specified byte offset from the start of the view. There is +// * no alignment constraint; multi-byte values may be fetched from any offset. +// * @param byteOffset The place in the buffer at which the value should be retrieved. +// */ +// getUint8(byteOffset: number): number; + +// /** +// * Gets the Uint16 value at the specified byte offset from the start of the view. There is +// * no alignment constraint; multi-byte values may be fetched from any offset. +// * @param byteOffset The place in the buffer at which the value should be retrieved. +// */ +// getUint16(byteOffset: number, littleEndian?: boolean): number; + +// /** +// * Gets the Uint32 value at the specified byte offset from the start of the view. There is +// * no alignment constraint; multi-byte values may be fetched from any offset. +// * @param byteOffset The place in the buffer at which the value should be retrieved. +// */ +// getUint32(byteOffset: number, littleEndian?: boolean): number; + +// /** +// * Stores an Float32 value at the specified byte offset from the start of the view. +// * @param byteOffset The place in the buffer at which the value should be set. +// * @param value The value to set. +// * @param littleEndian If false or undefined, a big-endian value should be written, +// * otherwise a little-endian value should be written. +// */ +// setFloat32(byteOffset: number, value: number, littleEndian?: boolean): void; + +// /** +// * Stores an Float64 value at the specified byte offset from the start of the view. +// * @param byteOffset The place in the buffer at which the value should be set. +// * @param value The value to set. +// * @param littleEndian If false or undefined, a big-endian value should be written, +// * otherwise a little-endian value should be written. +// */ +// setFloat64(byteOffset: number, value: number, littleEndian?: boolean): void; + +// /** +// * Stores an Int8 value at the specified byte offset from the start of the view. +// * @param byteOffset The place in the buffer at which the value should be set. +// * @param value The value to set. +// */ +// setInt8(byteOffset: number, value: number): void; + +// /** +// * Stores an Int16 value at the specified byte offset from the start of the view. +// * @param byteOffset The place in the buffer at which the value should be set. +// * @param value The value to set. +// * @param littleEndian If false or undefined, a big-endian value should be written, +// * otherwise a little-endian value should be written. +// */ +// setInt16(byteOffset: number, value: number, littleEndian?: boolean): void; + +// /** +// * Stores an Int32 value at the specified byte offset from the start of the view. +// * @param byteOffset The place in the buffer at which the value should be set. +// * @param value The value to set. +// * @param littleEndian If false or undefined, a big-endian value should be written, +// * otherwise a little-endian value should be written. +// */ +// setInt32(byteOffset: number, value: number, littleEndian?: boolean): void; + +// /** +// * Stores an Uint8 value at the specified byte offset from the start of the view. +// * @param byteOffset The place in the buffer at which the value should be set. +// * @param value The value to set. +// */ +// setUint8(byteOffset: number, value: number): void; + +// /** +// * Stores an Uint16 value at the specified byte offset from the start of the view. +// * @param byteOffset The place in the buffer at which the value should be set. +// * @param value The value to set. +// * @param littleEndian If false or undefined, a big-endian value should be written, +// * otherwise a little-endian value should be written. +// */ +// setUint16(byteOffset: number, value: number, littleEndian?: boolean): void; + +// /** +// * Stores an Uint32 value at the specified byte offset from the start of the view. +// * @param byteOffset The place in the buffer at which the value should be set. +// * @param value The value to set. +// * @param littleEndian If false or undefined, a big-endian value should be written, +// * otherwise a little-endian value should be written. +// */ +// setUint32(byteOffset: number, value: number, littleEndian?: boolean): void; +// } + +// interface DataViewConstructor { +// new (buffer: ArrayBuffer, byteOffset?: number, byteLength?: number): DataView; +// } +// declare const DataView: DataViewConstructor; + + +// /** +// * A typed array of 8-bit integer values. The contents are initialized to 0. If the requested +// * number of bytes could not be allocated an exception is raised. +// */ +// interface Int8Array { +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; + +// /** +// * The ArrayBuffer instance referenced by the array. +// */ +// readonly buffer: ArrayBuffer; + +// /** +// * The length in bytes of the array. +// */ +// readonly byteLength: number; + +// /** +// * The offset in bytes of the array. +// */ +// readonly byteOffset: number; + +// /** +// * The length of the array. +// */ +// readonly length: number; + +// /** +// * Sets a value or an array of values. +// * @param index The index of the location to set. +// * @param value The value to set. +// */ +// set(index: number, value: number): void; + +// /** +// * Sets a value or an array of values. +// * @param array A typed or untyped array of values to set. +// * @param offset The index in the current array at which the values are to be written. +// */ +// set(array: ArrayLike, offset?: number): void; + +// /** +// * Converts a number to a string by using the current locale. +// */ +// toLocaleString(): string; + +// /** +// * Returns a string representation of an array. +// */ +// toString(): string; + +// [index: number]: number; +// } +// interface Int8ArrayConstructor { +// readonly prototype: Int8Array; +// new (length: number): Int8Array; +// new (array: ArrayLike): Int8Array; +// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Int8Array; + +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; + +// } +// declare const Int8Array: Int8ArrayConstructor; + +// /** +// * A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the +// * requested number of bytes could not be allocated an exception is raised. +// */ +// interface Uint8Array { +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; + +// /** +// * The ArrayBuffer instance referenced by the array. +// */ +// readonly buffer: ArrayBuffer; + +// /** +// * The length in bytes of the array. +// */ +// readonly byteLength: number; + +// /** +// * The offset in bytes of the array. +// */ +// readonly byteOffset: number; + +// /** +// * The length of the array. +// */ +// readonly length: number; + +// /** +// * Sets a value or an array of values. +// * @param index The index of the location to set. +// * @param value The value to set. +// */ +// set(index: number, value: number): void; + +// /** +// * Sets a value or an array of values. +// * @param array A typed or untyped array of values to set. +// * @param offset The index in the current array at which the values are to be written. +// */ +// set(array: ArrayLike, offset?: number): void; + +// /** +// * Converts a number to a string by using the current locale. +// */ +// toLocaleString(): string; + +// /** +// * Returns a string representation of an array. +// */ +// toString(): string; + +// [index: number]: number; +// } + +// interface Uint8ArrayConstructor { +// readonly prototype: Uint8Array; +// new (length: number): Uint8Array; +// new (array: ArrayLike): Uint8Array; +// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint8Array; + +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; + +// } +// declare const Uint8Array: Uint8ArrayConstructor; + + +// /** +// * A typed array of 16-bit signed integer values. The contents are initialized to 0. If the +// * requested number of bytes could not be allocated an exception is raised. +// */ +// interface Int16Array { +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; + +// /** +// * The ArrayBuffer instance referenced by the array. +// */ +// readonly buffer: ArrayBuffer; + +// /** +// * The length in bytes of the array. +// */ +// readonly byteLength: number; + +// /** +// * The offset in bytes of the array. +// */ +// readonly byteOffset: number; + +// /** +// * The length of the array. +// */ +// readonly length: number; + +// /** +// * Sets a value or an array of values. +// * @param index The index of the location to set. +// * @param value The value to set. +// */ +// set(index: number, value: number): void; + +// /** +// * Sets a value or an array of values. +// * @param array A typed or untyped array of values to set. +// * @param offset The index in the current array at which the values are to be written. +// */ +// set(array: ArrayLike, offset?: number): void; + +// /** +// * Converts a number to a string by using the current locale. +// */ +// toLocaleString(): string; + +// /** +// * Returns a string representation of an array. +// */ +// toString(): string; + +// [index: number]: number; +// } + +// interface Int16ArrayConstructor { +// readonly prototype: Int16Array; +// new (length: number): Int16Array; +// new (array: ArrayLike): Int16Array; +// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Int16Array; + +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; + +// } +// declare const Int16Array: Int16ArrayConstructor; + +// /** +// * A typed array of 16-bit unsigned integer values. The contents are initialized to 0. If the +// * requested number of bytes could not be allocated an exception is raised. +// */ +// interface Uint16Array { +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; + +// /** +// * The ArrayBuffer instance referenced by the array. +// */ +// readonly buffer: ArrayBuffer; + +// /** +// * The length in bytes of the array. +// */ +// readonly byteLength: number; + +// /** +// * The offset in bytes of the array. +// */ +// readonly byteOffset: number; + +// /** +// * The length of the array. +// */ +// readonly length: number; + +// /** +// * Sets a value or an array of values. +// * @param index The index of the location to set. +// * @param value The value to set. +// */ +// set(index: number, value: number): void; + +// /** +// * Sets a value or an array of values. +// * @param array A typed or untyped array of values to set. +// * @param offset The index in the current array at which the values are to be written. +// */ +// set(array: ArrayLike, offset?: number): void; + +// /** +// * Converts a number to a string by using the current locale. +// */ +// toLocaleString(): string; + +// /** +// * Returns a string representation of an array. +// */ +// toString(): string; + +// [index: number]: number; +// } + +// interface Uint16ArrayConstructor { +// readonly prototype: Uint16Array; +// new (length: number): Uint16Array; +// new (array: ArrayLike): Uint16Array; +// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint16Array; + +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; + +// } +// declare const Uint16Array: Uint16ArrayConstructor; +// /** +// * A typed array of 32-bit signed integer values. The contents are initialized to 0. If the +// * requested number of bytes could not be allocated an exception is raised. +// */ +// interface Int32Array { +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; + +// /** +// * The ArrayBuffer instance referenced by the array. +// */ +// readonly buffer: ArrayBuffer; + +// /** +// * The length in bytes of the array. +// */ +// readonly byteLength: number; + +// /** +// * The offset in bytes of the array. +// */ +// readonly byteOffset: number; + +// /** +// * The length of the array. +// */ +// readonly length: number; + +// /** +// * Sets a value or an array of values. +// * @param index The index of the location to set. +// * @param value The value to set. +// */ +// set(index: number, value: number): void; + +// /** +// * Sets a value or an array of values. +// * @param array A typed or untyped array of values to set. +// * @param offset The index in the current array at which the values are to be written. +// */ +// set(array: ArrayLike, offset?: number): void; + +// /** +// * Converts a number to a string by using the current locale. +// */ +// toLocaleString(): string; + +// /** +// * Returns a string representation of an array. +// */ +// toString(): string; + +// [index: number]: number; +// } + +// interface Int32ArrayConstructor { +// readonly prototype: Int32Array; +// new (length: number): Int32Array; +// new (array: ArrayLike): Int32Array; +// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Int32Array; + +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; +// } + +// declare const Int32Array: Int32ArrayConstructor; + +// /** +// * A typed array of 32-bit unsigned integer values. The contents are initialized to 0. If the +// * requested number of bytes could not be allocated an exception is raised. +// */ +// interface Uint32Array { +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; + +// /** +// * The ArrayBuffer instance referenced by the array. +// */ +// readonly buffer: ArrayBuffer; + +// /** +// * The length in bytes of the array. +// */ +// readonly byteLength: number; + +// /** +// * The offset in bytes of the array. +// */ +// readonly byteOffset: number; + +// /** +// * The length of the array. +// */ +// readonly length: number; + +// /** +// * Sets a value or an array of values. +// * @param index The index of the location to set. +// * @param value The value to set. +// */ +// set(index: number, value: number): void; + +// /** +// * Sets a value or an array of values. +// * @param array A typed or untyped array of values to set. +// * @param offset The index in the current array at which the values are to be written. +// */ +// set(array: ArrayLike, offset?: number): void; + +// /** +// * Converts a number to a string by using the current locale. +// */ +// toLocaleString(): string; + +// /** +// * Returns a string representation of an array. +// */ +// toString(): string; + +// [index: number]: number; +// } + +// interface Uint32ArrayConstructor { +// readonly prototype: Uint32Array; +// new (length: number): Uint32Array; +// new (array: ArrayLike): Uint32Array; +// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint32Array; + +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; +// } + +// declare const Uint32Array: Uint32ArrayConstructor; + +// /** +// * A typed array of 32-bit float values. The contents are initialized to 0. If the requested number +// * of bytes could not be allocated an exception is raised. +// */ +// interface Float32Array { +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; + +// /** +// * The ArrayBuffer instance referenced by the array. +// */ +// readonly buffer: ArrayBuffer; + +// /** +// * The length in bytes of the array. +// */ +// readonly byteLength: number; + +// /** +// * The offset in bytes of the array. +// */ +// readonly byteOffset: number; + +// /** +// * The length of the array. +// */ +// readonly length: number; + +// /** +// * Sets a value or an array of values. +// * @param index The index of the location to set. +// * @param value The value to set. +// */ +// set(index: number, value: number): void; + +// /** +// * Sets a value or an array of values. +// * @param array A typed or untyped array of values to set. +// * @param offset The index in the current array at which the values are to be written. +// */ +// set(array: ArrayLike, offset?: number): void; + +// /** +// * Converts a number to a string by using the current locale. +// */ +// toLocaleString(): string; + +// /** +// * Returns a string representation of an array. +// */ +// toString(): string; + +// [index: number]: number; +// } + +// interface Float32ArrayConstructor { +// readonly prototype: Float32Array; +// new (length: number): Float32Array; +// new (array: ArrayLike): Float32Array; +// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Float32Array; + +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; + +// } +// declare const Float32Array: Float32ArrayConstructor; + +// /** +// * A typed array of 64-bit float values. The contents are initialized to 0. If the requested +// * number of bytes could not be allocated an exception is raised. +// */ +// interface Float64Array { +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; + +// /** +// * The ArrayBuffer instance referenced by the array. +// */ +// readonly buffer: ArrayBuffer; + +// /** +// * The length in bytes of the array. +// */ +// readonly byteLength: number; + +// /** +// * The offset in bytes of the array. +// */ +// readonly byteOffset: number; + +// /** +// * The length of the array. +// */ +// readonly length: number; + +// /** +// * Sets a value or an array of values. +// * @param index The index of the location to set. +// * @param value The value to set. +// */ +// set(index: number, value: number): void; + +// /** +// * Sets a value or an array of values. +// * @param array A typed or untyped array of values to set. +// * @param offset The index in the current array at which the values are to be written. +// */ +// set(array: ArrayLike, offset?: number): void; + +// /** +// * Converts a number to a string by using the current locale. +// */ +// toLocaleString(): string; + +// /** +// * Returns a string representation of an array. +// */ +// toString(): string; + +// [index: number]: number; +// } + +// interface Float64ArrayConstructor { +// readonly prototype: Float64Array; +// new (length: number): Float64Array; +// new (array: ArrayLike): Float64Array; +// new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Float64Array; + +// /** +// * The size in bytes of each element in the array. +// */ +// readonly BYTES_PER_ELEMENT: number; +// } + +// declare const Float64Array: Float64ArrayConstructor; diff --git a/src/typings/lib.webworker.importscripts.d.ts b/src/typings/lib.webworker.importscripts.d.ts new file mode 100644 index 0000000000..e84f717c9a --- /dev/null +++ b/src/typings/lib.webworker.importscripts.d.ts @@ -0,0 +1,23 @@ +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ + + + + +///////////////////////////// +/// WorkerGlobalScope APIs +///////////////////////////// +// These are only available in a Web Worker +declare function importScripts(...urls: string[]): void; diff --git a/src/typings/pty.js.d.ts b/src/typings/pty.js.d.ts deleted file mode 100644 index c038e1d4b6..0000000000 --- a/src/typings/pty.js.d.ts +++ /dev/null @@ -1,23 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -declare module 'pty.js' { - export function fork(file: string, args: string[], options: any): Terminal; - export function spawn(file: string, args: string[], options: any): Terminal; - export function createTerminal(file: string, args: string[], options: any): Terminal; - - export interface Terminal { - /** - * The title of the active process. - */ - process: string; - - on(event: string, callback: (data: any) => void): void; - - resize(columns: number, rows: number): void; - - write(data: string): void; - } -} \ No newline at end of file diff --git a/build/gulpfile.test.js b/src/typings/vscode-windows-registry.d.ts similarity index 57% rename from build/gulpfile.test.js rename to src/typings/vscode-windows-registry.d.ts index b7808d111a..fad75f2b64 100644 --- a/build/gulpfile.test.js +++ b/src/typings/vscode-windows-registry.d.ts @@ -3,13 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; - -const gulp = require('gulp'); -const mocha = require('gulp-mocha'); - -gulp.task('test', function () { - return gulp.src('test/all.js') - .pipe(mocha({ ui: 'tdd', delay: true })) - .once('end', function () { process.exit(); }); -}); +declare module 'vscode-windows-registry' { + export type HKEY = "HKEY_CURRENT_USER" | "HKEY_LOCAL_MACHINE" | "HKEY_CLASSES_ROOT" | "HKEY_USERS" | "HKEY_CURRENT_CONFIG"; + export function GetStringRegKey(hive: HKEY, path: string, name: string): string | undefined; +} diff --git a/src/typings/xterm.d.ts b/src/typings/xterm.d.ts deleted file mode 100644 index 0f26b76a5a..0000000000 --- a/src/typings/xterm.d.ts +++ /dev/null @@ -1,547 +0,0 @@ -/** - * @license MIT - * - * This contains the type declarations for the xterm.js library. Note that - * some interfaces differ between this file and the actual implementation in - * src/, that's because this file declares the *public* API which is intended - * to be stable and consumed by external programs. - */ - -declare module 'xterm' { - /** - * An object containing start up options for the terminal. - */ - interface ITerminalOptions { - /** - * A data uri of the sound to use for the bell (needs bellStyle = 'sound'). - */ - bellSound?: string; - - /** - * The type of the bell notification the terminal will use. - */ - bellStyle?: 'none' | 'visual' | 'sound' | 'both'; - - /** - * The number of columns in the terminal. - */ - cols?: number; - - /** - * Whether the cursor blinks. - */ - cursorBlink?: boolean; - - /** - * The style of the cursor. - */ - cursorStyle?: 'block' | 'underline' | 'bar'; - - /** - * Whether input should be disabled. - */ - disableStdin?: boolean; - - /** - * Whether to enable the rendering of bold text. - */ - enableBold?: boolean; - - /** - * The font size used to render text. - */ - fontSize?: number; - - /** - * The font family used to render text. - */ - fontFamily?: string; - - /** - * The line height used to render text. - */ - lineHeight?: number; - - /** - * The number of rows in the terminal. - */ - rows?: number; - - /** - * The amount of scrollback in the terminal. Scrollback is the amount of rows - * that are retained when lines are scrolled beyond the initial viewport. - */ - scrollback?: number; - - /** - * The size of tab stops in the terminal. - */ - tabStopWidth?: number; - - /** - * The color theme of the terminal. - */ - theme?: ITheme; - } - - /** - * Contains colors to theme the terminal with. - */ - interface ITheme { - /** The default foreground color */ - foreground?: string, - /** The default background color */ - background?: string, - /** The cursor color */ - cursor?: string, - /** The selection color (can be transparent) */ - selection?: string, - /** The accent color of the cursor (used as the foreground color for a block cursor) */ - cursorAccent?: string, - /** ANSI black (eg. `\x1b[30m`) */ - black?: string, - /** ANSI red (eg. `\x1b[31m`) */ - red?: string, - /** ANSI green (eg. `\x1b[32m`) */ - green?: string, - /** ANSI yellow (eg. `\x1b[33m`) */ - yellow?: string, - /** ANSI blue (eg. `\x1b[34m`) */ - blue?: string, - /** ANSI magenta (eg. `\x1b[35m`) */ - magenta?: string, - /** ANSI cyan (eg. `\x1b[36m`) */ - cyan?: string, - /** ANSI white (eg. `\x1b[37m`) */ - white?: string, - /** ANSI bright black (eg. `\x1b[1;30m`) */ - brightBlack?: string, - /** ANSI bright red (eg. `\x1b[1;31m`) */ - brightRed?: string, - /** ANSI bright green (eg. `\x1b[1;32m`) */ - brightGreen?: string, - /** ANSI bright yellow (eg. `\x1b[1;33m`) */ - brightYellow?: string, - /** ANSI bright blue (eg. `\x1b[1;34m`) */ - brightBlue?: string, - /** ANSI bright magenta (eg. `\x1b[1;35m`) */ - brightMagenta?: string, - /** ANSI bright cyan (eg. `\x1b[1;36m`) */ - brightCyan?: string, - /** ANSI bright white (eg. `\x1b[1;37m`) */ - brightWhite?: string - } - - /** - * An object containing options for a link matcher. - */ - interface ILinkMatcherOptions { - /** - * The index of the link from the regex.match(text) call. This defaults to 0 - * (for regular expressions without capture groups). - */ - matchIndex?: number; - - /** - * A callback that validates an individual link, returning true if valid and - * false if invalid. - */ - validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void; - - /** - * A callback that fires when the mouse hovers over a link for a moment. - */ - tooltipCallback?: (event: MouseEvent, uri: string) => boolean | void; - - /** - * A callback that fires when the mouse leaves a link. Note that this can - * happen even when tooltipCallback hasn't fired for the link yet. - */ - leaveCallback?: (event: MouseEvent, uri: string) => boolean | void; - - /** - * The priority of the link matcher, this defines the order in which the link - * matcher is evaluated relative to others, from highest to lowest. The - * default value is 0. - */ - priority?: number; - } - - /** - * The class that represents an xterm.js terminal. - */ - export class Terminal { - /** - * The element containing the terminal. - */ - element: HTMLElement; - - /** - * The textarea that accepts input for the terminal. - */ - textarea: HTMLTextAreaElement; - - /** - * The number of rows in the terminal's viewport. - */ - rows: number; - - /** - * The number of columns in the terminal's viewport. - */ - cols: number; - - /** - * Creates a new `Terminal` object. - * - * @param options An object containing a set of options. - */ - constructor(options?: ITerminalOptions); - - /** - * Unfocus the terminal. - */ - blur(): void; - - /** - * Focus the terminal. - */ - focus(): void; - - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - */ - on(type: 'blur' | 'focus' | 'lineFeed', listener: () => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - */ - on(type: 'data', listener: (data?: string) => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - */ - on(type: 'key', listener: (key?: string, event?: KeyboardEvent) => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - */ - on(type: 'keypress' | 'keydown', listener: (event?: KeyboardEvent) => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - */ - on(type: 'refresh', listener: (data?: { start: number, end: number }) => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - */ - on(type: 'resize', listener: (data?: { cols: number, rows: number }) => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - */ - on(type: 'scroll', listener: (ydisp?: number) => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - */ - on(type: 'title', listener: (title?: string) => void): void; - /** - * Registers an event listener. - * @param type The type of the event. - * @param listener The listener. - */ - on(type: string, listener: (...args: any[]) => void): void; - - /** - * Deregisters an event listener. - * @param type The type of the event. - * @param listener The listener. - */ - off(type: 'blur' | 'focus' | 'lineFeed' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title' | string, listener: (...args: any[]) => void): void; - - /** - * Resizes the terminal. - * @param x The number of columns to resize to. - * @param y The number of rows to resize to. - */ - resize(columns: number, rows: number): void; - - /** - * Writes text to the terminal, followed by a break line character (\n). - * @param data The text to write to the terminal. - */ - writeln(data: string): void; - - /** - * Opens the terminal within an element. - * @param parent The element to create the terminal within. - */ - open(parent: HTMLElement): void; - - /** - * Attaches a custom key event handler which is run before keys are - * processed, giving consumers of xterm.js ultimate control as to what keys - * should be processed by the terminal and what keys should not. - * @param customKeyEventHandler The custom KeyboardEvent handler to attach. - * This is a function that takes a KeyboardEvent, allowing consumers to stop - * propogation and/or prevent the default action. The function returns - * whether the event should be processed by xterm.js. - */ - attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void; - - /** - * (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to - * be matched and handled. - * @param regex The regular expression to search for, specifically this - * searches the textContent of the rows. You will want to use \s to match a - * space ' ' character for example. - * @param handler The callback when the link is called. - * @param options Options for the link matcher. - * @return The ID of the new matcher, this can be used to deregister. - */ - registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number; - - /** - * (EXPERIMENTAL) Deregisters a link matcher if it has been registered. - * @param matcherId The link matcher's ID (returned after register) - */ - deregisterLinkMatcher(matcherId: number): void; - - /** - * Gets whether the terminal has an active selection. - */ - hasSelection(): boolean; - - /** - * Gets the terminal's current selection, this is useful for implementing - * copy behavior outside of xterm.js. - */ - getSelection(): string; - - /** - * Clears the current terminal selection. - */ - clearSelection(): void; - - /** - * Selects all text within the terminal. - */ - selectAll(): void; - - // /** - // * Find the next instance of the term, then scroll to and select it. If it - // * doesn't exist, do nothing. - // * @param term Tne search term. - // * @return Whether a result was found. - // */ - // findNext(term: string): boolean; - - // /** - // * Find the previous instance of the term, then scroll to and select it. If it - // * doesn't exist, do nothing. - // * @param term Tne search term. - // * @return Whether a result was found. - // */ - // findPrevious(term: string): boolean; - - /** - * Destroys the terminal and detaches it from the DOM. - */ - destroy(): void; - - /** - * Scroll the display of the terminal - * @param amount The number of lines to scroll down (negative scroll up). - */ - scrollLines(amount: number): void; - - /** - * Scroll the display of the terminal by a number of pages. - * @param pageCount The number of pages to scroll (negative scrolls up). - */ - scrollPages(pageCount: number): void; - - /** - * Scrolls the display of the terminal to the top. - */ - scrollToTop(): void; - - /** - * Scrolls the display of the terminal to the bottom. - */ - scrollToBottom(): void; - - /** - * Clear the entire buffer, making the prompt line the new first line. - */ - clear(): void; - - /** - * Writes text to the terminal. - * @param data The text to write to the terminal. - */ - write(data: string): void; - - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'termName'): string; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: 'colors'): string[]; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: 'cols' | 'fontSize' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: 'geometry'): [number, number]; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: 'handler'): (data: string) => void; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: string): any; - - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'fontFamily' | 'termName' | 'bellSound', value: string): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'bellStyle', value: null | 'none' | 'visual' | 'sound' | 'both'): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'cursorStyle', value: null | 'block' | 'underline' | 'bar'): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'colors', value: string[]): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'cols' | 'fontSize' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback', value: number): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'geometry', value: [number, number]): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'handler', value: (data: string) => void): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'theme', value: ITheme): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: string, value: any): void; - - /** - * Tells the renderer to refresh terminal content between two rows - * (inclusive) at the next opportunity. - * @param start The row to start from (between 0 and this.rows - 1). - * @param end The row to end at (between start and this.rows - 1). - */ - refresh(start: number, end: number): void; - - /** - * Perform a full reset (RIS, aka '\x1bc'). - */ - reset(): void - - /** - * Loads an addon, attaching it to the Terminal prototype and making it - * available to all newly created Terminals. - * @param addon The addon to load. - */ - static loadAddon(addon: 'attach' | 'fit' | 'fullscreen' | 'search' | 'terminado' | 'winptyCompat'): void; - - - - // Modifications to official .d.ts below - - buffer: { - /** - * The viewport position. - */ - ydisp: number; - }; - - /** - * Emit an event on the terminal. - */ - emit(type: string, data: any): void; - - /** - * Find the next instance of the term, then scroll to and select it. If it - * doesn't exist, do nothing. - * @param term Tne search term. - * @return Whether a result was found. - */ - findNext(term: string): boolean; - - /** - * Find the previous instance of the term, then scroll to and select it. If it - * doesn't exist, do nothing. - * @param term Tne search term. - * @return Whether a result was found. - */ - findPrevious(term: string): boolean; - - winptyCompatInit(): void; - } -} \ No newline at end of file diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index b212faa38a..0f99d839e8 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -5,7 +5,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; -import * as platform from 'vs/base/common/platform'; class WindowManager { @@ -35,7 +34,7 @@ class WindowManager { } // --- Zoom Factor - private _zoomFactor: number = 0; + private _zoomFactor: number = 1; public getZoomFactor(): number { return this._zoomFactor; @@ -72,23 +71,6 @@ class WindowManager { public isFullscreen(): boolean { return this._fullscreen; } - - // --- Accessibility - private _accessibilitySupport = platform.AccessibilitySupport.Unknown; - private readonly _onDidChangeAccessibilitySupport = new Emitter(); - - public readonly onDidChangeAccessibilitySupport: Event = this._onDidChangeAccessibilitySupport.event; - public setAccessibilitySupport(accessibilitySupport: platform.AccessibilitySupport): void { - if (this._accessibilitySupport === accessibilitySupport) { - return; - } - - this._accessibilitySupport = accessibilitySupport; - this._onDidChangeAccessibilitySupport.fire(); - } - public getAccessibilitySupport(): platform.AccessibilitySupport { - return this._accessibilitySupport; - } } /** A zoom index, e.g. 1, 2, 3 */ @@ -126,16 +108,6 @@ export function isFullscreen(): boolean { } export const onDidChangeFullscreen = WindowManager.INSTANCE.onDidChangeFullscreen; -export function setAccessibilitySupport(accessibilitySupport: platform.AccessibilitySupport): void { - WindowManager.INSTANCE.setAccessibilitySupport(accessibilitySupport); -} -export function getAccessibilitySupport(): platform.AccessibilitySupport { - return WindowManager.INSTANCE.getAccessibilitySupport(); -} -export function onDidChangeAccessibilitySupport(callback: () => void): IDisposable { - return WindowManager.INSTANCE.onDidChangeAccessibilitySupport(callback); -} - const userAgent = navigator.userAgent; export const isIE = (userAgent.indexOf('Trident') >= 0); diff --git a/src/vs/base/browser/dnd.ts b/src/vs/base/browser/dnd.ts index e839c72057..c6c9d68769 100644 --- a/src/vs/base/browser/dnd.ts +++ b/src/vs/base/browser/dnd.ts @@ -16,7 +16,9 @@ export class DelayedDragHandler extends Disposable { constructor(container: HTMLElement, callback: () => void) { super(); - this._register(addDisposableListener(container, 'dragover', () => { + this._register(addDisposableListener(container, 'dragover', e => { + e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome) + if (!this.timeout) { this.timeout = setTimeout(() => { callback(); @@ -71,7 +73,7 @@ export const DataTransfers = { TEXT: 'text/plain' }; -export function applyDragImage(event: DragEvent, label: string, clazz: string): void { +export function applyDragImage(event: DragEvent, label: string | null, clazz: string): void { const dragImage = document.createElement('div'); dragImage.className = clazz; dragImage.textContent = label; diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index fc677d15bd..9b4aa8ad96 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -615,7 +615,7 @@ export interface IDomNodePagePosition { height: number; } -export function size(element: HTMLElement, width: number, height: number): void { +export function size(element: HTMLElement, width: number | null, height: number | null): void { if (typeof width === 'number') { element.style.width = `${width}px`; } diff --git a/src/vs/base/browser/history.ts b/src/vs/base/browser/history.ts index 9b2c0933d7..8222d98952 100644 --- a/src/vs/base/browser/history.ts +++ b/src/vs/base/browser/history.ts @@ -5,8 +5,8 @@ export interface IHistoryNavigationWidget { - showPreviousValue(); + showPreviousValue(): void; - showNextValue(); + showNextValue(): void; } \ No newline at end of file diff --git a/src/vs/base/browser/keyboardEvent.ts b/src/vs/base/browser/keyboardEvent.ts index f6743b0980..6a9177865c 100644 --- a/src/vs/base/browser/keyboardEvent.ts +++ b/src/vs/base/browser/keyboardEvent.ts @@ -179,6 +179,9 @@ export function getCodeForKeyCode(keyCode: KeyCode): number { } export interface IKeyboardEvent { + + readonly _standardKeyboardEventBrand: true; + readonly browserEvent: KeyboardEvent; readonly target: HTMLElement; @@ -206,6 +209,8 @@ const metaKeyMod = (platform.isMacintosh ? KeyMod.CtrlCmd : KeyMod.WinCtrl); export class StandardKeyboardEvent implements IKeyboardEvent { + readonly _standardKeyboardEventBrand = true; + public readonly browserEvent: KeyboardEvent; public readonly target: HTMLElement; diff --git a/src/vs/base/browser/ui/aria/aria.ts b/src/vs/base/browser/ui/aria/aria.ts index ed364cdad3..f03afaf0e8 100644 --- a/src/vs/base/browser/ui/aria/aria.ts +++ b/src/vs/base/browser/ui/aria/aria.ts @@ -33,48 +33,50 @@ export function setARIAContainer(parent: HTMLElement) { /** * Given the provided message, will make sure that it is read as alert to screen readers. */ -export function alert(msg: string): void { - insertMessage(alertContainer, msg); +export function alert(msg: string, disableRepeat?: boolean): void { + insertMessage(alertContainer, msg, disableRepeat); } /** * Given the provided message, will make sure that it is read as status to screen readers. */ -export function status(msg: string): void { +export function status(msg: string, disableRepeat?: boolean): void { if (isMacintosh) { - alert(msg); // VoiceOver does not seem to support status role + alert(msg, disableRepeat); // VoiceOver does not seem to support status role } else { - insertMessage(statusContainer, msg); + insertMessage(statusContainer, msg, disableRepeat); } } let repeatedTimes = 0; let prevText: string | undefined = undefined; -function insertMessage(target: HTMLElement, msg: string): void { +function insertMessage(target: HTMLElement, msg: string, disableRepeat?: boolean): void { if (!ariaContainer) { - // console.warn('ARIA support needs a container. Call setARIAContainer() first.'); return; } - if (prevText === msg) { - repeatedTimes++; - } - else { - prevText = msg; - repeatedTimes = 0; - } + // If the same message should be inserted that is already present, a screen reader would + // not announce this message because it matches the previous one. As a workaround, we + // alter the message with the number of occurences unless this is explicitly disabled + // via the disableRepeat flag. + if (!disableRepeat) { + if (prevText === msg) { + repeatedTimes++; + } else { + prevText = msg; + repeatedTimes = 0; + } - - switch (repeatedTimes) { - case 0: break; - case 1: msg = nls.localize('repeated', "{0} (occurred again)", msg); break; - default: msg = nls.localize('repeatedNtimes', "{0} (occurred {1} times)", msg, repeatedTimes); break; + switch (repeatedTimes) { + case 0: break; + case 1: msg = nls.localize('repeated', "{0} (occurred again)", msg); break; + default: msg = nls.localize('repeatedNtimes', "{0} (occurred {1} times)", msg, repeatedTimes); break; + } } dom.clearNode(target); target.textContent = msg; - // See https://www.paciellogroup.com/blog/2012/06/html5-accessibility-chops-aria-rolealert-browser-support/ target.style.visibility = 'hidden'; target.style.visibility = 'visible'; diff --git a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts index 3659e26f31..db789dd53d 100644 --- a/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts +++ b/src/vs/base/browser/ui/breadcrumbs/breadcrumbsWidget.ts @@ -200,8 +200,8 @@ export class BreadcrumbsWidget { return this._items[this._focusedItemIdx]; } - setFocused(item: BreadcrumbsItem, payload?: any): void { - this._focus(this._items.indexOf(item), payload); + setFocused(item: BreadcrumbsItem | undefined, payload?: any): void { + this._focus(this._items.indexOf(item!), payload); } focusPrev(payload?: any): any { @@ -256,8 +256,8 @@ export class BreadcrumbsWidget { return this._items[this._selectedItemIdx]; } - setSelection(item: BreadcrumbsItem, payload?: any): void { - this._select(this._items.indexOf(item), payload); + setSelection(item: BreadcrumbsItem | undefined, payload?: any): void { + this._select(this._items.indexOf(item!), payload); } private _select(nth: number, payload: any): void { diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 793897ed69..7af439b18e 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -32,7 +32,7 @@ export interface IFindInputOptions extends IFindInputStyles { } export interface IFindInputStyles extends IInputBoxStyles { - inputActiveOptionBorder?: Color | null; + inputActiveOptionBorder?: Color; } const NLS_DEFAULT_LABEL = nls.localize('defaultLabel', "input"); @@ -42,26 +42,25 @@ export class FindInput extends Widget { static readonly OPTION_CHANGE: string = 'optionChange'; private contextViewProvider: IContextViewProvider; - private width: number; private placeholder: string; private validation?: IInputValidator; private label: string; private fixFocusOnOptionClickEnabled = true; - private inputActiveOptionBorder?: Color | null; - private inputBackground?: Color | null; - private inputForeground?: Color | null; - private inputBorder?: Color | null; + private inputActiveOptionBorder?: Color; + private inputBackground?: Color; + private inputForeground?: Color; + private inputBorder?: Color; - private inputValidationInfoBorder?: Color | null; - private inputValidationInfoBackground?: Color | null; - private inputValidationInfoForeground?: Color | null; - private inputValidationWarningBorder?: Color | null; - private inputValidationWarningBackground?: Color | null; - private inputValidationWarningForeground?: Color | null; - private inputValidationErrorBorder?: Color | null; - private inputValidationErrorBackground?: Color | null; - private inputValidationErrorForeground?: Color | null; + private inputValidationInfoBorder?: Color; + private inputValidationInfoBackground?: Color; + private inputValidationInfoForeground?: Color; + private inputValidationWarningBorder?: Color; + private inputValidationWarningBackground?: Color; + private inputValidationWarningForeground?: Color; + private inputValidationErrorBorder?: Color; + private inputValidationErrorBackground?: Color; + private inputValidationErrorForeground?: Color; private regex: RegexCheckbox; private wholeWords: WholeWordsCheckbox; @@ -93,7 +92,6 @@ export class FindInput extends Widget { constructor(parent: HTMLElement | null, contextViewProvider: IContextViewProvider, private readonly _showOptionButtons: boolean, options: IFindInputOptions) { super(); this.contextViewProvider = contextViewProvider; - this.width = options.width || 100; this.placeholder = options.placeholder || ''; this.validation = options.validation; this.label = options.label || NLS_DEFAULT_LABEL; @@ -159,13 +157,6 @@ export class FindInput extends Widget { this.focus(); } - public setWidth(newWidth: number): void { - this.width = newWidth; - this.domNode.style.width = this.width + 'px'; - this.contextViewProvider.layout(); - this.setInputWidth(); - } - public getValue(): string { return this.inputBox.value; } @@ -202,7 +193,7 @@ export class FindInput extends Widget { protected applyStyles(): void { if (this.domNode) { const checkBoxStyles: ICheckboxStyles = { - inputActiveOptionBorder: this.inputActiveOptionBorder || undefined, + inputActiveOptionBorder: this.inputActiveOptionBorder, }; this.regex.style(checkBoxStyles); this.wholeWords.style(checkBoxStyles); @@ -240,7 +231,6 @@ export class FindInput extends Widget { public setCaseSensitive(value: boolean): void { this.caseSensitive.checked = value; - this.setInputWidth(); } public getWholeWords(): boolean { @@ -249,7 +239,6 @@ export class FindInput extends Widget { public setWholeWords(value: boolean): void { this.wholeWords.checked = value; - this.setInputWidth(); } public getRegex(): boolean { @@ -258,7 +247,6 @@ export class FindInput extends Widget { public setRegex(value: boolean): void { this.regex.checked = value; - this.setInputWidth(); this.validate(); } @@ -277,15 +265,8 @@ export class FindInput extends Widget { dom.addClass(this.domNode, 'highlight-' + (this._lastHighlightFindOptions)); } - private setInputWidth(): void { - let w = this.width - this.caseSensitive.width() - this.wholeWords.width() - this.regex.width(); - this.inputBox.width = w; - this.inputBox.layout(); - } - private buildDomNode(appendCaseSensitiveLabel: string, appendWholeWordsLabel: string, appendRegexLabel: string, history: string[], flexibleHeight: boolean): void { this.domNode = document.createElement('div'); - this.domNode.style.width = this.width + 'px'; dom.addClass(this.domNode, 'monaco-findInput'); this.inputBox = this._register(new HistoryInputBox(this.domNode, this.contextViewProvider, { @@ -313,14 +294,13 @@ export class FindInput extends Widget { this.regex = this._register(new RegexCheckbox({ appendTitle: appendRegexLabel, isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder || undefined + inputActiveOptionBorder: this.inputActiveOptionBorder })); this._register(this.regex.onChange(viaKeyboard => { this._onDidOptionChange.fire(viaKeyboard); if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { this.inputBox.focus(); } - this.setInputWidth(); this.validate(); })); this._register(this.regex.onKeyDown(e => { @@ -330,34 +310,40 @@ export class FindInput extends Widget { this.wholeWords = this._register(new WholeWordsCheckbox({ appendTitle: appendWholeWordsLabel, isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder || undefined + inputActiveOptionBorder: this.inputActiveOptionBorder })); this._register(this.wholeWords.onChange(viaKeyboard => { this._onDidOptionChange.fire(viaKeyboard); if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { this.inputBox.focus(); } - this.setInputWidth(); this.validate(); })); this.caseSensitive = this._register(new CaseSensitiveCheckbox({ appendTitle: appendCaseSensitiveLabel, isChecked: false, - inputActiveOptionBorder: this.inputActiveOptionBorder || undefined + inputActiveOptionBorder: this.inputActiveOptionBorder })); this._register(this.caseSensitive.onChange(viaKeyboard => { this._onDidOptionChange.fire(viaKeyboard); if (!viaKeyboard && this.fixFocusOnOptionClickEnabled) { this.inputBox.focus(); } - this.setInputWidth(); this.validate(); })); this._register(this.caseSensitive.onKeyDown(e => { this._onCaseSensitiveKeyDown.fire(e); })); + if (this._showOptionButtons) { + const paddingRight = (this.caseSensitive.width() + this.wholeWords.width() + this.regex.width()) + 'px'; + this.inputBox.inputElement.style.paddingRight = paddingRight; + if (this.inputBox.mirrorElement) { + this.inputBox.mirrorElement.style.paddingRight = paddingRight; + } + } + // Arrow-Key support to navigate between options let indexes = [this.caseSensitive.domNode, this.wholeWords.domNode, this.regex.domNode]; this.onkeydown(this.domNode, (event: IKeyboardEvent) => { @@ -386,7 +372,6 @@ export class FindInput extends Widget { } }); - this.setInputWidth(); let controls = document.createElement('div'); controls.className = 'controls'; diff --git a/src/vs/base/browser/ui/grid/grid.ts b/src/vs/base/browser/ui/grid/grid.ts index 301c7e5716..e77aa196cd 100644 --- a/src/vs/base/browser/ui/grid/grid.ts +++ b/src/vs/base/browser/ui/grid/grid.ts @@ -230,6 +230,10 @@ export class Grid implements IDisposable { this.gridview.layout(width, height); } + hasView(view: T): boolean { + return this.views.has(view); + } + addView(newView: T, size: number | Sizing, referenceView: T, direction: Direction): void { if (this.views.has(newView)) { throw new Error('Can\'t add same view twice'); @@ -258,7 +262,7 @@ export class Grid implements IDisposable { this._addView(newView, viewSize, location); } - protected _addView(newView: T, size: number | GridViewSizing, location): void { + protected _addView(newView: T, size: number | GridViewSizing, location: number[]): void { this.views.set(newView, newView.element); this.gridview.addView(newView, size, location); } diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index e2a8c936de..1df8594e5b 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -95,7 +95,7 @@ export class HighlightedLabel { let total = 0; let extra = 0; - return text.replace(/\r\n|\r|\n/, (match, offset) => { + return text.replace(/\r\n|\r|\n/g, (match, offset) => { extra = match === '\r\n' ? -1 : 0; offset += total; diff --git a/src/vs/base/browser/ui/inputbox/inputBox.ts b/src/vs/base/browser/ui/inputbox/inputBox.ts index b2477bda66..b87b08569c 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.ts +++ b/src/vs/base/browser/ui/inputbox/inputBox.ts @@ -37,18 +37,18 @@ export interface IInputOptions extends IInputBoxStyles { } export interface IInputBoxStyles { - readonly inputBackground?: Color | null; - readonly inputForeground?: Color | null; - readonly inputBorder?: Color | null; - readonly inputValidationInfoBorder?: Color | null; - readonly inputValidationInfoBackground?: Color | null; - readonly inputValidationInfoForeground?: Color | null; - readonly inputValidationWarningBorder?: Color | null; - readonly inputValidationWarningBackground?: Color | null; - readonly inputValidationWarningForeground?: Color | null; - readonly inputValidationErrorBorder?: Color | null; - readonly inputValidationErrorBackground?: Color | null; - readonly inputValidationErrorForeground?: Color | null; + readonly inputBackground?: Color; + readonly inputForeground?: Color; + readonly inputBorder?: Color; + readonly inputValidationInfoBorder?: Color; + readonly inputValidationInfoBackground?: Color; + readonly inputValidationInfoForeground?: Color; + readonly inputValidationWarningBorder?: Color; + readonly inputValidationWarningBackground?: Color; + readonly inputValidationWarningForeground?: Color; + readonly inputValidationErrorBorder?: Color; + readonly inputValidationErrorBackground?: Color; + readonly inputValidationErrorForeground?: Color; } export interface IInputValidator { @@ -104,20 +104,20 @@ export class InputBox extends Widget { // {{SQL CARBON EDIT}} - Add showValidationMessage and set inputBackground, inputForeground, and inputBorder as protected protected showValidationMessage: boolean; - protected inputBackground?: Color | null; - protected inputForeground?: Color | null; - protected inputBorder?: Color | null; + protected inputBackground?: Color; + protected inputForeground?: Color; + protected inputBorder?: Color; // {{SQL CARBON EDIT}} - End - private inputValidationInfoBorder?: Color | null; - private inputValidationInfoBackground?: Color | null; - private inputValidationInfoForeground?: Color | null; - private inputValidationWarningBorder?: Color | null; - private inputValidationWarningBackground?: Color | null; - private inputValidationWarningForeground?: Color | null; - private inputValidationErrorBorder?: Color | null; - private inputValidationErrorBackground?: Color | null; - private inputValidationErrorForeground?: Color | null; + private inputValidationInfoBorder?: Color; + private inputValidationInfoBackground?: Color; + private inputValidationInfoForeground?: Color; + private inputValidationWarningBorder?: Color; + private inputValidationWarningBackground?: Color; + private inputValidationWarningForeground?: Color; + private inputValidationErrorBorder?: Color; + private inputValidationErrorBackground?: Color; + private inputValidationErrorForeground?: Color; private _onDidChange = this._register(new Emitter()); public readonly onDidChange: Event = this._onDidChange.event; @@ -253,6 +253,10 @@ export class InputBox extends Widget { } } + public get mirrorElement(): HTMLElement { + return this.mirror; + } + public get inputElement(): HTMLInputElement { return this.input; } diff --git a/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.css b/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.css index 3f9a7119d2..a6968773b7 100644 --- a/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.css +++ b/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.css @@ -20,6 +20,15 @@ color: #555; font-size: 11px; padding: 3px 5px; + margin: 0 2px; +} + +.monaco-keybinding > .monaco-keybinding-key:first-child { + margin-left: 0; +} + +.monaco-keybinding > .monaco-keybinding-key:last-child { + margin-right: 0; } .hc-black .monaco-keybinding > .monaco-keybinding-key, @@ -36,5 +45,5 @@ } .monaco-keybinding > .monaco-keybinding-key-chord-separator { - width: 2px; + width: 6px; } \ No newline at end of file diff --git a/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts b/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts index 4403232219..4bb7504483 100644 --- a/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts +++ b/src/vs/base/browser/ui/keybindingLabel/keybindingLabel.ts @@ -33,7 +33,7 @@ export interface KeybindingLabelOptions { export class KeybindingLabel { private domNode: HTMLElement; - private keybinding: ResolvedKeybinding; + private keybinding: ResolvedKeybinding | null | undefined; private matches: Matches | undefined; private didEverRender: boolean; @@ -47,7 +47,7 @@ export class KeybindingLabel { return this.domNode; } - set(keybinding: ResolvedKeybinding, matches?: Matches) { + set(keybinding: ResolvedKeybinding | null | undefined, matches?: Matches) { if (this.didEverRender && this.keybinding === keybinding && KeybindingLabel.areSame(this.matches, matches)) { return; } diff --git a/src/vs/base/browser/ui/list/list.css b/src/vs/base/browser/ui/list/list.css index fe4a6aaf01..b7314d9306 100644 --- a/src/vs/base/browser/ui/list/list.css +++ b/src/vs/base/browser/ui/list/list.css @@ -60,7 +60,7 @@ } /* Dnd */ -.monaco-list-drag-image { +.monaco-drag-image { display: inline-block; padding: 1px 7px; border-radius: 10px; diff --git a/src/vs/base/browser/ui/list/list.ts b/src/vs/base/browser/ui/list/list.ts index 6ceeb8e55e..279343ea2a 100644 --- a/src/vs/base/browser/ui/list/list.ts +++ b/src/vs/base/browser/ui/list/list.ts @@ -16,8 +16,8 @@ export interface IListVirtualDelegate { export interface IListRenderer { templateId: string; renderTemplate(container: HTMLElement): TTemplateData; - renderElement(element: T, index: number, templateData: TTemplateData): void; - disposeElement?(element: T, index: number, templateData: TTemplateData): void; + renderElement(element: T, index: number, templateData: TTemplateData, dynamicHeightProbing?: boolean): void; + disposeElement?(element: T, index: number, templateData: TTemplateData, dynamicHeightProbing?: boolean): void; disposeTemplate(templateData: TTemplateData): void; } @@ -55,7 +55,7 @@ export interface IListContextMenuEvent { browserEvent: UIEvent; element: T | undefined; index: number | undefined; - anchor: HTMLElement | { x: number; y: number; } | undefined; + anchor: HTMLElement | { x: number; y: number; }; } export interface IIdentityProvider { diff --git a/src/vs/base/browser/ui/list/listPaging.ts b/src/vs/base/browser/ui/list/listPaging.ts index 99b514dd3a..034a33d4ad 100644 --- a/src/vs/base/browser/ui/list/listPaging.ts +++ b/src/vs/base/browser/ui/list/listPaging.ts @@ -35,7 +35,7 @@ class PagedRenderer implements IListRenderer { } } }; } - renderElement(index: number, _: number, data: ITemplateData): void { + renderElement(index: number, _: number, data: ITemplateData, dynamicHeightProbing?: boolean): void { if (data.disposable) { data.disposable.dispose(); } @@ -47,7 +47,7 @@ class PagedRenderer implements IListRenderer implements IListRenderer cts.cancel() }; this.renderer.renderPlaceholder(index, data.data); - promise.then(entry => this.renderer.renderElement(entry, index, data.data!)); + promise.then(entry => this.renderer.renderElement(entry, index, data.data!, dynamicHeightProbing)); } disposeTemplate(data: ITemplateData): void { @@ -194,6 +194,10 @@ export class PagedList implements IDisposable { this.list.layout(height, width); } + toggleKeyboardNavigation(): void { + this.list.toggleKeyboardNavigation(); + } + reveal(index: number, relativeTop?: number): void { this.list.reveal(index, relativeTop); } diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 1be89e4cd5..a6e52c0531 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -41,6 +41,11 @@ export interface IListViewDragAndDrop extends IListDragAndDrop { getDragElements(element: T): T[]; } +export interface IAriaSetProvider { + getSetSize(element: T, index: number, listLength: number): number; + getPosInSet(element: T, index: number): number; +} + export interface IListViewOptions { readonly dnd?: IListViewDragAndDrop; readonly useShadows?: boolean; @@ -49,6 +54,7 @@ export interface IListViewOptions { readonly supportDynamicHeights?: boolean; readonly mouseSupport?: boolean; readonly horizontalScrolling?: boolean; + readonly ariaSetProvider?: IAriaSetProvider; } const DefaultOptions = { @@ -142,6 +148,9 @@ function equalsDragFeedback(f1: number[] | undefined, f2: number[] | undefined): export class ListView implements ISpliceable, IDisposable { + private static InstanceCount = 0; + readonly domId = `list_id_${++ListView.InstanceCount}`; + readonly domNode: HTMLElement; private items: IItem[]; @@ -165,6 +174,7 @@ export class ListView implements ISpliceable, IDisposable { private setRowLineHeight: boolean; private supportDynamicHeights: boolean; private horizontalScrolling: boolean; + private ariaSetProvider: IAriaSetProvider; private scrollWidth: number | undefined; private canUseTranslate3d: boolean | undefined = undefined; @@ -195,7 +205,7 @@ export class ListView implements ISpliceable, IDisposable { container: HTMLElement, private virtualDelegate: IListVirtualDelegate, renderers: IListRenderer[], - options: IListViewOptions = DefaultOptions + options: IListViewOptions = DefaultOptions as IListViewOptions ) { if (options.horizontalScrolling && options.supportDynamicHeights) { throw new Error('Horizontal scrolling and dynamic heights not supported simultaneously'); @@ -216,11 +226,17 @@ export class ListView implements ISpliceable, IDisposable { this.domNode = document.createElement('div'); this.domNode.className = 'monaco-list'; + + DOM.addClass(this.domNode, this.domId); + this.domNode.tabIndex = 0; + DOM.toggleClass(this.domNode, 'mouse-support', typeof options.mouseSupport === 'boolean' ? options.mouseSupport : true); this.horizontalScrolling = getOrDefault(options, o => o.horizontalScrolling, DefaultOptions.horizontalScrolling); DOM.toggleClass(this.domNode, 'horizontal-scrolling', this.horizontalScrolling); + this.ariaSetProvider = options.ariaSetProvider || { getSetSize: (e, i, length) => length, getPosInSet: (_, index) => index + 1 }; + this.rowsContainer = document.createElement('div'); this.rowsContainer.className = 'monaco-list-rows'; Gesture.addTarget(this.rowsContainer); @@ -344,7 +360,7 @@ export class ListView implements ISpliceable, IDisposable { this.eventuallyUpdateScrollDimensions(); if (this.supportDynamicHeights) { - this.rerender(this.scrollTop, this.renderHeight); + this._rerender(this.scrollTop, this.renderHeight); } return deleted.map(i => i.element); @@ -406,6 +422,18 @@ export class ListView implements ISpliceable, IDisposable { } } + rerender(): void { + if (!this.supportDynamicHeights) { + return; + } + + for (const item of this.items) { + item.lastDynamicHeightWidth = undefined; + } + + this._rerender(this.lastRenderTop, this.lastRenderHeight); + } + get length(): number { return this.items.length; } @@ -415,6 +443,25 @@ export class ListView implements ISpliceable, IDisposable { return scrollDimensions.height; } + get firstVisibleIndex(): number { + const range = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); + const firstElTop = this.rangeMap.positionAt(range.start); + const nextElTop = this.rangeMap.positionAt(range.start + 1); + if (nextElTop !== -1) { + const firstElMidpoint = (nextElTop - firstElTop) / 2 + firstElTop; + if (firstElMidpoint < this.scrollTop) { + return range.start + 1; + } + } + + return range.start; + } + + get lastVisibleIndex(): number { + const range = this.getRenderRange(this.lastRenderTop, this.lastRenderHeight); + return range.end - 1; + } + element(index: number): T { return this.items[index].element; } @@ -457,7 +504,7 @@ export class ListView implements ISpliceable, IDisposable { this.renderWidth = width; if (this.supportDynamicHeights) { - this.rerender(this.scrollTop, this.renderHeight); + this._rerender(this.scrollTop, this.renderHeight); } if (this.horizontalScrolling) { @@ -528,6 +575,7 @@ export class ListView implements ISpliceable, IDisposable { if (!item.row) { item.row = this.cache.alloc(item.templateId); + item.row!.domNode!.setAttribute('role', 'treeitem'); } if (!item.row.domNode!.parentElement) { @@ -595,8 +643,10 @@ export class ListView implements ISpliceable, IDisposable { item.row!.domNode!.setAttribute('data-index', `${index}`); item.row!.domNode!.setAttribute('data-last-element', index === this.length - 1 ? 'true' : 'false'); - item.row!.domNode!.setAttribute('aria-setsize', `${this.length}`); - item.row!.domNode!.setAttribute('aria-posinset', `${index + 1}`); + item.row!.domNode!.setAttribute('aria-setsize', String(this.ariaSetProvider.getSetSize(item.element, index, this.length))); + item.row!.domNode!.setAttribute('aria-posinset', String(this.ariaSetProvider.getPosInSet(item.element, index))); + item.row!.domNode!.setAttribute('id', this.getElementDomId(index)); + DOM.toggleClass(item.row!.domNode!, 'drop-target', item.dropTarget); } @@ -691,7 +741,7 @@ export class ListView implements ISpliceable, IDisposable { this.render(e.scrollTop, e.height, e.scrollLeft, e.scrollWidth); if (this.supportDynamicHeights) { - this.rerender(e.scrollTop, e.height); + this._rerender(e.scrollTop, e.height); } } catch (err) { console.error('Got bad scroll event:', e); @@ -729,7 +779,7 @@ export class ListView implements ISpliceable, IDisposable { label = String(elements.length); } - const dragImage = DOM.$('.monaco-list-drag-image'); + const dragImage = DOM.$('.monaco-drag-image'); dragImage.textContent = label; document.body.appendChild(dragImage); event.dataTransfer.setDragImage(dragImage, -10, -10); @@ -745,6 +795,8 @@ export class ListView implements ISpliceable, IDisposable { } private onDragOver(event: IListDragEvent): boolean { + event.browserEvent.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome) + this.onDragLeaveTimeout.dispose(); if (StaticDND.CurrentDragAndDropData && StaticDND.CurrentDragAndDropData.getData() === 'vscode-ui') { @@ -954,7 +1006,7 @@ export class ListView implements ISpliceable, IDisposable { * Given a stable rendered state, checks every rendered element whether it needs * to be probed for dynamic height. Adjusts scroll height and top if necessary. */ - private rerender(renderTop: number, renderHeight: number): void { + private _rerender(renderTop: number, renderHeight: number): void { const previousRenderRange = this.getRenderRange(renderTop, renderHeight); // Let's remember the second element's position, this helps in scrolling up @@ -1038,14 +1090,20 @@ export class ListView implements ISpliceable, IDisposable { } const size = item.size; - const renderer = this.renderers.get(item.templateId); const row = this.cache.alloc(item.templateId); row.domNode!.style.height = ''; this.rowsContainer.appendChild(row.domNode!); + + const renderer = this.renderers.get(item.templateId); if (renderer) { - renderer.renderElement(item.element, index, row.templateData); + renderer.renderElement(item.element, index, row.templateData, true); + + if (renderer.disposeElement) { + renderer.disposeElement(item.element, index, row.templateData, true); + } } + item.size = row.domNode!.offsetHeight; item.lastDynamicHeightWidth = this.renderWidth; this.rowsContainer.removeChild(row.domNode!); @@ -1074,6 +1132,10 @@ export class ListView implements ISpliceable, IDisposable { return nextToLastItem.row.domNode; } + getElementDomId(index: number): string { + return `${this.domId}_${index}`; + } + // Dispose dispose() { diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index f819f74af6..94307e3cfc 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -17,7 +17,7 @@ import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardE import { Event, Emitter, EventBufferer } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, IKeyboardNavigationLabelProvider, IListDragAndDrop, IListDragOverReaction, ListAriaRootRole } from './list'; -import { ListView, IListViewOptions, IListViewDragAndDrop } from './listView'; +import { ListView, IListViewOptions, IListViewDragAndDrop, IAriaSetProvider } from './listView'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; @@ -179,16 +179,12 @@ class Trait implements ISpliceable, IDisposable { class FocusTrait extends Trait { - constructor( - private getDomId: (index: number) => string - ) { + constructor() { super('focused'); } renderIndex(index: number, container: HTMLElement): void { super.renderIndex(index, container); - container.setAttribute('role', 'treeitem'); - container.setAttribute('id', this.getDomId(index)); if (this.contains(index)) { container.setAttribute('aria-selected', 'true'); @@ -338,6 +334,10 @@ class TypeLabelController implements IDisposable { private enabled = false; private state: TypeLabelControllerState = TypeLabelControllerState.Idle; + + private automaticKeyboardNavigation = true; + private triggered = false; + private enabledDisposables: IDisposable[] = []; private disposables: IDisposable[] = []; @@ -346,11 +346,10 @@ class TypeLabelController implements IDisposable { private view: ListView, private keyboardNavigationLabelProvider: IKeyboardNavigationLabelProvider ) { - list.onDidUpdateOptions(this.onDidUpdateListOptions, this, this.disposables); - this.onDidUpdateListOptions(list.options); + this.updateOptions(list.options); } - private onDidUpdateListOptions(options: IListOptions): void { + updateOptions(options: IListOptions): void { const enableKeyboardNavigation = typeof options.enableKeyboardNavigation === 'undefined' ? true : !!options.enableKeyboardNavigation; if (enableKeyboardNavigation) { @@ -358,6 +357,14 @@ class TypeLabelController implements IDisposable { } else { this.disable(); } + + if (typeof options.automaticKeyboardNavigation !== 'undefined') { + this.automaticKeyboardNavigation = options.automaticKeyboardNavigation; + } + } + + toggle(): void { + this.triggered = !this.triggered; } private enable(): void { @@ -367,8 +374,10 @@ class TypeLabelController implements IDisposable { const onChar = Event.chain(domEvent(this.view.domNode, 'keydown')) .filter(e => !isInputElement(e.target as HTMLElement)) + .filter(() => this.automaticKeyboardNavigation || this.triggered) .map(event => new StandardKeyboardEvent(event)) .filter(this.keyboardNavigationLabelProvider.mightProducePrintableCharacter ? e => this.keyboardNavigationLabelProvider.mightProducePrintableCharacter!(e) : e => mightProducePrintableCharacter(e)) + .forEach(e => { e.stopPropagation(); e.preventDefault(); }) .map(event => event.browserEvent.key) .event; @@ -378,6 +387,7 @@ class TypeLabelController implements IDisposable { onInput(this.onInput, this, this.enabledDisposables); this.enabled = true; + this.triggered = false; } private disable(): void { @@ -387,11 +397,13 @@ class TypeLabelController implements IDisposable { this.enabledDisposables = dispose(this.enabledDisposables); this.enabled = false; + this.triggered = false; } private onInput(word: string | null): void { if (!word) { this.state = TypeLabelControllerState.Idle; + this.triggered = false; return; } @@ -502,37 +514,38 @@ const DefaultOpenController: IOpenController = { } }; -class MouseController implements IDisposable { +export class MouseController implements IDisposable { private multipleSelectionSupport: boolean; readonly multipleSelectionController: IMultipleSelectionController; private openController: IOpenController; + private mouseSupport: boolean; private disposables: IDisposable[] = []; - constructor( - private list: List, - private view: ListView, - options: IListOptions = {} - ) { - this.multipleSelectionSupport = !(options.multipleSelectionSupport === false); + constructor(protected list: List) { + this.multipleSelectionSupport = !(list.options.multipleSelectionSupport === false); if (this.multipleSelectionSupport) { - this.multipleSelectionController = options.multipleSelectionController || DefaultMultipleSelectionContoller; + this.multipleSelectionController = list.options.multipleSelectionController || DefaultMultipleSelectionContoller; } - this.openController = options.openController || DefaultOpenController; + this.openController = list.options.openController || DefaultOpenController; + this.mouseSupport = typeof list.options.mouseSupport === 'undefined' || !!list.options.mouseSupport; - view.onMouseDown(this.onMouseDown, this, this.disposables); - view.onContextMenu(this.onContextMenu, this, this.disposables); - view.onMouseClick(this.onPointer, this, this.disposables); + if (this.mouseSupport) { + list.onMouseDown(this.onMouseDown, this, this.disposables); + list.onContextMenu(this.onContextMenu, this, this.disposables); + list.onMouseDblClick(this.onDoubleClick, this, this.disposables); + list.onTouchStart(this.onMouseDown, this, this.disposables); + Gesture.addTarget(list.getHTMLElement()); + } + + list.onMouseClick(this.onPointer, this, this.disposables); list.onMouseMiddleClick(this.onPointer, this, this.disposables); - view.onMouseDblClick(this.onDoubleClick, this, this.disposables); - view.onTouchStart(this.onMouseDown, this, this.disposables); - view.onTap(this.onPointer, this, this.disposables); - Gesture.addTarget(view.domNode); + list.onTap(this.onPointer, this, this.disposables); } - private isSelectionSingleChangeEvent(event: IListMouseEvent | IListTouchEvent): boolean { + protected isSelectionSingleChangeEvent(event: IListMouseEvent | IListTouchEvent): boolean { if (this.multipleSelectionController) { return this.multipleSelectionController.isSelectionSingleChangeEvent(event); } @@ -540,7 +553,7 @@ class MouseController implements IDisposable { return platform.isMacintosh ? event.browserEvent.metaKey : event.browserEvent.ctrlKey; } - private isSelectionRangeChangeEvent(event: IListMouseEvent | IListTouchEvent): boolean { + protected isSelectionRangeChangeEvent(event: IListMouseEvent | IListTouchEvent): boolean { if (this.multipleSelectionController) { return this.multipleSelectionController.isSelectionRangeChangeEvent(event); } @@ -554,16 +567,20 @@ class MouseController implements IDisposable { private onMouseDown(e: IListMouseEvent | IListTouchEvent): void { if (document.activeElement !== e.browserEvent.target) { - this.view.domNode.focus(); + this.list.domFocus(); } } - private onContextMenu(e: IListMouseEvent | IListTouchEvent): void { + private onContextMenu(e: IListContextMenuEvent): void { const focus = typeof e.index === 'undefined' ? [] : [e.index]; this.list.setFocus(focus, e.browserEvent); } - private onPointer(e: IListMouseEvent): void { + protected onPointer(e: IListMouseEvent): void { + if (!this.mouseSupport) { + return; + } + if (isInputElement(e.browserEvent.target as HTMLElement)) { return; } @@ -708,14 +725,14 @@ export class DefaultStyleController implements IStyleController { if (styles.listFocusAndSelectionBackground) { content.push(` - .monaco-list-drag-image, + .monaco-drag-image, .monaco-list${suffix}:focus .monaco-list-row.selected.focused { background-color: ${styles.listFocusAndSelectionBackground}; } `); } if (styles.listFocusAndSelectionForeground) { content.push(` - .monaco-list-drag-image, + .monaco-drag-image, .monaco-list${suffix}:focus .monaco-list-row.selected.focused { color: ${styles.listFocusAndSelectionForeground}; } `); } @@ -748,7 +765,7 @@ export class DefaultStyleController implements IStyleController { if (styles.listFocusOutline) { content.push(` - .monaco-list-drag-image, + .monaco-drag-image, .monaco-list${suffix}:focus .monaco-list-row.focused { outline: 1px solid ${styles.listFocusOutline}; outline-offset: -1px; } `); } @@ -795,6 +812,7 @@ export interface IListOptions extends IListStyles { readonly identityProvider?: IIdentityProvider; readonly dnd?: IListDragAndDrop; readonly enableKeyboardNavigation?: boolean; + readonly automaticKeyboardNavigation?: boolean; readonly keyboardNavigationLabelProvider?: IKeyboardNavigationLabelProvider; readonly ariaRole?: ListAriaRootRole; readonly ariaLabel?: string; @@ -812,6 +830,7 @@ export interface IListOptions extends IListStyles { readonly supportDynamicHeights?: boolean; readonly mouseSupport?: boolean; readonly horizontalScrolling?: boolean; + readonly ariaSetProvider?: IAriaSetProvider; } export interface IListStyles { @@ -957,20 +976,20 @@ class PipelineRenderer implements IListRenderer { return this.renderers.map(r => r.renderTemplate(container)); } - renderElement(element: T, index: number, templateData: any[]): void { + renderElement(element: T, index: number, templateData: any[], dynamicHeightProbing?: boolean): void { let i = 0; for (const renderer of this.renderers) { - renderer.renderElement(element, index, templateData[i++]); + renderer.renderElement(element, index, templateData[i++], dynamicHeightProbing); } } - disposeElement(element: T, index: number, templateData: any[]): void { + disposeElement(element: T, index: number, templateData: any[], dynamicHeightProbing?: boolean): void { let i = 0; for (const renderer of this.renderers) { if (renderer.disposeElement) { - renderer.disposeElement(element, index, templateData[i]); + renderer.disposeElement(element, index, templateData[i], dynamicHeightProbing); } i += 1; @@ -1058,13 +1077,11 @@ class ListViewDragAndDrop implements IListViewDragAndDrop { export interface IListOptionsUpdate { readonly enableKeyboardNavigation?: boolean; + readonly automaticKeyboardNavigation?: boolean; } export class List implements ISpliceable, IDisposable { - private static InstanceCount = 0; - private idPrefix = `list_id_${++List.InstanceCount}`; - private focus: Trait; private selection: Trait; private eventBufferer = new EventBufferer(); @@ -1072,14 +1089,7 @@ export class List implements ISpliceable, IDisposable { private spliceable: ISpliceable; private styleElement: HTMLStyleElement; private styleController: IStyleController; - private mouseController: MouseController | undefined; - - get multipleSelectionController(): IMultipleSelectionController { - return (this.mouseController && this.mouseController.multipleSelectionController) || DefaultMultipleSelectionContoller; - } - - private _onDidUpdateOptions = new Emitter>(); - readonly onDidUpdateOptions = this._onDidUpdateOptions.event; + private typeLabelController?: TypeLabelController; protected disposables: IDisposable[]; @@ -1117,13 +1127,7 @@ export class List implements ISpliceable, IDisposable { .map(e => new StandardKeyboardEvent(e)) .filter(e => this.didJustPressContextMenuKey = e.keyCode === KeyCode.ContextMenu || (e.shiftKey && e.keyCode === KeyCode.F10)) .filter(e => { e.preventDefault(); e.stopPropagation(); return false; }) - .map(event => { - const index = this.getFocus()[0]; - const element = this.view.element(index); - const anchor = this.view.domElement(index) || undefined; - return { index, element, anchor, browserEvent: event.browserEvent }; - }) - .event; + .event as Event; const fromKeyup = Event.chain(domEvent(this.view.domNode, 'keyup')) .filter(() => { @@ -1131,14 +1135,13 @@ export class List implements ISpliceable, IDisposable { this.didJustPressContextMenuKey = false; return didJustPressContextMenuKey; }) - .filter(() => this.getFocus().length > 0) + .filter(() => this.getFocus().length > 0 && !!this.view.domElement(this.getFocus()[0])) .map(browserEvent => { const index = this.getFocus()[0]; const element = this.view.element(index); - const anchor = this.view.domElement(index) || undefined; + const anchor = this.view.domElement(index) as HTMLElement; return { index, element, anchor, browserEvent }; }) - .filter(({ anchor }) => !!anchor) .event; const fromMouse = Event.chain(this.view.onContextMenu) @@ -1165,7 +1168,7 @@ export class List implements ISpliceable, IDisposable { renderers: IListRenderer[], private _options: IListOptions = DefaultOptions ) { - this.focus = new FocusTrait(i => this.getElementDomId(i)); + this.focus = new FocusTrait(); this.selection = new Trait('selected'); mixin(_options, defaultStyles, false); @@ -1191,12 +1194,9 @@ export class List implements ISpliceable, IDisposable { this.view.domNode.setAttribute('role', _options.ariaRole); } - DOM.addClass(this.view.domNode, this.idPrefix); - this.view.domNode.tabIndex = 0; - this.styleElement = DOM.createStyleSheet(this.view.domNode); - this.styleController = _options.styleController || new DefaultStyleController(this.styleElement, this.idPrefix); + this.styleController = _options.styleController || new DefaultStyleController(this.styleElement, this.view.domId); this.spliceable = new CombinedSpliceable([ new TraitSpliceable(this.focus, this.view, _options.identityProvider), @@ -1217,14 +1217,11 @@ export class List implements ISpliceable, IDisposable { } if (_options.keyboardNavigationLabelProvider) { - const controller = new TypeLabelController(this, this.view, _options.keyboardNavigationLabelProvider); - this.disposables.push(controller); + this.typeLabelController = new TypeLabelController(this, this.view, _options.keyboardNavigationLabelProvider); + this.disposables.push(this.typeLabelController); } - if (typeof _options.mouseSupport === 'boolean' ? _options.mouseSupport : true) { - this.mouseController = new MouseController(this, this.view, _options); - this.disposables.push(this.mouseController); - } + this.disposables.push(this.createMouseController(_options)); this.onFocusChange(this._onFocusChange, this, this.disposables); this.onSelectionChange(this._onSelectionChange, this, this.disposables); @@ -1236,9 +1233,16 @@ export class List implements ISpliceable, IDisposable { this.style(_options); } + protected createMouseController(options: IListOptions): MouseController { + return new MouseController(this); + } + updateOptions(optionsUpdate: IListOptionsUpdate = {}): void { this._options = { ...this._options, ...optionsUpdate }; - this._onDidUpdateOptions.fire(this._options); + + if (this.typeLabelController) { + this.typeLabelController.updateOptions(this._options); + } } get options(): IListOptions { @@ -1265,6 +1269,10 @@ export class List implements ISpliceable, IDisposable { this.view.updateWidth(index); } + rerender(): void { + this.view.rerender(); + } + element(index: number): T { return this.view.element(index); } @@ -1297,6 +1305,14 @@ export class List implements ISpliceable, IDisposable { return this.view.renderHeight; } + get firstVisibleIndex(): number { + return this.view.firstVisibleIndex; + } + + get lastVisibleIndex(): number { + return this.view.lastVisibleIndex; + } + domFocus(): void { this.view.domNode.focus(); } @@ -1305,6 +1321,12 @@ export class List implements ISpliceable, IDisposable { this.view.layout(height, width); } + toggleKeyboardNavigation(): void { + if (this.typeLabelController) { + this.typeLabelController.toggle(); + } + } + setSelection(indexes: number[], browserEvent?: UIEvent): void { for (const index of indexes) { if (index < 0 || index >= this.length) { @@ -1525,10 +1547,6 @@ export class List implements ISpliceable, IDisposable { return Math.abs((scrollTop - elementTop) / m); } - private getElementDomId(index: number): string { - return `${this.idPrefix}_${index}`; - } - isDOMFocused(): boolean { return this.view.domNode === document.activeElement; } @@ -1569,7 +1587,7 @@ export class List implements ISpliceable, IDisposable { const focus = this.focus.get(); if (focus.length > 0) { - this.view.domNode.setAttribute('aria-activedescendant', this.getElementDomId(focus[0])); + this.view.domNode.setAttribute('aria-activedescendant', this.view.getElementDomId(focus[0])); } else { this.view.domNode.removeAttribute('aria-activedescendant'); } diff --git a/src/vs/base/browser/ui/menu/menu.css b/src/vs/base/browser/ui/menu/menu.css index 3c5f1ac418..aec27d7afd 100644 --- a/src/vs/base/browser/ui/menu/menu.css +++ b/src/vs/base/browser/ui/menu/menu.css @@ -117,7 +117,6 @@ /* Context Menu */ .context-view.monaco-menu-container { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif; outline: 0; border: none; -webkit-animation: fadeIn 0.083s linear; @@ -182,7 +181,6 @@ } .menubar .menubar-menu-items-holder.monaco-menu-container { - font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "Ubuntu", "Droid Sans", sans-serif; outline: 0; border: none; } diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index effb8e081c..6dff868dff 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import * as strings from 'vs/base/common/strings'; import { IActionRunner, IAction, Action } from 'vs/base/common/actions'; import { ActionBar, IActionItemProvider, ActionsOrientation, Separator, ActionItem, IActionItemOptions, BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { ResolvedKeybinding, KeyCode, KeyCodeUtils } from 'vs/base/common/keyCodes'; +import { ResolvedKeybinding, KeyCode } from 'vs/base/common/keyCodes'; import { addClass, EventType, EventHelper, EventLike, removeTabIndexAndUpdateFocus, isAncestor, hasClass, addDisposableListener, removeClass, append, $, addClasses, removeClasses } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -20,8 +20,22 @@ import { Event, Emitter } from 'vs/base/common/event'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { isLinux } from 'vs/base/common/platform'; -export const MENU_MNEMONIC_REGEX: RegExp = /\(&{1,2}(.)\)|&{1,2}(.)/; -export const MENU_ESCAPED_MNEMONIC_REGEX: RegExp = /(?:&){1,2}(.)/; +function createMenuMnemonicRegExp() { + try { + return new RegExp('\\(&([^\\s&])\\)|(?>; + private mnemonics: Map>; private menuDisposables: IDisposable[]; private scrollableElement: DomScrollableElement; private menuElement: HTMLElement; @@ -93,7 +107,7 @@ export class Menu extends ActionBar { if (options.enableMnemonics) { this.menuDisposables.push(addDisposableListener(menuElement, EventType.KEY_DOWN, (e) => { - const key = KeyCodeUtils.fromString(e.key); + const key = e.key.toLocaleLowerCase(); if (this.mnemonics.has(key)) { EventHelper.stop(e, true); const actions = this.mnemonics.get(key)!; @@ -175,7 +189,7 @@ export class Menu extends ActionBar { parent: this }; - this.mnemonics = new Map>(); + this.mnemonics = new Map>(); this.push(actions, { icon: true, label: true, isMenu: true }); @@ -349,7 +363,7 @@ class MenuActionItem extends BaseActionItem { private label: HTMLElement; private check: HTMLElement; - private mnemonic: KeyCode; + private mnemonic: string; private cssClass: string; protected menuStyle: IMenuStyles; @@ -368,7 +382,7 @@ class MenuActionItem extends BaseActionItem { if (label) { let matches = MENU_MNEMONIC_REGEX.exec(label); if (matches) { - this.mnemonic = KeyCodeUtils.fromString((!!matches[1] ? matches[1] : matches[2]).toLocaleLowerCase()); + this.mnemonic = (!!matches[1] ? matches[1] : matches[2]).toLocaleLowerCase(); } } } @@ -440,13 +454,16 @@ class MenuActionItem extends BaseActionItem { label = cleanLabel; } - this.label.setAttribute('aria-label', cleanLabel); + this.label.setAttribute('aria-label', cleanLabel.replace(/&&/g, '&')); const matches = MENU_MNEMONIC_REGEX.exec(label); if (matches) { label = strings.escape(label).replace(MENU_ESCAPED_MNEMONIC_REGEX, ''); + label = label.replace(/&&/g, '&'); this.item.setAttribute('aria-keyshortcuts', (!!matches[1] ? matches[1] : matches[2]).toLocaleLowerCase()); + } else { + label = label.replace(/&&/g, '&'); } } @@ -519,7 +536,7 @@ class MenuActionItem extends BaseActionItem { } } - getMnemonic(): KeyCode { + getMnemonic(): string { return this.mnemonic; } diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 4c4eac0784..a016772fe8 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -14,15 +14,17 @@ import { cleanMnemonic, IMenuOptions, Menu, MENU_ESCAPED_MNEMONIC_REGEX, MENU_MN import { ActionRunner, IAction, IActionRunner } from 'vs/base/common/actions'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; -import { KeyCode, KeyCodeUtils, ResolvedKeybinding } from 'vs/base/common/keyCodes'; +import { KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { withNullAsUndefined } from 'vs/base/common/types'; const $ = DOM.$; export interface IMenuBarOptions { enableMnemonics?: boolean; visibility?: string; - getKeybinding?: (action: IAction) => ResolvedKeybinding; + getKeybinding?: (action: IAction) => ResolvedKeybinding | undefined; + alwaysOnMnemonics?: boolean; } export interface MenuBarMenu { @@ -69,14 +71,14 @@ export class MenuBar extends Disposable { private openedViaKeyboard: boolean; private awaitingAltRelease: boolean; private ignoreNextMouseUp: boolean; - private mnemonics: Map; + private mnemonics: Map; private updatePending: boolean; private _focusState: MenubarState; private actionRunner: IActionRunner; - private _onVisibilityChange: Emitter; - private _onFocusStateChange: Emitter; + private readonly _onVisibilityChange: Emitter; + private readonly _onFocusStateChange: Emitter; private numMenusShown: number; private menuStyle: IMenuStyles; @@ -85,10 +87,10 @@ export class MenuBar extends Disposable { constructor(private container: HTMLElement, private options: IMenuBarOptions = {}) { super(); - this.container.attributes['role'] = 'menubar'; + this.container.setAttribute('role', 'menubar'); this.menuCache = []; - this.mnemonics = new Map(); + this.mnemonics = new Map(); this._focusState = MenubarState.VISIBLE; @@ -109,7 +111,7 @@ export class MenuBar extends Disposable { this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_DOWN, (e) => { let event = new StandardKeyboardEvent(e as KeyboardEvent); let eventHandled = true; - const key = !!e.key ? KeyCodeUtils.fromString(e.key) : KeyCode.Unknown; + const key = !!e.key ? e.key.toLocaleLowerCase() : ''; if (event.equals(KeyCode.LeftArrow)) { this.focusPrevious(); @@ -150,11 +152,14 @@ export class MenuBar extends Disposable { this._register(DOM.addDisposableListener(this.container, DOM.EventType.FOCUS_OUT, (e) => { let event = e as FocusEvent; - if (event.relatedTarget) { - if (!this.container.contains(event.relatedTarget as HTMLElement)) { - this.focusToReturn = undefined; - this.setUnfocusedState(); - } + // We are losing focus and there is no related target, e.g. webview case + if (!event.relatedTarget) { + this.setUnfocusedState(); + } + // We are losing focus and there is a target, reset focusToReturn value as not to redirect + else if (event.relatedTarget && !this.container.contains(event.relatedTarget as HTMLElement)) { + this.focusToReturn = undefined; + this.setUnfocusedState(); } })); @@ -163,7 +168,7 @@ export class MenuBar extends Disposable { return; } - const key = KeyCodeUtils.fromString(e.key); + const key = e.key.toLocaleLowerCase(); if (!this.mnemonics.has(key)) { return; } @@ -459,8 +464,8 @@ export class MenuBar extends Disposable { // Update the button label to reflect mnemonics titleElement.innerHTML = this.options.enableMnemonics ? - strings.escape(label).replace(MENU_ESCAPED_MNEMONIC_REGEX, '') : - cleanMenuLabel; + strings.escape(label).replace(MENU_ESCAPED_MNEMONIC_REGEX, '').replace(/&&/g, '&') : + cleanMenuLabel.replace(/&&/g, '&'); let mnemonicMatches = MENU_MNEMONIC_REGEX.exec(label); @@ -506,7 +511,7 @@ export class MenuBar extends Disposable { } private registerMnemonic(menuIndex: number, mnemonic: string): void { - this.mnemonics.set(KeyCodeUtils.fromString(mnemonic), menuIndex); + this.mnemonics.set(mnemonic.toLocaleLowerCase(), menuIndex); } private hideMenubar(): void { @@ -725,7 +730,7 @@ export class MenuBar extends Disposable { if (menuBarMenu.titleElement.children.length) { let child = menuBarMenu.titleElement.children.item(0) as HTMLElement; if (child) { - child.style.textDecoration = visible ? 'underline' : null; + child.style.textDecoration = (this.options.alwaysOnMnemonics || visible) ? 'underline' : null; } } }); @@ -852,8 +857,8 @@ export class MenuBar extends Disposable { let menuOptions: IMenuOptions = { getKeyBinding: this.options.getKeybinding, actionRunner: this.actionRunner, - enableMnemonics: this.mnemonicsInUse && this.options.enableMnemonics, - ariaLabel: customMenu.buttonElement.attributes['aria-label'].value + enableMnemonics: this.options.alwaysOnMnemonics || (this.mnemonicsInUse && this.options.enableMnemonics), + ariaLabel: withNullAsUndefined(customMenu.buttonElement.getAttribute('aria-label')) }; let menuWidget = this._register(new Menu(menuHolder, customMenu.actions, menuOptions)); @@ -955,6 +960,16 @@ class ModifierKeyEmitter extends Emitter { this._keyStatus.lastKeyPressed = undefined; })); + this._subscriptions.push(domEvent(document.body, 'mouseup', true)(e => { + this._keyStatus.lastKeyPressed = undefined; + })); + + this._subscriptions.push(domEvent(document.body, 'mousemove', true)(e => { + if (e.buttons) { + this._keyStatus.lastKeyPressed = undefined; + } + })); + this._subscriptions.push(domEvent(window, 'blur')(e => { this._keyStatus.lastKeyPressed = undefined; this._keyStatus.lastKeyReleased = undefined; diff --git a/src/vs/base/browser/ui/sash/sash.css b/src/vs/base/browser/ui/sash/sash.css index f140739567..73916ddab7 100644 --- a/src/vs/base/browser/ui/sash/sash.css +++ b/src/vs/base/browser/ui/sash/sash.css @@ -84,6 +84,7 @@ .monaco-sash.disabled { cursor: default !important; + pointer-events: none !important; } /** Touch **/ @@ -98,10 +99,14 @@ /** Debug **/ -.monaco-sash.debug:not(.disabled) { +.monaco-sash.debug { background: cyan; } +.monaco-sash.debug.disabled { + background: rgba(0, 255, 255, 0.2); +} + .monaco-sash.debug:not(.disabled).orthogonal-start::before, .monaco-sash.debug:not(.disabled).orthogonal-end::after { background: red; diff --git a/src/vs/base/browser/ui/selectBox/selectBox.ts b/src/vs/base/browser/ui/selectBox/selectBox.ts index a1dc8a957e..d8ec30e46b 100644 --- a/src/vs/base/browser/ui/selectBox/selectBox.ts +++ b/src/vs/base/browser/ui/selectBox/selectBox.ts @@ -22,9 +22,9 @@ export interface ISelectBoxDelegate { // Public SelectBox Interface readonly onDidSelect: Event; - setOptions(options: ISelectOptionItem[], selected?: number); + setOptions(options: ISelectOptionItem[], selected?: number): void; select(index: number): void; - setAriaLabel(label: string); + setAriaLabel(label: string): void; focus(): void; blur(): void; dispose(): void; diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index d565d6c25c..3ae2320d90 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -124,12 +124,6 @@ export class SelectBoxList implements ISelectBoxDelegate, IListVirtualDelegate(); readonly onDidChange: Event = this._onDidChange.event; + protected disposables: IDisposable[] = []; + get draggableElement(): HTMLElement { return this.header; } @@ -131,8 +133,19 @@ export abstract class Panel implements IView { this._expanded = !!expanded; this.updateHeader(); - this._onDidChange.fire(expanded ? this.expandedSize : undefined); + if (expanded) { + if (typeof this.animationTimer === 'number') { + clearTimeout(this.animationTimer); + } + append(this.element, this.body); + } else { + this.animationTimer = window.setTimeout(() => { + this.body.remove(); + }, 200); + } + + this._onDidChange.fire(expanded ? this.expandedSize : undefined); return true; } @@ -159,8 +172,9 @@ export abstract class Panel implements IView { this.renderHeader(this.header); const focusTracker = trackFocus(this.header); - focusTracker.onDidFocus(() => addClass(this.header, 'focused')); - focusTracker.onDidBlur(() => removeClass(this.header, 'focused')); + this.disposables.push(focusTracker); + focusTracker.onDidFocus(() => addClass(this.header, 'focused'), null, this.disposables); + focusTracker.onDidBlur(() => removeClass(this.header, 'focused'), null, this.disposables); this.updateHeader(); @@ -179,15 +193,8 @@ export abstract class Panel implements IView { domEvent(this.header, 'click') (() => this.setExpanded(!this.isExpanded()), null, this.disposables); - // TODO@Joao move this down to panelview - // onHeaderKeyDown.filter(e => e.keyCode === KeyCode.UpArrow) - // .event(focusPrevious, this, this.disposables); - - // onHeaderKeyDown.filter(e => e.keyCode === KeyCode.DownArrow) - // .event(focusNext, this, this.disposables); - - const body = append(this.element, $('.panel-body')); - this.renderBody(body); + this.body = append(this.element, $('.panel-body')); + this.renderBody(this.body); } layout(height: number): void { @@ -268,7 +275,7 @@ class PanelDraggable extends Disposable { e.dataTransfer.effectAllowed = 'move'; - const dragImage = append(document.body, $('.monaco-panel-drag-image', {}, this.panel.draggableElement.textContent || '')); + const dragImage = append(document.body, $('.monaco-drag-image', {}, this.panel.draggableElement.textContent || '')); e.dataTransfer.setDragImage(dragImage, -10, -10); setTimeout(() => document.body.removeChild(dragImage), 0); @@ -373,7 +380,7 @@ export class PanelView extends Disposable { private panelItems: IPanelItem[] = []; private width: number; private splitview: SplitView; - private animationTimer: number | null = null; + private animationTimer: number | undefined = undefined; private _onDidDrop = this._register(new Emitter<{ from: Panel, to: Panel }>()); readonly onDidDrop: Event<{ from: Panel, to: Panel }> = this._onDidDrop.event; @@ -475,7 +482,7 @@ export class PanelView extends Disposable { addClass(this.el, 'animated'); this.animationTimer = window.setTimeout(() => { - this.animationTimer = null; + this.animationTimer = undefined; removeClass(this.el, 'animated'); }, 200); } diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 220031cfd3..d3891181af 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -47,7 +47,6 @@ export interface IView { readonly maximumSize: number; readonly onDidChange: Event; readonly priority?: LayoutPriority; - readonly snapSize?: number; layout(size: number, orientation: Orientation): void; } @@ -227,7 +226,7 @@ export class SplitView extends Disposable { // Add sash if (this.viewItems.length > 1) { const orientation = this.orientation === Orientation.VERTICAL ? Orientation.HORIZONTAL : Orientation.VERTICAL; - const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: sash => this.getSashPosition(sash) } : { getVerticalSashLeft: sash => this.getSashPosition(sash) }; + const layoutProvider = this.orientation === Orientation.VERTICAL ? { getHorizontalSashTop: (sash: Sash) => this.getSashPosition(sash) } : { getVerticalSashLeft: (sash: Sash) => this.getSashPosition(sash) }; const sash = new Sash(this.sashContainer, layoutProvider, { orientation, orthogonalStartSash: this.orthogonalStartSash, @@ -352,7 +351,7 @@ export class SplitView extends Disposable { } else { for (let i = 0; i < this.viewItems.length; i++) { const item = this.viewItems[i]; - item.size = SplitView.clamp(item, Math.round(this.proportions[i] * size)); + item.size = clamp(Math.round(this.proportions[i] * size), item.view.minimumSize, item.view.maximumSize); } } @@ -444,7 +443,7 @@ export class SplitView extends Disposable { } size = typeof size === 'number' ? size : item.size; - size = SplitView.clamp(item, size); + size = clamp(size, item.view.minimumSize, item.view.maximumSize); if (this.inverseAltBehavior && index > 0) { // In this case, we want the view to grow or shrink both sides equally @@ -551,29 +550,27 @@ export class SplitView extends Disposable { const downItems = downIndexes.map(i => this.viewItems[i]); const downSizes = downIndexes.map(i => sizes[i]); - const minDeltaUp = upIndexes.reduce((r, i) => r + ((typeof this.viewItems[i].view.snapSize === 'number' ? 0 : this.viewItems[i].view.minimumSize) - sizes[i]), 0); + const minDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.minimumSize - sizes[i]), 0); const maxDeltaUp = upIndexes.reduce((r, i) => r + (this.viewItems[i].view.maximumSize - sizes[i]), 0); - const maxDeltaDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - (typeof this.viewItems[i].view.snapSize === 'number' ? 0 : this.viewItems[i].view.minimumSize)), 0); + const maxDeltaDown = downIndexes.length === 0 ? Number.POSITIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.minimumSize), 0); const minDeltaDown = downIndexes.length === 0 ? Number.NEGATIVE_INFINITY : downIndexes.reduce((r, i) => r + (sizes[i] - this.viewItems[i].view.maximumSize), 0); const minDelta = Math.max(minDeltaUp, minDeltaDown, overloadMinDelta); const maxDelta = Math.min(maxDeltaDown, maxDeltaUp, overloadMaxDelta); - const tentativeDelta = clamp(delta, minDelta, maxDelta); - let actualDelta = 0; + delta = clamp(delta, minDelta, maxDelta); - for (let i = 0, deltaUp = tentativeDelta; i < upItems.length; i++) { + for (let i = 0, deltaUp = delta; i < upItems.length; i++) { const item = upItems[i]; - const size = SplitView.clamp(item, upSizes[i] + deltaUp/* , upIndexes[i] === index */); + const size = clamp(upSizes[i] + deltaUp, item.view.minimumSize, item.view.maximumSize); const viewDelta = size - upSizes[i]; - actualDelta += viewDelta; deltaUp -= viewDelta; item.size = size; } - for (let i = 0, deltaDown = actualDelta; i < downItems.length; i++) { + for (let i = 0, deltaDown = delta; i < downItems.length; i++) { const item = downItems[i]; - const size = SplitView.clamp(item, downSizes[i] - deltaDown); + const size = clamp(downSizes[i] - deltaDown, item.view.minimumSize, item.view.maximumSize); const viewDelta = size - downSizes[i]; deltaDown += viewDelta; @@ -583,24 +580,13 @@ export class SplitView extends Disposable { return delta; } - private static clamp(item: IViewItem, size: number): number { - const result = clamp(size, item.view.minimumSize, item.view.maximumSize); - - if (typeof item.view.snapSize !== 'number' || size >= item.view.minimumSize) { - return result; - } - - const snapSize = Math.min(item.view.snapSize, item.view.minimumSize); - return size < snapSize ? 0 : item.view.minimumSize; - } - private distributeEmptySpace(): void { let contentSize = this.viewItems.reduce((r, i) => r + i.size, 0); let emptyDelta = this.size - contentSize; for (let i = this.viewItems.length - 1; emptyDelta !== 0 && i >= 0; i--) { const item = this.viewItems[i]; - const size = SplitView.clamp(item, item.size + emptyDelta); + const size = clamp(item.size + emptyDelta, item.view.minimumSize, item.view.maximumSize); const viewDelta = size - item.size; emptyDelta -= viewDelta; diff --git a/src/vs/base/browser/ui/toolbar/toolbar.ts b/src/vs/base/browser/ui/toolbar/toolbar.ts index fa10ea23a3..c8cc094509 100644 --- a/src/vs/base/browser/ui/toolbar/toolbar.ts +++ b/src/vs/base/browser/ui/toolbar/toolbar.ts @@ -11,6 +11,7 @@ import { IContextMenuProvider, DropdownMenuActionItem } from 'vs/base/browser/ui import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { Disposable } from 'vs/base/common/lifecycle'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { withNullAsUndefined } from 'vs/base/common/types'; export const CONTEXT = 'context.toolbar'; @@ -135,7 +136,7 @@ export class ToolBar extends Disposable { private getKeybindingLabel(action: IAction): string | undefined { const key = this.lookupKeybindings && this.options.getKeyBinding ? this.options.getKeyBinding(action) : undefined; - return (key && key.getLabel()) || undefined; + return withNullAsUndefined(key && key.getLabel()); } addPrimaryAction(primaryAction: IAction): () => void { diff --git a/src/vs/base/browser/ui/tree/abstractTree.ts b/src/vs/base/browser/ui/tree/abstractTree.ts index f76e846cb8..7ee6817e99 100644 --- a/src/vs/base/browser/ui/tree/abstractTree.ts +++ b/src/vs/base/browser/ui/tree/abstractTree.ts @@ -5,16 +5,16 @@ import 'vs/css!./media/tree'; import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IListOptions, List, IListStyles, mightProducePrintableCharacter } from 'vs/base/browser/ui/list/listWidget'; -import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list'; +import { IListOptions, List, IListStyles, mightProducePrintableCharacter, MouseController } from 'vs/base/browser/ui/list/listWidget'; +import { IListVirtualDelegate, IListRenderer, IListMouseEvent, IListEvent, IListContextMenuEvent, IListDragAndDrop, IListDragOverReaction, IKeyboardNavigationLabelProvider, IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { append, $, toggleClass, getDomNodePagePosition, removeClass, addClass, hasClass } from 'vs/base/browser/dom'; import { Event, Relay, Emitter, EventBufferer } from 'vs/base/common/event'; import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter, ITreeNavigator, ICollapseStateChangeEvent, ITreeDragAndDrop, TreeDragOverBubble, TreeVisibility, TreeFilterResult } from 'vs/base/browser/ui/tree/tree'; +import { ITreeModel, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeFilter, ITreeNavigator, ICollapseStateChangeEvent, ITreeDragAndDrop, TreeDragOverBubble, TreeVisibility, TreeFilterResult, ITreeModelSpliceEvent } from 'vs/base/browser/ui/tree/tree'; import { ISpliceable } from 'vs/base/common/sequence'; import { IDragAndDropData, StaticDND, DragAndDropData } from 'vs/base/browser/dnd'; -import { range } from 'vs/base/common/arrays'; +import { range, equals } from 'vs/base/common/arrays'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { domEvent } from 'vs/base/browser/event'; import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters'; @@ -150,7 +150,15 @@ function asListOptions(modelProvider: () => ITreeModel implements IListRenderer>(); private renderedNodes = new Map, ITreeListTemplateData>(); - private indent: number; + private indent: number = TreeRenderer.DefaultIndent; private disposables: IDisposable[] = []; constructor( @@ -207,7 +215,9 @@ class TreeRenderer implements IListRenderer { templateData.twistie.style.marginLeft = `${node.depth * this.indent}px`; @@ -223,25 +233,28 @@ class TreeRenderer implements IListRenderer, index: number, templateData: ITreeListTemplateData): void { - this.renderedNodes.set(node, templateData); - this.renderedElements.set(node.element, node); + renderElement(node: ITreeNode, index: number, templateData: ITreeListTemplateData, dynamicHeightProbing?: boolean): void { + if (!dynamicHeightProbing) { + this.renderedNodes.set(node, templateData); + this.renderedElements.set(node.element, node); + } const indent = TreeRenderer.DefaultIndent + (node.depth - 1) * this.indent; templateData.twistie.style.marginLeft = `${indent}px`; - templateData.container.setAttribute('aria-posinset', String(node.visibleChildIndex + 1)); - templateData.container.setAttribute('aria-setsize', String(node.parent!.visibleChildrenCount)); this.update(node, templateData); - this.renderer.renderElement(node, index, templateData.templateData); + this.renderer.renderElement(node, index, templateData.templateData, dynamicHeightProbing); } - disposeElement(node: ITreeNode, index: number, templateData: ITreeListTemplateData): void { + disposeElement(node: ITreeNode, index: number, templateData: ITreeListTemplateData, dynamicHeightProbing?: boolean): void { if (this.renderer.disposeElement) { - this.renderer.disposeElement(node, index, templateData.templateData); + this.renderer.disposeElement(node, index, templateData.templateData, dynamicHeightProbing); + } + + if (!dynamicHeightProbing) { + this.renderedNodes.delete(node); + this.renderedElements.delete(node.element); } - this.renderedNodes.delete(node); - this.renderedElements.set(node.element); } disposeTemplate(templateData: ITreeListTemplateData): void { @@ -386,6 +399,15 @@ class TypeFilterController implements IDisposable { private _pattern = ''; get pattern(): string { return this._pattern; } + private _filterOnType: boolean; + get filterOnType(): boolean { return this._filterOnType; } + + private _empty: boolean; + get empty(): boolean { return this._empty; } + + private _onDidChangeEmptyState = new Emitter(); + readonly onDidChangeEmptyState: Event = Event.latch(this._onDidChangeEmptyState.event); + private positionClassName = 'ne'; private domNode: HTMLElement; private messageDomNode: HTMLElement; @@ -394,9 +416,12 @@ class TypeFilterController implements IDisposable { private clearDomNode: HTMLElement; private keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter; - private automaticKeyboardNavigation: boolean; + private automaticKeyboardNavigation = true; private triggered = false; + private _onDidChangePattern = new Emitter(); + readonly onDidChangePattern = this._onDidChangePattern.event; + private enabledDisposables: IDisposable[] = []; private disposables: IDisposable[] = []; @@ -416,9 +441,10 @@ class TypeFilterController implements IDisposable { this.labelDomNode = append(this.domNode, $('span.label')); const controls = append(this.domNode, $('.controls')); + this._filterOnType = !!tree.options.filterOnType; this.filterOnTypeDomNode = append(controls, $('input.filter')); this.filterOnTypeDomNode.type = 'checkbox'; - this.filterOnTypeDomNode.checked = !!tree.options.filterOnType; + this.filterOnTypeDomNode.checked = this._filterOnType; this.filterOnTypeDomNode.tabIndex = -1; this.updateFilterOnTypeTitle(); domEvent(this.filterOnTypeDomNode, 'input')(this.onDidChangeFilterOnType, this, this.disposables); @@ -440,8 +466,15 @@ class TypeFilterController implements IDisposable { this.enable(); } - this.filterOnTypeDomNode.checked = !!options.filterOnType; - this.automaticKeyboardNavigation = typeof options.automaticKeyboardNavigation === 'undefined' ? true : options.automaticKeyboardNavigation; + if (typeof options.filterOnType !== 'undefined') { + this._filterOnType = !!options.filterOnType; + this.filterOnTypeDomNode.checked = this._filterOnType; + } + + if (typeof options.automaticKeyboardNavigation !== 'undefined') { + this.automaticKeyboardNavigation = options.automaticKeyboardNavigation; + } + this.tree.refilter(); this.render(); @@ -466,10 +499,10 @@ class TypeFilterController implements IDisposable { const isPrintableCharEvent = this.keyboardNavigationLabelProvider.mightProducePrintableCharacter ? (e: IKeyboardEvent) => this.keyboardNavigationLabelProvider.mightProducePrintableCharacter!(e) : (e: IKeyboardEvent) => mightProducePrintableCharacter(e); const onKeyDown = Event.chain(domEvent(this.view.getHTMLElement(), 'keydown')) .filter(e => !isInputElement(e.target as HTMLElement) || e.target === this.filterOnTypeDomNode) + .map(e => new StandardKeyboardEvent(e)) .filter(this.keyboardNavigationEventFilter || (() => true)) .filter(() => this.automaticKeyboardNavigation || this.triggered) - .map(e => new StandardKeyboardEvent(e)) - .filter(e => isPrintableCharEvent(e) || ((this._pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? e.altKey : e.ctrlKey)))) + .filter(e => isPrintableCharEvent(e) || ((this.pattern.length > 0 || this.triggered) && ((e.keyCode === KeyCode.Escape || e.keyCode === KeyCode.Backspace) && !e.altKey && !e.ctrlKey && !e.metaKey) || (e.keyCode === KeyCode.Backspace && (isMacintosh ? e.altKey : e.ctrlKey) && !e.shiftKey))) .forEach(e => { e.stopPropagation(); e.preventDefault(); }) .event; @@ -521,9 +554,14 @@ class TypeFilterController implements IDisposable { } this._pattern = pattern; + this._onDidChangePattern.fire(pattern); + this.filter.pattern = pattern; this.tree.refilter(); - this.tree.focusNext(0, true); + + if (pattern) { + this.tree.focusNext(0, true, undefined, node => !FuzzyScore.isDefault(node.filterData as any as FuzzyScore)); + } const focus = this.tree.getFocus(); @@ -565,6 +603,8 @@ class TypeFilterController implements IDisposable { }; const onDragOver = (event: DragEvent) => { + event.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome) + const x = event.screenX - left; if (event.dataTransfer) { event.dataTransfer.dropEffect = 'none'; @@ -619,7 +659,7 @@ class TypeFilterController implements IDisposable { } private updateFilterOnTypeTitle(): void { - if (this.filterOnTypeDomNode.checked) { + if (this.filterOnType) { this.filterOnTypeDomNode.title = localize('disable filter on type', "Disable Filter on Type"); } else { this.filterOnTypeDomNode.title = localize('enable filter on type', "Enable Filter on Type"); @@ -631,17 +671,34 @@ class TypeFilterController implements IDisposable { if (this.pattern && this.tree.options.filterOnType && noMatches) { this.messageDomNode.textContent = localize('empty', "No elements found"); + this._empty = true; } else { this.messageDomNode.innerHTML = ''; + this._empty = false; } toggleClass(this.domNode, 'no-matches', noMatches); this.domNode.title = localize('found', "Matched {0} out of {1} elements", this.filter.matchCount, this.filter.totalCount); this.labelDomNode.textContent = this.pattern.length > 16 ? '…' + this.pattern.substr(this.pattern.length - 16) : this.pattern; + + this._onDidChangeEmptyState.fire(this._empty); + } + + shouldAllowFocus(node: ITreeNode): boolean { + if (!this.enabled || !this.pattern || this.filterOnType) { + return true; + } + + if (this.filter.totalCount > 0 && this.filter.matchCount <= 1) { + return true; + } + + return !FuzzyScore.isDefault(node.filterData as any as FuzzyScore); } dispose() { this.disable(); + this._onDidChangePattern.dispose(); this.disposables = dispose(this.disposables); } } @@ -673,7 +730,7 @@ function asTreeContextMenuEvent(event: IListContextMenuEvent extends IAbstractTr readonly dnd?: ITreeDragAndDrop; readonly autoExpandSingleChildren?: boolean; readonly keyboardNavigationEventFilter?: IKeyboardNavigationEventFilter; + readonly expandOnlyOnTwistieClick?: boolean | ((e: T) => boolean); +} + +function dfs(node: ITreeNode, fn: (node: ITreeNode) => void): void { + fn(node); + node.children.forEach(child => dfs(child, fn)); } /** @@ -706,17 +769,19 @@ class Trait { private _nodeSet: Set> | undefined; private get nodeSet(): Set> { if (!this._nodeSet) { - this._nodeSet = new Set(); - - for (const node of this.nodes) { - this._nodeSet.add(node); - } + this._nodeSet = this.createNodeSet(); } return this._nodeSet; } + constructor(private identityProvider?: IIdentityProvider) { } + set(nodes: ITreeNode[], browserEvent?: UIEvent): void { + if (equals(this.nodes, nodes)) { + return; + } + this.nodes = [...nodes]; this.elements = undefined; this._nodeSet = undefined; @@ -737,27 +802,101 @@ class Trait { return this.nodeSet.has(node); } - remove(nodes: ITreeNode[]): void { - if (nodes.length === 0) { + onDidModelSplice({ insertedNodes, deletedNodes }: ITreeModelSpliceEvent): void { + if (!this.identityProvider) { + const set = this.createNodeSet(); + const visit = node => set.delete(node); + deletedNodes.forEach(node => dfs(node, visit)); + this.set(values(set)); return; } - const set = this.nodeSet; - const visit = (node: ITreeNode) => { - set.delete(node); - node.children.forEach(visit); - }; + const identityProvider = this.identityProvider; + const nodesByIdentity = new Map>(); + this.nodes.forEach(node => nodesByIdentity.set(identityProvider.getId(node.element).toString(), node)); - nodes.forEach(visit); - this.set(values(set)); + const toDeleteByIdentity = new Map>(); + const toRemoveSetter = node => toDeleteByIdentity.set(identityProvider.getId(node.element).toString(), node); + const toRemoveDeleter = node => toDeleteByIdentity.delete(identityProvider.getId(node.element).toString()); + deletedNodes.forEach(node => dfs(node, toRemoveSetter)); + insertedNodes.forEach(node => dfs(node, toRemoveDeleter)); + + toDeleteByIdentity.forEach((_, id) => nodesByIdentity.delete(id)); + this.set(values(nodesByIdentity)); } + + private createNodeSet(): Set> { + const set = new Set>(); + + for (const node of this.nodes) { + set.add(node); + } + + return set; + } +} + +class TreeNodeListMouseController extends MouseController> { + + constructor(list: TreeNodeList, private tree: AbstractTree) { + super(list); + } + + protected onPointer(e: IListMouseEvent>): void { + if (isInputElement(e.browserEvent.target as HTMLElement)) { + return; + } + + const node = e.element; + + if (!node) { + return super.onPointer(e); + } + + if (this.isSelectionRangeChangeEvent(e) || this.isSelectionSingleChangeEvent(e)) { + return super.onPointer(e); + } + + const onTwistie = hasClass(e.browserEvent.target as HTMLElement, 'monaco-tl-twistie'); + + if (!this.tree.openOnSingleClick && e.browserEvent.detail !== 2 && !onTwistie) { + return super.onPointer(e); + } + + let expandOnlyOnTwistieClick = false; + + if (typeof this.tree.expandOnlyOnTwistieClick === 'function') { + expandOnlyOnTwistieClick = this.tree.expandOnlyOnTwistieClick(node.element); + } else { + expandOnlyOnTwistieClick = !!this.tree.expandOnlyOnTwistieClick; + } + + if (expandOnlyOnTwistieClick && !onTwistie) { + return super.onPointer(e); + } + + const model = ((this.tree as any).model as ITreeModel); // internal + const location = model.getNodeLocation(node); + const recursive = e.browserEvent.altKey; + model.setCollapsed(location, undefined, recursive); + + if (expandOnlyOnTwistieClick && onTwistie) { + return; + } + + super.onPointer(e); + } +} + +interface ITreeNodeListOptions extends IListOptions> { + readonly tree: AbstractTree; } /** * We use this List subclass to restore selection and focus as nodes * get rendered in the list, possibly due to a node expand() call. */ -class TreeNodeList extends List> { +class TreeNodeList extends List> { constructor( container: HTMLElement, @@ -765,11 +904,15 @@ class TreeNodeList extends List> { renderers: IListRenderer[], private focusTrait: Trait, private selectionTrait: Trait, - options?: IListOptions> + options: ITreeNodeListOptions ) { super(container, virtualDelegate, renderers, options); } + protected createMouseController(options: ITreeNodeListOptions): MouseController> { + return new TreeNodeListMouseController(this, options.tree); + } + splice(start: number, deleteCount: number, elements: ITreeNode[] = []): void { super.splice(start, deleteCount, elements); @@ -818,20 +961,20 @@ class TreeNodeList extends List> { export abstract class AbstractTree implements IDisposable { - private view: TreeNodeList; + protected view: TreeNodeList; private renderers: TreeRenderer[]; - private focusNavigationFilter: ((node: ITreeNode) => boolean) | undefined; protected model: ITreeModel; - private focus = new Trait(); - private selection = new Trait(); + private focus: Trait; + private selection: Trait; private eventBufferer = new EventBufferer(); private typeFilterController?: TypeFilterController; + private focusNavigationFilter: ((node: ITreeNode) => boolean) | undefined; protected disposables: IDisposable[] = []; get onDidScroll(): Event { return this.view.onDidScroll; } - readonly onDidChangeFocus: Event> = this.eventBufferer.wrapEvent(this.focus.onDidChange); - readonly onDidChangeSelection: Event> = this.eventBufferer.wrapEvent(this.selection.onDidChange); + get onDidChangeFocus(): Event> { return this.eventBufferer.wrapEvent(this.focus.onDidChange); } + get onDidChangeSelection(): Event> { return this.eventBufferer.wrapEvent(this.selection.onDidChange); } get onDidOpen(): Event> { return Event.map(this.view.onDidOpen, asTreeEvent); } get onMouseClick(): Event> { return Event.map(this.view.onMouseClick, asTreeMouseEvent); } @@ -852,7 +995,14 @@ export abstract class AbstractTree implements IDisposable readonly onWillRefilter: Event = this._onWillRefilter.event; get filterOnType(): boolean { return !!this._options.filterOnType; } + get onDidChangeTypeFilterPattern(): Event { return this.typeFilterController ? this.typeFilterController.onDidChangePattern : Event.None; } + + // Options TODO@joao expose options only, not Optional<> get openOnSingleClick(): boolean { return typeof this._options.openOnSingleClick === 'undefined' ? true : this._options.openOnSingleClick; } + get expandOnlyOnTwistieClick(): boolean | ((e: T) => boolean) { return typeof this._options.expandOnlyOnTwistieClick === 'undefined' ? false : this._options.expandOnlyOnTwistieClick; } + + private _onDidUpdateOptions = new Emitter>(); + readonly onDidUpdateOptions: Event> = this._onDidUpdateOptions.event; get onDidDispose(): Event { return this.view.onDidDispose; } @@ -871,49 +1021,22 @@ export abstract class AbstractTree implements IDisposable let filter: TypeFilter | undefined; if (_options.keyboardNavigationLabelProvider) { - filter = new TypeFilter(this, _options.keyboardNavigationLabelProvider, _options.filter as ITreeFilter); + filter = new TypeFilter(this, _options.keyboardNavigationLabelProvider, _options.filter as any as ITreeFilter); _options = { ..._options, filter: filter as ITreeFilter }; // TODO need typescript help here this.disposables.push(filter); } - this.view = new TreeNodeList(container, treeDelegate, this.renderers, this.focus, this.selection, asListOptions(() => this.model, _options)); + this.focus = new Trait(_options.identityProvider); + this.selection = new Trait(_options.identityProvider); + this.view = new TreeNodeList(container, treeDelegate, this.renderers, this.focus, this.selection, { ...asListOptions(() => this.model, _options), tree: this }); this.model = this.createModel(this.view, _options); onDidChangeCollapseStateRelay.input = this.model.onDidChangeCollapseState; - if (this.options.identityProvider) { - const identityProvider = this.options.identityProvider; - - this.model.onDidSplice(e => { - if (e.deletedNodes.length === 0) { - return; - } - - this.eventBufferer.bufferEvents(() => { - const map = new Map>(); - - for (const node of e.deletedNodes) { - map.set(identityProvider.getId(node.element).toString(), node); - } - - for (const node of e.insertedNodes) { - map.delete(identityProvider.getId(node.element).toString()); - } - - if (map.size === 0) { - return; - } - - const deletedNodes = values(map); - - this.focus.remove(deletedNodes); - this.selection.remove(deletedNodes); - }); - }, null, this.disposables); - } - - this.view.onTap(this.reactOnMouseClick, this, this.disposables); - this.view.onMouseClick(this.reactOnMouseClick, this, this.disposables); + this.model.onDidSplice(e => { + this.focus.onDidModelSplice(e); + this.selection.onDidModelSplice(e); + }, null, this.disposables); if (_options.keyboardSupport !== false) { const onKeyDown = Event.chain(this.view.onKeyDown) @@ -927,17 +1050,7 @@ export abstract class AbstractTree implements IDisposable if (_options.keyboardNavigationLabelProvider) { this.typeFilterController = new TypeFilterController(this, this.model, this.view, filter!, _options.keyboardNavigationLabelProvider); - this.focusNavigationFilter = node => { - if (!this.typeFilterController!.enabled || !this.typeFilterController!.pattern) { - return true; - } - - if (filter!.totalCount > 0 && filter!.matchCount <= 1) { - return true; - } - - return !FuzzyScore.isDefault(node.filterData as any as FuzzyScore); - }; + this.focusNavigationFilter = node => this.typeFilterController!.shouldAllowFocus(node); this.disposables.push(this.typeFilterController!); } } @@ -949,11 +1062,16 @@ export abstract class AbstractTree implements IDisposable renderer.updateOptions(optionsUpdate); } - this.view.updateOptions({ enableKeyboardNavigation: this._options.simpleKeyboardNavigation }); + this.view.updateOptions({ + enableKeyboardNavigation: this._options.simpleKeyboardNavigation, + automaticKeyboardNavigation: this._options.automaticKeyboardNavigation + }); if (this.typeFilterController) { this.typeFilterController.updateOptions(this._options); } + + this._onDidUpdateOptions.fire(this._options); } get options(): IAbstractTreeOptions { @@ -977,11 +1095,21 @@ export abstract class AbstractTree implements IDisposable } get contentHeight(): number { + if (this.typeFilterController && this.typeFilterController.filterOnType && this.typeFilterController.empty) { + return 100; + } + return this.view.contentHeight; } get onDidChangeContentHeight(): Event { - return this.view.onDidChangeContentHeight; + let result = this.view.onDidChangeContentHeight; + + if (this.typeFilterController) { + result = Event.any(result, Event.map(this.typeFilterController.onDidChangeEmptyState, () => this.contentHeight)); + } + + return result; } get scrollTop(): number { @@ -1000,6 +1128,18 @@ export abstract class AbstractTree implements IDisposable return this.view.renderHeight; } + get firstVisibleElement(): T { + const index = this.view.firstVisibleIndex; + const node = this.view.element(index); + return node.element; + } + + get lastVisibleElement(): T { + const index = this.view.lastVisibleIndex; + const node = this.view.element(index); + return node.element; + } + domFocus(): void { this.view.domFocus(); } @@ -1061,11 +1201,11 @@ export abstract class AbstractTree implements IDisposable } toggleKeyboardNavigation(): void { - if (!this.typeFilterController) { - return; - } + this.view.toggleKeyboardNavigation(); - this.typeFilterController.toggle(); + if (this.typeFilterController) { + this.typeFilterController.toggle(); + } } refilter(): void { @@ -1093,40 +1233,42 @@ export abstract class AbstractTree implements IDisposable this.view.setFocus(indexes, browserEvent, true); } - focusNext(n = 1, loop = false, browserEvent?: UIEvent): void { - this.view.focusNext(n, loop, browserEvent, this.focusNavigationFilter); + focusNext(n = 1, loop = false, browserEvent?: UIEvent, filter = this.focusNavigationFilter): void { + this.view.focusNext(n, loop, browserEvent, filter); } - focusPrevious(n = 1, loop = false, browserEvent?: UIEvent): void { - this.view.focusPrevious(n, loop, browserEvent, this.focusNavigationFilter); + focusPrevious(n = 1, loop = false, browserEvent?: UIEvent, filter = this.focusNavigationFilter): void { + this.view.focusPrevious(n, loop, browserEvent, filter); } - focusNextPage(browserEvent?: UIEvent): void { - this.view.focusNextPage(browserEvent, this.focusNavigationFilter); + focusNextPage(browserEvent?: UIEvent, filter = this.focusNavigationFilter): void { + this.view.focusNextPage(browserEvent, filter); } - focusPreviousPage(browserEvent?: UIEvent): void { - this.view.focusPreviousPage(browserEvent, this.focusNavigationFilter); + focusPreviousPage(browserEvent?: UIEvent, filter = this.focusNavigationFilter): void { + this.view.focusPreviousPage(browserEvent, filter); } - focusLast(browserEvent?: UIEvent): void { - this.view.focusLast(browserEvent, this.focusNavigationFilter); + focusLast(browserEvent?: UIEvent, filter = this.focusNavigationFilter): void { + this.view.focusLast(browserEvent, filter); } - focusFirst(browserEvent?: UIEvent): void { - this.view.focusFirst(browserEvent, this.focusNavigationFilter); + focusFirst(browserEvent?: UIEvent, filter = this.focusNavigationFilter): void { + this.view.focusFirst(browserEvent, filter); } getFocus(): T[] { return this.focus.get(); } - open(elements: TRef[]): void { + open(elements: TRef[], browserEvent?: UIEvent): void { const indexes = elements.map(e => this.model.getListIndex(e)); - this.view.open(indexes); + this.view.open(indexes, browserEvent); } reveal(location: TRef, relativeTop?: number): void { + this.model.expandTo(location); + const index = this.model.getListIndex(location); if (index === -1) { @@ -1152,37 +1294,6 @@ export abstract class AbstractTree implements IDisposable // List - get visibleNodeCount(): number { - return this.view.length; - } - - private reactOnMouseClick(e: IListMouseEvent>): void { - if (isInputElement(e.browserEvent.target as HTMLElement)) { - return; - } - - const node = e.element; - - if (!node) { - return; - } - - if (this.view.multipleSelectionController.isSelectionRangeChangeEvent(e) || this.view.multipleSelectionController.isSelectionSingleChangeEvent(e)) { - return; - } - - const onTwistie = hasClass(e.browserEvent.target as HTMLElement, 'monaco-tl-twistie'); - - if (!this.openOnSingleClick && e.browserEvent.detail !== 2 && !onTwistie) { - return; - } - - const location = this.model.getNodeLocation(node); - const recursive = e.browserEvent.altKey; - - this.model.setCollapsed(location, undefined, recursive); - } - private onLeftArrow(e: StandardKeyboardEvent): void { e.preventDefault(); e.stopPropagation(); diff --git a/src/vs/base/browser/ui/tree/asyncDataTree.ts b/src/vs/base/browser/ui/tree/asyncDataTree.ts index 1ff84fa8d5..51a7fb8ec2 100644 --- a/src/vs/base/browser/ui/tree/asyncDataTree.ts +++ b/src/vs/base/browser/ui/tree/asyncDataTree.ts @@ -9,7 +9,7 @@ import { IListVirtualDelegate, IIdentityProvider, IListDragAndDrop, IListDragOve import { ITreeElement, ITreeNode, ITreeRenderer, ITreeEvent, ITreeMouseEvent, ITreeContextMenuEvent, ITreeSorter, ICollapseStateChangeEvent, IAsyncDataSource, ITreeDragAndDrop } from 'vs/base/browser/ui/tree/tree'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; -import { timeout, always, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; import { Iterator } from 'vs/base/common/iterator'; import { IDragAndDropData } from 'vs/base/browser/dnd'; @@ -17,24 +17,35 @@ import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors'; import { toggleClass } from 'vs/base/browser/dom'; -const enum AsyncDataTreeNodeState { - Uninitialized = 'uninitialized', - Loaded = 'loaded', - Loading = 'loading' -} - interface IAsyncDataTreeNode { element: TInput | T; readonly parent: IAsyncDataTreeNode | null; readonly children: IAsyncDataTreeNode[]; readonly id?: string | null; - state: AsyncDataTreeNodeState; + loading: boolean; hasChildren: boolean; - needsRefresh: boolean; + stale: boolean; slow: boolean; disposed: boolean; } +interface IAsyncDataTreeNodeRequiredProps extends Partial> { + readonly element: TInput | T; + readonly parent: IAsyncDataTreeNode | null; + readonly hasChildren: boolean; +} + +function createAsyncDataTreeNode(props: IAsyncDataTreeNodeRequiredProps): IAsyncDataTreeNode { + return { + ...props, + children: [], + loading: false, + stale: true, + disposed: false, + slow: false + }; +} + function isAncestor(ancestor: IAsyncDataTreeNode, descendant: IAsyncDataTreeNode): boolean { if (!descendant.parent) { return false; @@ -87,8 +98,8 @@ class DataTreeRenderer implements ITreeRe return { templateData }; } - renderElement(node: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { - this.renderer.renderElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData); + renderElement(node: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData, dynamicHeightProbing?: boolean): void { + this.renderer.renderElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, dynamicHeightProbing); } renderTwistie(element: IAsyncDataTreeNode, twistieElement: HTMLElement): boolean { @@ -96,9 +107,9 @@ class DataTreeRenderer implements ITreeRe return false; } - disposeElement(node: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData): void { + disposeElement(node: ITreeNode, TFilterData>, index: number, templateData: IDataTreeListTemplateData, dynamicHeightProbing?: boolean): void { if (this.renderer.disposeElement) { - this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData); + this.renderer.disposeElement(new AsyncDataTreeNodeWrapper(node), index, templateData.templateData, dynamicHeightProbing); } } @@ -217,20 +228,26 @@ function asObjectTreeOptions(options?: IAsyncDataTreeOpt return options.keyboardNavigationLabelProvider!.getKeyboardNavigationLabel(e.element as T); } }, - sorter: undefined + sorter: undefined, + expandOnlyOnTwistieClick: typeof options.expandOnlyOnTwistieClick === 'undefined' ? undefined : ( + typeof options.expandOnlyOnTwistieClick !== 'function' ? options.expandOnlyOnTwistieClick : ( + e => (options.expandOnlyOnTwistieClick as ((e: T) => boolean))(e.element as T) + ) + ), + ariaSetProvider: undefined }; } function asTreeElement(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext): ITreeElement> { let collapsed: boolean | undefined; - if (viewStateContext && node.id) { + if (viewStateContext && viewStateContext.viewState.expanded && node.id) { collapsed = viewStateContext.viewState.expanded.indexOf(node.id) === -1; } return { element: node, - children: Iterator.map(Iterator.fromArray(node.children), child => asTreeElement(child, viewStateContext)), + children: node.hasChildren ? Iterator.map(Iterator.fromArray(node.children), child => asTreeElement(child, viewStateContext)) : [], collapsible: node.hasChildren, collapsed }; @@ -245,9 +262,10 @@ export interface IAsyncDataTreeOptions extends IAsyncData } export interface IAsyncDataTreeViewState { - readonly focus: string[]; - readonly selection: string[]; - readonly expanded: string[]; + readonly focus?: string[]; + readonly selection?: string[]; + readonly expanded?: string[]; + readonly scrollTop?: number; } interface IAsyncDataTreeViewStateContext { @@ -260,7 +278,7 @@ export class AsyncDataTree implements IDisposable private readonly tree: ObjectTree, TFilterData>; private readonly root: IAsyncDataTreeNode; - private readonly renderedNodes = new Map>(); + private readonly nodes = new Map>(); private readonly sorter?: ITreeSorter; private readonly subTreeRefreshPromises = new Map, Promise>(); @@ -280,12 +298,15 @@ export class AsyncDataTree implements IDisposable get onDidChangeSelection(): Event> { return Event.map(this.tree.onDidChangeSelection, asTreeEvent); } get onDidOpen(): Event> { return Event.map(this.tree.onDidOpen, asTreeEvent); } + get onKeyDown(): Event { return this.tree.onKeyDown; } get onMouseClick(): Event> { return Event.map(this.tree.onMouseClick, asTreeMouseEvent); } get onMouseDblClick(): Event> { return Event.map(this.tree.onMouseDblClick, asTreeMouseEvent); } get onContextMenu(): Event> { return Event.map(this.tree.onContextMenu, asTreeContextMenuEvent); } get onDidFocus(): Event { return this.tree.onDidFocus; } get onDidBlur(): Event { return this.tree.onDidBlur; } + get onDidUpdateOptions(): Event { return this.tree.onDidUpdateOptions; } + get filterOnType(): boolean { return this.tree.filterOnType; } get openOnSingleClick(): boolean { return this.tree.openOnSingleClick; } @@ -308,16 +329,11 @@ export class AsyncDataTree implements IDisposable this.tree = new ObjectTree(container, objectTreeDelegate, objectTreeRenderers, objectTreeOptions); - this.root = { + this.root = createAsyncDataTreeNode({ element: undefined!, parent: null, - children: [], - state: AsyncDataTreeNodeState.Uninitialized, - hasChildren: true, - needsRefresh: false, - disposed: false, - slow: false - }; + hasChildren: true + }); if (this.identityProvider) { this.root = { @@ -326,7 +342,7 @@ export class AsyncDataTree implements IDisposable }; } - this.renderedNodes.set(null, this.root); + this.nodes.set(null, this.root); this.tree.onDidChangeCollapseState(this._onDidChangeCollapseState, this, this.disposables); } @@ -365,6 +381,14 @@ export class AsyncDataTree implements IDisposable return this.tree.renderHeight; } + get firstVisibleElement(): T { + return this.tree.firstVisibleElement!.element as T; + } + + get lastVisibleElement(): T { + return this.tree.lastVisibleElement!.element as T; + } + domFocus(): void { this.tree.domFocus(); } @@ -397,6 +421,10 @@ export class AsyncDataTree implements IDisposable this.tree.setFocus(viewStateContext.focus); this.tree.setSelection(viewStateContext.selection); } + + if (viewState && typeof viewState.scrollTop === 'number') { + this.scrollTop = viewState.scrollTop; + } } async updateChildren(element: TInput | T = this.root.element, recursive = true, viewStateContext?: IAsyncDataTreeViewStateContext): Promise { @@ -404,7 +432,7 @@ export class AsyncDataTree implements IDisposable throw new Error('Tree input not set'); } - if (this.root.state === AsyncDataTreeNodeState.Loading) { + if (this.root.loading) { await this.subTreeRefreshPromises.get(this.root)!; await Event.toPromise(this._onDidRender.event); } @@ -412,15 +440,24 @@ export class AsyncDataTree implements IDisposable await this.refreshAndRenderNode(this.getDataNode(element), recursive, ChildrenResolutionReason.Refresh, viewStateContext); } + resort(element: TInput | T = this.root.element, recursive = true): void { + this.tree.resort(this.getDataNode(element), recursive); + } + hasNode(element: TInput | T): boolean { - return element === this.root.element || this.renderedNodes.has(element as T); + return element === this.root.element || this.nodes.has(element as T); } // View - refresh(element: T): void { + rerender(element?: T): void { + if (element === undefined) { + this.tree.rerender(); + return; + } + const node = this.getDataNode(element); - this.tree.refresh(node); + this.tree.rerender(node); } updateWidth(element: T): void { @@ -446,20 +483,20 @@ export class AsyncDataTree implements IDisposable throw new Error('Tree input not set'); } - if (this.root.state === AsyncDataTreeNodeState.Loading) { + if (this.root.loading) { await this.subTreeRefreshPromises.get(this.root)!; await Event.toPromise(this._onDidRender.event); } const node = this.getDataNode(element); - if (node !== this.root && node.state !== AsyncDataTreeNodeState.Loading && !this.tree.isCollapsed(node)) { + if (node !== this.root && !node.loading && !this.tree.isCollapsed(node)) { return false; } const result = this.tree.expand(node === this.root ? null : node, recursive); - if (node.state === AsyncDataTreeNodeState.Loading) { + if (node.loading) { await this.subTreeRefreshPromises.get(node)!; await Event.toPromise(this._onDidRender.event); } @@ -565,16 +602,10 @@ export class AsyncDataTree implements IDisposable return (node && node.element)!; } - // List - - get visibleNodeCount(): number { - return this.tree.visibleNodeCount; - } - // Implementation private getDataNode(element: TInput | T): IAsyncDataTreeNode { - const node: IAsyncDataTreeNode | undefined = this.renderedNodes.get((element === this.root.element ? null : element) as T); + const node: IAsyncDataTreeNode | undefined = this.nodes.get((element === this.root.element ? null : element) as T); if (!node) { throw new Error(`Data tree node not found: ${element}`); @@ -626,39 +657,19 @@ export class AsyncDataTree implements IDisposable } private async doRefreshSubTree(node: IAsyncDataTreeNode, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext): Promise { - node.state = AsyncDataTreeNodeState.Loading; + node.loading = true; try { - await this.doRefreshNode(node, recursive, viewStateContext); + const childrenToRefresh = await this.doRefreshNode(node, recursive, viewStateContext); + node.stale = false; - if (recursive) { - const childrenToRefresh = node.children - .filter(child => { - if (child.needsRefresh) { - child.needsRefresh = false; - return true; - } - - // TODO@joao: is this still needed? - if (child.hasChildren && child.state === AsyncDataTreeNodeState.Loaded) { - return true; - } - - if (!viewStateContext || !child.id) { - return false; - } - - return viewStateContext.viewState.expanded.indexOf(child.id) > -1; - }); - - await Promise.all(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext))); - } + await Promise.all(childrenToRefresh.map(child => this.doRefreshSubTree(child, recursive, viewStateContext))); } finally { - node.state = AsyncDataTreeNodeState.Loaded; + node.loading = false; } } - private async doRefreshNode(node: IAsyncDataTreeNode, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext): Promise { + private async doRefreshNode(node: IAsyncDataTreeNode, recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext): Promise[]> { node.hasChildren = !!this.dataSource.hasChildren(node.element!); let childrenPromise: Promise; @@ -673,21 +684,20 @@ export class AsyncDataTree implements IDisposable this._onDidChangeNodeSlowState.fire(node); }, _ => null); - childrenPromise = always(this.doGetChildren(node), () => slowTimeout.cancel()); + childrenPromise = this.doGetChildren(node) + .finally(() => slowTimeout.cancel()); } try { const children = await childrenPromise; - this.setChildren(node, children, recursive, viewStateContext); + return this.setChildren(node, children, recursive, viewStateContext); } catch (err) { - node.needsRefresh = true; - if (node !== this.root) { this.tree.collapse(node === this.root ? null : node); } if (isPromiseCanceledError(err)) { - return; + return []; } throw err; @@ -715,12 +725,14 @@ export class AsyncDataTree implements IDisposable return children; }); + this.refreshPromises.set(node, result); - return always(result, () => this.refreshPromises.delete(node)); + + return result.finally(() => this.refreshPromises.delete(node)); } private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent, any>): void { - if (!node.collapsed && (node.element.state === AsyncDataTreeNodeState.Uninitialized || node.element.needsRefresh)) { + if (!node.collapsed && node.element.stale) { if (deep) { this.collapse(node.element.element as T); } else { @@ -730,7 +742,12 @@ export class AsyncDataTree implements IDisposable } } - private setChildren(node: IAsyncDataTreeNode, childrenElements: T[], recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext): void { + private setChildren(node: IAsyncDataTreeNode, childrenElements: T[], recursive: boolean, viewStateContext?: IAsyncDataTreeViewStateContext): IAsyncDataTreeNode[] { + // perf: if the node was and still is a leaf, avoid all this hassle + if (node.children.length === 0 && childrenElements.length === 0) { + return []; + } + let nodeChildren: Map> | undefined; if (this.identityProvider) { @@ -741,66 +758,57 @@ export class AsyncDataTree implements IDisposable } } + let childrenToRefresh: IAsyncDataTreeNode[] = []; + const children = childrenElements.map>(element => { if (!this.identityProvider) { - const hasChildren = !!this.dataSource.hasChildren(element); - - return { + return createAsyncDataTreeNode({ element, parent: node, - children: [], - state: AsyncDataTreeNodeState.Uninitialized, - hasChildren, - needsRefresh: false, - disposed: false, - slow: false - }; + hasChildren: !!this.dataSource.hasChildren(element), + }); } const id = this.identityProvider.getId(element).toString(); const asyncDataTreeNode = nodeChildren!.get(id); - if (!asyncDataTreeNode) { - const childAsyncDataTreeNode: IAsyncDataTreeNode = { - element, - parent: node, - children: [], - id, - state: AsyncDataTreeNodeState.Uninitialized, - hasChildren: !!this.dataSource.hasChildren(element), - needsRefresh: false, - disposed: false, - slow: false - }; + if (asyncDataTreeNode) { + asyncDataTreeNode.element = element; + asyncDataTreeNode.stale = asyncDataTreeNode.stale || recursive; + asyncDataTreeNode.hasChildren = !!this.dataSource.hasChildren(element); - if (viewStateContext) { - if (viewStateContext.viewState.focus.indexOf(id) > -1) { - viewStateContext.focus.push(childAsyncDataTreeNode); - } - - if (viewStateContext.viewState.selection.indexOf(id) > -1) { - viewStateContext.selection.push(childAsyncDataTreeNode); - } + if (recursive && !this.tree.isCollapsed(asyncDataTreeNode)) { + childrenToRefresh.push(asyncDataTreeNode); } - return childAsyncDataTreeNode; + return asyncDataTreeNode; } - asyncDataTreeNode.element = element; + const childAsyncDataTreeNode = createAsyncDataTreeNode({ + element, + parent: node, + id, + hasChildren: !!this.dataSource.hasChildren(element) + }); - if (asyncDataTreeNode.state === AsyncDataTreeNodeState.Loaded || asyncDataTreeNode.hasChildren !== !!this.dataSource.hasChildren(asyncDataTreeNode.element)) { - asyncDataTreeNode.needsRefresh = true; + if (viewStateContext && viewStateContext.viewState.focus && viewStateContext.viewState.focus.indexOf(id) > -1) { + viewStateContext.focus.push(childAsyncDataTreeNode); } - return asyncDataTreeNode; + if (viewStateContext && viewStateContext.viewState.selection && viewStateContext.viewState.selection.indexOf(id) > -1) { + viewStateContext.selection.push(childAsyncDataTreeNode); + } + + if (viewStateContext && viewStateContext.viewState.expanded && viewStateContext.viewState.expanded.indexOf(id) > -1) { + childrenToRefresh.push(childAsyncDataTreeNode); + } + + return childAsyncDataTreeNode; }); - // perf: if the node was and still is a leaf, avoid all these expensive no-ops - if (node.children.length === 0 && childrenElements.length === 0) { - return; - } - node.children.splice(0, node.children.length, ...children); + + return childrenToRefresh; } private render(node: IAsyncDataTreeNode, viewStateContext?: IAsyncDataTreeViewStateContext): void { @@ -809,7 +817,7 @@ export class AsyncDataTree implements IDisposable const onDidCreateNode = (treeNode: ITreeNode, TFilterData>) => { if (treeNode.element.element) { insertedElements.add(treeNode.element.element as T); - this.renderedNodes.set(treeNode.element.element as T, treeNode.element); + this.nodes.set(treeNode.element.element as T, treeNode.element); } }; @@ -817,7 +825,7 @@ export class AsyncDataTree implements IDisposable if (treeNode.element.element) { if (!insertedElements.has(treeNode.element.element as T)) { treeNode.element.disposed = true; - this.renderedNodes.delete(treeNode.element.element as T); + this.nodes.delete(treeNode.element.element as T); } } }; @@ -853,7 +861,7 @@ export class AsyncDataTree implements IDisposable queue.push(...node.children); } - return { focus, selection, expanded }; + return { focus, selection, expanded, scrollTop: this.scrollTop }; } dispose(): void { diff --git a/src/vs/base/browser/ui/tree/dataTree.ts b/src/vs/base/browser/ui/tree/dataTree.ts index 2a5f30240c..a7d1208cfd 100644 --- a/src/vs/base/browser/ui/tree/dataTree.ts +++ b/src/vs/base/browser/ui/tree/dataTree.ts @@ -17,7 +17,7 @@ export interface IDataTreeOptions extends IAbstractTreeOp export interface IDataTreeViewState { readonly focus: string[]; readonly selection: string[]; - readonly collapsed: string[]; + readonly expanded: string[]; } export class DataTree extends AbstractTree { @@ -26,6 +26,7 @@ export class DataTree extends AbstractTree | undefined; + private nodesByIdentity = new Map>(); constructor( container: HTMLElement, @@ -61,19 +62,22 @@ export class DataTree extends AbstractTree { const id = this.identityProvider!.getId(element).toString(); + return viewState.expanded.indexOf(id) === -1; + }; + + const onDidCreateNode = (node: ITreeNode) => { + const id = this.identityProvider!.getId(node.element).toString(); if (viewState.focus.indexOf(id) > -1) { - focus.push(element); + focus.push(node.element); } if (viewState.selection.indexOf(id) > -1) { - selection.push(element); + selection.push(node.element); } - - return id in viewState.collapsed; }; - this._refresh(input, isCollapsed); + this._refresh(input, isCollapsed, onDidCreateNode); this.setFocus(focus); this.setSelection(selection); } @@ -83,29 +87,81 @@ export class DataTree extends AbstractTree boolean | undefined) | undefined; + + if (this.identityProvider) { + isCollapsed = element => { + const id = this.identityProvider!.getId(element).toString(); + const node = this.nodesByIdentity.get(id); + + if (!node) { + return undefined; + } + + return node.collapsed; + }; + } + + this._refresh(element, isCollapsed); + } + + resort(element: T | TInput = this.input!, recursive = true): void { + this.model.resort((element === this.input ? null : element) as T, recursive); } // View - refresh(element: T): void { - this.model.refresh(element); + refresh(element?: T): void { + if (element === undefined) { + this.view.rerender(); + return; + } + + this.model.rerender(element); } // Implementation - private _refresh(element: TInput | T, isCollapsed?: (el: T) => boolean): void { - this.model.setChildren((element === this.input ? null : element) as T, this.createIterator(element, isCollapsed)); + private _refresh(element: TInput | T, isCollapsed?: (el: T) => boolean | undefined, onDidCreateNode?: (node: ITreeNode) => void): void { + let onDidDeleteNode: ((node: ITreeNode) => void) | undefined; + + if (this.identityProvider) { + const insertedElements = new Set(); + + const outerOnDidCreateNode = onDidCreateNode; + onDidCreateNode = (node: ITreeNode) => { + const id = this.identityProvider!.getId(node.element).toString(); + + insertedElements.add(id); + this.nodesByIdentity.set(id, node); + + if (outerOnDidCreateNode) { + outerOnDidCreateNode(node); + } + }; + + onDidDeleteNode = (node: ITreeNode) => { + const id = this.identityProvider!.getId(node.element).toString(); + + if (!insertedElements.has(id)) { + this.nodesByIdentity.delete(id); + } + }; + } + + this.model.setChildren((element === this.input ? null : element) as T, this.iterate(element, isCollapsed).elements, onDidCreateNode, onDidDeleteNode); } - private createIterator(element: TInput | T, isCollapsed?: (el: T) => boolean): Iterator> { - const children = Iterator.fromArray(this.dataSource.getChildren(element)); + private iterate(element: TInput | T, isCollapsed?: (el: T) => boolean | undefined): { elements: Iterator>, size: number } { + const children = this.dataSource.getChildren(element); + const elements = Iterator.map>(Iterator.fromArray(children), element => { + const { elements: children, size } = this.iterate(element, isCollapsed); + const collapsed = size === 0 ? undefined : (isCollapsed && isCollapsed(element)); - return Iterator.map>(children, element => ({ - element, - children: this.createIterator(element), - collapsed: isCollapsed && isCollapsed(element) - })); + return { element, children, collapsed }; + }); + + return { elements, size: children.length }; } protected createModel(view: ISpliceable>, options: IDataTreeOptions): ITreeModel { @@ -123,20 +179,20 @@ export class DataTree extends AbstractTree 0) { const node = queue.shift()!; - if (node !== root && node.collapsed) { - collapsed.push(getId(node.element!)); + if (node !== root && node.collapsible && !node.collapsed) { + expanded.push(getId(node.element!)); } queue.push(...node.children); } - return { focus, selection, collapsed }; + return { focus, selection, expanded }; } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/tree/indexTree.ts b/src/vs/base/browser/ui/tree/indexTree.ts index 51b7f36b54..19c60512fc 100644 --- a/src/vs/base/browser/ui/tree/indexTree.ts +++ b/src/vs/base/browser/ui/tree/indexTree.ts @@ -31,11 +31,16 @@ export class IndexTree extends AbstractTree>, options: IIndexTreeOptions): ITreeModel { return new IndexTreeModel(view, this.rootElement, options); } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/tree/indexTreeModel.ts b/src/vs/base/browser/ui/tree/indexTreeModel.ts index ed0c23876e..c6b907087e 100644 --- a/src/vs/base/browser/ui/tree/indexTreeModel.ts +++ b/src/vs/base/browser/ui/tree/indexTreeModel.ts @@ -71,8 +71,6 @@ export class IndexTreeModel, TFilterData = voi this.filter = options.filter; this.autoExpandSingleChildren = typeof options.autoExpandSingleChildren === 'undefined' ? false : options.autoExpandSingleChildren; - // this.onDidChangeCollapseState(node => console.log(node.collapsed, node)); - this.root = { parent: undefined, element: rootElement, @@ -177,7 +175,7 @@ export class IndexTreeModel, TFilterData = voi return result; } - refresh(location: number[]): void { + rerender(location: number[]): void { if (location.length === 0) { throw new Error('Invalid tree location'); } @@ -280,6 +278,21 @@ export class IndexTreeModel, TFilterData = voi return result; } + expandTo(location: number[]): void { + this.eventBufferer.bufferEvents(() => { + let node = this.getTreeNode(location); + + while (node.parent) { + node = node.parent; + location = location.slice(0, location.length - 1); + + if (node.collapsed) { + this._setCollapsed(location, false); + } + } + }); + } + refilter(): void { const previousRenderNodeCount = this.root.renderNodeCount; const toInsert = this.updateNodeAfterFilterChange(this.root); @@ -582,4 +595,4 @@ export class IndexTreeModel, TFilterData = voi return this._getLastElementAncestor(node.children[node.children.length - 1]); } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/tree/objectTree.ts b/src/vs/base/browser/ui/tree/objectTree.ts index fff75d7bcc..0fa4a67f2c 100644 --- a/src/vs/base/browser/ui/tree/objectTree.ts +++ b/src/vs/base/browser/ui/tree/objectTree.ts @@ -36,11 +36,20 @@ export class ObjectTree, TFilterData = void> extends return this.model.setChildren(element, children, onDidCreateNode, onDidDeleteNode); } - refresh(element: T): void { - this.model.refresh(element); + rerender(element?: T): void { + if (element === undefined) { + this.view.rerender(); + return; + } + + this.model.rerender(element); + } + + resort(element: T, recursive = true): void { + this.model.resort(element, recursive); } protected createModel(view: ISpliceable>, options: IObjectTreeOptions): ITreeModel { return new ObjectTreeModel(view, options); } -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/tree/objectTreeModel.ts b/src/vs/base/browser/ui/tree/objectTreeModel.ts index 9029cab3d3..d0c590656c 100644 --- a/src/vs/base/browser/ui/tree/objectTreeModel.ts +++ b/src/vs/base/browser/ui/tree/objectTreeModel.ts @@ -19,7 +19,7 @@ export class ObjectTreeModel, TFilterData extends Non private model: IndexTreeModel; private nodes = new Map>(); - private sorter?: ITreeSorter>; + private sorter?: ITreeSorter<{ element: T; }>; readonly onDidSplice: Event>; readonly onDidChangeCollapseState: Event>; @@ -49,6 +49,15 @@ export class ObjectTreeModel, TFilterData extends Non onDidDeleteNode?: (node: ITreeNode) => void ): Iterator> { const location = this.getElementLocation(element); + return this._setChildren(location, this.preserveCollapseState(children), onDidCreateNode, onDidDeleteNode); + } + + private _setChildren( + location: number[], + children: ISequence> | undefined, + onDidCreateNode?: (node: ITreeNode) => void, + onDidDeleteNode?: (node: ITreeNode) => void + ): Iterator> { const insertedElements = new Set(); const _onDidCreateNode = (node: ITreeNode) => { @@ -73,13 +82,13 @@ export class ObjectTreeModel, TFilterData extends Non return this.model.splice( [...location, 0], Number.MAX_VALUE, - this.preserveCollapseState(children), + children, _onDidCreateNode, _onDidDeleteNode ); } - private preserveCollapseState(elements: ISequence> | undefined): ISequence> { + private preserveCollapseState(elements: ISequence> | undefined): ISequence> { let iterator = elements ? getSequenceIterator(elements) : Iterator.empty>(); if (this.sorter) { @@ -90,11 +99,14 @@ export class ObjectTreeModel, TFilterData extends Non const node = this.nodes.get(treeElement.element); if (!node) { - return treeElement; + return { + ...treeElement, + children: this.preserveCollapseState(treeElement.children) + }; } const collapsible = typeof treeElement.collapsible === 'boolean' ? treeElement.collapsible : node.collapsible; - const collapsed = typeof treeElement.collapsed !== 'undefined' ? treeElement.collapsed : (collapsible && node.collapsed); + const collapsed = typeof treeElement.collapsed !== 'undefined' ? treeElement.collapsed : node.collapsed; return { ...treeElement, @@ -105,9 +117,35 @@ export class ObjectTreeModel, TFilterData extends Non }); } - refresh(element: T): void { + rerender(element: T): void { const location = this.getElementLocation(element); - this.model.refresh(location); + this.model.rerender(location); + } + + resort(element: T | null = null, recursive = true): void { + if (!this.sorter) { + return; + } + + const location = this.getElementLocation(element); + const node = this.model.getNode(location); + + this._setChildren(location, this.resortChildren(node, recursive)); + } + + private resortChildren(node: ITreeNode, recursive: boolean, first = true): ISequence> { + let childrenNodes = Iterator.fromArray(node.children as ITreeNode[]); + + if (recursive || first) { + childrenNodes = Iterator.fromArray(Iterator.collect(childrenNodes).sort(this.sorter!.compare.bind(this.sorter))); + } + + return Iterator.map, ITreeElement>(childrenNodes, node => ({ + element: node.element as T, + collapsible: node.collapsible, + collapsed: node.collapsed, + children: this.resortChildren(node, recursive, false) + })); } getParentElement(ref: T | null = null): T | null { @@ -150,6 +188,11 @@ export class ObjectTreeModel, TFilterData extends Non return this.model.setCollapsed(location, collapsed, recursive); } + expandTo(element: T): void { + const location = this.getElementLocation(element); + this.model.expandTo(location); + } + refilter(): void { this.model.refilter(); } diff --git a/src/vs/base/browser/ui/tree/tree.ts b/src/vs/base/browser/ui/tree/tree.ts index 11d2a03509..1987721713 100644 --- a/src/vs/base/browser/ui/tree/tree.ts +++ b/src/vs/base/browser/ui/tree/tree.ts @@ -122,6 +122,7 @@ export interface ITreeModel { isCollapsible(location: TRef): boolean; isCollapsed(location: TRef): boolean; setCollapsed(location: TRef, collapsed?: boolean, recursive?: boolean): boolean; + expandTo(location: TRef): void; refilter(): void; } @@ -144,7 +145,7 @@ export interface ITreeMouseEvent { export interface ITreeContextMenuEvent { browserEvent: UIEvent; element: T | null; - anchor: HTMLElement | { x: number; y: number; } | undefined; + anchor: HTMLElement | { x: number; y: number; }; } export interface ITreeNavigator { diff --git a/src/vs/base/common/arrays.ts b/src/vs/base/common/arrays.ts index 50c4e4671d..455060a851 100644 --- a/src/vs/base/common/arrays.ts +++ b/src/vs/base/common/arrays.ts @@ -51,8 +51,8 @@ export function binarySearch(array: ReadonlyArray, key: T, comparator: (op high = array.length - 1; while (low <= high) { - let mid = ((low + high) / 2) | 0; - let comp = comparator(array[mid], key); + const mid = ((low + high) / 2) | 0; + const comp = comparator(array[mid], key); if (comp < 0) { low = mid + 1; } else if (comp > 0) { @@ -75,7 +75,7 @@ export function findFirstInSorted(array: ReadonlyArray, p: (x: T) => boole return 0; // no children } while (low < high) { - let mid = Math.floor((low + high) / 2); + const mid = Math.floor((low + high) / 2); if (p(array[mid])) { high = mid; } else { @@ -122,7 +122,7 @@ function _sort(a: T[], compare: Compare, lo: number, hi: number, aux: T[]) if (hi <= lo) { return; } - let mid = lo + ((hi - lo) / 2) | 0; + const mid = lo + ((hi - lo) / 2) | 0; _sort(a, compare, lo, mid, aux); _sort(a, compare, mid + 1, hi, aux); if (compare(a[mid], a[mid + 1]) <= 0) { @@ -393,6 +393,7 @@ export function firstIndex(array: ReadonlyArray, fn: (item: T) => boolean) export function first(array: ReadonlyArray, fn: (item: T) => boolean, notFoundValue: T): T; export function first(array: ReadonlyArray, fn: (item: T) => boolean): T | null; +export function first(array: ReadonlyArray, fn: (item: T) => boolean, notFoundValue: T | null): T | null; export function first(array: ReadonlyArray, fn: (item: T) => boolean, notFoundValue: T | null = null): T | null { const index = firstIndex(array, fn); return index < 0 ? notFoundValue : array[index]; @@ -501,8 +502,8 @@ export function shuffle(array: T[], _seed?: number): void { } for (let i = array.length - 1; i > 0; i -= 1) { - let j = Math.floor(rand() * (i + 1)); - let temp = array[i]; + const j = Math.floor(rand() * (i + 1)); + const temp = array[i]; array[i] = array[j]; array[j] = temp; } diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 5d3678d0bd..dec35a7ffb 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -45,14 +45,14 @@ export function createCancelablePromise(callback: (token: CancellationToken) return this.then(undefined, reject); } finally(onfinally?: (() => void) | undefined | null): Promise { - return always(promise, onfinally || (() => { })); + return promise.finally(onfinally); } }; } export function asPromise(callback: () => T | Thenable): Promise { return new Promise((resolve, reject) => { - let item = callback(); + const item = callback(); if (isThenable(item)) { item.then(resolve, reject); } else { @@ -326,25 +326,6 @@ export function disposableTimeout(handler: () => void, timeout = 0): IDisposable return toDisposable(() => clearTimeout(timer)); } -/** - * Returns a new promise that joins the provided promise. Upon completion of - * the provided promise the provided function will always be called. This - * method is comparable to a try-finally code block. - * @param promise a promise - * @param callback a function that will be call in the success and error case. - */ -export function always(promise: Promise, callback: () => void): Promise { - function safeCallback() { - try { - callback(); - } catch (err) { - errors.onUnexpectedError(err); - } - } - promise.then(_ => safeCallback(), _ => safeCallback()); - return Promise.resolve(promise); -} - export function ignoreErrors(promise: Promise): Promise { return promise.then(undefined, _ => undefined); } @@ -707,12 +688,12 @@ declare function cancelIdleCallback(handle: number): void; (function () { if (typeof requestIdleCallback !== 'function' || typeof cancelIdleCallback !== 'function') { - let dummyIdle: IdleDeadline = Object.freeze({ + const dummyIdle: IdleDeadline = Object.freeze({ didTimeout: true, timeRemaining() { return 15; } }); runWhenIdle = (runner) => { - let handle = setTimeout(() => runner(dummyIdle)); + const handle = setTimeout(() => runner(dummyIdle)); let disposed = false; return { dispose() { @@ -726,7 +707,7 @@ declare function cancelIdleCallback(handle: number): void; }; } else { runWhenIdle = (runner, timeout?) => { - let handle: number = requestIdleCallback(runner, typeof timeout === 'number' ? { timeout } : undefined); + const handle: number = requestIdleCallback(runner, typeof timeout === 'number' ? { timeout } : undefined); let disposed = false; return { dispose() { diff --git a/src/vs/base/common/cache.ts b/src/vs/base/common/cache.ts index 926a9c8d71..74ef0199ff 100644 --- a/src/vs/base/common/cache.ts +++ b/src/vs/base/common/cache.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { always } from 'vs/base/common/async'; export interface CacheResult { promise: Promise; @@ -23,7 +22,7 @@ export class Cache { const cts = new CancellationTokenSource(); const promise = this.task(cts.token); - always(promise, () => cts.dispose()); + promise.finally(() => cts.dispose()); this.result = { promise, diff --git a/src/vs/base/common/cancellation.ts b/src/vs/base/common/cancellation.ts index 70796b90a2..f160d44357 100644 --- a/src/vs/base/common/cancellation.ts +++ b/src/vs/base/common/cancellation.ts @@ -16,7 +16,7 @@ export interface CancellationToken { } const shortcutEvent = Object.freeze(function (callback, context?): IDisposable { - let handle = setTimeout(callback.bind(context), 0); + const handle = setTimeout(callback.bind(context), 0); return { dispose() { clearTimeout(handle); } }; } as Event); diff --git a/src/vs/base/common/color.ts b/src/vs/base/common/color.ts index fa4792df10..6b50c2665e 100644 --- a/src/vs/base/common/color.ts +++ b/src/vs/base/common/color.ts @@ -387,7 +387,7 @@ export class Color { const thisA = this.rgba.a; const colorA = rgba.a; - let a = thisA + colorA * (1 - thisA); + const a = thisA + colorA * (1 - thisA); if (a < 1e-6) { return Color.transparent; } diff --git a/src/vs/base/common/comparers.ts b/src/vs/base/common/comparers.ts index 3d3f3fd7f0..898369543e 100644 --- a/src/vs/base/common/comparers.ts +++ b/src/vs/base/common/comparers.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as strings from 'vs/base/common/strings'; -import * as paths from 'vs/base/common/paths'; +import { sep } from 'vs/base/common/path'; import { IdleValue } from 'vs/base/common/async'; let intlFileNameCollator: IdleValue<{ collator: Intl.Collator, collatorIsNumeric: boolean }>; @@ -116,8 +116,8 @@ function comparePathComponents(one: string, other: string, caseSensitive = false } export function comparePaths(one: string, other: string, caseSensitive = false): number { - const oneParts = one.split(paths.nativeSep); - const otherParts = other.split(paths.nativeSep); + const oneParts = one.split(sep); + const otherParts = other.split(sep); const lastOne = oneParts.length - 1; const lastOther = otherParts.length - 1; @@ -144,8 +144,8 @@ export function comparePaths(one: string, other: string, caseSensitive = false): } export function compareAnything(one: string, other: string, lookFor: string): number { - let elementAName = one.toLowerCase(); - let elementBName = other.toLowerCase(); + const elementAName = one.toLowerCase(); + const elementBName = other.toLowerCase(); // Sort prefix matches over non prefix matches const prefixCompare = compareByPrefix(one, other, lookFor); @@ -154,14 +154,14 @@ export function compareAnything(one: string, other: string, lookFor: string): nu } // Sort suffix matches over non suffix matches - let elementASuffixMatch = strings.endsWith(elementAName, lookFor); - let elementBSuffixMatch = strings.endsWith(elementBName, lookFor); + const elementASuffixMatch = strings.endsWith(elementAName, lookFor); + const elementBSuffixMatch = strings.endsWith(elementBName, lookFor); if (elementASuffixMatch !== elementBSuffixMatch) { return elementASuffixMatch ? -1 : 1; } // Understand file names - let r = compareFileNames(elementAName, elementBName); + const r = compareFileNames(elementAName, elementBName); if (r !== 0) { return r; } @@ -171,12 +171,12 @@ export function compareAnything(one: string, other: string, lookFor: string): nu } export function compareByPrefix(one: string, other: string, lookFor: string): number { - let elementAName = one.toLowerCase(); - let elementBName = other.toLowerCase(); + const elementAName = one.toLowerCase(); + const elementBName = other.toLowerCase(); // Sort prefix matches over non prefix matches - let elementAPrefixMatch = strings.startsWith(elementAName, lookFor); - let elementBPrefixMatch = strings.startsWith(elementBName, lookFor); + const elementAPrefixMatch = strings.startsWith(elementAName, lookFor); + const elementBPrefixMatch = strings.startsWith(elementBName, lookFor); if (elementAPrefixMatch !== elementBPrefixMatch) { return elementAPrefixMatch ? -1 : 1; } diff --git a/src/vs/base/common/errors.ts b/src/vs/base/common/errors.ts index 342a8803a2..99dfffcec4 100644 --- a/src/vs/base/common/errors.ts +++ b/src/vs/base/common/errors.ts @@ -102,7 +102,7 @@ export function transformErrorForSerialization(error: any): any; export function transformErrorForSerialization(error: any): any { if (error instanceof Error) { let { name, message } = error; - let stack: string = (error).stacktrace || (error).stack; + const stack: string = (error).stacktrace || (error).stack; return { $isError: true, name, @@ -146,7 +146,7 @@ export function isPromiseCanceledError(error: any): boolean { * Returns an error that signals cancellation. */ export function canceled(): Error { - let error = new Error(canceledName); + const error = new Error(canceledName); error.name = error.message; return error; } diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 272dd2e659..136a7697d0 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -27,8 +27,8 @@ export namespace Event { return (listener, thisArgs = null, disposables?) => { // we need this, in case the event fires during the listener call let didFire = false; - - const result = event(e => { + let result: IDisposable; + result = event(e => { if (didFire) { return; } else if (result) { @@ -53,7 +53,7 @@ export namespace Event { * throught the mapping function. */ export function map(event: Event, map: (i: I) => O): Event { - return (listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables); + return snapshot((listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables)); } /** @@ -61,7 +61,7 @@ export namespace Event { * the `each` function per each element. */ export function forEach(event: Event, each: (i: I) => void): Event { - return (listener, thisArgs = null, disposables?) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables); + return snapshot((listener, thisArgs = null, disposables?) => event(i => { each(i); listener.call(thisArgs, i); }, null, disposables)); } /** @@ -71,7 +71,7 @@ export namespace Event { export function filter(event: Event, filter: (e: T) => boolean): Event; export function filter(event: Event, filter: (e: T | R) => e is R): Event; export function filter(event: Event, filter: (e: T) => boolean): Event { - return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables); + return snapshot((listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables)); } /** @@ -102,6 +102,25 @@ export namespace Event { }); } + /** + * Given a chain of event processing functions (filter, map, etc), each + * function will be invoked per event & per listener. Snapshotting an event + * chain allows each function to be invoked just once per event. + */ + export function snapshot(event: Event): Event { + let listener: IDisposable; + const emitter = new Emitter({ + onFirstListenerAdd() { + listener = event(emitter.fire, emitter); + }, + onLastListenerRemove() { + listener.dispose(); + } + }); + + return emitter.event; + } + /** * Debounces the provided event, given a `merge` function. * @@ -133,7 +152,7 @@ export namespace Event { clearTimeout(handle); handle = setTimeout(() => { - let _output = output; + const _output = output; output = undefined; handle = undefined; if (!leading || numDebouncedCalls > 1) { @@ -171,7 +190,7 @@ export namespace Event { let cache: T; return filter(event, value => { - let shouldEmit = firstCall || value !== cache; + const shouldEmit = firstCall || value !== cache; firstCall = false; cache = value; return shouldEmit; @@ -261,7 +280,7 @@ export namespace Event { const flush = (listener: (e: T) => any, thisArgs?: any) => buffer.forEach(e => listener.call(thisArgs, e)); const emitter = new Emitter({ - onListenerDidAdd(emitter, listener: (e: T) => any, thisArgs?: any) { + onListenerDidAdd(emitter: Emitter, listener: (e: T) => any, thisArgs?: any) { if (nextTick) { setTimeout(() => flush(listener, thisArgs)); } else { @@ -286,36 +305,34 @@ export namespace Event { class ChainableEvent implements IChainableEvent { - get event(): Event { return this._event; } - - constructor(private _event: Event) { } + constructor(readonly event: Event) { } map(fn: (i: T) => O): IChainableEvent { - return new ChainableEvent(map(this._event, fn)); + return new ChainableEvent(map(this.event, fn)); } forEach(fn: (i: T) => void): IChainableEvent { - return new ChainableEvent(forEach(this._event, fn)); + return new ChainableEvent(forEach(this.event, fn)); } filter(fn: (e: T) => boolean): IChainableEvent { - return new ChainableEvent(filter(this._event, fn)); + return new ChainableEvent(filter(this.event, fn)); } reduce(merge: (last: R | undefined, event: T) => R, initial?: R): IChainableEvent { - return new ChainableEvent(reduce(this._event, merge, initial)); + return new ChainableEvent(reduce(this.event, merge, initial)); } latch(): IChainableEvent { - return new ChainableEvent(latch(this._event)); + return new ChainableEvent(latch(this.event)); } on(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[]) { - return this._event(listener, thisArgs, disposables); + return this.event(listener, thisArgs, disposables); } once(listener: (e: T) => any, thisArgs: any, disposables: IDisposable[]) { - return once(this._event)(listener, thisArgs, disposables); + return once(this.event)(listener, thisArgs, disposables); } } @@ -372,7 +389,7 @@ export interface EmitterOptions { let _globalLeakWarningThreshold = -1; export function setGlobalLeakWarningThreshold(n: number): IDisposable { - let oldValue = _globalLeakWarningThreshold; + const oldValue = _globalLeakWarningThreshold; _globalLeakWarningThreshold = n; return { dispose() { @@ -411,8 +428,8 @@ class LeakageMonitor { if (!this._stacks) { this._stacks = new Map(); } - let stack = new Error().stack!.split('\n').slice(3).join('\n'); - let count = (this._stacks.get(stack) || 0); + const stack = new Error().stack!.split('\n').slice(3).join('\n'); + const count = (this._stacks.get(stack) || 0); this._stacks.set(stack, count + 1); this._warnCountdown -= 1; @@ -436,7 +453,7 @@ class LeakageMonitor { } return () => { - let count = (this._stacks!.get(stack) || 0); + const count = (this._stacks!.get(stack) || 0); this._stacks!.set(stack, count - 1); }; } @@ -610,7 +627,7 @@ export class AsyncEmitter extends Emitter { } for (let iter = this._listeners.iterator(), e = iter.next(); !e.done; e = iter.next()) { - let thenables: Promise[] = []; + const thenables: Promise[] = []; this._asyncDeliveryQueue.push([e.value, eventFn(thenables, typeof e.value === 'function' ? e.value : e.value[0]), thenables]); } diff --git a/src/vs/base/common/paths.ts b/src/vs/base/common/extpath.ts similarity index 50% rename from src/vs/base/common/paths.ts rename to src/vs/base/common/extpath.ts index e559574d80..8bdc589e6f 100644 --- a/src/vs/base/common/paths.ts +++ b/src/vs/base/common/extpath.ts @@ -6,152 +6,19 @@ import { isWindows } from 'vs/base/common/platform'; import { startsWithIgnoreCase, equalsIgnoreCase } from 'vs/base/common/strings'; import { CharCode } from 'vs/base/common/charCode'; - -/** - * The forward slash path separator. - */ -export const sep = '/'; - -/** - * The native path separator depending on the OS. - */ -export const nativeSep = isWindows ? '\\' : '/'; - +import { sep, posix } from 'vs/base/common/path'; function isPathSeparator(code: number) { return code === CharCode.Slash || code === CharCode.Backslash; } /** - * @param path the path to get the dirname from - * @param separator the separator to use - * @returns the directory name of a path. - * '.' is returned for empty paths or single segment relative paths (as done by NodeJS) - * For paths consisting only of a root, the input path is returned + * Takes a Windows OS path and changes backward slashes to forward slashes. + * This should only be done for OS paths from Windows (or user provided paths potentially from Windows). + * Using it on a Linux or MaxOS path might change it. */ -export function dirname(path: string, separator = nativeSep): string { - const len = path.length; - if (len === 0) { - return '.'; - } else if (len === 1) { - return isPathSeparator(path.charCodeAt(0)) ? path : '.'; - } - const root = getRoot(path, separator); - let rootLength = root.length; - if (rootLength >= len) { - return path; // matched the root - } - if (rootLength === 0 && isPathSeparator(path.charCodeAt(0))) { - rootLength = 1; // absolute paths stay absolute paths. - } - - let i = len - 1; - if (i > rootLength) { - i--; // no need to look at the last character. If it's a trailing slash, we ignore it. - while (i > rootLength && !isPathSeparator(path.charCodeAt(i))) { - i--; - } - } - if (i === 0) { - return '.'; // it was a relative path with a single segment, no root. Nodejs returns '.' here. - } - return path.substr(0, i); -} - -/** - * @returns the base name of a path. - */ -export function basename(path: string): string { - const idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\'); - if (idx === 0) { - return path; - } else if (~idx === path.length - 1) { - return basename(path.substring(0, path.length - 1)); - } else { - return path.substr(~idx + 1); - } -} - -/** - * @returns `.far` from `boo.far` or the empty string. - */ -export function extname(path: string): string { - path = basename(path); - const idx = ~path.lastIndexOf('.'); - return idx ? path.substring(~idx) : ''; -} - -const _posixBadPath = /(\/\.\.?\/)|(\/\.\.?)$|^(\.\.?\/)|(\/\/+)|(\\)/; -const _winBadPath = /(\\\.\.?\\)|(\\\.\.?)$|^(\.\.?\\)|(\\\\+)|(\/)/; - -function _isNormal(path: string, win: boolean): boolean { - return win - ? !_winBadPath.test(path) - : !_posixBadPath.test(path); -} - -export function normalize(path: undefined, toOSPath?: boolean): undefined; -export function normalize(path: null, toOSPath?: boolean): null; -export function normalize(path: string, toOSPath?: boolean): string; -export function normalize(path: string | null | undefined, toOSPath?: boolean): string | null | undefined { - - if (path === null || path === undefined) { - return path; - } - - const len = path.length; - if (len === 0) { - return '.'; - } - - const wantsBackslash = !!(isWindows && toOSPath); - if (_isNormal(path, wantsBackslash)) { - return path; - } - - const sep = wantsBackslash ? '\\' : '/'; - const root = getRoot(path, sep); - - // skip the root-portion of the path - let start = root.length; - let skip = false; - let res = ''; - - for (let end = root.length; end <= len; end++) { - - // either at the end or at a path-separator character - if (end === len || isPathSeparator(path.charCodeAt(end))) { - - if (streql(path, start, end, '..')) { - // skip current and remove parent (if there is already something) - let prev_start = res.lastIndexOf(sep); - let prev_part = res.slice(prev_start + 1); - if ((root || prev_part.length > 0) && prev_part !== '..') { - res = prev_start === -1 ? '' : res.slice(0, prev_start); - skip = true; - } - } else if (streql(path, start, end, '.') && (root || res || end < len - 1)) { - // skip current (if there is already something or if there is more to come) - skip = true; - } - - if (!skip) { - let part = path.slice(start, end); - if (res !== '' && res[res.length - 1] !== sep) { - res += sep; - } - res += part; - } - start = end + 1; - skip = false; - } - } - - return root + res; -} - -function streql(value: string, start: number, end: number, other: string): boolean { - return start + other.length === end && value.indexOf(other, start) === start; +export function toSlashes(osPath: string) { + return osPath.replace(/[\\/]/g, posix.sep); } /** @@ -159,13 +26,13 @@ function streql(value: string, start: number, end: number, other: string): boole * `getRoot('files:///files/path') === files:///`, * or `getRoot('\\server\shares\path') === \\server\shares\` */ -export function getRoot(path: string, sep: string = '/'): string { +export function getRoot(path: string, sep: string = posix.sep): string { if (!path) { return ''; } - let len = path.length; + const len = path.length; const firstLetter = path.charCodeAt(0); if (isPathSeparator(firstLetter)) { if (isPathSeparator(path.charCodeAt(1))) { @@ -173,7 +40,7 @@ export function getRoot(path: string, sep: string = '/'): string { // ^^^^^^^^^^^^^^^^^^^ if (!isPathSeparator(path.charCodeAt(2))) { let pos = 3; - let start = pos; + const start = pos; for (; pos < len; pos++) { if (isPathSeparator(path.charCodeAt(pos))) { break; @@ -227,33 +94,6 @@ export function getRoot(path: string, sep: string = '/'): string { return ''; } -export const join: (...parts: string[]) => string = function () { - // Not using a function with var-args because of how TS compiles - // them to JS - it would result in 2*n runtime cost instead - // of 1*n, where n is parts.length. - - let value = ''; - for (let i = 0; i < arguments.length; i++) { - let part = arguments[i]; - if (i > 0) { - // add the separater between two parts unless - // there already is one - let last = value.charCodeAt(value.length - 1); - if (!isPathSeparator(last)) { - let next = part.charCodeAt(0); - if (!isPathSeparator(next)) { - - value += sep; - } - } - } - value += part; - } - - return normalize(value); -}; - - /** * Check if the path follows this pattern: `\\hostname\sharename`. * @@ -281,7 +121,7 @@ export function isUNC(path: string): boolean { return false; } let pos = 2; - let start = pos; + const start = pos; for (; pos < path.length; pos++) { code = path.charCodeAt(pos); if (code === CharCode.Backslash) { @@ -327,6 +167,10 @@ export function isValidBasename(name: string | null | undefined): boolean { return false; // Windows: file cannot end with a whitespace } + if (name.length > 255) { + return false; // most file systems do not allow files > 255 lenth + } + return true; } @@ -343,7 +187,7 @@ export function isEqual(pathA: string, pathB: string, ignoreCase?: boolean): boo return equalsIgnoreCase(pathA, pathB); } -export function isEqualOrParent(path: string, candidate: string, ignoreCase?: boolean, separator = nativeSep): boolean { +export function isEqualOrParent(path: string, candidate: string, ignoreCase?: boolean, separator = sep): boolean { if (path === candidate) { return true; } @@ -381,39 +225,6 @@ export function isEqualOrParent(path: string, candidate: string, ignoreCase?: bo return path.indexOf(candidate) === 0; } -/** - * Adapted from Node's path.isAbsolute functions - */ -export function isAbsolute(path: string): boolean { - return isWindows ? - isAbsolute_win32(path) : - isAbsolute_posix(path); -} - -export function isAbsolute_win32(path: string): boolean { - if (!path) { - return false; - } - - const char0 = path.charCodeAt(0); - if (isPathSeparator(char0)) { - return true; - } else if (isWindowsDriveLetter(char0)) { - if (path.length > 2 && path.charCodeAt(1) === CharCode.Colon) { - const char2 = path.charCodeAt(2); - if (isPathSeparator(char2)) { - return true; - } - } - } - - return false; -} - -export function isAbsolute_posix(path: string): boolean { - return !!(path && path.charCodeAt(0) === CharCode.Slash); -} - export function isWindowsDriveLetter(char0: number): boolean { return char0 >= CharCode.A && char0 <= CharCode.Z || char0 >= CharCode.a && char0 <= CharCode.z; -} +} \ No newline at end of file diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index 42e34577d4..8c74239f88 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -28,7 +28,7 @@ export interface IMatch { export function or(...filter: IFilter[]): IFilter { return function (word: string, wordToMatchAgainst: string): IMatch[] | null { for (let i = 0, len = filter.length; i < len; i++) { - let match = filter[i](word, wordToMatchAgainst); + const match = filter[i](word, wordToMatchAgainst); if (match) { return match; } @@ -64,7 +64,7 @@ function _matchesPrefix(ignoreCase: boolean, word: string, wordToMatchAgainst: s // Contiguous Substring export function matchesContiguousSubString(word: string, wordToMatchAgainst: string): IMatch[] | null { - let index = wordToMatchAgainst.toLowerCase().indexOf(word.toLowerCase()); + const index = wordToMatchAgainst.toLowerCase().indexOf(word.toLowerCase()); if (index === -1) { return null; } @@ -136,7 +136,7 @@ function join(head: IMatch, tail: IMatch[]): IMatch[] { function nextAnchor(camelCaseWord: string, start: number): number { for (let i = start; i < camelCaseWord.length; i++) { - let c = camelCaseWord.charCodeAt(i); + const c = camelCaseWord.charCodeAt(i); if (isUpper(c) || isNumber(c) || (i > 0 && !isAlphanumeric(camelCaseWord.charCodeAt(i - 1)))) { return i; } @@ -184,10 +184,10 @@ function analyzeCamelCaseWord(word: string): ICamelCaseAnalysis { if (isNumber(code)) { numeric++; } } - let upperPercent = upper / word.length; - let lowerPercent = lower / word.length; - let alphaPercent = alpha / word.length; - let numericPercent = numeric / word.length; + const upperPercent = upper / word.length; + const lowerPercent = lower / word.length; + const alphaPercent = alpha / word.length; + const numericPercent = numeric / word.length; return { upperPercent, lowerPercent, alphaPercent, numericPercent }; } @@ -307,7 +307,7 @@ function _matchesWords(word: string, target: string, i: number, j: number, conti function nextWord(word: string, start: number): number { for (let i = start; i < word.length; i++) { - let c = word.charCodeAt(i); + const c = word.charCodeAt(i); if (isWhitespace(c) || (i > 0 && isWhitespace(word.charCodeAt(i - 1)))) { return i; } @@ -334,7 +334,7 @@ export function matchesFuzzy(word: string, wordToMatchAgainst: string, enableSep } // RegExp Filter - let match = regexp.exec(wordToMatchAgainst); + const match = regexp.exec(wordToMatchAgainst); if (match) { return [{ start: match.index, end: match.index + match[0].length }]; } @@ -348,7 +348,7 @@ export function matchesFuzzy(word: string, wordToMatchAgainst: string, enableSep * powerfull than `matchesFuzzy` */ export function matchesFuzzy2(pattern: string, word: string): IMatch[] | null { - let score = fuzzyScore(pattern, pattern.toLowerCase(), 0, word, word.toLowerCase(), 0, true); + const score = fuzzyScore(pattern, pattern.toLowerCase(), 0, word, word.toLowerCase(), 0, true); return score ? createMatches(score) : null; } @@ -404,7 +404,7 @@ function initTable() { row.push(-i); } for (let i = 0; i <= _maxLen; i++) { - let thisRow = row.slice(0); + const thisRow = row.slice(0); thisRow[0] = -i; table.push(thisRow); } @@ -566,9 +566,9 @@ export function fuzzyScore(pattern: string, patternLow: string, patternPos: numb _scores[patternPos][wordPos] = score; - let diag = _table[patternPos - 1][wordPos - 1] + (score > 1 ? 1 : score); - let top = _table[patternPos - 1][wordPos] + -1; - let left = _table[patternPos][wordPos - 1] + -1; + const diag = _table[patternPos - 1][wordPos - 1] + (score > 1 ? 1 : score); + const top = _table[patternPos - 1][wordPos] + -1; + const left = _table[patternPos][wordPos - 1] + -1; if (left >= top) { // left or diag @@ -635,8 +635,8 @@ function _findAllMatches2(patternPos: number, wordPos: number, total: number, ma while (patternPos > _patternStartPos && wordPos > 0) { - let score = _scores[patternPos][wordPos]; - let arrow = _arrows[patternPos][wordPos]; + const score = _scores[patternPos][wordPos]; + const arrow = _arrows[patternPos][wordPos]; if (arrow === Arrow.Left) { // left -> no match, skip a word character @@ -733,11 +733,11 @@ function fuzzyScoreWithPermutations(pattern: string, lowPattern: string, pattern // permutations of the pattern to find a better match. The // permutations only swap neighbouring characters, e.g // `cnoso` becomes `conso`, `cnsoo`, `cnoos`. - let tries = Math.min(7, pattern.length - 1); + const tries = Math.min(7, pattern.length - 1); for (let movingPatternPos = patternPos + 1; movingPatternPos < tries; movingPatternPos++) { - let newPattern = nextTypoPermutation(pattern, movingPatternPos); + const newPattern = nextTypoPermutation(pattern, movingPatternPos); if (newPattern) { - let candidate = fuzzyScore(newPattern, newPattern.toLowerCase(), patternPos, word, lowWord, wordPos, firstMatchCanBeWeak); + const candidate = fuzzyScore(newPattern, newPattern.toLowerCase(), patternPos, word, lowWord, wordPos, firstMatchCanBeWeak); if (candidate) { candidate[0] -= 3; // permutation penalty if (!top || candidate[0] > top[0]) { @@ -757,8 +757,8 @@ function nextTypoPermutation(pattern: string, patternPos: number): string | unde return undefined; } - let swap1 = pattern[patternPos]; - let swap2 = pattern[patternPos + 1]; + const swap1 = pattern[patternPos]; + const swap2 = pattern[patternPos + 1]; if (swap1 === swap2) { return undefined; diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts index 948574287a..ef8fed56f7 100644 --- a/src/vs/base/common/glob.ts +++ b/src/vs/base/common/glob.ts @@ -5,7 +5,8 @@ import * as arrays from 'vs/base/common/arrays'; import * as strings from 'vs/base/common/strings'; -import * as paths from 'vs/base/common/paths'; +import * as extpath from 'vs/base/common/extpath'; +import * as paths from 'vs/base/common/path'; import { LRUCache } from 'vs/base/common/map'; import { CharCode } from 'vs/base/common/charCode'; import { isThenable } from 'vs/base/common/async'; @@ -17,7 +18,6 @@ export interface IExpression { export interface IRelativePattern { base: string; pattern: string; - pathToRelative(from: string, to: string): string; } export function getEmptyExpression(): IExpression { @@ -53,7 +53,7 @@ export function splitGlobAware(pattern: string, splitChar: string): string[] { return []; } - let segments: string[] = []; + const segments: string[] = []; let inBraces = false; let inBrackets = false; @@ -102,7 +102,7 @@ function parseRegExp(pattern: string): string { let regEx = ''; // Split up into segments for each slash found - let segments = splitGlobAware(pattern, GLOB_SPLIT); + const segments = splitGlobAware(pattern, GLOB_SPLIT); // Special case where we only have globstars if (segments.every(s => s === GLOBSTAR)) { @@ -179,10 +179,10 @@ function parseRegExp(pattern: string): string { continue; case '}': - let choices = splitGlobAware(braceVal, ','); + const choices = splitGlobAware(braceVal, ','); // Converts {foo,bar} => [foo|bar] - let braceRegExp = `(?:${choices.map(c => parseRegExp(c)).join('|')})`; + const braceRegExp = `(?:${choices.map(c => parseRegExp(c)).join('|')})`; regEx += braceRegExp; @@ -302,7 +302,7 @@ function parsePattern(arg1: string | IRelativePattern, options: IGlobOptions): P if (T1.test(pattern)) { // common pattern: **/*.txt just need endsWith check const base = pattern.substr(4); // '**/*'.length === 4 parsedPattern = function (path, basename) { - return path && strings.endsWith(path, base) ? pattern : null; + return typeof path === 'string' && strings.endsWith(path, base) ? pattern : null; }; } else if (match = T2.exec(trimForExclusions(pattern, options))) { // common pattern: **/some.txt just need basename check parsedPattern = trivia2(match[1], pattern); @@ -331,11 +331,11 @@ function wrapRelativePattern(parsedPattern: ParsedStringPattern, arg2: string | } return function (path, basename) { - if (!paths.isEqualOrParent(path, arg2.base)) { + if (!extpath.isEqualOrParent(path, arg2.base)) { return null; } - return parsedPattern(arg2.pathToRelative(arg2.base, path), basename); + return parsedPattern(paths.relative(arg2.base, path), basename); }; } @@ -348,7 +348,7 @@ function trivia2(base: string, originalPattern: string): ParsedStringPattern { const slashBase = `/${base}`; const backslashBase = `\\${base}`; const parsedPattern: ParsedStringPattern = function (path, basename) { - if (!path) { + if (typeof path !== 'string') { return null; } if (basename) { @@ -396,12 +396,12 @@ function trivia3(pattern: string, options: IGlobOptions): ParsedStringPattern { // common patterns: **/something/else just need endsWith check, something/else just needs and equals check function trivia4and5(path: string, pattern: string, matchPathEnds: boolean): ParsedStringPattern { - const nativePath = paths.nativeSep !== paths.sep ? path.replace(ALL_FORWARD_SLASHES, paths.nativeSep) : path; - const nativePathEnd = paths.nativeSep + nativePath; + const nativePath = paths.sep !== paths.posix.sep ? path.replace(ALL_FORWARD_SLASHES, paths.sep) : path; + const nativePathEnd = paths.sep + nativePath; const parsedPattern: ParsedStringPattern = matchPathEnds ? function (path, basename) { - return path && (path === nativePath || strings.endsWith(path, nativePathEnd)) ? pattern : null; + return typeof path === 'string' && (path === nativePath || strings.endsWith(path, nativePathEnd)) ? pattern : null; } : function (path, basename) { - return path && path === nativePath ? pattern : null; + return typeof path === 'string' && path === nativePath ? pattern : null; }; parsedPattern.allPaths = [(matchPathEnds ? '*/' : './') + path]; return parsedPattern; @@ -412,7 +412,7 @@ function toRegExp(pattern: string): ParsedStringPattern { const regExp = new RegExp(`^${parseRegExp(pattern)}$`); return function (path: string, basename: string) { regExp.lastIndex = 0; // reset RegExp to its initial state to reuse it! - return path && regExp.test(path) ? pattern : null; + return typeof path === 'string' && regExp.test(path) ? pattern : null; }; } catch (error) { return NULL; @@ -430,7 +430,7 @@ function toRegExp(pattern: string): ParsedStringPattern { export function match(pattern: string | IRelativePattern, path: string): boolean; export function match(expression: IExpression, path: string, hasSibling?: (name: string) => boolean): string /* the matching pattern */; export function match(arg1: string | IExpression | IRelativePattern, path: string, hasSibling?: (name: string) => boolean): any { - if (!arg1 || !path) { + if (!arg1 || typeof path !== 'string') { return false; } @@ -515,7 +515,7 @@ function listToMap(list: string[]) { export function isRelativePattern(obj: any): obj is IRelativePattern { const rp = obj as IRelativePattern; - return rp && typeof rp.base === 'string' && typeof rp.pattern === 'string' && typeof rp.pathToRelative === 'function'; + return rp && typeof rp.base === 'string' && typeof rp.pattern === 'string'; } /** @@ -675,7 +675,7 @@ function aggregateBasenameMatches(parsedPatterns: Array[]); } const aggregate: ParsedStringPattern = function (path, basename) { - if (!path) { + if (typeof path !== 'string') { return null; } if (!basename) { diff --git a/src/vs/base/common/hash.ts b/src/vs/base/common/hash.ts index 7911cf5447..7c418a752d 100644 --- a/src/vs/base/common/hash.ts +++ b/src/vs/base/common/hash.ts @@ -69,4 +69,4 @@ export class Hasher { this._value = hash(obj, this._value); return this._value; } -} \ No newline at end of file +} diff --git a/src/vs/base/common/history.ts b/src/vs/base/common/history.ts index 1524c2e8d9..f0798acd51 100644 --- a/src/vs/base/common/history.ts +++ b/src/vs/base/common/history.ts @@ -66,7 +66,7 @@ export class HistoryNavigator implements INavigator { } private _reduceToLimit() { - let data = this._elements; + const data = this._elements; if (data.length > this._limit) { this._initialize(data.slice(data.length - this._limit)); } diff --git a/src/vs/base/common/json.ts b/src/vs/base/common/json.ts index 2c2e9b563c..35a31245fb 100644 --- a/src/vs/base/common/json.ts +++ b/src/vs/base/common/json.ts @@ -209,7 +209,7 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON let digits = 0; let value = 0; while (digits < count) { - let ch = text.charCodeAt(pos); + const ch = text.charCodeAt(pos); if (ch >= CharacterCodes._0 && ch <= CharacterCodes._9) { value = value * 16 + ch - CharacterCodes._0; } @@ -240,7 +240,7 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON } function scanNumber(): string { - let start = pos; + const start = pos; if (text.charCodeAt(pos) === CharacterCodes._0) { pos++; } else { @@ -331,7 +331,7 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON result += '\t'; break; case CharacterCodes.u: - let ch = scanHexDigits(4); + const ch = scanHexDigits(4); if (ch >= 0) { result += String.fromCharCode(ch); } else { @@ -424,7 +424,7 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON // comments case CharacterCodes.slash: - let start = pos - 1; + const start = pos - 1; // Single-line comment if (text.charCodeAt(pos + 1) === CharacterCodes.slash) { pos += 2; @@ -444,10 +444,10 @@ export function createScanner(text: string, ignoreTrivia: boolean = false): JSON if (text.charCodeAt(pos + 1) === CharacterCodes.asterisk) { pos += 2; - let safeLength = len - 1; // For lookahead. + const safeLength = len - 1; // For lookahead. let commentClosed = false; while (pos < safeLength) { - let ch = text.charCodeAt(pos); + const ch = text.charCodeAt(pos); if (ch === CharacterCodes.asterisk && text.charCodeAt(pos + 1) === CharacterCodes.slash) { pos += 2; @@ -720,8 +720,8 @@ interface NodeImpl extends Node { * For a given offset, evaluate the location in the JSON document. Each segment in the location path is either a property name or an array index. */ export function getLocation(text: string, position: number): Location { - let segments: Segment[] = []; // strings or numbers - let earlyReturnException = new Object(); + const segments: Segment[] = []; // strings or numbers + const earlyReturnException = new Object(); let previousNode: NodeImpl | undefined = undefined; const previousNodeInst: NodeImpl = { value: {}, @@ -800,7 +800,7 @@ export function getLocation(text: string, position: number): Location { isAtPropertyKey = false; previousNode = undefined; } else if (sep === ',') { - let last = segments[segments.length - 1]; + const last = segments[segments.length - 1]; if (typeof last === 'number') { segments[segments.length - 1] = last + 1; } else { @@ -843,7 +843,7 @@ export function getLocation(text: string, position: number): Location { export function parse(text: string, errors: ParseError[] = [], options: ParseOptions = ParseOptions.DEFAULT): any { let currentProperty: string | null = null; let currentParent: any = []; - let previousParents: any[] = []; + const previousParents: any[] = []; function onValue(value: any) { if (Array.isArray(currentParent)) { @@ -853,9 +853,9 @@ export function parse(text: string, errors: ParseError[] = [], options: ParseOpt } } - let visitor: JSONVisitor = { + const visitor: JSONVisitor = { onObjectBegin: () => { - let object = {}; + const object = {}; onValue(object); previousParents.push(currentParent); currentParent = object; @@ -868,7 +868,7 @@ export function parse(text: string, errors: ParseError[] = [], options: ParseOpt currentParent = previousParents.pop(); }, onArrayBegin: () => { - let array: any[] = []; + const array: any[] = []; onValue(array); previousParents.push(currentParent); currentParent = array; @@ -905,7 +905,7 @@ export function parseTree(text: string, errors: ParseError[] = [], options: Pars return valueNode; } - let visitor: JSONVisitor = { + const visitor: JSONVisitor = { onObjectBegin: (offset: number) => { currentParent = onValue({ type: 'object', offset, length: -1, parent: currentParent, children: [] }); }, @@ -945,7 +945,7 @@ export function parseTree(text: string, errors: ParseError[] = [], options: Pars }; visit(text, visitor, options); - let result = currentParent.children![0]; + const result = currentParent.children![0]; if (result) { delete result.parent; } @@ -977,7 +977,7 @@ export function findNodeAtLocation(root: Node, path: JSONPath): Node | undefined return undefined; } } else { - let index = segment; + const index = segment; if (node.type !== 'array' || index < 0 || !Array.isArray(node.children) || index >= node.children.length) { return undefined; } @@ -994,12 +994,12 @@ export function getNodePath(node: Node): JSONPath { if (!node.parent || !node.parent.children) { return []; } - let path = getNodePath(node.parent); + const path = getNodePath(node.parent); if (node.parent.type === 'property') { - let key = node.parent.children[0].value; + const key = node.parent.children[0].value; path.push(key); } else if (node.parent.type === 'array') { - let index = node.parent.children.indexOf(node); + const index = node.parent.children.indexOf(node); if (index !== -1) { path.push(index); } @@ -1015,9 +1015,9 @@ export function getNodeValue(node: Node): any { case 'array': return node.children!.map(getNodeValue); case 'object': - let obj = Object.create(null); + const obj = Object.create(null); for (let prop of node.children!) { - let valueNode = prop.children![1]; + const valueNode = prop.children![1]; if (valueNode) { obj[prop.children![0].value] = getNodeValue(valueNode); } @@ -1043,10 +1043,10 @@ export function contains(node: Node, offset: number, includeRightBound = false): */ export function findNodeAtOffset(node: Node, offset: number, includeRightBound = false): Node | undefined { if (contains(node, offset, includeRightBound)) { - let children = node.children; + const children = node.children; if (Array.isArray(children)) { for (let i = 0; i < children.length && children[i].offset <= offset; i++) { - let item = findNodeAtOffset(children[i], offset, includeRightBound); + const item = findNodeAtOffset(children[i], offset, includeRightBound); if (item) { return item; } @@ -1064,7 +1064,7 @@ export function findNodeAtOffset(node: Node, offset: number, includeRightBound = */ export function visit(text: string, visitor: JSONVisitor, options: ParseOptions = ParseOptions.DEFAULT): any { - let _scanner = createScanner(text, false); + const _scanner = createScanner(text, false); function toNoArgVisit(visitFunction?: (offset: number, length: number) => void): () => void { return visitFunction ? () => visitFunction(_scanner.getTokenOffset(), _scanner.getTokenLength()) : () => true; @@ -1073,7 +1073,7 @@ export function visit(text: string, visitor: JSONVisitor, options: ParseOptions return visitFunction ? (arg: T) => visitFunction(arg, _scanner.getTokenOffset(), _scanner.getTokenLength()) : () => true; } - let onObjectBegin = toNoArgVisit(visitor.onObjectBegin), + const onObjectBegin = toNoArgVisit(visitor.onObjectBegin), onObjectProperty = toOneArgVisit(visitor.onObjectProperty), onObjectEnd = toNoArgVisit(visitor.onObjectEnd), onArrayBegin = toNoArgVisit(visitor.onArrayBegin), @@ -1083,11 +1083,11 @@ export function visit(text: string, visitor: JSONVisitor, options: ParseOptions onComment = toNoArgVisit(visitor.onComment), onError = toOneArgVisit(visitor.onError); - let disallowComments = options && options.disallowComments; - let allowTrailingComma = options && options.allowTrailingComma; + const disallowComments = options && options.disallowComments; + const allowTrailingComma = options && options.allowTrailingComma; function scanNext(): SyntaxKind { while (true) { - let token = _scanner.scan(); + const token = _scanner.scan(); switch (_scanner.getTokenError()) { case ScanError.InvalidUnicode: handleError(ParseErrorCode.InvalidUnicode); @@ -1148,7 +1148,7 @@ export function visit(text: string, visitor: JSONVisitor, options: ParseOptions } function parseString(isValue: boolean): boolean { - let value = _scanner.getTokenValue(); + const value = _scanner.getTokenValue(); if (isValue) { onLiteralValue(value); } else { diff --git a/src/vs/base/common/jsonEdit.ts b/src/vs/base/common/jsonEdit.ts index 02d646498d..87a2fe2af7 100644 --- a/src/vs/base/common/jsonEdit.ts +++ b/src/vs/base/common/jsonEdit.ts @@ -12,9 +12,9 @@ export function removeProperty(text: string, path: JSONPath, formattingOptions: } export function setProperty(text: string, originalPath: JSONPath, value: any, formattingOptions: FormattingOptions, getInsertionIndex?: (properties: string[]) => number): Edit[] { - let path = originalPath.slice(); - let errors: ParseError[] = []; - let root = parseTree(text, errors); + const path = originalPath.slice(); + const errors: ParseError[] = []; + const root = parseTree(text, errors); let parent: Node | undefined = undefined; let lastSegment: Segment | undefined = undefined; @@ -39,24 +39,24 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo } return withFormatting(text, { offset: root ? root.offset : 0, length: root ? root.length : 0, content: JSON.stringify(value) }, formattingOptions); } else if (parent.type === 'object' && typeof lastSegment === 'string' && Array.isArray(parent.children)) { - let existing = findNodeAtLocation(parent, [lastSegment]); + const existing = findNodeAtLocation(parent, [lastSegment]); if (existing !== undefined) { if (value === undefined) { // delete if (!existing.parent) { throw new Error('Malformed AST'); } - let propertyIndex = parent.children.indexOf(existing.parent); + const propertyIndex = parent.children.indexOf(existing.parent); let removeBegin: number; let removeEnd = existing.parent.offset + existing.parent.length; if (propertyIndex > 0) { // remove the comma of the previous node - let previous = parent.children[propertyIndex - 1]; + const previous = parent.children[propertyIndex - 1]; removeBegin = previous.offset + previous.length; } else { removeBegin = parent.offset + 1; if (parent.children.length > 1) { // remove the comma of the next node - let next = parent.children[1]; + const next = parent.children[1]; removeEnd = next.offset; } } @@ -69,11 +69,11 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo if (value === undefined) { // delete return []; // property does not exist, nothing to do } - let newProperty = `${JSON.stringify(lastSegment)}: ${JSON.stringify(value)}`; - let index = getInsertionIndex ? getInsertionIndex(parent.children.map(p => p.children![0].value)) : parent.children.length; + const newProperty = `${JSON.stringify(lastSegment)}: ${JSON.stringify(value)}`; + const index = getInsertionIndex ? getInsertionIndex(parent.children.map(p => p.children![0].value)) : parent.children.length; let edit: Edit; if (index > 0) { - let previous = parent.children[index - 1]; + const previous = parent.children[index - 1]; edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty }; } else if (parent.children.length === 0) { edit = { offset: parent.offset + 1, length: 0, content: newProperty }; @@ -83,32 +83,32 @@ export function setProperty(text: string, originalPath: JSONPath, value: any, fo return withFormatting(text, edit, formattingOptions); } } else if (parent.type === 'array' && typeof lastSegment === 'number' && Array.isArray(parent.children)) { - let insertIndex = lastSegment; + const insertIndex = lastSegment; if (insertIndex === -1) { // Insert - let newProperty = `${JSON.stringify(value)}`; + const newProperty = `${JSON.stringify(value)}`; let edit: Edit; if (parent.children.length === 0) { edit = { offset: parent.offset + 1, length: 0, content: newProperty }; } else { - let previous = parent.children[parent.children.length - 1]; + const previous = parent.children[parent.children.length - 1]; edit = { offset: previous.offset + previous.length, length: 0, content: ',' + newProperty }; } return withFormatting(text, edit, formattingOptions); } else { if (value === undefined && parent.children.length >= 0) { //Removal - let removalIndex = lastSegment; - let toRemove = parent.children[removalIndex]; + const removalIndex = lastSegment; + const toRemove = parent.children[removalIndex]; let edit: Edit; if (parent.children.length === 1) { // only item edit = { offset: parent.offset + 1, length: parent.length - 2, content: '' }; } else if (parent.children.length - 1 === removalIndex) { // last item - let previous = parent.children[removalIndex - 1]; - let offset = previous.offset + previous.length; - let parentEndOffset = parent.offset + parent.length; + const previous = parent.children[removalIndex - 1]; + const offset = previous.offset + previous.length; + const parentEndOffset = parent.offset + parent.length; edit = { offset, length: parentEndOffset - 2 - offset, content: '' }; } else { edit = { offset: toRemove.offset, length: parent.children[removalIndex + 1].offset - toRemove.offset, content: '' }; @@ -139,18 +139,18 @@ function withFormatting(text: string, edit: Edit, formattingOptions: FormattingO } } - let edits = format(newText, { offset: begin, length: end - begin }, formattingOptions); + const edits = format(newText, { offset: begin, length: end - begin }, formattingOptions); // apply the formatting edits and track the begin and end offsets of the changes for (let i = edits.length - 1; i >= 0; i--) { - let edit = edits[i]; + const edit = edits[i]; newText = applyEdit(newText, edit); begin = Math.min(begin, edit.offset); end = Math.max(end, edit.offset + edit.length); end += edit.content.length - edit.length; } // create a single edit with all changes - let editLength = text.length - (newText.length - end) - begin; + const editLength = text.length - (newText.length - end) - begin; return [{ offset: begin, length: editLength, content: newText.substring(begin, end) }]; } diff --git a/src/vs/base/common/jsonFormatter.ts b/src/vs/base/common/jsonFormatter.ts index 54037c277c..5f23835921 100644 --- a/src/vs/base/common/jsonFormatter.ts +++ b/src/vs/base/common/jsonFormatter.ts @@ -80,7 +80,7 @@ export function format(documentText: string, range: Range | undefined, options: rangeStart = 0; rangeEnd = documentText.length; } - let eol = getEOL(options, documentText); + const eol = getEOL(options, documentText); let lineBreak = false; let indentLevel = 0; @@ -91,7 +91,7 @@ export function format(documentText: string, range: Range | undefined, options: indentValue = '\t'; } - let scanner = createScanner(formatText, false); + const scanner = createScanner(formatText, false); let hasError = false; function newLineAndIndent(): string { @@ -107,7 +107,7 @@ export function format(documentText: string, range: Range | undefined, options: hasError = token === SyntaxKind.Unknown || scanner.getTokenError() !== ScanError.None; return token; } - let editOperations: Edit[] = []; + const editOperations: Edit[] = []; function addEdit(text: string, startOffset: number, endOffset: number) { if (!hasError && startOffset < rangeEnd && endOffset > rangeStart && documentText.substring(startOffset, endOffset) !== text) { editOperations.push({ offset: startOffset, length: endOffset - startOffset, content: text }); @@ -117,8 +117,8 @@ export function format(documentText: string, range: Range | undefined, options: let firstToken = scanNext(); if (firstToken !== SyntaxKind.EOF) { - let firstTokenStart = scanner.getTokenOffset() + formatTextStart; - let initialIndent = repeat(indentValue, initialIndentLevel); + const firstTokenStart = scanner.getTokenOffset() + formatTextStart; + const initialIndent = repeat(indentValue, initialIndentLevel); addEdit(initialIndent, formatTextStart, firstTokenStart); } @@ -129,7 +129,7 @@ export function format(documentText: string, range: Range | undefined, options: let replaceContent = ''; while (!lineBreak && (secondToken === SyntaxKind.LineCommentTrivia || secondToken === SyntaxKind.BlockCommentTrivia)) { // comments on the same line: keep them on the same line, but ignore them otherwise - let commentTokenStart = scanner.getTokenOffset() + formatTextStart; + const commentTokenStart = scanner.getTokenOffset() + formatTextStart; addEdit(' ', firstTokenEnd, commentTokenStart); firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + formatTextStart; replaceContent = secondToken === SyntaxKind.LineCommentTrivia ? newLineAndIndent() : ''; @@ -195,7 +195,7 @@ export function format(documentText: string, range: Range | undefined, options: } } - let secondTokenStart = scanner.getTokenOffset() + formatTextStart; + const secondTokenStart = scanner.getTokenOffset() + formatTextStart; addEdit(replaceContent, firstTokenEnd, secondTokenStart); firstToken = secondToken; } @@ -213,9 +213,9 @@ function repeat(s: string, count: number): string { function computeIndentLevel(content: string, options: FormattingOptions): number { let i = 0; let nChars = 0; - let tabSize = options.tabSize || 4; + const tabSize = options.tabSize || 4; while (i < content.length) { - let ch = content.charAt(i); + const ch = content.charAt(i); if (ch === ' ') { nChars++; } else if (ch === '\t') { @@ -230,7 +230,7 @@ function computeIndentLevel(content: string, options: FormattingOptions): number function getEOL(options: FormattingOptions, text: string): string { for (let i = 0; i < text.length; i++) { - let ch = text.charAt(i); + const ch = text.charAt(i); if (ch === '\r') { if (i + 1 < text.length && text.charAt(i + 1) === '\n') { return '\r\n'; diff --git a/src/vs/base/common/keyCodes.ts b/src/vs/base/common/keyCodes.ts index dd2085e39f..631c3ad06f 100644 --- a/src/vs/base/common/keyCodes.ts +++ b/src/vs/base/common/keyCodes.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { OperatingSystem } from 'vs/base/common/platform'; +import { illegalArgument } from 'vs/base/common/errors'; /** * Virtual Key Codes, the value does not hold any inherent meaning. @@ -406,7 +407,7 @@ export const enum KeyMod { } export function KeyChord(firstPart: number, secondPart: number): number { - let chordPart = ((secondPart & 0x0000FFFF) << 16) >>> 0; + const chordPart = ((secondPart & 0x0000FFFF) << 16) >>> 0; return (firstPart | chordPart) >>> 0; } @@ -417,12 +418,12 @@ export function createKeybinding(keybinding: number, OS: OperatingSystem): Keybi const firstPart = (keybinding & 0x0000FFFF) >>> 0; const chordPart = (keybinding & 0xFFFF0000) >>> 16; if (chordPart !== 0) { - return new ChordKeybinding( + return new ChordKeybinding([ createSimpleKeybinding(firstPart, OS), - createSimpleKeybinding(chordPart, OS), - ); + createSimpleKeybinding(chordPart, OS) + ]); } - return createSimpleKeybinding(firstPart, OS); + return new ChordKeybinding([createSimpleKeybinding(firstPart, OS)]); } export function createSimpleKeybinding(keybinding: number, OS: OperatingSystem): SimpleKeybinding { @@ -439,14 +440,7 @@ export function createSimpleKeybinding(keybinding: number, OS: OperatingSystem): return new SimpleKeybinding(ctrlKey, shiftKey, altKey, metaKey, keyCode); } -export const enum KeybindingType { - Simple = 1, - Chord = 2 -} - export class SimpleKeybinding { - public readonly type = KeybindingType.Simple; - public readonly ctrlKey: boolean; public readonly shiftKey: boolean; public readonly altKey: boolean; @@ -461,10 +455,7 @@ export class SimpleKeybinding { this.keyCode = keyCode; } - public equals(other: Keybinding): boolean { - if (other.type !== KeybindingType.Simple) { - return false; - } + public equals(other: SimpleKeybinding): boolean { return ( this.ctrlKey === other.ctrlKey && this.shiftKey === other.shiftKey @@ -475,10 +466,10 @@ export class SimpleKeybinding { } public getHashCode(): string { - let ctrl = this.ctrlKey ? '1' : '0'; - let shift = this.shiftKey ? '1' : '0'; - let alt = this.altKey ? '1' : '0'; - let meta = this.metaKey ? '1' : '0'; + const ctrl = this.ctrlKey ? '1' : '0'; + const shift = this.shiftKey ? '1' : '0'; + const alt = this.altKey ? '1' : '0'; + const meta = this.metaKey ? '1' : '0'; return `${ctrl}${shift}${alt}${meta}${this.keyCode}`; } @@ -492,6 +483,10 @@ export class SimpleKeybinding { ); } + public toChord(): ChordKeybinding { + return new ChordKeybinding([this]); + } + /** * Does this keybinding refer to the key code of a modifier and it also has the modifier flag? */ @@ -506,22 +501,43 @@ export class SimpleKeybinding { } export class ChordKeybinding { - public readonly type = KeybindingType.Chord; + public readonly parts: SimpleKeybinding[]; - public readonly firstPart: SimpleKeybinding; - public readonly chordPart: SimpleKeybinding; - - constructor(firstPart: SimpleKeybinding, chordPart: SimpleKeybinding) { - this.firstPart = firstPart; - this.chordPart = chordPart; + constructor(parts: SimpleKeybinding[]) { + if (parts.length === 0) { + throw illegalArgument(`parts`); + } + this.parts = parts; } public getHashCode(): string { - return `${this.firstPart.getHashCode()};${this.chordPart.getHashCode()}`; + let result = ''; + for (let i = 0, len = this.parts.length; i < len; i++) { + if (i !== 0) { + result += ';'; + } + result += this.parts[i].getHashCode(); + } + return result; + } + + public equals(other: ChordKeybinding | null): boolean { + if (other === null) { + return false; + } + if (this.parts.length !== other.parts.length) { + return false; + } + for (let i = 0; i < this.parts.length; i++) { + if (!this.parts[i].equals(other.parts[i])) { + return false; + } + } + return true; } } -export type Keybinding = SimpleKeybinding | ChordKeybinding; +export type Keybinding = ChordKeybinding; export class ResolvedKeybindingPart { readonly ctrlKey: boolean; @@ -574,12 +590,13 @@ export abstract class ResolvedKeybinding { public abstract isChord(): boolean; /** - * Returns the firstPart, chordPart that should be used for dispatching. + * Returns the parts that comprise of the keybinding. + * Simple keybindings return one element. */ - public abstract getDispatchParts(): [string | null, string | null]; + public abstract getParts(): ResolvedKeybindingPart[]; + /** - * Returns the firstPart, chordPart of the keybinding. - * For simple keybindings, the second element will be null. + * Returns the parts that should be used for dispatching. */ - public abstract getParts(): [ResolvedKeybindingPart, ResolvedKeybindingPart | null]; + public abstract getDispatchParts(): (string | null)[]; } diff --git a/src/vs/base/common/keybindingLabels.ts b/src/vs/base/common/keybindingLabels.ts index 8bc0dca51d..977f079715 100644 --- a/src/vs/base/common/keybindingLabels.ts +++ b/src/vs/base/common/keybindingLabels.ts @@ -21,6 +21,10 @@ export interface Modifiers { readonly metaKey: boolean; } +export interface KeyLabelProvider { + (keybinding: T): string | null; +} + export class ModifierLabelProvider { public readonly modifierLabels: ModifierLabels[]; @@ -32,11 +36,22 @@ export class ModifierLabelProvider { this.modifierLabels[OperatingSystem.Linux] = linux; } - public toLabel(firstPartMod: Modifiers | null, firstPartKey: string | null, chordPartMod: Modifiers | null, chordPartKey: string | null, OS: OperatingSystem): string | null { - if (firstPartMod === null || firstPartKey === null) { + public toLabel(OS: OperatingSystem, parts: T[], keyLabelProvider: KeyLabelProvider): string | null { + if (parts.length === 0) { return null; } - return _asString(firstPartMod, firstPartKey, chordPartMod, chordPartKey, this.modifierLabels[OS]); + + const result: string[] = []; + for (let i = 0, len = parts.length; i < len; i++) { + const part = parts[i]; + const keyLabel = keyLabelProvider(part); + if (keyLabel === null) { + // this keybinding cannot be expressed... + return null; + } + result[i] = _simpleAsString(part, keyLabel, this.modifierLabels[OS]); + } + return result.join(' '); } } @@ -147,7 +162,7 @@ function _simpleAsString(modifiers: Modifiers, key: string, labels: ModifierLabe return ''; } - let result: string[] = []; + const result: string[] = []; // translate modifier keys: Ctrl-Shift-Alt-Meta if (modifiers.ctrlKey) { @@ -171,14 +186,3 @@ function _simpleAsString(modifiers: Modifiers, key: string, labels: ModifierLabe return result.join(labels.separator); } - -function _asString(firstPartMod: Modifiers, firstPartKey: string, chordPartMod: Modifiers | null, chordPartKey: string | null, labels: ModifierLabels): string { - let result = _simpleAsString(firstPartMod, firstPartKey, labels); - - if (chordPartMod !== null && chordPartKey !== null) { - result += ' '; - result += _simpleAsString(chordPartMod, chordPartKey, labels); - } - - return result; -} \ No newline at end of file diff --git a/src/vs/base/common/keybindingParser.ts b/src/vs/base/common/keybindingParser.ts index 30074eccce..8dec67a921 100644 --- a/src/vs/base/common/keybindingParser.ts +++ b/src/vs/base/common/keybindingParser.ts @@ -85,16 +85,14 @@ export class KeybindingParser { return null; } - let [firstPart, remains] = this.parseSimpleKeybinding(input); - let chordPart: SimpleKeybinding | null = null; - if (remains.length > 0) { - [chordPart] = this.parseSimpleKeybinding(remains); - } + const parts: SimpleKeybinding[] = []; + let part: SimpleKeybinding; - if (chordPart) { - return new ChordKeybinding(firstPart, chordPart); - } - return firstPart; + do { + [part, input] = this.parseSimpleKeybinding(input); + parts.push(part); + } while (input.length > 0); + return new ChordKeybinding(parts); } private static parseSimpleUserBinding(input: string): [SimpleKeybinding | ScanCodeBinding, string] { @@ -109,16 +107,18 @@ export class KeybindingParser { return [new SimpleKeybinding(mods.ctrl, mods.shift, mods.alt, mods.meta, keyCode), mods.remains]; } - static parseUserBinding(input: string): [SimpleKeybinding | ScanCodeBinding | null, SimpleKeybinding | ScanCodeBinding | null] { + static parseUserBinding(input: string): (SimpleKeybinding | ScanCodeBinding)[] { if (!input) { - return [null, null]; + return []; } - let [firstPart, remains] = this.parseSimpleUserBinding(input); - let chordPart: SimpleKeybinding | ScanCodeBinding | null = null; - if (remains.length > 0) { - [chordPart] = this.parseSimpleUserBinding(remains); + const parts: (SimpleKeybinding | ScanCodeBinding)[] = []; + let part: SimpleKeybinding | ScanCodeBinding; + + while (input.length > 0) { + [part, input] = this.parseSimpleUserBinding(input); + parts.push(part); } - return [firstPart, chordPart]; + return parts; } } \ No newline at end of file diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index a3af1921c4..e7093a367f 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -4,11 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import { nativeSep, normalize, basename as pathsBasename, sep } from 'vs/base/common/paths'; +import { sep, posix, normalize } from 'vs/base/common/path'; import { endsWith, ltrim, startsWithIgnoreCase, rtrim, startsWith } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform'; -import { isEqual } from 'vs/base/common/resources'; +import { isEqual, basename } from 'vs/base/common/resources'; +import { CharCode } from 'vs/base/common/charCode'; export interface IWorkspaceFolderProvider { getWorkspaceFolder(resource: URI): { uri: URI, name?: string } | null; @@ -36,14 +37,15 @@ export function getPathLabel(resource: URI | string, userHomeProvider?: IUserHom const hasMultipleRoots = rootProvider.getWorkspace().folders.length > 1; let pathLabel: string; - if (isEqual(baseResource.uri, resource, !isLinux)) { + if (isEqual(baseResource.uri, resource)) { pathLabel = ''; // no label if paths are identical } else { - pathLabel = normalize(ltrim(resource.path.substr(baseResource.uri.path.length), sep)!, true); + // TODO: isidor use resources.relative + pathLabel = normalize(ltrim(resource.path.substr(baseResource.uri.path.length), posix.sep)!); } if (hasMultipleRoots) { - const rootName = (baseResource && baseResource.name) ? baseResource.name : pathsBasename(baseResource.uri.fsPath); + const rootName = (baseResource && baseResource.name) ? baseResource.name : basename(baseResource.uri); pathLabel = pathLabel ? (rootName + ' • ' + pathLabel) : rootName; // always show root basename if there are multiple } @@ -58,11 +60,11 @@ export function getPathLabel(resource: URI | string, userHomeProvider?: IUserHom // convert c:\something => C:\something if (hasDriveLetter(resource.fsPath)) { - return normalize(normalizeDriveLetter(resource.fsPath), true); + return normalize(normalizeDriveLetter(resource.fsPath)); } // normalize and tildify (macOS, Linux only) - let res = normalize(resource.fsPath, true); + let res = normalize(resource.fsPath); if (!isWindows && userHomeProvider) { res = tildify(res, userHomeProvider.userHome); } @@ -81,7 +83,7 @@ export function getBaseLabel(resource: URI | string | undefined): string | undef resource = URI.file(resource); } - const base = pathsBasename(resource.path) || (resource.scheme === Schemas.file ? resource.fsPath : resource.path) /* can be empty string if '/' is passed in */; + const base = basename(resource) || (resource.scheme === Schemas.file ? resource.fsPath : resource.path) /* can be empty string if '/' is passed in */; // convert c: => C: if (hasDriveLetter(base)) { @@ -112,7 +114,7 @@ export function tildify(path: string, userHome: string): string { // Keep a normalized user home path as cache to prevent accumulated string creation let normalizedUserHome = normalizedUserHomeCached.original === userHome ? normalizedUserHomeCached.normalized : undefined; if (!normalizedUserHome) { - normalizedUserHome = `${rtrim(userHome, sep)}${sep}`; + normalizedUserHome = `${rtrim(userHome, posix.sep)}${posix.sep}`; normalizedUserHomeCached = { original: userHome, normalized: normalizedUserHome }; } @@ -169,7 +171,7 @@ export function shorten(paths: string[]): string[] { let path = paths[pathIndex]; if (path === '') { - shortenedPaths[pathIndex] = `.${nativeSep}`; + shortenedPaths[pathIndex] = `.${sep}`; continue; } @@ -185,20 +187,20 @@ export function shorten(paths: string[]): string[] { if (path.indexOf(unc) === 0) { prefix = path.substr(0, path.indexOf(unc) + unc.length); path = path.substr(path.indexOf(unc) + unc.length); - } else if (path.indexOf(nativeSep) === 0) { - prefix = path.substr(0, path.indexOf(nativeSep) + nativeSep.length); - path = path.substr(path.indexOf(nativeSep) + nativeSep.length); + } else if (path.indexOf(sep) === 0) { + prefix = path.substr(0, path.indexOf(sep) + sep.length); + path = path.substr(path.indexOf(sep) + sep.length); } else if (path.indexOf(home) === 0) { prefix = path.substr(0, path.indexOf(home) + home.length); path = path.substr(path.indexOf(home) + home.length); } // pick the first shortest subpath found - const segments: string[] = path.split(nativeSep); + const segments: string[] = path.split(sep); for (let subpathLength = 1; match && subpathLength <= segments.length; subpathLength++) { for (let start = segments.length - subpathLength; match && start >= 0; start--) { match = false; - let subpath = segments.slice(start, start + subpathLength).join(nativeSep); + let subpath = segments.slice(start, start + subpathLength).join(sep); // that is unique to any other path for (let otherPathIndex = 0; !match && otherPathIndex < paths.length; otherPathIndex++) { @@ -209,7 +211,7 @@ export function shorten(paths: string[]): string[] { // Adding separator as prefix for subpath, such that 'endsWith(src, trgt)' considers subpath as directory name instead of plain string. // prefix is not added when either subpath is root directory or path[otherPathIndex] does not have multiple directories. - const subpathWithSep: string = (start > 0 && paths[otherPathIndex].indexOf(nativeSep) > -1) ? nativeSep + subpath : subpath; + const subpathWithSep: string = (start > 0 && paths[otherPathIndex].indexOf(sep) > -1) ? sep + subpath : subpath; const isOtherPathEnding: boolean = endsWith(paths[otherPathIndex], subpathWithSep); match = !isSubpathEnding || isOtherPathEnding; @@ -226,11 +228,11 @@ export function shorten(paths: string[]): string[] { // extend subpath to include disk drive prefix start = 0; subpathLength++; - subpath = segments[0] + nativeSep + subpath; + subpath = segments[0] + sep + subpath; } if (start > 0) { - result = segments[0] + nativeSep; + result = segments[0] + sep; } result = prefix + result; @@ -238,14 +240,14 @@ export function shorten(paths: string[]): string[] { // add ellipsis at the beginning if neeeded if (start > 0) { - result = result + ellipsis + nativeSep; + result = result + ellipsis + sep; } result = result + subpath; // add ellipsis at the end if needed if (start + subpathLength < segments.length) { - result = result + nativeSep + ellipsis; + result = result + sep + ellipsis; } shortenedPaths[pathIndex] = result; @@ -282,7 +284,7 @@ interface ISegment { * @param value string to which templating is applied * @param values the values of the templates to use */ -export function template(template: string, values: { [key: string]: string | ISeparator } = Object.create(null)): string { +export function template(template: string, values: { [key: string]: string | ISeparator | null } = Object.create(null)): string { const segments: ISegment[] = []; let inVariable = false; @@ -355,10 +357,10 @@ export function template(template: string, values: { [key: string]: string | ISe */ export function mnemonicMenuLabel(label: string, forceDisableMnemonics?: boolean): string { if (isMacintosh || forceDisableMnemonics) { - return label.replace(/\(&&\w\)|&&/g, ''); + return label.replace(/\(&&\w\)|&&/g, '').replace(/&/g, isMacintosh ? '&' : '&&'); } - return label.replace(/&&/g, '&'); + return label.replace(/&&|&/g, m => m === '&' ? '&&' : '&'); } /** @@ -382,3 +384,16 @@ export function mnemonicButtonLabel(label: string): string { export function unmnemonicLabel(label: string): string { return label.replace(/&/g, '&&'); } + +/** + * Splits a path in name and parent path, supporting both '/' and '\' + */ +export function splitName(fullPath: string): { name: string, parentPath: string } { + for (let i = fullPath.length - 1; i >= 1; i--) { + const code = fullPath.charCodeAt(i); + if (code === CharCode.Slash || code === CharCode.Backslash) { + return { parentPath: fullPath.substr(0, i), name: fullPath.substr(i + 1) }; + } + } + return { parentPath: '', name: fullPath }; +} diff --git a/src/vs/base/common/linkedList.ts b/src/vs/base/common/linkedList.ts index 02610624fb..2405511f81 100644 --- a/src/vs/base/common/linkedList.ts +++ b/src/vs/base/common/linkedList.ts @@ -97,7 +97,7 @@ export class LinkedList { } if (candidate.prev && candidate.next) { // middle - let anchor = candidate.prev; + const anchor = candidate.prev; anchor.next = candidate.next; candidate.next.prev = anchor; @@ -144,7 +144,7 @@ export class LinkedList { } toArray(): E[] { - let result: E[] = []; + const result: E[] = []; for (let node = this._first; node instanceof Node; node = node.next) { result.push(node.element); } diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index 5dfac6d38b..a15da16937 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -9,7 +9,7 @@ import { Iterator, IteratorResult, FIN } from './iterator'; export function values(set: Set): V[]; export function values(map: Map): V[]; -export function values(forEachable: { forEach(callback: (value: V, ...more: any[]) => any) }): V[] { +export function values(forEachable: { forEach(callback: (value: V, ...more: any[]) => any): void }): V[] { const result: V[] = []; forEachable.forEach(value => result.push(value)); return result; @@ -100,8 +100,8 @@ export class StringIterator implements IKeyIterator { } cmp(a: string): number { - let aCode = a.charCodeAt(0); - let thisCode = this._value.charCodeAt(this._pos); + const aCode = a.charCodeAt(0); + const thisCode = this._value.charCodeAt(this._pos); return aCode - thisCode; } @@ -149,11 +149,11 @@ export class PathIterator implements IKeyIterator { cmp(a: string): number { let aPos = 0; - let aLen = a.length; + const aLen = a.length; let thisPos = this._from; while (aPos < aLen && thisPos < this._to) { - let cmp = a.charCodeAt(aPos) - this._value.charCodeAt(thisPos); + const cmp = a.charCodeAt(aPos) - this._value.charCodeAt(thisPos); if (cmp !== 0) { return cmp; } @@ -210,7 +210,7 @@ export class TernarySearchTree { } set(key: string, element: E): E | undefined { - let iter = this._iter.reset(key); + const iter = this._iter.reset(key); let node: TernarySearchTreeNode; if (!this._root) { @@ -220,7 +220,7 @@ export class TernarySearchTree { node = this._root; while (true) { - let val = iter.cmp(node.segment); + const val = iter.cmp(node.segment); if (val > 0) { // left if (!node.left) { @@ -256,10 +256,10 @@ export class TernarySearchTree { } get(key: string): E | undefined { - let iter = this._iter.reset(key); + const iter = this._iter.reset(key); let node = this._root; while (node) { - let val = iter.cmp(node.segment); + const val = iter.cmp(node.segment); if (val > 0) { // left node = node.left; @@ -279,13 +279,13 @@ export class TernarySearchTree { delete(key: string): void { - let iter = this._iter.reset(key); - let stack: [-1 | 0 | 1, TernarySearchTreeNode][] = []; + const iter = this._iter.reset(key); + const stack: [-1 | 0 | 1, TernarySearchTreeNode][] = []; let node = this._root; // find and unset node while (node) { - let val = iter.cmp(node.segment); + const val = iter.cmp(node.segment); if (val > 0) { // left stack.push([1, node]); @@ -319,11 +319,11 @@ export class TernarySearchTree { } findSubstr(key: string): E | undefined { - let iter = this._iter.reset(key); + const iter = this._iter.reset(key); let node = this._root; let candidate: E | undefined = undefined; while (node) { - let val = iter.cmp(node.segment); + const val = iter.cmp(node.segment); if (val > 0) { // left node = node.left; @@ -343,10 +343,10 @@ export class TernarySearchTree { } findSuperstr(key: string): Iterator | undefined { - let iter = this._iter.reset(key); + const iter = this._iter.reset(key); let node = this._root; while (node) { - let val = iter.cmp(node.segment); + const val = iter.cmp(node.segment); if (val > 0) { // left node = node.left; @@ -373,7 +373,7 @@ export class TernarySearchTree { let res: { done: false; value: E; }; let idx: number; let data: E[]; - let next = (): IteratorResult => { + const next = (): IteratorResult => { if (!data) { // lazy till first invocation data = []; @@ -610,7 +610,7 @@ export class LinkedMap { } values(): V[] { - let result: V[] = []; + const result: V[] = []; let current = this._head; while (current) { result.push(current.value); @@ -620,7 +620,7 @@ export class LinkedMap { } keys(): K[] { - let result: K[] = []; + const result: K[] = []; let current = this._head; while (current) { result.push(current.key); @@ -631,14 +631,14 @@ export class LinkedMap { /* VS Code / Monaco editor runs on es5 which has no Symbol.iterator keys(): IterableIterator { - let current = this._head; - let iterator: IterableIterator = { + const current = this._head; + const iterator: IterableIterator = { [Symbol.iterator]() { return iterator; }, next():IteratorResult { if (current) { - let result = { value: current.key, done: false }; + const result = { value: current.key, done: false }; current = current.next; return result; } else { @@ -650,14 +650,14 @@ export class LinkedMap { } values(): IterableIterator { - let current = this._head; - let iterator: IterableIterator = { + const current = this._head; + const iterator: IterableIterator = { [Symbol.iterator]() { return iterator; }, next():IteratorResult { if (current) { - let result = { value: current.value, done: false }; + const result = { value: current.value, done: false }; current = current.next; return result; } else { @@ -723,9 +723,21 @@ export class LinkedMap { this._tail = undefined; } else if (item === this._head) { + // This can only happend if size === 1 which is handle + // by the case above. + if (!item.next) { + throw new Error('Invalid list'); + } + item.next.previous = undefined; this._head = item.next; } else if (item === this._tail) { + // This can only happend if size === 1 which is handle + // by the case above. + if (!item.previous) { + throw new Error('Invalid list'); + } + item.previous.next = undefined; this._tail = item.previous; } else { @@ -737,6 +749,8 @@ export class LinkedMap { next.previous = previous; previous.next = next; } + item.next = undefined; + item.previous = undefined; } private touch(item: Item, touch: Touch): void { diff --git a/src/vs/base/common/mime.ts b/src/vs/base/common/mime.ts index 145dc5ccdb..fa281cf1ef 100644 --- a/src/vs/base/common/mime.ts +++ b/src/vs/base/common/mime.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as paths from 'vs/base/common/paths'; -import * as strings from 'vs/base/common/strings'; -import * as arrays from 'vs/base/common/arrays'; +import { basename, posix, extname } from 'vs/base/common/path'; +import { endsWith, startsWithUTF8BOM, startsWith } from 'vs/base/common/strings'; +import { coalesce } from 'vs/base/common/arrays'; import { match } from 'vs/base/common/glob'; export const MIME_TEXT = 'text/plain'; @@ -85,7 +85,7 @@ function toTextMimeAssociationItem(association: ITextMimeAssociation): ITextMime filenameLowercase: association.filename ? association.filename.toLowerCase() : undefined, extensionLowercase: association.extension ? association.extension.toLowerCase() : undefined, filepatternLowercase: association.filepattern ? association.filepattern.toLowerCase() : undefined, - filepatternOnPath: association.filepattern ? association.filepattern.indexOf(paths.sep) >= 0 : false + filepatternOnPath: association.filepattern ? association.filepattern.indexOf(posix.sep) >= 0 : false }; } @@ -112,7 +112,7 @@ export function guessMimeTypes(path: string | null, firstLine?: string): string[ } path = path.toLowerCase(); - const filename = paths.basename(path); + const filename = basename(path); // 1.) User configured mappings have highest priority const configuredMime = guessMimeTypeByPath(path, filename, userRegisteredAssociations); @@ -166,7 +166,7 @@ function guessMimeTypeByPath(path: string, filename: string, associations: IText // Longest extension match if (association.extension) { if (!extensionMatch || association.extension.length > extensionMatch.extension!.length) { - if (strings.endsWith(filename, association.extensionLowercase!)) { + if (endsWith(filename, association.extensionLowercase!)) { extensionMatch = association; } } @@ -192,7 +192,7 @@ function guessMimeTypeByPath(path: string, filename: string, associations: IText } function guessMimeTypeByFirstline(firstLine: string): string | null { - if (strings.startsWithUTF8BOM(firstLine)) { + if (startsWithUTF8BOM(firstLine)) { firstLine = firstLine.substr(1); } @@ -230,12 +230,12 @@ export function isUnspecific(mime: string[] | string): boolean { * 2. Otherwise, if there are other extensions, suggest the first one. * 3. Otherwise, suggest the prefix. */ -export function suggestFilename(langId: string, prefix: string): string { +export function suggestFilename(langId: string | null, prefix: string): string { const extensions = registeredAssociations .filter(assoc => !assoc.userConfigured && assoc.extension && assoc.id === langId) .map(assoc => assoc.extension); - const extensionsWithDotFirst = arrays.coalesce(extensions) - .filter(assoc => strings.startsWith(assoc, '.')); + const extensionsWithDotFirst = coalesce(extensions) + .filter(assoc => startsWith(assoc, '.')); if (extensionsWithDotFirst.length > 0) { return prefix + extensionsWithDotFirst[0]; @@ -299,6 +299,6 @@ const mapExtToMediaMimes: MapExtToMediaMimes = { }; export function getMediaMime(path: string): string | undefined { - const ext = paths.extname(path); + const ext = extname(path); return mapExtToMediaMimes[ext.toLowerCase()]; } diff --git a/src/vs/base/common/objects.ts b/src/vs/base/common/objects.ts index 420ba15d92..6ca46fab65 100644 --- a/src/vs/base/common/objects.ts +++ b/src/vs/base/common/objects.ts @@ -30,11 +30,11 @@ export function deepFreeze(obj: T): T { } const stack: any[] = [obj]; while (stack.length > 0) { - let obj = stack.shift(); + const obj = stack.shift(); Object.freeze(obj); for (const key in obj) { if (_hasOwnProperty.call(obj, key)) { - let prop = obj[key]; + const prop = obj[key]; if (typeof prop === 'object' && !Object.isFrozen(prop)) { stack.push(prop); } diff --git a/src/vs/base/common/parsers.ts b/src/vs/base/common/parsers.ts index ffc05a2da4..3694cef195 100644 --- a/src/vs/base/common/parsers.ts +++ b/src/vs/base/common/parsers.ts @@ -81,8 +81,8 @@ export abstract class Parser { protected static merge(destination: T, source: T, overwrite: boolean): void { Object.keys(source).forEach((key: string) => { - let destValue = destination[key]; - let sourceValue = source[key]; + const destValue = destination[key]; + const sourceValue = source[key]; if (Types.isUndefined(sourceValue)) { return; } diff --git a/src/vs/base/common/path.ts b/src/vs/base/common/path.ts new file mode 100644 index 0000000000..31f4b32538 --- /dev/null +++ b/src/vs/base/common/path.ts @@ -0,0 +1,1684 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// NOTE: VSCode's copy of nodejs path library to be usable in common (non-node) namespace +// Copied from: https://github.com/nodejs/node/tree/43dd49c9782848c25e5b03448c8a0f923f13c158 + +/** + * Copyright Joyent, Inc. and other Node contributors. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to permit + * persons to whom the Software is furnished to do so, subject to the + * following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN + * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +import * as process from 'vs/base/common/process'; + +const CHAR_UPPERCASE_A = 65;/* A */ +const CHAR_LOWERCASE_A = 97; /* a */ +const CHAR_UPPERCASE_Z = 90; /* Z */ +const CHAR_LOWERCASE_Z = 122; /* z */ +const CHAR_DOT = 46; /* . */ +const CHAR_FORWARD_SLASH = 47; /* / */ +const CHAR_BACKWARD_SLASH = 92; /* \ */ +const CHAR_COLON = 58; /* : */ +const CHAR_QUESTION_MARK = 63; /* ? */ + +class ErrorInvalidArgType extends Error { + code: 'ERR_INVALID_ARG_TYPE'; + constructor(name: string, expected: string, actual: string) { + // determiner: 'must be' or 'must not be' + let determiner; + if (typeof expected === 'string' && expected.indexOf('not ') === 0) { + determiner = 'must not be'; + expected = expected.replace(/^not /, ''); + } else { + determiner = 'must be'; + } + + let msg; + const type = name.indexOf('.') !== -1 ? 'property' : 'argument'; + msg = `The "${name}" ${type} ${determiner} of type ${expected}`; + + msg += `. Received type ${typeof actual}`; + super(msg); + } +} + +function validateString(value: string, name) { + if (typeof value !== 'string') { + throw new ErrorInvalidArgType(name, 'string', value); + } +} + +function isPathSeparator(code) { + return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; +} + +function isPosixPathSeparator(code) { + return code === CHAR_FORWARD_SLASH; +} + +function isWindowsDeviceRoot(code) { + return code >= CHAR_UPPERCASE_A && code <= CHAR_UPPERCASE_Z || + code >= CHAR_LOWERCASE_A && code <= CHAR_LOWERCASE_Z; +} + +// Resolves . and .. elements in a path with directory names +function normalizeString(path, allowAboveRoot, separator, isPathSeparator) { + let res = ''; + let lastSegmentLength = 0; + let lastSlash = -1; + let dots = 0; + let code; + for (let i = 0; i <= path.length; ++i) { + if (i < path.length) { + code = path.charCodeAt(i); + } + else if (isPathSeparator(code)) { + break; + } + else { + code = CHAR_FORWARD_SLASH; + } + + if (isPathSeparator(code)) { + if (lastSlash === i - 1 || dots === 1) { + // NOOP + } else if (lastSlash !== i - 1 && dots === 2) { + if (res.length < 2 || lastSegmentLength !== 2 || + res.charCodeAt(res.length - 1) !== CHAR_DOT || + res.charCodeAt(res.length - 2) !== CHAR_DOT) { + if (res.length > 2) { + const lastSlashIndex = res.lastIndexOf(separator); + if (lastSlashIndex === -1) { + res = ''; + lastSegmentLength = 0; + } else { + res = res.slice(0, lastSlashIndex); + lastSegmentLength = res.length - 1 - res.lastIndexOf(separator); + } + lastSlash = i; + dots = 0; + continue; + } else if (res.length === 2 || res.length === 1) { + res = ''; + lastSegmentLength = 0; + lastSlash = i; + dots = 0; + continue; + } + } + if (allowAboveRoot) { + if (res.length > 0) { + res += `${separator}..`; + } + else { + res = '..'; + } + lastSegmentLength = 2; + } + } else { + if (res.length > 0) { + res += separator + path.slice(lastSlash + 1, i); + } + else { + res = path.slice(lastSlash + 1, i); + } + lastSegmentLength = i - lastSlash - 1; + } + lastSlash = i; + dots = 0; + } else if (code === CHAR_DOT && dots !== -1) { + ++dots; + } else { + dots = -1; + } + } + return res; +} + +function _format(sep, pathObject) { + const dir = pathObject.dir || pathObject.root; + const base = pathObject.base || + ((pathObject.name || '') + (pathObject.ext || '')); + if (!dir) { + return base; + } + if (dir === pathObject.root) { + return dir + base; + } + return dir + sep + base; +} + +interface ParsedPath { + root: string; + dir: string; + base: string; + ext: string; + name: string; +} + +interface IPath { + normalize(path: string): string; + isAbsolute(path: string): boolean; + join(...paths: string[]): string; + resolve(...pathSegments: string[]): string; + relative(from: string, to: string): string; + dirname(path: string): string; + basename(path: string, ext?: string): string; + extname(path: string): string; + format(pathObject): string; + parse(path: string): ParsedPath; + toNamespacedPath(path: string): string; + sep: '\\' | '/'; + delimiter: string; + win32: IPath | null; + posix: IPath | null; +} + +export const win32: IPath = { + // path.resolve([from ...], to) + resolve(...pathSegments: string[]): string { + let resolvedDevice = ''; + let resolvedTail = ''; + let resolvedAbsolute = false; + + for (let i = pathSegments.length - 1; i >= -1; i--) { + let path; + if (i >= 0) { + path = pathSegments[i]; + } else if (!resolvedDevice) { + path = process.cwd(); + } else { + // Windows has the concept of drive-specific current working + // directories. If we've resolved a drive letter but not yet an + // absolute path, get cwd for that drive, or the process cwd if + // the drive cwd is not available. We're sure the device is not + // a UNC path at this points, because UNC paths are always absolute. + path = process.env['=' + resolvedDevice] || process.cwd(); + + // Verify that a cwd was found and that it actually points + // to our drive. If not, default to the drive's root. + if (path === undefined || + path.slice(0, 3).toLowerCase() !== + resolvedDevice.toLowerCase() + '\\') { + path = resolvedDevice + '\\'; + } + } + + validateString(path, 'path'); + + // Skip empty entries + if (path.length === 0) { + continue; + } + + const len = path.length; + let rootEnd = 0; + let device = ''; + let isAbsolute = false; + const code = path.charCodeAt(0); + + // Try to match a root + if (len > 1) { + if (isPathSeparator(code)) { + // Possible UNC root + + // If we started with a separator, we know we at least have an + // absolute path of some kind (UNC or otherwise) + isAbsolute = true; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) { + break; + } + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + if (!isPathSeparator(path.charCodeAt(j))) { + break; + } + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) { + break; + } + } + if (j === len) { + // We matched a UNC root only + + device = '\\\\' + firstPart + '\\' + path.slice(last); + rootEnd = j; + } else if (j !== last) { + // We matched a UNC root with leftovers + + device = '\\\\' + firstPart + '\\' + path.slice(last, j); + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (path.charCodeAt(1) === CHAR_COLON) { + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2) { + if (isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } + } + } + } + } else if (isPathSeparator(code)) { + // `path` contains just a path separator + rootEnd = 1; + isAbsolute = true; + } + + if (device.length > 0 && + resolvedDevice.length > 0 && + device.toLowerCase() !== resolvedDevice.toLowerCase()) { + // This path points to another device so it is not applicable + continue; + } + + if (resolvedDevice.length === 0 && device.length > 0) { + resolvedDevice = device; + } + if (!resolvedAbsolute) { + resolvedTail = path.slice(rootEnd) + '\\' + resolvedTail; + resolvedAbsolute = isAbsolute; + } + + if (resolvedDevice.length > 0 && resolvedAbsolute) { + break; + } + } + + // At this point the path should be resolved to a full absolute path, + // but handle relative paths to be safe (might happen when process.cwd() + // fails) + + // Normalize the tail path + resolvedTail = normalizeString(resolvedTail, !resolvedAbsolute, '\\', + isPathSeparator); + + return (resolvedDevice + (resolvedAbsolute ? '\\' : '') + resolvedTail) || + '.'; + }, + + normalize(path: string): string { + validateString(path, 'path'); + const len = path.length; + if (len === 0) { + return '.'; + } + let rootEnd = 0; + let device; + let isAbsolute = false; + const code = path.charCodeAt(0); + + // Try to match a root + if (len > 1) { + if (isPathSeparator(code)) { + // Possible UNC root + + // If we started with a separator, we know we at least have an absolute + // path of some kind (UNC or otherwise) + isAbsolute = true; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) { + break; + } + } + if (j < len && j !== last) { + const firstPart = path.slice(last, j); + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + if (!isPathSeparator(path.charCodeAt(j))) { + break; + } + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) { + break; + } + } + if (j === len) { + // We matched a UNC root only + // Return the normalized version of the UNC root since there + // is nothing left to process + + return '\\\\' + firstPart + '\\' + path.slice(last) + '\\'; + } else if (j !== last) { + // We matched a UNC root with leftovers + + device = '\\\\' + firstPart + '\\' + path.slice(last, j); + rootEnd = j; + } + } + } + } else { + rootEnd = 1; + } + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (path.charCodeAt(1) === CHAR_COLON) { + device = path.slice(0, 2); + rootEnd = 2; + if (len > 2) { + if (isPathSeparator(path.charCodeAt(2))) { + // Treat separator following drive name as an absolute path + // indicator + isAbsolute = true; + rootEnd = 3; + } + } + } + } + } else if (isPathSeparator(code)) { + // `path` contains just a path separator, exit early to avoid unnecessary + // work + return '\\'; + } + + let tail; + if (rootEnd < len) { + tail = normalizeString(path.slice(rootEnd), !isAbsolute, '\\', + isPathSeparator); + } else { + tail = ''; + } + if (tail.length === 0 && !isAbsolute) { + tail = '.'; + } + if (tail.length > 0 && isPathSeparator(path.charCodeAt(len - 1))) { + tail += '\\'; + } + if (device === undefined) { + if (isAbsolute) { + if (tail.length > 0) { + return '\\' + tail; + } + else { + return '\\'; + } + } else if (tail.length > 0) { + return tail; + } else { + return ''; + } + } else if (isAbsolute) { + if (tail.length > 0) { + return device + '\\' + tail; + } + else { + return device + '\\'; + } + } else if (tail.length > 0) { + return device + tail; + } else { + return device; + } + }, + + isAbsolute(path: string): boolean { + validateString(path, 'path'); + const len = path.length; + if (len === 0) { + return false; + } + + const code = path.charCodeAt(0); + if (isPathSeparator(code)) { + return true; + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (len > 2 && path.charCodeAt(1) === CHAR_COLON) { + if (isPathSeparator(path.charCodeAt(2))) { + return true; + } + } + } + return false; + }, + + join(...paths: string[]): string { + if (paths.length === 0) { + return '.'; + } + + let joined; + let firstPart; + for (let i = 0; i < paths.length; ++i) { + const arg = paths[i]; + validateString(arg, 'path'); + if (arg.length > 0) { + if (joined === undefined) { + joined = firstPart = arg; + } + else { + joined += '\\' + arg; + } + } + } + + if (joined === undefined) { + return '.'; + } + + // Make sure that the joined path doesn't start with two slashes, because + // normalize() will mistake it for an UNC path then. + // + // This step is skipped when it is very clear that the user actually + // intended to point at an UNC path. This is assumed when the first + // non-empty string arguments starts with exactly two slashes followed by + // at least one more non-slash character. + // + // Note that for normalize() to treat a path as an UNC path it needs to + // have at least 2 components, so we don't filter for that here. + // This means that the user can use join to construct UNC paths from + // a server name and a share name; for example: + // path.join('//server', 'share') -> '\\\\server\\share\\') + let needsReplace = true; + let slashCount = 0; + if (isPathSeparator(firstPart.charCodeAt(0))) { + ++slashCount; + const firstLen = firstPart.length; + if (firstLen > 1) { + if (isPathSeparator(firstPart.charCodeAt(1))) { + ++slashCount; + if (firstLen > 2) { + if (isPathSeparator(firstPart.charCodeAt(2))) { + ++slashCount; + } + else { + // We matched a UNC path in the first part + needsReplace = false; + } + } + } + } + } + if (needsReplace) { + // Find any more consecutive slashes we need to replace + for (; slashCount < joined.length; ++slashCount) { + if (!isPathSeparator(joined.charCodeAt(slashCount))) { + break; + } + } + + // Replace the slashes if needed + if (slashCount >= 2) { + joined = '\\' + joined.slice(slashCount); + } + } + + return win32.normalize(joined); + }, + + + // It will solve the relative path from `from` to `to`, for instance: + // from = 'C:\\orandea\\test\\aaa' + // to = 'C:\\orandea\\impl\\bbb' + // The output of the function should be: '..\\..\\impl\\bbb' + relative(from: string, to: string): string { + validateString(from, 'from'); + validateString(to, 'to'); + + if (from === to) { + return ''; + } + + const fromOrig = win32.resolve(from); + const toOrig = win32.resolve(to); + + if (fromOrig === toOrig) { + return ''; + } + + from = fromOrig.toLowerCase(); + to = toOrig.toLowerCase(); + + if (from === to) { + return ''; + } + + // Trim any leading backslashes + let fromStart = 0; + for (; fromStart < from.length; ++fromStart) { + if (from.charCodeAt(fromStart) !== CHAR_BACKWARD_SLASH) { + break; + } + } + // Trim trailing backslashes (applicable to UNC paths only) + let fromEnd = from.length; + for (; fromEnd - 1 > fromStart; --fromEnd) { + if (from.charCodeAt(fromEnd - 1) !== CHAR_BACKWARD_SLASH) { + break; + } + } + const fromLen = (fromEnd - fromStart); + + // Trim any leading backslashes + let toStart = 0; + for (; toStart < to.length; ++toStart) { + if (to.charCodeAt(toStart) !== CHAR_BACKWARD_SLASH) { + break; + } + } + // Trim trailing backslashes (applicable to UNC paths only) + let toEnd = to.length; + for (; toEnd - 1 > toStart; --toEnd) { + if (to.charCodeAt(toEnd - 1) !== CHAR_BACKWARD_SLASH) { + break; + } + } + const toLen = (toEnd - toStart); + + // Compare paths to find the longest common path from root + const length = (fromLen < toLen ? fromLen : toLen); + let lastCommonSep = -1; + let i = 0; + for (; i <= length; ++i) { + if (i === length) { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='C:\\foo\\bar'; to='C:\\foo\\bar\\baz' + return toOrig.slice(toStart + i + 1); + } else if (i === 2) { + // We get here if `from` is the device root. + // For example: from='C:\\'; to='C:\\foo' + return toOrig.slice(toStart + i); + } + } + if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_BACKWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='C:\\foo\\bar'; to='C:\\foo' + lastCommonSep = i; + } else if (i === 2) { + // We get here if `to` is the device root. + // For example: from='C:\\foo\\bar'; to='C:\\' + lastCommonSep = 3; + } + } + break; + } + const fromCode = from.charCodeAt(fromStart + i); + const toCode = to.charCodeAt(toStart + i); + if (fromCode !== toCode) { + break; + } + else if (fromCode === CHAR_BACKWARD_SLASH) { + lastCommonSep = i; + } + } + + // We found a mismatch before the first common path separator was seen, so + // return the original `to`. + if (i !== length && lastCommonSep === -1) { + return toOrig; + } + + let out = ''; + if (lastCommonSep === -1) { + lastCommonSep = 0; + } + // Generate the relative path based on the path difference between `to` and + // `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === CHAR_BACKWARD_SLASH) { + if (out.length === 0) { + out += '..'; + } + else { + out += '\\..'; + } + } + } + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) { + return out + toOrig.slice(toStart + lastCommonSep, toEnd); + } + else { + toStart += lastCommonSep; + if (toOrig.charCodeAt(toStart) === CHAR_BACKWARD_SLASH) { + ++toStart; + } + return toOrig.slice(toStart, toEnd); + } + }, + + toNamespacedPath(path: string): string { + // Note: this will *probably* throw somewhere. + if (typeof path !== 'string') { + return path; + } + + if (path.length === 0) { + return ''; + } + + const resolvedPath = win32.resolve(path); + + if (resolvedPath.length >= 3) { + if (resolvedPath.charCodeAt(0) === CHAR_BACKWARD_SLASH) { + // Possible UNC root + + if (resolvedPath.charCodeAt(1) === CHAR_BACKWARD_SLASH) { + const code = resolvedPath.charCodeAt(2); + if (code !== CHAR_QUESTION_MARK && code !== CHAR_DOT) { + // Matched non-long UNC root, convert the path to a long UNC path + return '\\\\?\\UNC\\' + resolvedPath.slice(2); + } + } + } else if (isWindowsDeviceRoot(resolvedPath.charCodeAt(0))) { + // Possible device root + + if (resolvedPath.charCodeAt(1) === CHAR_COLON && + resolvedPath.charCodeAt(2) === CHAR_BACKWARD_SLASH) { + // Matched device root, convert the path to a long UNC path + return '\\\\?\\' + resolvedPath; + } + } + } + + return path; + }, + + dirname(path: string): string { + validateString(path, 'path'); + const len = path.length; + if (len === 0) { + return '.'; + } + let rootEnd = -1; + let end = -1; + let matchedSlash = true; + let offset = 0; + const code = path.charCodeAt(0); + + // Try to match a root + if (len > 1) { + if (isPathSeparator(code)) { + // Possible UNC root + + rootEnd = offset = 1; + + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) { + break; + } + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + if (!isPathSeparator(path.charCodeAt(j))) { + break; + } + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) { + break; + } + } + if (j === len) { + // We matched a UNC root only + return path; + } + if (j !== last) { + // We matched a UNC root with leftovers + + // Offset by 1 to include the separator after the UNC root to + // treat it as a "normal root" on top of a (UNC) root + rootEnd = offset = j + 1; + } + } + } + } + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (path.charCodeAt(1) === CHAR_COLON) { + rootEnd = offset = 2; + if (len > 2) { + if (isPathSeparator(path.charCodeAt(2))) { + rootEnd = offset = 3; + } + } + } + } + } else if (isPathSeparator(code)) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work + return path; + } + + for (let i = len - 1; i >= offset; --i) { + if (isPathSeparator(path.charCodeAt(i))) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } + + if (end === -1) { + if (rootEnd === -1) { + return '.'; + } + else { + end = rootEnd; + } + } + return path.slice(0, end); + }, + + basename(path: string, ext?: string): string { + if (ext !== undefined) { + validateString(ext, 'ext'); + } + validateString(path, 'path'); + let start = 0; + let end = -1; + let matchedSlash = true; + let i; + + // Check for a drive letter prefix so as not to mistake the following + // path separator as an extra separator at the end of the path that can be + // disregarded + if (path.length >= 2) { + const drive = path.charCodeAt(0); + if (isWindowsDeviceRoot(drive)) { + if (path.charCodeAt(1) === CHAR_COLON) { + start = 2; + } + } + } + + if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { + if (ext.length === path.length && ext === path) { + return ''; + } + let extIdx = ext.length - 1; + let firstNonSlashEnd = -1; + for (i = path.length - 1; i >= start; --i) { + const code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } + + if (start === end) { + end = firstNonSlashEnd; + } + else if (end === -1) { + end = path.length; + } + return path.slice(start, end); + } else { + for (i = path.length - 1; i >= start; --i) { + if (isPathSeparator(path.charCodeAt(i))) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) { + return ''; + } + return path.slice(start, end); + } + }, + + extname(path: string): string { + validateString(path, 'path'); + let start = 0; + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Check for a drive letter prefix so as not to mistake the following + // path separator as an extra separator at the end of the path that can be + // disregarded + + if (path.length >= 2 && + path.charCodeAt(1) === CHAR_COLON && + isWindowsDeviceRoot(path.charCodeAt(0))) { + start = startPart = 2; + } + + for (let i = path.length - 1; i >= start; --i) { + const code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) { + startDot = i; + } + else if (preDotState !== 1) { + preDotState = 1; + } + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + return ''; + } + return path.slice(startDot, end); + }, + + format(pathObject): string { + if (pathObject === null || typeof pathObject !== 'object') { + throw new ErrorInvalidArgType('pathObject', 'Object', pathObject); + } + + return _format('\\', pathObject); + }, + + + parse(path) { + validateString(path, 'path'); + + const ret = { root: '', dir: '', base: '', ext: '', name: '' }; + if (path.length === 0) { + return ret; + } + + const len = path.length; + let rootEnd = 0; + let code = path.charCodeAt(0); + + // Try to match a root + if (len > 1) { + if (isPathSeparator(code)) { + // Possible UNC root + + rootEnd = 1; + if (isPathSeparator(path.charCodeAt(1))) { + // Matched double path separator at beginning + let j = 2; + let last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) { + break; + } + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more path separators + for (; j < len; ++j) { + if (!isPathSeparator(path.charCodeAt(j))) { + break; + } + } + if (j < len && j !== last) { + // Matched! + last = j; + // Match 1 or more non-path separators + for (; j < len; ++j) { + if (isPathSeparator(path.charCodeAt(j))) { + break; + } + } + if (j === len) { + // We matched a UNC root only + + rootEnd = j; + } else if (j !== last) { + // We matched a UNC root with leftovers + + rootEnd = j + 1; + } + } + } + } + } else if (isWindowsDeviceRoot(code)) { + // Possible device root + + if (path.charCodeAt(1) === CHAR_COLON) { + rootEnd = 2; + if (len > 2) { + if (isPathSeparator(path.charCodeAt(2))) { + if (len === 3) { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + rootEnd = 3; + } + } else { + // `path` contains just a drive root, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + } + } + } else if (isPathSeparator(code)) { + // `path` contains just a path separator, exit early to avoid + // unnecessary work + ret.root = ret.dir = path; + return ret; + } + + if (rootEnd > 0) { + ret.root = path.slice(0, rootEnd); + } + + let startDot = -1; + let startPart = rootEnd; + let end = -1; + let matchedSlash = true; + let i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Get non-dir info + for (; i >= rootEnd; --i) { + code = path.charCodeAt(i); + if (isPathSeparator(code)) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) { + startDot = i; + } + else if (preDotState !== 1) { + preDotState = 1; + } + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + if (end !== -1) { + ret.base = ret.name = path.slice(startPart, end); + } + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + ret.ext = path.slice(startDot, end); + } + + // If the directory is the root, use the entire root as the `dir` including + // the trailing slash if any (`C:\abc` -> `C:\`). Otherwise, strip out the + // trailing slash (`C:\abc\def` -> `C:\abc`). + if (startPart > 0 && startPart !== rootEnd) { + ret.dir = path.slice(0, startPart - 1); + } + else { + ret.dir = ret.root; + } + + return ret; + }, + + sep: '\\', + delimiter: ';', + win32: null, + posix: null +}; + +export const posix: IPath = { + // path.resolve([from ...], to) + resolve(...pathSegments: string[]): string { + let resolvedPath = ''; + let resolvedAbsolute = false; + + for (let i = pathSegments.length - 1; i >= -1 && !resolvedAbsolute; i--) { + let path; + if (i >= 0) { + path = pathSegments[i]; + } + else { + path = process.cwd(); + } + + validateString(path, 'path'); + + // Skip empty entries + if (path.length === 0) { + continue; + } + + resolvedPath = path + '/' + resolvedPath; + resolvedAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + } + + // At this point the path should be resolved to a full absolute path, but + // handle relative paths to be safe (might happen when process.cwd() fails) + + // Normalize the path + resolvedPath = normalizeString(resolvedPath, !resolvedAbsolute, '/', + isPosixPathSeparator); + + if (resolvedAbsolute) { + if (resolvedPath.length > 0) { + return '/' + resolvedPath; + } + else { + return '/'; + } + } else if (resolvedPath.length > 0) { + return resolvedPath; + } else { + return '.'; + } + }, + + normalize(path: string): string { + validateString(path, 'path'); + + if (path.length === 0) { + return '.'; + } + + const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + const trailingSeparator = + path.charCodeAt(path.length - 1) === CHAR_FORWARD_SLASH; + + // Normalize the path + path = normalizeString(path, !isAbsolute, '/', isPosixPathSeparator); + + if (path.length === 0 && !isAbsolute) { + path = '.'; + } + if (path.length > 0 && trailingSeparator) { + path += '/'; + } + + if (isAbsolute) { + return '/' + path; + } + return path; + }, + + isAbsolute(path: string): boolean { + validateString(path, 'path'); + return path.length > 0 && path.charCodeAt(0) === CHAR_FORWARD_SLASH; + }, + + join(...paths: string[]): string { + if (paths.length === 0) { + return '.'; + } + let joined; + for (let i = 0; i < paths.length; ++i) { + const arg = arguments[i]; + validateString(arg, 'path'); + if (arg.length > 0) { + if (joined === undefined) { + joined = arg; + } + else { + joined += '/' + arg; + } + } + } + if (joined === undefined) { + return '.'; + } + return posix.normalize(joined); + }, + + relative(from: string, to: string): string { + validateString(from, 'from'); + validateString(to, 'to'); + + if (from === to) { + return ''; + } + + from = posix.resolve(from); + to = posix.resolve(to); + + if (from === to) { + return ''; + } + + // Trim any leading backslashes + let fromStart = 1; + for (; fromStart < from.length; ++fromStart) { + if (from.charCodeAt(fromStart) !== CHAR_FORWARD_SLASH) { + break; + } + } + const fromEnd = from.length; + const fromLen = (fromEnd - fromStart); + + // Trim any leading backslashes + let toStart = 1; + for (; toStart < to.length; ++toStart) { + if (to.charCodeAt(toStart) !== CHAR_FORWARD_SLASH) { + break; + } + } + const toEnd = to.length; + const toLen = (toEnd - toStart); + + // Compare paths to find the longest common path from root + const length = (fromLen < toLen ? fromLen : toLen); + let lastCommonSep = -1; + let i = 0; + for (; i <= length; ++i) { + if (i === length) { + if (toLen > length) { + if (to.charCodeAt(toStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `from` is the exact base path for `to`. + // For example: from='/foo/bar'; to='/foo/bar/baz' + return to.slice(toStart + i + 1); + } else if (i === 0) { + // We get here if `from` is the root + // For example: from='/'; to='/foo' + return to.slice(toStart + i); + } + } else if (fromLen > length) { + if (from.charCodeAt(fromStart + i) === CHAR_FORWARD_SLASH) { + // We get here if `to` is the exact base path for `from`. + // For example: from='/foo/bar/baz'; to='/foo/bar' + lastCommonSep = i; + } else if (i === 0) { + // We get here if `to` is the root. + // For example: from='/foo'; to='/' + lastCommonSep = 0; + } + } + break; + } + const fromCode = from.charCodeAt(fromStart + i); + const toCode = to.charCodeAt(toStart + i); + if (fromCode !== toCode) { + break; + } + else if (fromCode === CHAR_FORWARD_SLASH) { + lastCommonSep = i; + } + } + + let out = ''; + // Generate the relative path based on the path difference between `to` + // and `from` + for (i = fromStart + lastCommonSep + 1; i <= fromEnd; ++i) { + if (i === fromEnd || from.charCodeAt(i) === CHAR_FORWARD_SLASH) { + if (out.length === 0) { + out += '..'; + } + else { + out += '/..'; + } + } + } + + // Lastly, append the rest of the destination (`to`) path that comes after + // the common path parts + if (out.length > 0) { + return out + to.slice(toStart + lastCommonSep); + } + else { + toStart += lastCommonSep; + if (to.charCodeAt(toStart) === CHAR_FORWARD_SLASH) { + ++toStart; + } + return to.slice(toStart); + } + }, + + toNamespacedPath(path: string): string { + // Non-op on posix systems + return path; + }, + + dirname(path: string): string { + validateString(path, 'path'); + if (path.length === 0) { + return '.'; + } + const hasRoot = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + let end = -1; + let matchedSlash = true; + for (let i = path.length - 1; i >= 1; --i) { + if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { + if (!matchedSlash) { + end = i; + break; + } + } else { + // We saw the first non-path separator + matchedSlash = false; + } + } + + if (end === -1) { + return hasRoot ? '/' : '.'; + } + if (hasRoot && end === 1) { + return '//'; + } + return path.slice(0, end); + }, + + basename(path: string, ext?: string): string { + if (ext !== undefined) { + validateString(ext, 'ext'); + } + validateString(path, 'path'); + + let start = 0; + let end = -1; + let matchedSlash = true; + let i; + + if (ext !== undefined && ext.length > 0 && ext.length <= path.length) { + if (ext.length === path.length && ext === path) { + return ''; + } + let extIdx = ext.length - 1; + let firstNonSlashEnd = -1; + for (i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else { + if (firstNonSlashEnd === -1) { + // We saw the first non-path separator, remember this index in case + // we need it if the extension ends up not matching + matchedSlash = false; + firstNonSlashEnd = i + 1; + } + if (extIdx >= 0) { + // Try to match the explicit extension + if (code === ext.charCodeAt(extIdx)) { + if (--extIdx === -1) { + // We matched the extension, so mark this as the end of our path + // component + end = i; + } + } else { + // Extension does not match, so our result is the entire path + // component + extIdx = -1; + end = firstNonSlashEnd; + } + } + } + } + + if (start === end) { + end = firstNonSlashEnd; + } + else if (end === -1) { + end = path.length; + } + return path.slice(start, end); + } else { + for (i = path.length - 1; i >= 0; --i) { + if (path.charCodeAt(i) === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + start = i + 1; + break; + } + } else if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // path component + matchedSlash = false; + end = i + 1; + } + } + + if (end === -1) { + return ''; + } + return path.slice(start, end); + } + }, + + extname(path: string): string { + validateString(path, 'path'); + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + for (let i = path.length - 1; i >= 0; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) { + startDot = i; + } + else if (preDotState !== 1) { + preDotState = 1; + } + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + return ''; + } + return path.slice(startDot, end); + }, + + format(pathObject): string { + if (pathObject === null || typeof pathObject !== 'object') { + throw new ErrorInvalidArgType('pathObject', 'Object', pathObject); + } + + return _format('/', pathObject); + }, + + parse(path: string): ParsedPath { + validateString(path, 'path'); + + const ret = { root: '', dir: '', base: '', ext: '', name: '' }; + if (path.length === 0) { + return ret; + } + const isAbsolute = path.charCodeAt(0) === CHAR_FORWARD_SLASH; + let start; + if (isAbsolute) { + ret.root = '/'; + start = 1; + } else { + start = 0; + } + let startDot = -1; + let startPart = 0; + let end = -1; + let matchedSlash = true; + let i = path.length - 1; + + // Track the state of characters (if any) we see before our first dot and + // after any path separator we find + let preDotState = 0; + + // Get non-dir info + for (; i >= start; --i) { + const code = path.charCodeAt(i); + if (code === CHAR_FORWARD_SLASH) { + // If we reached a path separator that was not part of a set of path + // separators at the end of the string, stop now + if (!matchedSlash) { + startPart = i + 1; + break; + } + continue; + } + if (end === -1) { + // We saw the first non-path separator, mark this as the end of our + // extension + matchedSlash = false; + end = i + 1; + } + if (code === CHAR_DOT) { + // If this is our first dot, mark it as the start of our extension + if (startDot === -1) { + startDot = i; + } + else if (preDotState !== 1) { + preDotState = 1; + } + } else if (startDot !== -1) { + // We saw a non-dot and non-path separator before our dot, so we should + // have a good chance at having a non-empty extension + preDotState = -1; + } + } + + if (startDot === -1 || + end === -1 || + // We saw a non-dot character immediately before the dot + preDotState === 0 || + // The (right-most) trimmed path component is exactly '..' + (preDotState === 1 && + startDot === end - 1 && + startDot === startPart + 1)) { + if (end !== -1) { + if (startPart === 0 && isAbsolute) { + ret.base = ret.name = path.slice(1, end); + } + else { + ret.base = ret.name = path.slice(startPart, end); + } + } + } else { + if (startPart === 0 && isAbsolute) { + ret.name = path.slice(1, startDot); + ret.base = path.slice(1, end); + } else { + ret.name = path.slice(startPart, startDot); + ret.base = path.slice(startPart, end); + } + ret.ext = path.slice(startDot, end); + } + + if (startPart > 0) { + ret.dir = path.slice(0, startPart - 1); + } + else if (isAbsolute) { + ret.dir = '/'; + } + + return ret; + }, + + sep: '/', + delimiter: ':', + win32: null, + posix: null +}; + +posix.win32 = win32.win32 = win32; +posix.posix = win32.posix = posix; + +export const normalize = (process.platform === 'win32' ? win32.normalize : posix.normalize); +export const isAbsolute = (process.platform === 'win32' ? win32.isAbsolute : posix.isAbsolute); +export const join = (process.platform === 'win32' ? win32.join : posix.join); +export const resolve = (process.platform === 'win32' ? win32.resolve : posix.resolve); +export const relative = (process.platform === 'win32' ? win32.relative : posix.relative); +export const dirname = (process.platform === 'win32' ? win32.dirname : posix.dirname); +export const basename = (process.platform === 'win32' ? win32.basename : posix.basename); +export const extname = (process.platform === 'win32' ? win32.extname : posix.extname); +export const format = (process.platform === 'win32' ? win32.format : posix.format); +export const parse = (process.platform === 'win32' ? win32.parse : posix.parse); +export const toNamespacedPath = (process.platform === 'win32' ? win32.toNamespacedPath : posix.toNamespacedPath); +export const sep = (process.platform === 'win32' ? win32.sep : posix.sep); +export const delimiter = (process.platform === 'win32' ? win32.delimiter : posix.delimiter); diff --git a/src/vs/base/common/performance.js b/src/vs/base/common/performance.js index 3fc564cf19..06d070f529 100644 --- a/src/vs/base/common/performance.js +++ b/src/vs/base/common/performance.js @@ -5,39 +5,26 @@ 'use strict'; -/*global define*/ +//@ts-check -// This module can be loaded in an amd and commonjs-context. -// Because we want both instances to use the same perf-data -// we store them globally -// stores data as: 'name','timestamp' +function _factory(sharedObj) { -if (typeof define !== "function" && typeof module === "object" && typeof module.exports === "object") { - // this is commonjs, fake amd - global.define = function (_dep, callback) { - module.exports = callback(); - global.define = undefined; - }; -} - -define([], function () { - - global._performanceEntries = global._performanceEntries || []; + sharedObj._performanceEntries = sharedObj._performanceEntries || []; const _dataLen = 2; const _timeStamp = typeof console.timeStamp === 'function' ? console.timeStamp.bind(console) : () => { }; function importEntries(entries) { - global._performanceEntries.splice(0, 0, ...entries); + sharedObj._performanceEntries.splice(0, 0, ...entries); } function exportEntries() { - return global._performanceEntries.slice(0); + return sharedObj._performanceEntries.slice(0); } function getEntries() { const result = []; - const entries = global._performanceEntries; + const entries = sharedObj._performanceEntries; for (let i = 0; i < entries.length; i += _dataLen) { result.push({ name: entries[i], @@ -48,7 +35,7 @@ define([], function () { } function getEntry(name) { - const entries = global._performanceEntries; + const entries = sharedObj._performanceEntries; for (let i = 0; i < entries.length; i += _dataLen) { if (entries[i] === name) { return { @@ -60,7 +47,7 @@ define([], function () { } function getDuration(from, to) { - const entries = global._performanceEntries; + const entries = sharedObj._performanceEntries; let target = to; let endIndex = 0; for (let i = entries.length - _dataLen; i >= 0; i -= _dataLen) { @@ -79,11 +66,11 @@ define([], function () { } function mark(name) { - global._performanceEntries.push(name, Date.now()); + sharedObj._performanceEntries.push(name, Date.now()); _timeStamp(name); } - var exports = { + const exports = { mark: mark, getEntries: getEntries, getEntry: getEntry, @@ -93,4 +80,29 @@ define([], function () { }; return exports; -}); +} + +// This module can be loaded in an amd and commonjs-context. +// Because we want both instances to use the same perf-data +// we store them globally + +let sharedObj; +if (typeof global === 'object') { + // nodejs + sharedObj = global; +} else if (typeof self === 'object') { + // browser + sharedObj = self; +} else { + sharedObj = {}; +} + +if (typeof define === 'function') { + // amd + define([], function () { return _factory(sharedObj); }); +} else if (typeof module === "object" && typeof module.exports === "object") { + // commonjs + module.exports = _factory(sharedObj); +} else { + // invalid context... +} diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index a33eda5fdc..39e671c41c 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -3,13 +3,15 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +const LANGUAGE_DEFAULT = 'en'; + let _isWindows = false; let _isMacintosh = false; let _isLinux = false; let _isNative = false; let _isWeb = false; let _locale: string | undefined = undefined; -let _language: string | undefined = undefined; +let _language: string = LANGUAGE_DEFAULT; let _translationsConfigFile: string | undefined = undefined; interface NLSConfig { @@ -32,17 +34,15 @@ interface INodeProcess { }; type?: string; } -declare let process: INodeProcess; -declare let global: any; +declare const process: INodeProcess; +declare const global: any; interface INavigator { userAgent: string; language: string; } -declare let navigator: INavigator; -declare let self: any; - -export const LANGUAGE_DEFAULT = 'en'; +declare const navigator: INavigator; +declare const self: any; const isElectronRenderer = (typeof process !== 'undefined' && typeof process.versions !== 'undefined' && typeof process.versions.electron !== 'undefined' && process.type === 'renderer'); @@ -120,6 +120,27 @@ export function isRootUser(): boolean { */ export const language = _language; +export namespace Language { + + export function value(): string { + return language; + } + + export function isDefaultVariant(): boolean { + if (language.length === 2) { + return language === 'en'; + } else if (language.length >= 3) { + return language[0] === 'e' && language[1] === 'n' && language[2] === '-'; + } else { + return false; + } + } + + export function isDefault(): boolean { + return language === 'en'; + } +} + /** * The OS locale or the locale specified by --locale. The format of * the string is all lower case (e.g. zh-tw for Traditional @@ -155,14 +176,3 @@ export const enum OperatingSystem { Linux = 3 } export const OS = (_isMacintosh ? OperatingSystem.Macintosh : (_isWindows ? OperatingSystem.Windows : OperatingSystem.Linux)); - -export const enum AccessibilitySupport { - /** - * This should be the browser case where it is not known if a screen reader is attached or no. - */ - Unknown = 0, - - Disabled = 1, - - Enabled = 2 -} diff --git a/src/vs/base/common/process.ts b/src/vs/base/common/process.ts new file mode 100644 index 0000000000..412f649106 --- /dev/null +++ b/src/vs/base/common/process.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { isWindows, isMacintosh, setImmediate } from 'vs/base/common/platform'; + +interface IProcess { + platform: string; + env: object; + + cwd(): string; + nextTick(callback: (...args: any[]) => void): number; +} + +declare const process: IProcess; +const safeProcess: IProcess = (typeof process === 'undefined') ? { + cwd(): string { return '/'; }, + env: Object.create(null), + get platform(): string { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; }, + nextTick(callback: (...args: any[]) => void): number { return setImmediate(callback); } +} : process; + +export const cwd = safeProcess.cwd; +export const env = safeProcess.env; +export const platform = safeProcess.platform; +export const nextTick = safeProcess.nextTick; diff --git a/src/vs/base/common/processes.ts b/src/vs/base/common/processes.ts index b883e4fd29..d0232d52c2 100644 --- a/src/vs/base/common/processes.ts +++ b/src/vs/base/common/processes.ts @@ -3,6 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IProcessEnvironment } from 'vs/base/common/platform'; + /** * Options to be passed to the external program or shell. */ @@ -84,3 +86,30 @@ export const enum TerminateResponseCode { AccessDenied = 2, ProcessNotFound = 3, } + +/** + * Sanitizes a VS Code process environment by removing all Electron/VS Code-related values. + */ +export function sanitizeProcessEnvironment(env: IProcessEnvironment, ...preserve: string[]): void { + const set = preserve.reduce((set, key) => { + set[key] = true; + return set; + }, {} as Record); + const keysToRemove = [ + /^ELECTRON_.+$/, + /^GOOGLE_API_KEY$/, + /^VSCODE_.+$/, + /^SNAP(|_.*)$/ + ]; + const envKeys = Object.keys(env); + envKeys + .filter(key => !set[key]) + .forEach(envKey => { + for (let i = 0; i < keysToRemove.length; i++) { + if (envKey.search(keysToRemove[i]) !== -1) { + delete env[envKey]; + break; + } + } + }); +} diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts index c82c4fb355..09ecf79499 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -3,12 +3,15 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as paths from 'vs/base/common/paths'; +import * as extpath from 'vs/base/common/extpath'; +import * as paths from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { CharCode } from 'vs/base/common/charCode'; +import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob'; +import { TernarySearchTree } from 'vs/base/common/map'; export function getComparisonKey(resource: URI): string { return hasToIgnoreCase(resource) ? resource.toString().toLowerCase() : resource.toString(); @@ -32,22 +35,24 @@ export function basenameOrAuthority(resource: URI): string { export function isEqualOrParent(base: URI, parentCandidate: URI, ignoreCase = hasToIgnoreCase(base)): boolean { if (base.scheme === parentCandidate.scheme) { if (base.scheme === Schemas.file) { - return paths.isEqualOrParent(fsPath(base), fsPath(parentCandidate), ignoreCase); + return extpath.isEqualOrParent(originalFSPath(base), originalFSPath(parentCandidate), ignoreCase); } - if (isEqualAuthority(base.authority, parentCandidate.authority, ignoreCase)) { - return paths.isEqualOrParent(base.path, parentCandidate.path, ignoreCase, '/'); + if (isEqualAuthority(base.authority, parentCandidate.authority)) { + return extpath.isEqualOrParent(base.path, parentCandidate.path, ignoreCase, '/'); } } return false; } -function isEqualAuthority(a1: string, a2: string, ignoreCase?: boolean) { - return a1 === a2 || ignoreCase && a1 && a2 && equalsIgnoreCase(a1, a2); +/** + * Tests wheter the two authorities are the same + */ +export function isEqualAuthority(a1: string, a2: string) { + return a1 === a2 || equalsIgnoreCase(a1, a2); } export function isEqual(first: URI | undefined, second: URI | undefined, ignoreCase = hasToIgnoreCase(first)): boolean { - const identityEquals = (first === second); - if (identityEquals) { + if (first === second) { return true; } @@ -55,15 +60,20 @@ export function isEqual(first: URI | undefined, second: URI | undefined, ignoreC return false; } - if (ignoreCase) { - return equalsIgnoreCase(first.toString(), second.toString()); + if (first.scheme !== second.scheme || !isEqualAuthority(first.authority, second.authority)) { + return false; } - return first.toString() === second.toString(); + const p1 = first.path || '/', p2 = second.path || '/'; + return p1 === p2 || ignoreCase && equalsIgnoreCase(p1 || '/', p2 || '/'); } export function basename(resource: URI): string { - return paths.basename(resource.path); + return paths.posix.basename(resource.path); +} + +export function extname(resource: URI): string { + return paths.posix.extname(resource.path); } /** @@ -72,16 +82,17 @@ export function basename(resource: URI): string { * @param resource The input URI. * @returns The URI representing the directory of the input URI. */ -export function dirname(resource: URI): URI | null { +export function dirname(resource: URI): URI { if (resource.path.length === 0) { return resource; } if (resource.scheme === Schemas.file) { - return URI.file(paths.dirname(fsPath(resource))); + return URI.file(paths.dirname(originalFSPath(resource))); } - let dirname = paths.dirname(resource.path, '/'); + let dirname = paths.posix.dirname(resource.path); if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) { - return null; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character + console.error(`dirname("${resource.toString})) resulted in a relative path`); + dirname = '/'; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character } return resource.with({ path: dirname @@ -89,18 +100,18 @@ export function dirname(resource: URI): URI | null { } /** - * Join a URI path with a path fragment and normalizes the resulting path. + * Join a URI path with path fragments and normalizes the resulting path. * * @param resource The input URI. * @param pathFragment The path fragment to add to the URI path. * @returns The resulting URI. */ -export function joinPath(resource: URI, pathFragment: string): URI { +export function joinPath(resource: URI, ...pathFragment: string[]): URI { let joinedPath: string; if (resource.scheme === Schemas.file) { - joinedPath = URI.file(paths.join(fsPath(resource), pathFragment)).path; + joinedPath = URI.file(paths.join(originalFSPath(resource), ...pathFragment)).path; } else { - joinedPath = paths.join(resource.path, pathFragment); + joinedPath = paths.posix.join(resource.path || '/', ...pathFragment); } return resource.with({ path: joinedPath @@ -119,9 +130,9 @@ export function normalizePath(resource: URI): URI { } let normalizedPath: string; if (resource.scheme === Schemas.file) { - normalizedPath = URI.file(paths.normalize(fsPath(resource))).path; + normalizedPath = URI.file(paths.normalize(originalFSPath(resource))).path; } else { - normalizedPath = paths.normalize(resource.path); + normalizedPath = paths.posix.normalize(resource.path); } return resource.with({ path: normalizedPath @@ -132,7 +143,7 @@ export function normalizePath(resource: URI): URI { * Returns the fsPath of an URI where the drive letter is not normalized. * See #56403. */ -export function fsPath(uri: URI): string { +export function originalFSPath(uri: URI): string { let value: string; const uriPath = uri.path; if (uri.authority && uriPath.length > 1 && uri.scheme === 'file') { @@ -141,7 +152,7 @@ export function fsPath(uri: URI): string { } else if ( isWindows && uriPath.charCodeAt(0) === CharCode.Slash - && paths.isWindowsDriveLetter(uriPath.charCodeAt(1)) + && extpath.isWindowsDriveLetter(uriPath.charCodeAt(1)) && uriPath.charCodeAt(2) === CharCode.Colon ) { value = uriPath.substr(1); @@ -159,7 +170,64 @@ export function fsPath(uri: URI): string { * Returns true if the URI path is absolute. */ export function isAbsolutePath(resource: URI): boolean { - return paths.isAbsolute(resource.path); + return !!resource.path && resource.path[0] === '/'; +} + +/** + * Returns true if the URI path has a trailing path separator + */ +export function hasTrailingPathSeparator(resource: URI): boolean { + if (resource.scheme === Schemas.file) { + const fsp = originalFSPath(resource); + return fsp.length > extpath.getRoot(fsp).length && fsp[fsp.length - 1] === paths.sep; + } else { + const p = resource.path; + return p.length > 1 && p.charCodeAt(p.length - 1) === CharCode.Slash; // ignore the slash at offset 0 + } +} + + +/** + * Removes a trailing path seperator, if theres one. + * Important: Doesn't remove the first slash, it would make the URI invalid + */ +export function removeTrailingPathSeparator(resource: URI): URI { + if (hasTrailingPathSeparator(resource)) { + return resource.with({ path: resource.path.substr(0, resource.path.length - 1) }); + } + return resource; +} + + +/** + * Returns a relative path between two URIs. If the URIs don't have the same schema or authority, `undefined` is returned. + * The returned relative path always uses forward slashes. + */ +export function relativePath(from: URI, to: URI): string | undefined { + if (from.scheme !== to.scheme || !isEqualAuthority(from.authority, to.authority)) { + return undefined; + } + if (from.scheme === Schemas.file) { + const relativePath = paths.relative(from.path, to.path); + return isWindows ? extpath.toSlashes(relativePath) : relativePath; + } + return paths.posix.relative(from.path || '/', to.path || '/'); +} + +/** + * Resolves a absolute or relative path against a base URI. + */ +export function resolvePath(base: URI, path: string): URI { + if (base.scheme === Schemas.file) { + const newURI = URI.file(paths.resolve(originalFSPath(base), path)); + return base.with({ + authority: newURI.authority, + path: newURI.path + }); + } + return base.with({ + path: paths.posix.resolve(base.path, path) + }); } export function distinctParents(items: T[], resourceAccessor: (item: T) => URI): T[] { @@ -182,21 +250,6 @@ export function distinctParents(items: T[], resourceAccessor: (item: T) => UR return distinctParents; } -/** - * Tests whether the given URL is a file URI created by `URI.parse` instead of `URI.file`. - * Such URI have no scheme or scheme that consist of a single letter (windows drive letter) - * @param candidate The URI to test - * @returns A corrected, real file URI if the input seems to be malformed. - * Undefined is returned if the input URI looks fine. - */ -export function isMalformedFileUri(candidate: URI): URI | undefined { - if (!candidate.scheme || isWindows && candidate.scheme.match(/^[a-zA-Z]$/)) { - return URI.file((candidate.scheme ? candidate.scheme + ':' : '') + candidate.path); - } - return undefined; -} - - /** * Data URI related helpers. */ @@ -230,3 +283,31 @@ export namespace DataUri { return metadata; } } + + +export class ResourceGlobMatcher { + + private readonly globalExpression: ParsedExpression; + private readonly expressionsByRoot: TernarySearchTree<{ root: URI, expression: ParsedExpression }> = TernarySearchTree.forPaths<{ root: URI, expression: ParsedExpression }>(); + + constructor( + globalExpression: IExpression, + rootExpressions: { root: URI, expression: IExpression }[] + ) { + this.globalExpression = parse(globalExpression); + for (const expression of rootExpressions) { + this.expressionsByRoot.set(expression.root.toString(), { root: expression.root, expression: parse(expression.expression) }); + } + } + + matches(resource: URI): boolean { + const rootExpression = this.expressionsByRoot.findSubstr(resource.toString()); + if (rootExpression) { + const path = relativePath(rootExpression.root, resource); + if (path && !!rootExpression.expression(path)) { + return true; + } + } + return !!this.globalExpression(resource.path); + } +} diff --git a/src/vs/base/common/scrollable.ts b/src/vs/base/common/scrollable.ts index b5ded68666..7e1b33d2d3 100644 --- a/src/vs/base/common/scrollable.ts +++ b/src/vs/base/common/scrollable.ts @@ -117,13 +117,13 @@ export class ScrollState implements IScrollDimensions, IScrollPosition { } public createScrollEvent(previous: ScrollState): ScrollEvent { - let widthChanged = (this.width !== previous.width); - let scrollWidthChanged = (this.scrollWidth !== previous.scrollWidth); - let scrollLeftChanged = (this.scrollLeft !== previous.scrollLeft); + const widthChanged = (this.width !== previous.width); + const scrollWidthChanged = (this.scrollWidth !== previous.scrollWidth); + const scrollLeftChanged = (this.scrollLeft !== previous.scrollLeft); - let heightChanged = (this.height !== previous.height); - let scrollHeightChanged = (this.scrollHeight !== previous.scrollHeight); - let scrollTopChanged = (this.scrollTop !== previous.scrollTop); + const heightChanged = (this.height !== previous.height); + const scrollHeightChanged = (this.scrollHeight !== previous.scrollHeight); + const scrollTopChanged = (this.scrollTop !== previous.scrollTop); return { width: this.width, diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index dce97a87be..658ec817a9 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -21,8 +21,8 @@ export function isFalsyOrWhitespace(str: string | undefined): boolean { * @returns the provided number with the given number of preceding zeros. */ export function pad(n: number, l: number, char: string = '0'): string { - let str = '' + n; - let r = [str]; + const str = '' + n; + const r = [str]; for (let i = str.length; i < l; i++) { r.push(char); @@ -44,7 +44,7 @@ export function format(value: string, ...args: any[]): string { return value; } return value.replace(_formatRegexp, function (match, group) { - let idx = parseInt(group, 10); + const idx = parseInt(group, 10); return isNaN(idx) || idx < 0 || idx >= args.length ? match : args[idx]; @@ -79,7 +79,7 @@ export function escapeRegExpCharacters(value: string): string { * @param needle the thing to trim (default is a blank) */ export function trim(haystack: string, needle: string = ' '): string { - let trimmed = ltrim(haystack, needle); + const trimmed = ltrim(haystack, needle); return rtrim(trimmed, needle); } @@ -93,7 +93,7 @@ export function ltrim(haystack: string, needle: string): string { return haystack; } - let needleLen = needle.length; + const needleLen = needle.length; if (needleLen === 0 || haystack.length === 0) { return haystack; } @@ -116,7 +116,7 @@ export function rtrim(haystack: string, needle: string): string { return haystack; } - let needleLen = needle.length, + const needleLen = needle.length, haystackLen = haystack.length; if (needleLen === 0 || haystackLen === 0) { @@ -173,7 +173,7 @@ export function startsWith(haystack: string, needle: string): boolean { * Determines if haystack ends with needle. */ export function endsWith(haystack: string, needle: string): boolean { - let diff = haystack.length - needle.length; + const diff = haystack.length - needle.length; if (diff > 0) { return haystack.indexOf(needle, diff) === diff; } else if (diff === 0) { @@ -232,7 +232,7 @@ export function regExpLeadsToEndlessLoop(regexp: RegExp): boolean { // We check against an empty string. If the regular expression doesn't advance // (e.g. ends in an endless loop) it will match an empty string. - let match = regexp.exec(''); + const match = regexp.exec(''); return !!(match && regexp.lastIndex === 0); } @@ -253,7 +253,7 @@ export function regExpFlags(regexp: RegExp): string { */ export function firstNonWhitespaceIndex(str: string): number { for (let i = 0, len = str.length; i < len; i++) { - let chCode = str.charCodeAt(i); + const chCode = str.charCodeAt(i); if (chCode !== CharCode.Space && chCode !== CharCode.Tab) { return i; } @@ -267,7 +267,7 @@ export function firstNonWhitespaceIndex(str: string): number { */ export function getLeadingWhitespace(str: string, start: number = 0, end: number = str.length): string { for (let i = start; i < end; i++) { - let chCode = str.charCodeAt(i); + const chCode = str.charCodeAt(i); if (chCode !== CharCode.Space && chCode !== CharCode.Tab) { return str.substring(start, i); } @@ -281,7 +281,7 @@ export function getLeadingWhitespace(str: string, start: number = 0, end: number */ export function lastNonWhitespaceIndex(str: string, startIndex: number = str.length - 1): number { for (let i = startIndex; i >= 0; i--) { - let chCode = str.charCodeAt(i); + const chCode = str.charCodeAt(i); if (chCode !== CharCode.Space && chCode !== CharCode.Tab) { return i; } @@ -380,7 +380,7 @@ function doEqualsIgnoreCase(a: string, b: string, stopAt = a.length): boolean { // a-z A-Z if (isAsciiLetter(codeA) && isAsciiLetter(codeB)) { - let diff = Math.abs(codeA - codeB); + const diff = Math.abs(codeA - codeB); if (diff !== 0 && diff !== 32) { return false; } @@ -431,8 +431,8 @@ export function commonSuffixLength(a: string, b: string): number { let i: number, len = Math.min(a.length, b.length); - let aLastIndex = a.length - 1; - let bLastIndex = b.length - 1; + const aLastIndex = a.length - 1; + const bLastIndex = b.length - 1; for (i = 0; i < len; i++) { if (a.charCodeAt(aLastIndex - i) !== b.charCodeAt(bLastIndex - i)) { @@ -459,7 +459,7 @@ function substrEquals(a: string, aStart: number, aEnd: number, b: string, bStart * For instance `overlap("foobar", "arr, I'm a pirate") === 2`. */ export function overlap(a: string, b: string): number { - let aEnd = a.length; + const aEnd = a.length; let bEnd = b.length; let aStart = aEnd - bEnd; @@ -486,9 +486,9 @@ export function overlap(a: string, b: string): number { // Code points U+0000 to U+D7FF and U+E000 to U+FFFF are represented on a single character // Code points U+10000 to U+10FFFF are represented on two consecutive characters //export function getUnicodePoint(str:string, index:number, len:number):number { -// let chrCode = str.charCodeAt(index); +// const chrCode = str.charCodeAt(index); // if (0xD800 <= chrCode && chrCode <= 0xDBFF && index + 1 < len) { -// let nextChrCode = str.charCodeAt(index + 1); +// const nextChrCode = str.charCodeAt(index + 1); // if (0xDC00 <= nextChrCode && nextChrCode <= 0xDFFF) { // return (chrCode - 0xD800) << 10 + (nextChrCode - 0xDC00) + 0x10000; // } @@ -627,6 +627,21 @@ export function removeAnsiEscapeCodes(str: string): string { return str; } +export const removeAccents: (str: string) => string = (function () { + if (typeof (String.prototype as any).normalize !== 'function') { + // ☹️ no ES6 features... + return function (str: string) { return str; }; + } else { + // transform into NFD form and remove accents + // see: https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript/37511463#37511463 + const regex = /[\u0300-\u036f]/g; + return function (str: string) { + return (str as any).normalize('NFD').replace(regex, empty); + }; + } +})(); + + // -- UTF-8 BOM export const UTF8_BOM_CHARACTER = String.fromCharCode(CharCode.UTF8_BOM); @@ -670,7 +685,7 @@ export function fuzzyContains(target: string, query: string): boolean { let index = 0; let lastIndexOf = -1; while (index < queryLen) { - let indexOf = targetLower.indexOf(query[index], lastIndexOf + 1); + const indexOf = targetLower.indexOf(query[index], lastIndexOf + 1); if (indexOf < 0) { return false; } diff --git a/src/vs/base/common/types.ts b/src/vs/base/common/types.ts index 11168281bb..a180438f18 100644 --- a/src/vs/base/common/types.ts +++ b/src/vs/base/common/types.ts @@ -49,7 +49,7 @@ export function isStringArray(value: any): value is string[] { * @returns whether the provided parameter is of type `object` but **not** * `null`, an `array`, a `regexp`, nor a `date`. */ -export function isObject(obj: any): boolean { +export function isObject(obj: any): obj is Object { // The method can't do a type cast since there are type (like strings) which // are subclasses of any put not positvely matched by the function. Hence type // narrowing results in wrong results. @@ -143,8 +143,12 @@ export function validateConstraint(arg: any, constraint: TypeConstraint | undefi throw new Error(`argument does not match constraint: typeof ${constraint}`); } } else if (isFunction(constraint)) { - if (arg instanceof constraint) { - return; + try { + if (arg instanceof constraint) { + return; + } + } catch{ + // ignore } if (!isUndefinedOrNull(arg) && arg.constructor === constraint) { return; @@ -161,8 +165,42 @@ export function validateConstraint(arg: any, constraint: TypeConstraint | undefi * any additional argument supplied. */ export function create(ctor: Function, ...args: any[]): any { - let obj = Object.create(ctor.prototype); - ctor.apply(obj, args); - - return obj; + if (isNativeClass(ctor)) { + return new (ctor as any)(...args); + } else { + const obj = Object.create(ctor.prototype); + ctor.apply(obj, args); + return obj; + } } + +// https://stackoverflow.com/a/32235645/1499159 +function isNativeClass(thing): boolean { + return typeof thing === 'function' + && thing.hasOwnProperty('prototype') + && !thing.hasOwnProperty('arguments'); +} + +export function getAllPropertyNames(obj: object): string[] { + let res: string[] = []; + let proto = Object.getPrototypeOf(obj); + while (Object.prototype !== proto) { + res = res.concat(Object.getOwnPropertyNames(proto)); + proto = Object.getPrototypeOf(proto); + } + return res; +} + +/** + * Converts null to undefined, passes all other values through. + */ +export function withNullAsUndefined(x: T | null): T | undefined { + return x === null ? undefined : x; +} + +/** + * Converts undefined to null, passes all other values through. + */ +export function withUndefinedAsNull(x: T | undefined): T | null { + return typeof x === 'undefined' ? null : x; +} \ No newline at end of file diff --git a/src/vs/base/common/uint.ts b/src/vs/base/common/uint.ts new file mode 100644 index 0000000000..3886194553 --- /dev/null +++ b/src/vs/base/common/uint.ts @@ -0,0 +1,112 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function toUint8ArrayBuffer(str: string): ArrayBuffer { + + if (typeof TextEncoder !== 'undefined') { + return new TextEncoder().encode(str).buffer; + } + + let i: number, len: number, length = 0, charCode = 0, trailCharCode = 0, codepoint = 0; + + // First pass, for the size + for (i = 0, len = str.length; i < len; i++) { + charCode = str.charCodeAt(i); + + // Surrogate pair + if (charCode >= 0xD800 && charCode < 0xDC00) { + trailCharCode = str.charCodeAt(++i); + + if (!(trailCharCode >= 0xDC00 && trailCharCode < 0xE000)) { + throw new Error('Invalid char code'); + } + + // Code point can be obtained by subtracting 0xD800 and 0xDC00 from both char codes respectively + // and joining the 10 least significant bits from each, finally adding 0x10000. + codepoint = ((((charCode - 0xD800) & 0x3FF) << 10) | ((trailCharCode - 0xDC00) & 0x3FF)) + 0x10000; + + } else { + codepoint = charCode; + } + + length += byteSizeInUTF8(codepoint); + } + + let result = new ArrayBuffer(length); + let view = new Uint8Array(result); + let pos = 0; + + // Second pass, for the data + for (i = 0, len = str.length; i < len; i++) { + charCode = str.charCodeAt(i); + + if (charCode >= 0xD800 && charCode < 0xDC00) { + trailCharCode = str.charCodeAt(++i); + codepoint = ((((charCode - 0xD800) & 0x3FF) << 10) | ((trailCharCode - 0xDC00) & 0x3FF)) + 0x10000; + } else { + codepoint = charCode; + } + + pos += writeUTF8(codepoint, view, pos); + } + + return result; +} + +function byteSizeInUTF8(codePoint: number): number { + codePoint = codePoint >>> 0; + + if (codePoint < 0x80) { + return 1; + } else if (codePoint < 0x800) { + return 2; + } else if (codePoint < 0x10000) { + return 3; + } else if (codePoint < 0x200000) { + return 4; + } else if (codePoint < 0x4000000) { + return 5; + } else if (codePoint < 0x80000000) { + return 6; + } else { + throw new Error('Code point 0x' + toHexString(codePoint) + ' not encodable in UTF8.'); + } +} + +function writeUTF8(codePoint: number, buffer: Uint8Array, pos: number): number { + + // How many bits needed for codePoint + let byteSize = byteSizeInUTF8(codePoint); + + // 0xxxxxxx + if (byteSize === 1) { + buffer[pos] = codePoint; + return 1; + } + + // 110xxxxx 10xxxxxx + // 1110xxxx 10xxxxxx 10xxxxxx + // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + // 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + // 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + + // first byte + buffer[pos] = ((0xFC << (6 - byteSize)) | (codePoint >>> (6 * (byteSize - 1)))) & 0xFF; + + // successive bytes + for (let i = 1; i < byteSize; i++) { + buffer[pos + i] = (0x80 | (0x3F & (codePoint >>> (6 * (byteSize - i - 1))))) & 0xFF; + } + + return byteSize; +} + +function leftPad(value: string, length: number, char: string = '0'): string { + return new Array(length - value.length + 1).join(char) + value; +} + +function toHexString(value: number, bitsize: number = 32): string { + return leftPad((value >>> 0).toString(16), bitsize / 4); +} diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index 38bdd15a01..bea1c19f1f 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -56,6 +56,21 @@ function _validateUri(ret: URI, _strict?: boolean): void { } } +// for a while we allowed uris *without* schemes and this is the migration +// for them, e.g. an uri without scheme and without strict-mode warns and falls +// back to the file-scheme. that should cause the least carnage and still be a +// clear warning +function _schemeFix(scheme: string, _strict: boolean): string { + if (_strict || _throwOnMissingSchema) { + return scheme || _empty; + } + if (!scheme) { + console.trace('BAD uri lacks scheme, falling back to file-scheme.'); + scheme = 'file'; + } + return scheme; +} + // implements a bit of https://tools.ietf.org/html/rfc3986#section-5 function _referenceResolution(scheme: string, path: string): string { @@ -154,7 +169,7 @@ export class URI implements UriComponents { /** * @internal */ - protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string, _strict?: boolean) { + protected constructor(schemeOrData: string | UriComponents, authority?: string, path?: string, query?: string, fragment?: string, _strict: boolean = false) { if (typeof schemeOrData === 'object') { this.scheme = schemeOrData.scheme || _empty; @@ -166,7 +181,7 @@ export class URI implements UriComponents { // that creates uri components. // _validateUri(this); } else { - this.scheme = schemeOrData || _empty; + this.scheme = _schemeFix(schemeOrData, _strict); this.authority = authority || _empty; this.path = _referenceResolution(this.scheme, path || _empty); this.query = query || _empty; @@ -314,7 +329,7 @@ export class URI implements UriComponents { // check for authority as used in UNC shares // or use the path as given if (path[0] === _slash && path[1] === _slash) { - let idx = path.indexOf(_slash, 2); + const idx = path.indexOf(_slash, 2); if (idx === -1) { authority = path.substring(2); path = _slash; @@ -364,7 +379,7 @@ export class URI implements UriComponents { } else if (data instanceof URI) { return data; } else { - let result = new _URI(data); + const result = new _URI(data); result._fsPath = (data).fsPath; result._formatted = (data).external; return result; @@ -473,7 +488,7 @@ function encodeURIComponentFast(uriComponent: string, allowSlash: boolean): stri let nativeEncodePos = -1; for (let pos = 0; pos < uriComponent.length; pos++) { - let code = uriComponent.charCodeAt(pos); + const code = uriComponent.charCodeAt(pos); // unreserved characters: https://tools.ietf.org/html/rfc3986#section-2.3 if ( @@ -503,7 +518,7 @@ function encodeURIComponentFast(uriComponent: string, allowSlash: boolean): stri } // check with default table first - let escaped = encodeTable[code]; + const escaped = encodeTable[code]; if (escaped !== undefined) { // check if we are delaying native encode @@ -532,7 +547,7 @@ function encodeURIComponentFast(uriComponent: string, allowSlash: boolean): stri function encodeURIComponentMinimal(path: string): string { let res: string | undefined = undefined; for (let pos = 0; pos < path.length; pos++) { - let code = path.charCodeAt(pos); + const code = path.charCodeAt(pos); if (code === CharCode.Hash || code === CharCode.QuestionMark) { if (res === undefined) { res = path.substr(0, pos); @@ -622,12 +637,12 @@ function _asFormatted(uri: URI, skipEncoding: boolean): string { if (path) { // lower-case windows drive letters in /C:/fff or C:/fff if (path.length >= 3 && path.charCodeAt(0) === CharCode.Slash && path.charCodeAt(2) === CharCode.Colon) { - let code = path.charCodeAt(1); + const code = path.charCodeAt(1); if (code >= CharCode.A && code <= CharCode.Z) { path = `/${String.fromCharCode(code + 32)}:${path.substr(3)}`; // "/c:".length === 3 } } else if (path.length >= 2 && path.charCodeAt(1) === CharCode.Colon) { - let code = path.charCodeAt(0); + const code = path.charCodeAt(0); if (code >= CharCode.A && code <= CharCode.Z) { path = `${String.fromCharCode(code + 32)}:${path.substr(2)}`; // "/c:".length === 3 } diff --git a/src/vs/base/common/worker/simpleWorker.ts b/src/vs/base/common/worker/simpleWorker.ts index 39e15d0088..1655a685ed 100644 --- a/src/vs/base/common/worker/simpleWorker.ts +++ b/src/vs/base/common/worker/simpleWorker.ts @@ -6,6 +6,7 @@ import { transformErrorForSerialization } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; import { isWeb } from 'vs/base/common/platform'; +import { getAllPropertyNames } from 'vs/base/common/types'; const INITIALIZE = '$initialize'; @@ -324,7 +325,7 @@ export class SimpleWorkerServer { if (this._requestHandler) { // static request handler let methods: string[] = []; - for (let prop in this._requestHandler) { + for (const prop of getAllPropertyNames(this._requestHandler)) { if (typeof this._requestHandler[prop] === 'function') { methods.push(prop); } @@ -360,7 +361,7 @@ export class SimpleWorkerServer { } let methods: string[] = []; - for (let prop in this._requestHandler) { + for (const prop of getAllPropertyNames(this._requestHandler)) { if (typeof this._requestHandler[prop] === 'function') { methods.push(prop); } diff --git a/src/vs/base/node/config.ts b/src/vs/base/node/config.ts index 7e03bc9594..1f2814184c 100644 --- a/src/vs/base/node/config.ts +++ b/src/vs/base/node/config.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; -import { dirname, basename } from 'path'; +import { dirname, basename } from 'vs/base/common/path'; import * as objects from 'vs/base/common/objects'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; diff --git a/src/vs/base/node/decoder.ts b/src/vs/base/node/decoder.ts index 9c164bf4fd..1857c7fbf9 100644 --- a/src/vs/base/node/decoder.ts +++ b/src/vs/base/node/decoder.ts @@ -24,8 +24,8 @@ export class LineDecoder { } public write(buffer: Buffer): string[] { - let result: string[] = []; - let value = this.remaining + const result: string[] = []; + const value = this.remaining ? this.remaining + this.stringDecoder.write(buffer) : this.stringDecoder.write(buffer); @@ -41,7 +41,7 @@ export class LineDecoder { result.push(value.substring(start, idx)); idx++; if (idx < value.length) { - let lastChar = ch; + const lastChar = ch; ch = value.charCodeAt(idx); if ((lastChar === CharCode.CarriageReturn && ch === CharCode.LineFeed) || (lastChar === CharCode.LineFeed && ch === CharCode.CarriageReturn)) { idx++; diff --git a/src/vs/base/node/extfs.ts b/src/vs/base/node/extfs.ts index 73a0cd792e..7023e65dfc 100644 --- a/src/vs/base/node/extfs.ts +++ b/src/vs/base/node/extfs.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; -import * as paths from 'path'; +import * as paths from 'vs/base/common/path'; import { nfcall } from 'vs/base/common/async'; import { normalizeNFC } from 'vs/base/common/normalization'; import * as platform from 'vs/base/common/platform'; @@ -219,7 +219,7 @@ export function del(path: string, tmpFolder: string, callback: (error: Error | n } function rmRecursive(path: string, callback: (error: Error | null) => void): void { - if (path === '\\' || path === '/') { + if (path === paths.win32.sep || path === paths.posix.sep) { return callback(new Error('Will not delete root!')); } @@ -277,6 +277,10 @@ function rmRecursive(path: string, callback: (error: Error | null) => void): voi } export function delSync(path: string): void { + if (path === paths.win32.sep || path === paths.posix.sep) { + throw new Error('Will not delete root!'); + } + try { const stat = fs.lstatSync(path); if (stat.isDirectory() && !stat.isSymbolicLink()) { diff --git a/src/vs/base/node/flow.ts b/src/vs/base/node/flow.ts index f0b1dba3da..e16f98631b 100644 --- a/src/vs/base/node/flow.ts +++ b/src/vs/base/node/flow.ts @@ -10,8 +10,8 @@ import * as assert from 'assert'; * array to the callback (callback). The resulting errors and results are evaluated by calling the provided callback function. */ export function parallel(list: T[], fn: (item: T, callback: (err: Error | null, result: E | null) => void) => void, callback: (err: Array | null, result: E[]) => void): void { - let results = new Array(list.length); - let errors = new Array(list.length); + const results = new Array(list.length); + const errors = new Array(list.length); let didErrorOccur = false; let doneCount = 0; @@ -68,9 +68,9 @@ export function loop(param: any, fn: (item: any, callback: (error: Error | nu // Expect the param to be an array and loop over it else { - let results: E[] = []; + const results: E[] = []; - let looper: (i: number) => void = function (i: number): void { + const looper: (i: number) => void = function (i: number): void { // Still work to do if (i < param.length) { @@ -126,11 +126,11 @@ function Sequence(sequences: { (...param: any[]): void; }[]): void { }); // Execute in Loop - let errorHandler = sequences.splice(0, 1)[0]; //Remove error handler + const errorHandler = sequences.splice(0, 1)[0]; //Remove error handler let sequenceResult: any = null; loop(sequences, (sequence, clb) => { - let sequenceFunction = function (error: any, result: any): void { + const sequenceFunction = function (error: any, result: any): void { // A method might only send a boolean value as return value (e.g. fs.exists), support this case gracefully if (error === true || error === false) { diff --git a/src/vs/base/node/id.ts b/src/vs/base/node/id.ts index d7e0f14951..15f59928d9 100644 --- a/src/vs/base/node/id.ts +++ b/src/vs/base/node/id.ts @@ -97,7 +97,7 @@ function getMacMachineId(): Promise { // TODO@sbatten: Remove this when getmac is patched setTimeout(() => { resolve(undefined); - }, 1000); + }, 10000); } catch (err) { errors.onUnexpectedError(err); resolve(undefined); diff --git a/src/vs/base/node/languagePacks.d.ts b/src/vs/base/node/languagePacks.d.ts new file mode 100644 index 0000000000..1bb78dae05 --- /dev/null +++ b/src/vs/base/node/languagePacks.d.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export interface NLSConfiguration { + locale: string; + availableLanguages: { + [key: string]: string; + }; + pseudo?: boolean; +} + +export interface InternalNLSConfiguration extends NLSConfiguration { + _languagePackId: string; + _translationsConfigFile: string; + _cacheRoot: string; + _resolvedLanguagePackCoreLocation: string; + _corruptedFile: string; + _languagePackSupport?: boolean; +} + +export function getNLSConfiguration(commit: string, userDataPath: string, metaDataFile: string, locale: string): Promise; diff --git a/src/vs/base/node/languagePacks.js b/src/vs/base/node/languagePacks.js new file mode 100644 index 0000000000..278242dc2c --- /dev/null +++ b/src/vs/base/node/languagePacks.js @@ -0,0 +1,332 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +//@ts-check + +/** + * @param {NodeRequire} nodeRequire + * @param {typeof import('path')} path + * @param {typeof import('fs')} fs + * @param {typeof import('../common/performance')} perf + */ +function factory(nodeRequire, path, fs, perf) { + + /** + * @param {string} file + * @returns {Promise} + */ + function exists(file) { + return new Promise(c => fs.exists(file, c)); + } + + /** + * @param {string} file + * @returns {Promise} + */ + function touch(file) { + return new Promise((c, e) => { const d = new Date(); fs.utimes(file, d, d, err => err ? e(err) : c()); }); + } + + /** + * @param {string} file + * @returns {Promise} + */ + function lstat(file) { + return new Promise((c, e) => fs.lstat(file, (err, stats) => err ? e(err) : c(stats))); + } + + /** + * @param {string} dir + * @returns {Promise} + */ + function readdir(dir) { + return new Promise((c, e) => fs.readdir(dir, (err, files) => err ? e(err) : c(files))); + } + + /** + * @param {string} dir + * @returns {Promise} + */ + function mkdir(dir) { + return new Promise((c, e) => fs.mkdir(dir, err => (err && err.code !== 'EEXIST') ? e(err) : c(dir))); + } + + /** + * @param {string} dir + * @returns {Promise} + */ + function rmdir(dir) { + return new Promise((c, e) => fs.rmdir(dir, err => err ? e(err) : c(undefined))); + } + + /** + * @param {string} file + * @returns {Promise} + */ + function unlink(file) { + return new Promise((c, e) => fs.unlink(file, err => err ? e(err) : c(undefined))); + } + + /** + * @param {string} location + * @returns {Promise} + */ + function rimraf(location) { + return lstat(location).then(stat => { + if (stat.isDirectory() && !stat.isSymbolicLink()) { + return readdir(location) + .then(children => Promise.all(children.map(child => rimraf(path.join(location, child))))) + .then(() => rmdir(location)); + } else { + return unlink(location); + } + }, err => { + if (err.code === 'ENOENT') { + return undefined; + } + throw err; + }); + } + + /** + * @param {string} dir + * @returns {Promise} + */ + function mkdirp(dir) { + return mkdir(dir).then(null, err => { + if (err && err.code === 'ENOENT') { + const parent = path.dirname(dir); + + if (parent !== dir) { // if not arrived at root + return mkdirp(parent).then(() => mkdir(dir)); + } + } + + throw err; + }); + } + + function readFile(file) { + return new Promise(function (resolve, reject) { + fs.readFile(file, 'utf8', function (err, data) { + if (err) { + reject(err); + return; + } + resolve(data); + }); + }); + } + + /** + * @param {string} file + * @param {string} content + * @returns {Promise} + */ + function writeFile(file, content) { + return new Promise(function (resolve, reject) { + fs.writeFile(file, content, 'utf8', function (err) { + if (err) { + reject(err); + return; + } + resolve(); + }); + }); + } + + + /** + * @param {string} userDataPath + * @returns {object} + */ + function getLanguagePackConfigurations(userDataPath) { + const configFile = path.join(userDataPath, 'languagepacks.json'); + try { + return nodeRequire(configFile); + } catch (err) { + // Do nothing. If we can't read the file we have no + // language pack config. + } + return undefined; + } + + /** + * @param {object} config + * @param {string} locale + */ + function resolveLanguagePackLocale(config, locale) { + try { + while (locale) { + if (config[locale]) { + return locale; + } else { + const index = locale.lastIndexOf('-'); + if (index > 0) { + locale = locale.substring(0, index); + } else { + return undefined; + } + } + } + } catch (err) { + console.error('Resolving language pack configuration failed.', err); + } + return undefined; + } + + /** + * @param {string} commit + * @param {string} userDataPath + * @param {string} metaDataFile + * @param {string} locale + */ + function getNLSConfiguration(commit, userDataPath, metaDataFile, locale) { + if (locale === 'pseudo') { + return Promise.resolve({ locale: locale, availableLanguages: {}, pseudo: true }); + } + + if (process.env['VSCODE_DEV']) { + return Promise.resolve({ locale: locale, availableLanguages: {} }); + } + + // We have a built version so we have extracted nls file. Try to find + // the right file to use. + + // Check if we have an English or English US locale. If so fall to default since that is our + // English translation (we don't ship *.nls.en.json files) + if (locale && (locale === 'en' || locale === 'en-us')) { + return Promise.resolve({ locale: locale, availableLanguages: {} }); + } + + const initialLocale = locale; + + perf.mark('nlsGeneration:start'); + + const defaultResult = function (locale) { + perf.mark('nlsGeneration:end'); + return Promise.resolve({ locale: locale, availableLanguages: {} }); + }; + try { + if (!commit) { + return defaultResult(initialLocale); + } + const configs = getLanguagePackConfigurations(userDataPath); + if (!configs) { + return defaultResult(initialLocale); + } + locale = resolveLanguagePackLocale(configs, locale); + if (!locale) { + return defaultResult(initialLocale); + } + const packConfig = configs[locale]; + let mainPack; + if (!packConfig || typeof packConfig.hash !== 'string' || !packConfig.translations || typeof (mainPack = packConfig.translations['vscode']) !== 'string') { + return defaultResult(initialLocale); + } + return exists(mainPack).then(fileExists => { + if (!fileExists) { + return defaultResult(initialLocale); + } + const packId = packConfig.hash + '.' + locale; + const cacheRoot = path.join(userDataPath, 'clp', packId); + const coreLocation = path.join(cacheRoot, commit); + const translationsConfigFile = path.join(cacheRoot, 'tcf.json'); + const corruptedFile = path.join(cacheRoot, 'corrupted.info'); + const result = { + locale: initialLocale, + availableLanguages: { '*': locale }, + _languagePackId: packId, + _translationsConfigFile: translationsConfigFile, + _cacheRoot: cacheRoot, + _resolvedLanguagePackCoreLocation: coreLocation, + _corruptedFile: corruptedFile + }; + return exists(corruptedFile).then(corrupted => { + // The nls cache directory is corrupted. + let toDelete; + if (corrupted) { + toDelete = rimraf(cacheRoot); + } else { + toDelete = Promise.resolve(undefined); + } + return toDelete.then(() => { + return exists(coreLocation).then(fileExists => { + if (fileExists) { + // We don't wait for this. No big harm if we can't touch + touch(coreLocation).catch(() => { }); + perf.mark('nlsGeneration:end'); + return result; + } + return mkdirp(coreLocation).then(() => { + return Promise.all([readFile(metaDataFile), readFile(mainPack)]); + }).then(values => { + const metadata = JSON.parse(values[0]); + const packData = JSON.parse(values[1]).contents; + const bundles = Object.keys(metadata.bundles); + const writes = []; + for (let bundle of bundles) { + const modules = metadata.bundles[bundle]; + const target = Object.create(null); + for (let module of modules) { + const keys = metadata.keys[module]; + const defaultMessages = metadata.messages[module]; + const translations = packData[module]; + let targetStrings; + if (translations) { + targetStrings = []; + for (let i = 0; i < keys.length; i++) { + const elem = keys[i]; + const key = typeof elem === 'string' ? elem : elem.key; + let translatedMessage = translations[key]; + if (translatedMessage === undefined) { + translatedMessage = defaultMessages[i]; + } + targetStrings.push(translatedMessage); + } + } else { + targetStrings = defaultMessages; + } + target[module] = targetStrings; + } + writes.push(writeFile(path.join(coreLocation, bundle.replace(/\//g, '!') + '.nls.json'), JSON.stringify(target))); + } + writes.push(writeFile(translationsConfigFile, JSON.stringify(packConfig.translations))); + return Promise.all(writes); + }).then(() => { + perf.mark('nlsGeneration:end'); + return result; + }).catch(err => { + console.error('Generating translation files failed.', err); + return defaultResult(locale); + }); + }); + }); + }); + }); + } catch (err) { + console.error('Generating translation files failed.', err); + return defaultResult(locale); + } + } + + return { + getNLSConfiguration + }; +} + + +if (typeof define === 'function') { + // amd + define(['path', 'fs', 'vs/base/common/performance'], function (path, fs, perf) { return factory(require.__$__nodeRequire, path, fs, perf); }); +} else if (typeof module === 'object' && typeof module.exports === 'object') { + const path = require('path'); + const fs = require('fs'); + const perf = require('../common/performance'); + module.exports = factory(require, path, fs, perf); +} else { + throw new Error('Unknown context'); +} diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 797ab960f5..e72ea26d4e 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as extfs from 'vs/base/node/extfs'; -import { join } from 'path'; +import { join } from 'vs/base/common/path'; import { nfcall, Queue } from 'vs/base/common/async'; import * as fs from 'fs'; import * as os from 'os'; diff --git a/src/vs/base/node/ports.ts b/src/vs/base/node/ports.ts index 60d40fb49d..e41b8c4438 100644 --- a/src/vs/base/node/ports.ts +++ b/src/vs/base/node/ports.ts @@ -9,8 +9,8 @@ import * as net from 'net'; * @returns Returns a random port between 1025 and 65535. */ export function randomPort(): number { - let min = 1025; - let max = 65535; + const min = 1025; + const max = 65535; return min + Math.floor((max - min) * Math.random()); } diff --git a/src/vs/base/node/processes.ts b/src/vs/base/node/processes.ts index 2e530d75d0..a98bcc4de4 100644 --- a/src/vs/base/node/processes.ts +++ b/src/vs/base/node/processes.ts @@ -3,14 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as fs from 'fs'; import * as cp from 'child_process'; import * as nls from 'vs/nls'; import * as Types from 'vs/base/common/types'; import { IStringDictionary } from 'vs/base/common/collections'; import * as Objects from 'vs/base/common/objects'; -import * as TPath from 'vs/base/common/paths'; +import * as extpath from 'vs/base/common/extpath'; import * as Platform from 'vs/base/common/platform'; import { LineDecoder } from 'vs/base/node/decoder'; import { CommandOptions, ForkOptions, SuccessData, Source, TerminateResponse, TerminateResponseCode, Executable } from 'vs/base/common/processes'; @@ -42,7 +42,7 @@ function getWindowsCode(status: number): TerminateResponseCode { export function terminateProcess(process: cp.ChildProcess, cwd?: string): TerminateResponse { if (Platform.isWindows) { try { - let options: any = { + const options: any = { stdio: ['pipe', 'pipe', 'ignore'] }; if (cwd) { @@ -54,8 +54,8 @@ export function terminateProcess(process: cp.ChildProcess, cwd?: string): Termin } } else if (Platform.isLinux || Platform.isMacintosh) { try { - let cmd = getPathFromAmdModule(require, 'vs/base/node/terminateProcess.sh'); - let result = cp.spawnSync(cmd, [process.pid.toString()]); + const cmd = getPathFromAmdModule(require, 'vs/base/node/terminateProcess.sh'); + const result = cp.spawnSync(cmd, [process.pid.toString()]); if (result.error) { return { success: false, error: result.error }; } @@ -72,26 +72,6 @@ export function getWindowsShell(): string { return process.env['comspec'] || 'cmd.exe'; } -/** - * Sanitizes a VS Code process environment by removing all Electron/VS Code-related values. - */ -export function sanitizeProcessEnvironment(env: Platform.IProcessEnvironment): void { - const keysToRemove = [ - /^ELECTRON_.+$/, - /^GOOGLE_API_KEY$/, - /^VSCODE_.+$/ - ]; - const envKeys = Object.keys(env); - envKeys.forEach(envKey => { - for (let i = 0; i < keysToRemove.length; i++) { - if (envKey.search(keysToRemove[i]) !== -1) { - delete env[envKey]; - break; - } - } - }); -} - export abstract class AbstractProcess { private cmd: string; private args: string[]; @@ -133,7 +113,7 @@ export abstract class AbstractProcess { this.shell = arg3; this.options = arg4; } else { - let executable = arg1; + const executable = arg1; this.cmd = executable.command; this.shell = executable.isShellCommand; this.args = executable.args.slice(0); @@ -144,7 +124,7 @@ export abstract class AbstractProcess { this.terminateRequested = false; if (this.options.env) { - let newEnv: IStringDictionary = Object.create(null); + const newEnv: IStringDictionary = Object.create(null); Object.keys(process.env).forEach((key) => { newEnv[key] = process.env[key]!; }); @@ -157,7 +137,7 @@ export abstract class AbstractProcess { public getSanitizedCommand(): string { let result = this.cmd.toLowerCase(); - let index = result.lastIndexOf(path.sep); + const index = result.lastIndexOf(path.sep); if (index !== -1) { result = result.substring(index + 1); } @@ -168,13 +148,13 @@ export abstract class AbstractProcess { } public start(pp: ProgressCallback): Promise { - if (Platform.isWindows && ((this.options && this.options.cwd && TPath.isUNC(this.options.cwd)) || !this.options && TPath.isUNC(process.cwd()))) { + if (Platform.isWindows && ((this.options && this.options.cwd && extpath.isUNC(this.options.cwd)) || !this.options && extpath.isUNC(process.cwd()))) { return Promise.reject(new Error(nls.localize('TaskRunner.UNC', 'Can\'t execute a shell command on a UNC drive.'))); } return this.useExec().then((useExec) => { let cc: ValueCallback; let ee: ErrorCallback; - let result = new Promise((c, e) => { + const result = new Promise((c, e) => { cc = c; ee = e; }); @@ -186,7 +166,7 @@ export abstract class AbstractProcess { } this.childProcess = cp.exec(cmd, this.options, (error, stdout, stderr) => { this.childProcess = null; - let err: any = error; + const err: any = error; // This is tricky since executing a command shell reports error back in case the executed command return an // error or the command didn't exist at all. So we can't blindly treat an error as a failed command. So we // always parse the output and report success unless the job got killed. @@ -198,11 +178,11 @@ export abstract class AbstractProcess { }); } else { let childProcess: cp.ChildProcess | null = null; - let closeHandler = (data: any) => { + const closeHandler = (data: any) => { this.childProcess = null; this.childProcessPromise = null; this.handleClose(data, cc, pp, ee); - let result: SuccessData = { + const result: SuccessData = { terminated: this.terminateRequested }; if (Types.isNumber(data)) { @@ -211,12 +191,12 @@ export abstract class AbstractProcess { cc(result); }; if (this.shell && Platform.isWindows) { - let options: any = Objects.deepClone(this.options); + const options: any = Objects.deepClone(this.options); options.windowsVerbatimArguments = true; options.detached = false; let quotedCommand: boolean = false; let quotedArg: boolean = false; - let commandLine: string[] = []; + const commandLine: string[] = []; let quoted = this.ensureQuotes(this.cmd); commandLine.push(quoted.value); quotedCommand = quoted.quoted; @@ -227,7 +207,7 @@ export abstract class AbstractProcess { quotedArg = quotedArg && quoted.quoted; }); } - let args: string[] = [ + const args: string[] = [ '/s', '/c', ]; @@ -307,7 +287,7 @@ export abstract class AbstractProcess { } return this.childProcessPromise.then((childProcess) => { this.terminateRequested = true; - let result = terminateProcess(childProcess, this.options.cwd); + const result = terminateProcess(childProcess, this.options.cwd); if (result.success) { this.childProcess = null; } @@ -320,14 +300,14 @@ export abstract class AbstractProcess { private useExec(): Promise { return new Promise((c, e) => { if (!this.shell || !Platform.isWindows) { - c(false); + return c(false); } - let cmdShell = cp.spawn(getWindowsShell(), ['/s', '/c']); + const cmdShell = cp.spawn(getWindowsShell(), ['/s', '/c']); cmdShell.on('error', (error: Error) => { - c(true); + return c(true); }); cmdShell.on('exit', (data: any) => { - c(false); + return c(false); }); }); } @@ -346,12 +326,12 @@ export class LineProcess extends AbstractProcess { protected handleExec(cc: ValueCallback, pp: ProgressCallback, error: Error, stdout: Buffer, stderr: Buffer) { [stdout, stderr].forEach((buffer: Buffer, index: number) => { - let lineDecoder = new LineDecoder(); - let lines = lineDecoder.write(buffer); + const lineDecoder = new LineDecoder(); + const lines = lineDecoder.write(buffer); lines.forEach((line) => { pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr }); }); - let line = lineDecoder.end(); + const line = lineDecoder.end(); if (line) { pp({ line: line, source: index === 0 ? Source.stdout : Source.stderr }); } @@ -363,11 +343,11 @@ export class LineProcess extends AbstractProcess { this.stdoutLineDecoder = new LineDecoder(); this.stderrLineDecoder = new LineDecoder(); childProcess.stdout.on('data', (data: Buffer) => { - let lines = this.stdoutLineDecoder.write(data); + const lines = this.stdoutLineDecoder.write(data); lines.forEach(line => pp({ line: line, source: Source.stdout })); }); childProcess.stderr.on('data', (data: Buffer) => { - let lines = this.stderrLineDecoder.write(data); + const lines = this.stderrLineDecoder.write(data); lines.forEach(line => pp({ line: line, source: Source.stderr })); }); } @@ -400,7 +380,7 @@ export function createQueuedSender(childProcess: cp.ChildProcess): IQueuedSender return; } - let result = childProcess.send(msg, (error: Error) => { + const result = childProcess.send(msg, (error: Error) => { if (error) { console.error(error); // unlikely to happen, best we can do is log this error } @@ -432,7 +412,7 @@ export namespace win32 { if (cwd === undefined) { cwd = process.cwd(); } - let dir = path.dirname(command); + const dir = path.dirname(command); if (dir !== '.') { // We have a directory and the directory is relative (see above). Make the path absolute // to the current working directory. @@ -469,4 +449,4 @@ export namespace win32 { } return path.join(cwd, command); } -} \ No newline at end of file +} diff --git a/src/vs/base/node/ps.ts b/src/vs/base/node/ps.ts index 947884ecb7..fe843a0129 100644 --- a/src/vs/base/node/ps.ts +++ b/src/vs/base/node/ps.ts @@ -151,7 +151,7 @@ export function listProcesses(rootPid: number): Promise { rootItem = processItems.get(rootPid); if (rootItem) { processItems.forEach(item => { - let parent = processItems.get(item.ppid); + const parent = processItems.get(item.ppid); if (parent) { if (!parent.children) { parent.children = []; @@ -186,7 +186,7 @@ export function listProcesses(rootPid: number): Promise { const lines = stdout.toString().split('\n'); for (const line of lines) { - let matches = PID_CMD.exec(line.trim()); + const matches = PID_CMD.exec(line.trim()); if (matches && matches.length === 6) { addToTree(parseInt(matches[1]), parseInt(matches[2]), matches[5], parseFloat(matches[3]), parseFloat(matches[4])); } diff --git a/src/vs/base/node/request.ts b/src/vs/base/node/request.ts index 36726387a6..e160fcd467 100644 --- a/src/vs/base/node/request.ts +++ b/src/vs/base/node/request.ts @@ -152,7 +152,7 @@ export function asText(context: IRequestContext): Promise { return c(null); } - let buffer: string[] = []; + const buffer: string[] = []; context.stream.on('data', (d: string) => buffer.push(d)); context.stream.on('end', () => c(buffer.join(''))); context.stream.on('error', e); diff --git a/src/vs/base/node/stats.ts b/src/vs/base/node/stats.ts deleted file mode 100644 index b64286b753..0000000000 --- a/src/vs/base/node/stats.ts +++ /dev/null @@ -1,197 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { readdir, stat, exists, readFile } from 'fs'; -import { join } from 'path'; -import { parse, ParseError } from 'vs/base/common/json'; - -export interface WorkspaceStatItem { - name: string; - count: number; -} - -export interface WorkspaceStats { - fileTypes: WorkspaceStatItem[]; - configFiles: WorkspaceStatItem[]; - fileCount: number; - maxFilesReached: boolean; -} - -function asSortedItems(map: Map): WorkspaceStatItem[] { - let a: WorkspaceStatItem[] = []; - map.forEach((value, index) => a.push({ name: index, count: value })); - return a.sort((a, b) => b.count - a.count); -} - -export function collectLaunchConfigs(folder: string): Promise { - let launchConfigs = new Map(); - - let launchConfig = join(folder, '.vscode', 'launch.json'); - return new Promise((resolve, reject) => { - exists(launchConfig, (doesExist) => { - if (doesExist) { - readFile(launchConfig, (err, contents) => { - if (err) { - return resolve([]); - } - - const errors: ParseError[] = []; - const json = parse(contents.toString(), errors); - if (errors.length) { - console.log(`Unable to parse ${launchConfig}`); - return resolve([]); - } - - if (json['configurations']) { - for (const each of json['configurations']) { - const type = each['type']; - if (type) { - if (launchConfigs.has(type)) { - launchConfigs.set(type, launchConfigs.get(type)! + 1); - } else { - launchConfigs.set(type, 1); - } - } - } - } - - return resolve(asSortedItems(launchConfigs)); - }); - } else { - return resolve([]); - } - }); - }); -} - -export function collectWorkspaceStats(folder: string, filter: string[]): Promise { - const configFilePatterns = [ - { 'tag': 'grunt.js', 'pattern': /^gruntfile\.js$/i }, - { 'tag': 'gulp.js', 'pattern': /^gulpfile\.js$/i }, - { 'tag': 'tsconfig.json', 'pattern': /^tsconfig\.json$/i }, - { 'tag': 'package.json', 'pattern': /^package\.json$/i }, - { 'tag': 'jsconfig.json', 'pattern': /^jsconfig\.json$/i }, - { 'tag': 'tslint.json', 'pattern': /^tslint\.json$/i }, - { 'tag': 'eslint.json', 'pattern': /^eslint\.json$/i }, - { 'tag': 'tasks.json', 'pattern': /^tasks\.json$/i }, - { 'tag': 'launch.json', 'pattern': /^launch\.json$/i }, - { 'tag': 'settings.json', 'pattern': /^settings\.json$/i }, - { 'tag': 'webpack.config.js', 'pattern': /^webpack\.config\.js$/i }, - { 'tag': 'project.json', 'pattern': /^project\.json$/i }, - { 'tag': 'makefile', 'pattern': /^makefile$/i }, - { 'tag': 'sln', 'pattern': /^.+\.sln$/i }, - { 'tag': 'csproj', 'pattern': /^.+\.csproj$/i }, - { 'tag': 'cmake', 'pattern': /^.+\.cmake$/i } - ]; - - let fileTypes = new Map(); - let configFiles = new Map(); - - const MAX_FILES = 20000; - - function walk(dir: string, filter: string[], token, done: (allFiles: string[]) => void): void { - let results: string[] = []; - readdir(dir, async (err, files) => { - // Ignore folders that can't be read - if (err) { - return done(results); - } - - let pending = files.length; - if (pending === 0) { - return done(results); - } - - for (const file of files) { - if (token.maxReached) { - return done(results); - } - - stat(join(dir, file), (err, stats) => { - // Ignore files that can't be read - if (err) { - if (--pending === 0) { - return done(results); - } - } else { - if (stats.isDirectory()) { - if (filter.indexOf(file) === -1) { - walk(join(dir, file), filter, token, (res: string[]) => { - results = results.concat(res); - - if (--pending === 0) { - return done(results); - } - }); - } else { - if (--pending === 0) { - done(results); - } - } - } else { - if (token.count >= MAX_FILES) { - token.maxReached = true; - } - - token.count++; - results.push(file); - - if (--pending === 0) { - done(results); - } - } - } - }); - } - }); - } - - let addFileType = (fileType: string) => { - if (fileTypes.has(fileType)) { - fileTypes.set(fileType, fileTypes.get(fileType)! + 1); - } - else { - fileTypes.set(fileType, 1); - } - }; - - let addConfigFiles = (fileName: string) => { - for (const each of configFilePatterns) { - if (each.pattern.test(fileName)) { - if (configFiles.has(each.tag)) { - configFiles.set(each.tag, configFiles.get(each.tag)! + 1); - } else { - configFiles.set(each.tag, 1); - } - } - } - }; - - let acceptFile = (name: string) => { - if (name.lastIndexOf('.') >= 0) { - let suffix: string | undefined = name.split('.').pop(); - if (suffix) { - addFileType(suffix); - } - } - addConfigFiles(name); - }; - - let token: { count: number, maxReached: boolean } = { count: 0, maxReached: false }; - - return new Promise((resolve, reject) => { - walk(folder, filter, token, (files) => { - files.forEach(acceptFile); - - resolve({ - configFiles: asSortedItems(configFiles), - fileTypes: asSortedItems(fileTypes), - fileCount: token.count, - maxFilesReached: token.maxReached - - }); - }); - }); -} \ No newline at end of file diff --git a/src/vs/base/node/storage.ts b/src/vs/base/node/storage.ts index 8063720253..a019e6a5f2 100644 --- a/src/vs/base/node/storage.ts +++ b/src/vs/base/node/storage.ts @@ -9,8 +9,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { ThrottledDelayer, timeout } from 'vs/base/common/async'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { mapToString, setToString } from 'vs/base/common/map'; -import { basename } from 'path'; -import { mark } from 'vs/base/common/performance'; +import { basename } from 'vs/base/common/path'; import { copy, renameIgnoreError, unlink } from 'vs/base/node/pfs'; import { fill } from 'vs/base/common/arrays'; @@ -62,8 +61,8 @@ export interface IStorage extends IDisposable { getBoolean(key: string, fallbackValue: boolean): boolean; getBoolean(key: string, fallbackValue?: boolean): boolean | undefined; - getInteger(key: string, fallbackValue: number): number; - getInteger(key: string, fallbackValue?: number): number | undefined; + getNumber(key: string, fallbackValue: number): number; + getNumber(key: string, fallbackValue?: number): number | undefined; set(key: string, value: string | boolean | number): Promise; delete(key: string): Promise; @@ -84,7 +83,7 @@ export class Storage extends Disposable implements IStorage { private static readonly DEFAULT_FLUSH_DELAY = 100; - private _onDidChangeStorage: Emitter = this._register(new Emitter()); + private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); get onDidChangeStorage(): Event { return this._onDidChangeStorage.event; } private state = StorageState.None; @@ -196,9 +195,9 @@ export class Storage extends Disposable implements IStorage { return value === 'true'; } - getInteger(key: string, fallbackValue: number): number; - getInteger(key: string, fallbackValue?: number): number | undefined; - getInteger(key: string, fallbackValue?: number): number | undefined { + getNumber(key: string, fallbackValue: number): number; + getNumber(key: string, fallbackValue?: number): number | undefined; + getNumber(key: string, fallbackValue?: number): number | undefined { const value = this.get(key); if (isUndefinedOrNull(value)) { @@ -326,8 +325,6 @@ export class SQLiteStorageDatabase implements IStorageDatabase { get onDidChangeItemsExternal(): Event { return Event.None; } // since we are the only client, there can be no external changes - private static measuredRequireDuration: boolean; // TODO@Ben remove me after a while - private static BUSY_OPEN_TIMEOUT = 2000; // timeout in ms to retry when opening DB fails with SQLITE_BUSY private static MAX_HOST_PARAMETERS = 256; // maximum number of parameters within a statement @@ -355,7 +352,7 @@ export class SQLiteStorageDatabase implements IStorageDatabase { rows.forEach(row => items.set(row.key, row.value)); if (this.logger.isTracing) { - this.logger.trace(`[storage ${this.name}] getItems(): ${mapToString(items)}`); + this.logger.trace(`[storage ${this.name}] getItems(): ${items.size} rows`); } return items; @@ -598,21 +595,8 @@ export class SQLiteStorageDatabase implements IStorageDatabase { } private doConnect(path: string): Promise { - - // TODO@Ben clean up performance markers return new Promise((resolve, reject) => { - let measureRequireDuration = false; - if (!SQLiteStorageDatabase.measuredRequireDuration) { - SQLiteStorageDatabase.measuredRequireDuration = true; - measureRequireDuration = true; - - mark('willRequireSQLite'); - } import('vscode-sqlite3').then(sqlite3 => { - if (measureRequireDuration) { - mark('didRequireSQLite'); - } - const connection: IDatabaseConnection = { db: new (this.logger.isTracing ? sqlite3.verbose().Database : sqlite3.Database)(path, error => { if (error) { @@ -622,17 +606,12 @@ export class SQLiteStorageDatabase implements IStorageDatabase { // The following exec() statement serves two purposes: // - create the DB if it does not exist yet // - validate that the DB is not corrupt (the open() call does not throw otherwise) - mark('willSetupSQLiteSchema'); return this.exec(connection, [ 'PRAGMA user_version = 1;', 'CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB)' ].join('')).then(() => { - mark('didSetupSQLiteSchema'); - return resolve(connection); }, error => { - mark('didSetupSQLiteSchema'); - return connection.db.close(() => reject(error)); }); }), diff --git a/src/vs/base/node/stream.ts b/src/vs/base/node/stream.ts index 1bb45c812f..78bdbcc6c3 100644 --- a/src/vs/base/node/stream.ts +++ b/src/vs/base/node/stream.ts @@ -92,7 +92,7 @@ export function readToMatchingString(file: string, matchingString: string, chunk }); } - let buffer = Buffer.allocUnsafe(maximumBytesToRead); + const buffer = Buffer.allocUnsafe(maximumBytesToRead); let offset = 0; function readChunk(): void { diff --git a/src/vs/platform/node/test/fixtures/extract.zip b/src/vs/base/node/test/fixtures/extract.zip similarity index 100% rename from src/vs/platform/node/test/fixtures/extract.zip rename to src/vs/base/node/test/fixtures/extract.zip diff --git a/src/vs/platform/node/test/zip.test.ts b/src/vs/base/node/test/zip.test.ts similarity index 86% rename from src/vs/platform/node/test/zip.test.ts rename to src/vs/base/node/test/zip.test.ts index 4df370cbf8..5743def405 100644 --- a/src/vs/platform/node/test/zip.test.ts +++ b/src/vs/base/node/test/zip.test.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as os from 'os'; -import { extract } from 'vs/platform/node/zip'; +import { extract } from 'vs/base/node/zip'; import { generateUuid } from 'vs/base/common/uuid'; import { rimraf, exists } from 'vs/base/node/pfs'; -import { NullLogService } from 'vs/platform/log/common/log'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { createCancelablePromise } from 'vs/base/common/async'; @@ -21,7 +20,7 @@ suite('Zip', () => { const fixture = path.join(fixtures, 'extract.zip'); const target = path.join(os.tmpdir(), generateUuid()); - return createCancelablePromise(token => extract(fixture, target, {}, new NullLogService(), token) + return createCancelablePromise(token => extract(fixture, target, {}, token) .then(() => exists(path.join(target, 'extension'))) .then(exists => assert(exists)) .then(() => rimraf(target))); diff --git a/src/vs/platform/node/zip.ts b/src/vs/base/node/zip.ts similarity index 93% rename from src/vs/platform/node/zip.ts rename to src/vs/base/node/zip.ts index b66d9614f8..4f2cd263b9 100644 --- a/src/vs/platform/node/zip.ts +++ b/src/vs/base/node/zip.ts @@ -4,21 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { createWriteStream, WriteStream } from 'fs'; import { Readable } from 'stream'; import { nfcall, ninvoke, Sequencer, createCancelablePromise } from 'vs/base/common/async'; import { mkdirp, rimraf } from 'vs/base/node/pfs'; import { open as _openZip, Entry, ZipFile } from 'yauzl'; import * as yazl from 'yazl'; -import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Event } from 'vs/base/common/event'; export interface IExtractOptions { overwrite?: boolean; - /** + /** * Source path within the ZIP archive. Only the files contained in this * path will be extracted. */ @@ -50,7 +49,7 @@ export class ExtractError extends Error { } function modeFromEntry(entry: Entry) { - let attr = entry.externalFileAttributes >> 16 || 33188; + const attr = entry.externalFileAttributes >> 16 || 33188; return [448 /* S_IRWXU */, 56 /* S_IRWXG */, 7 /* S_IRWXO */] .map(mask => attr & mask) @@ -87,7 +86,7 @@ function extractEntry(stream: Readable, fileName: string, mode: number, targetPa } }); - return Promise.resolve(mkdirp(targetDirName, undefined, token)).then(() => new Promise((c, e) => { + return Promise.resolve(mkdirp(targetDirName, undefined, token)).then(() => new Promise((c, e) => { if (token.isCancellationRequested) { return; } @@ -104,12 +103,11 @@ function extractEntry(stream: Readable, fileName: string, mode: number, targetPa })); } -function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, logService: ILogService, token: CancellationToken): Promise { +function extractZip(zipfile: ZipFile, targetPath: string, options: IOptions, token: CancellationToken): Promise { let last = createCancelablePromise(() => Promise.resolve()); let extractedEntriesCount = 0; Event.once(token.onCancellationRequested)(() => { - logService.debug(targetPath, 'Cancelled.'); last.cancel(); zipfile.close(); }); @@ -195,7 +193,7 @@ export function zip(zipPath: string, files: IFile[]): Promise { }); } -export function extract(zipPath: string, targetPath: string, options: IExtractOptions = {}, logService: ILogService, token: CancellationToken): Promise { +export function extract(zipPath: string, targetPath: string, options: IExtractOptions = {}, token: CancellationToken): Promise { const sourcePathRegex = new RegExp(options.sourcePath ? `^${options.sourcePath}` : ''); let promise = openZip(zipPath, true); @@ -204,7 +202,7 @@ export function extract(zipPath: string, targetPath: string, options: IExtractOp promise = promise.then(zipfile => rimraf(targetPath).then(() => zipfile)); } - return promise.then(zipfile => extractZip(zipfile, targetPath, { sourcePathRegex }, logService, token)); + return promise.then(zipfile => extractZip(zipfile, targetPath, { sourcePathRegex }, token)); } function read(zipPath: string, filePath: string): Promise { diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts new file mode 100644 index 0000000000..498a88ffea --- /dev/null +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancellationToken } from 'vs/base/common/cancellation'; +import { Event } from 'vs/base/common/event'; + +/** + * An `IChannel` is an abstraction over a collection of commands. + * You can `call` several commands on a channel, each taking at + * most one single argument. A `call` always returns a promise + * with at most one single return value. + */ +export interface IChannel { + call(command: string, arg?: any, cancellationToken?: CancellationToken): Promise; + listen(event: string, arg?: any): Event; +} + +/** + * An `IServerChannel` is the couter part to `IChannel`, + * on the server-side. You should implement this interface + * if you'd like to handle remote promises or events. + */ +export interface IServerChannel { + call(ctx: TContext, command: string, arg?: any, cancellationToken?: CancellationToken): Promise; + listen(ctx: TContext, event: string, arg?: any): Event; +} diff --git a/src/vs/base/parts/ipc/electron-browser/ipc.electron-browser.ts b/src/vs/base/parts/ipc/electron-browser/ipc.electron-browser.ts index 46dbed6a44..6caf45d5af 100644 --- a/src/vs/base/parts/ipc/electron-browser/ipc.electron-browser.ts +++ b/src/vs/base/parts/ipc/electron-browser/ipc.electron-browser.ts @@ -14,7 +14,7 @@ export class Client extends IPCClient implements IDisposable { private protocol: Protocol; private static createProtocol(): Protocol { - const onMessage = Event.fromNodeEventEmitter(ipcRenderer, 'ipc:message', (_, message: string) => message); + const onMessage = Event.fromNodeEventEmitter(ipcRenderer, 'ipc:message', (_, message: Buffer) => message); ipcRenderer.send('ipc:hello'); return new Protocol(ipcRenderer, onMessage); } diff --git a/src/vs/base/parts/ipc/electron-main/ipc.electron-main.ts b/src/vs/base/parts/ipc/electron-main/ipc.electron-main.ts index fed2c61647..55b7de5313 100644 --- a/src/vs/base/parts/ipc/electron-main/ipc.electron-main.ts +++ b/src/vs/base/parts/ipc/electron-main/ipc.electron-main.ts @@ -11,11 +11,11 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; interface IIPCEvent { event: { sender: Electron.WebContents; }; - message: string; + message: Buffer | null; } -function createScopedOnMessageEvent(senderId: number, eventName: string): Event { - const onMessage = Event.fromNodeEventEmitter(ipcMain, eventName, (event, message: string) => ({ event, message })); +function createScopedOnMessageEvent(senderId: number, eventName: string): Event { + const onMessage = Event.fromNodeEventEmitter(ipcMain, eventName, (event, message) => ({ event, message })); const onMessageFromSender = Event.filter(onMessage, ({ event }) => event.sender.id === senderId); return Event.map(onMessageFromSender, ({ message }) => message); } @@ -38,7 +38,7 @@ export class Server extends IPCServer { const onDidClientReconnect = new Emitter(); Server.Clients.set(id, toDisposable(() => onDidClientReconnect.fire())); - const onMessage = createScopedOnMessageEvent(id, 'ipc:message'); + const onMessage = createScopedOnMessageEvent(id, 'ipc:message') as Event; const onDidClientDisconnect = Event.any(Event.signal(createScopedOnMessageEvent(id, 'ipc:disconnect')), onDidClientReconnect.event); const protocol = new Protocol(webContents, onMessage); diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index 60f848ff0b..d6c4377453 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -5,14 +5,15 @@ import { ChildProcess, fork, ForkOptions } from 'child_process'; import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; -import { Delayer, always, createCancelablePromise } from 'vs/base/common/async'; +import { Delayer, createCancelablePromise } from 'vs/base/common/async'; import { deepClone, assign } from 'vs/base/common/objects'; import { Emitter, Event } from 'vs/base/common/event'; import { createQueuedSender } from 'vs/base/node/processes'; -import { ChannelServer as IPCServer, ChannelClient as IPCClient, IChannelClient, IChannel } from 'vs/base/parts/ipc/node/ipc'; +import { ChannelServer as IPCServer, ChannelClient as IPCClient, IChannelClient } from 'vs/base/parts/ipc/node/ipc'; import { isRemoteConsoleLog, log } from 'vs/base/node/console'; import { CancellationToken } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; /** * This implementation doesn't perform well since it uses base64 encoding for buffers. @@ -132,7 +133,7 @@ export class Client implements IChannelClient, IDisposable { const disposable = toDisposable(() => result.cancel()); this.activeRequests.add(disposable); - always(result, () => { + result.finally(() => { cancellationTokenListener.dispose(); this.activeRequests.delete(disposable); diff --git a/src/vs/base/parts/ipc/node/ipc.electron.ts b/src/vs/base/parts/ipc/node/ipc.electron.ts index cd9d32c5f1..c922569bf6 100644 --- a/src/vs/base/parts/ipc/node/ipc.electron.ts +++ b/src/vs/base/parts/ipc/node/ipc.electron.ts @@ -3,33 +3,20 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc'; -import { Event, Emitter } from 'vs/base/common/event'; - -/** - * This implementation doesn't perform well since it uses base64 encoding for buffers. - * Electron 3.0 should have suport for buffers in IPC: https://github.com/electron/electron/pull/13055 - */ +import { Event } from 'vs/base/common/event'; export interface Sender { - send(channel: string, msg: string | null): void; + send(channel: string, msg: Buffer | null): void; } export class Protocol implements IMessagePassingProtocol { - private listener: IDisposable; - - private _onMessage = new Emitter(); - get onMessage(): Event { return this._onMessage.event; } - - constructor(private sender: Sender, onMessageEvent: Event) { - onMessageEvent(msg => this._onMessage.fire(Buffer.from(msg, 'base64'))); - } + constructor(private sender: Sender, readonly onMessage: Event) { } send(message: Buffer): void { try { - this.sender.send('ipc:message', message.toString('base64')); + this.sender.send('ipc:message', message); } catch (e) { // systems are going down } @@ -37,6 +24,5 @@ export class Protocol implements IMessagePassingProtocol { dispose(): void { this.sender.send('ipc:disconnect', null); - this.listener = dispose(this.listener); } } \ No newline at end of file diff --git a/src/vs/base/parts/ipc/node/ipc.net.ts b/src/vs/base/parts/ipc/node/ipc.net.ts index 2bfe8e281f..a63bfd63a9 100644 --- a/src/vs/base/parts/ipc/node/ipc.net.ts +++ b/src/vs/base/parts/ipc/node/ipc.net.ts @@ -6,11 +6,10 @@ import { Socket, Server as NetServer, createConnection, createServer } from 'net'; import { Event, Emitter } from 'vs/base/common/event'; import { IMessagePassingProtocol, ClientConnectionEvent, IPCServer, IPCClient } from 'vs/base/parts/ipc/node/ipc'; -import { join } from 'path'; +import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; import { generateUuid } from 'vs/base/common/uuid'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { TimeoutTimer } from 'vs/base/common/async'; export function generateRandomPipeName(): string { const randomSuffix = generateUuid(); @@ -22,6 +21,80 @@ export function generateRandomPipeName(): string { } } +class ChunkStream { + + private _chunks: Buffer[]; + private _totalLength: number; + + public get byteLength() { + return this._totalLength; + } + + constructor() { + this._chunks = []; + this._totalLength = 0; + } + + public acceptChunk(buff: Buffer) { + this._chunks.push(buff); + this._totalLength += buff.byteLength; + } + + public readUInt32BE(): number { + let tmp = this.read(4); + return tmp.readUInt32BE(0); + } + + public read(byteCount: number): Buffer { + if (byteCount === 0) { + return Buffer.allocUnsafe(0); + } + + if (byteCount > this._totalLength) { + throw new Error(`Cannot read so many bytes!`); + } + + if (this._chunks[0].byteLength === byteCount) { + // super fast path, precisely first chunk must be returned + const result = this._chunks.shift()!; + this._totalLength -= byteCount; + return result; + } + + if (this._chunks[0].byteLength > byteCount) { + // fast path, the reading is entirely within the first chunk + const result = this._chunks[0].slice(0, byteCount); + this._chunks[0] = this._chunks[0].slice(byteCount); + this._totalLength -= byteCount; + return result; + } + + let result = Buffer.allocUnsafe(byteCount); + let resultOffset = 0; + while (byteCount > 0) { + const chunk = this._chunks[0]; + if (chunk.byteLength > byteCount) { + // this chunk will survive + this._chunks[0] = chunk.slice(byteCount); + + chunk.copy(result, resultOffset, 0, byteCount); + resultOffset += byteCount; + this._totalLength -= byteCount; + byteCount -= byteCount; + } else { + // this chunk will be entirely read + this._chunks.shift(); + + chunk.copy(result, resultOffset, 0, chunk.byteLength); + resultOffset += chunk.byteLength; + this._totalLength -= chunk.byteLength; + byteCount -= chunk.byteLength; + } + } + return result; + } +} + /** * A message has the following format: * @@ -35,9 +108,8 @@ export class Protocol implements IDisposable, IMessagePassingProtocol { private static readonly _headerLen = 4; private _isDisposed: boolean; - private _chunks: Buffer[]; + private _incomingData: ChunkStream; - private _firstChunkTimer: TimeoutTimer; private _socketDataListener: (data: Buffer) => void; private _socketEndListener: () => void; private _socketCloseListener: () => void; @@ -48,11 +120,9 @@ export class Protocol implements IDisposable, IMessagePassingProtocol { private _onClose = new Emitter(); readonly onClose: Event = this._onClose.event; - constructor(private _socket: Socket, firstDataChunk?: Buffer) { + constructor(private _socket: Socket) { this._isDisposed = false; - this._chunks = []; - - let totalLength = 0; + this._incomingData = new ChunkStream(); const state = { readHead: true, @@ -61,24 +131,15 @@ export class Protocol implements IDisposable, IMessagePassingProtocol { const acceptChunk = (data: Buffer) => { - this._chunks.push(data); - totalLength += data.length; + this._incomingData.acceptChunk(data); - while (totalLength > 0) { + while (this._incomingData.byteLength > 0) { if (state.readHead) { - // expecting header -> read 5bytes for header - // information: `bodyIsJson` and `bodyLen` - if (totalLength >= Protocol._headerLen) { - const all = Buffer.concat(this._chunks); - - state.bodyLen = all.readUInt32BE(0); + // expecting header -> read header + if (this._incomingData.byteLength >= Protocol._headerLen) { + state.bodyLen = this._incomingData.readUInt32BE(); state.readHead = false; - - const rest = all.slice(Protocol._headerLen); - totalLength = rest.length; - this._chunks = [rest]; - } else { break; } @@ -87,15 +148,8 @@ export class Protocol implements IDisposable, IMessagePassingProtocol { if (!state.readHead) { // expecting body -> read bodyLen-bytes for // the actual message or wait for more data - if (totalLength >= state.bodyLen) { - - const all = Buffer.concat(this._chunks); - const buffer = all.slice(0, state.bodyLen); - - // ensure the getBuffer returns a valid value if invoked from the event listeners - const rest = all.slice(state.bodyLen); - totalLength = rest.length; - this._chunks = [rest]; + if (this._incomingData.byteLength >= state.bodyLen) { + const buffer = this._incomingData.read(state.bodyLen); state.bodyLen = -1; state.readHead = true; @@ -113,28 +167,12 @@ export class Protocol implements IDisposable, IMessagePassingProtocol { } }; - const acceptFirstDataChunk = () => { - if (firstDataChunk && firstDataChunk.length > 0) { - let tmp = firstDataChunk; - firstDataChunk = undefined; - acceptChunk(tmp); - } - }; - - // Make sure to always handle the firstDataChunk if no more `data` event comes in - this._firstChunkTimer = new TimeoutTimer(); - this._firstChunkTimer.setIfNotSet(() => { - acceptFirstDataChunk(); - }, 0); - this._socketDataListener = (data: Buffer) => { - acceptFirstDataChunk(); acceptChunk(data); }; _socket.on('data', this._socketDataListener); this._socketEndListener = () => { - acceptFirstDataChunk(); }; _socket.on('end', this._socketEndListener); @@ -146,7 +184,6 @@ export class Protocol implements IDisposable, IMessagePassingProtocol { dispose(): void { this._isDisposed = true; - this._firstChunkTimer.dispose(); this._socket.removeListener('data', this._socketDataListener); this._socket.removeListener('end', this._socketEndListener); this._socket.removeListener('close', this._socketCloseListener); @@ -156,8 +193,8 @@ export class Protocol implements IDisposable, IMessagePassingProtocol { this._socket.end(); } - getBuffer(): Buffer { - return Buffer.concat(this._chunks); + readEntireBuffer(): Buffer { + return this._incomingData.read(this._incomingData.byteLength); } send(buffer: Buffer): void { diff --git a/src/vs/base/parts/ipc/node/ipc.ts b/src/vs/base/parts/ipc/node/ipc.ts index 04b59f52a8..dcf7d07058 100644 --- a/src/vs/base/parts/ipc/node/ipc.ts +++ b/src/vs/base/parts/ipc/node/ipc.ts @@ -5,9 +5,10 @@ import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter, Relay } from 'vs/base/common/event'; -import { always, CancelablePromise, createCancelablePromise, timeout } from 'vs/base/common/async'; +import { CancelablePromise, createCancelablePromise, timeout } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; +import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; export const enum RequestType { Promise = 100, @@ -51,27 +52,6 @@ enum State { Idle } -/** - * An `IChannel` is an abstraction over a collection of commands. - * You can `call` several commands on a channel, each taking at - * most one single argument. A `call` always returns a promise - * with at most one single return value. - */ -export interface IChannel { - call(command: string, arg?: any, cancellationToken?: CancellationToken): Promise; - listen(event: string, arg?: any): Event; -} - -/** - * An `IServerChannel` is the couter part to `IChannel`, - * on the server-side. You should implement this interface - * if you'd like to handle remote promises or events. - */ -export interface IServerChannel { - call(ctx: TContext, command: string, arg?: any, cancellationToken?: CancellationToken): Promise; - listen(ctx: TContext, event: string, arg?: any): Event; -} - /** * An `IChannelServer` hosts a collection of channels. You are * able to register channels onto it, provided a channel name. @@ -448,9 +428,7 @@ export class ChannelClient implements IChannelClient, IDisposable { this.activeRequests.add(disposable); }); - always(result, () => this.activeRequests.delete(disposable)); - - return result; + return result.finally(() => this.activeRequests.delete(disposable)); } private requestEvent(channelName: string, name: string, arg?: any): Event { @@ -690,7 +668,7 @@ export class IPCClient implements IChannelClient, IChannelSer export function getDelayedChannel(promise: Promise): T { return { call(command: string, arg?: any, cancellationToken?: CancellationToken): Promise { - return promise.then(c => c.call(command, arg, cancellationToken)); + return promise.then(c => c.call(command, arg, cancellationToken)); }, listen(event: string, arg?: any): Event { @@ -752,4 +730,4 @@ export class StaticRouter implements IClientRouter await Event.toPromise(hub.onDidChangeConnections); return await this.route(hub); } -} \ No newline at end of file +} diff --git a/src/vs/base/parts/ipc/test/node/ipc.cp.test.ts b/src/vs/base/parts/ipc/test/node/ipc.cp.test.ts index 98f2d75f55..bcec128f06 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.cp.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.cp.test.ts @@ -5,7 +5,6 @@ import * as assert from 'assert'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; -import { always } from 'vs/base/common/async'; import { TestServiceClient } from './testService'; import { getPathFromAmdModule } from 'vs/base/common/amd'; @@ -27,7 +26,7 @@ suite('IPC, Child Process', () => { assert.equal(r.outgoing, 'pong'); }); - return always(result, () => client.dispose()); + return result.finally(() => client.dispose()); }); test('events', () => { @@ -49,7 +48,7 @@ suite('IPC, Child Process', () => { const request = service.marco(); const result = Promise.all([request, event]); - return always(result, () => client.dispose()); + return result.finally(() => client.dispose()); }); test('event dispose', () => { @@ -74,6 +73,6 @@ suite('IPC, Child Process', () => { assert.equal(count, 2); }); - return always(result, () => client.dispose()); + return result.finally(() => client.dispose()); }); }); diff --git a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts index 3b2e0bf453..14f8b8b1c0 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.net.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.net.test.ts @@ -85,39 +85,4 @@ suite('IPC, Socket Protocol', () => { }); }); }); - - test('can devolve to a socket and evolve again without losing data', () => { - let resolve: (v: void) => void; - let result = new Promise((_resolve, _reject) => { - resolve = _resolve; - }); - const sender = new Protocol(stream); - const receiver1 = new Protocol(stream); - - assert.equal(stream.listenerCount('data'), 2); - assert.equal(stream.listenerCount('end'), 2); - - receiver1.onMessage((msg) => { - assert.equal(JSON.parse(msg.toString()).value, 1); - - let buffer = receiver1.getBuffer(); - receiver1.dispose(); - - assert.equal(stream.listenerCount('data'), 1); - assert.equal(stream.listenerCount('end'), 1); - - const receiver2 = new Protocol(stream, buffer); - receiver2.onMessage((msg) => { - assert.equal(JSON.parse(msg.toString()).value, 2); - resolve(undefined); - }); - }); - - const msg1 = { value: 1 }; - const msg2 = { value: 2 }; - sender.send(Buffer.from(JSON.stringify(msg1))); - sender.send(Buffer.from(JSON.stringify(msg2))); - - return result; - }); }); diff --git a/src/vs/base/parts/ipc/test/node/ipc.test.ts b/src/vs/base/parts/ipc/test/node/ipc.test.ts index 4a1faa6b89..e12cd8c339 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.test.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { IMessagePassingProtocol, IPCServer, ClientConnectionEvent, IPCClient, IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IMessagePassingProtocol, IPCServer, ClientConnectionEvent, IPCClient } from 'vs/base/parts/ipc/node/ipc'; import { Emitter, Event } from 'vs/base/common/event'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { canceled } from 'vs/base/common/errors'; @@ -71,7 +72,7 @@ class TestIPCClient extends IPCClient { class TestIPCServer extends IPCServer { - private onDidClientConnect: Emitter; + private readonly onDidClientConnect: Emitter; constructor() { const onDidClientConnect = new Emitter(); diff --git a/src/vs/base/parts/ipc/test/node/testService.ts b/src/vs/base/parts/ipc/test/node/testService.ts index 420053bdc6..39a0e59a85 100644 --- a/src/vs/base/parts/ipc/test/node/testService.ts +++ b/src/vs/base/parts/ipc/test/node/testService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event, Emitter } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts index 6b58504ced..894d810aa1 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts @@ -8,8 +8,8 @@ import * as types from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree'; import { IconLabel, IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; -import { IQuickNavigateConfiguration, IModel, IDataSource, IFilter, IAccessiblityProvider, IRenderer, IRunner, Mode } from 'vs/base/parts/quickopen/common/quickOpen'; -import { Action, IAction, IActionRunner } from 'vs/base/common/actions'; +import { IQuickNavigateConfiguration, IModel, IDataSource, IFilter, IAccessiblityProvider, IRenderer, IRunner, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; +import { Action, IAction, IActionRunner, IActionItem } from 'vs/base/common/actions'; import { compareAnything } from 'vs/base/common/comparers'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; @@ -35,15 +35,15 @@ let IDS = 0; export class QuickOpenItemAccessorClass implements IItemAccessor { - getItemLabel(entry: QuickOpenEntry): string { + getItemLabel(entry: QuickOpenEntry): string | null { return entry.getLabel(); } - getItemDescription(entry: QuickOpenEntry): string { + getItemDescription(entry: QuickOpenEntry): string | null { return entry.getDescription(); } - getItemPath(entry: QuickOpenEntry): string { + getItemPath(entry: QuickOpenEntry): string | undefined { const resource = entry.getResource(); return resource ? resource.fsPath : undefined; @@ -55,8 +55,8 @@ export const QuickOpenItemAccessor = new QuickOpenItemAccessorClass(); export class QuickOpenEntry { private id: string; private labelHighlights: IHighlight[]; - private descriptionHighlights: IHighlight[]; - private detailHighlights: IHighlight[]; + private descriptionHighlights?: IHighlight[]; + private detailHighlights?: IHighlight[]; private hidden: boolean; constructor(highlights: IHighlight[] = []) { @@ -75,14 +75,14 @@ export class QuickOpenEntry { /** * The label of the entry to identify it from others in the list */ - getLabel(): string { + getLabel(): string | null { return null; } /** * The options for the label to use for this entry */ - getLabelOptions(): IIconLabelValueOptions { + getLabelOptions(): IIconLabelValueOptions | null { return null; } @@ -97,42 +97,42 @@ export class QuickOpenEntry { /** * Detail information about the entry that is optional and can be shown below the label */ - getDetail(): string { + getDetail(): string | null { return null; } /** * The icon of the entry to identify it from others in the list */ - getIcon(): string { + getIcon(): string | null { return null; } /** * A secondary description that is optional and can be shown right to the label */ - getDescription(): string { + getDescription(): string | null { return null; } /** * A tooltip to show when hovering over the entry. */ - getTooltip(): string { + getTooltip(): string | null { return null; } /** * A tooltip to show when hovering over the description portion of the entry. */ - getDescriptionTooltip(): string { + getDescriptionTooltip(): string | null { return null; } /** * An optional keybinding to show for an entry. */ - getKeybinding(): ResolvedKeybinding { + getKeybinding(): ResolvedKeybinding | null { return null; } @@ -140,7 +140,7 @@ export class QuickOpenEntry { * A resource for this entry. Resource URIs can be used to compare different kinds of entries and group * them together. */ - getResource(): URI { + getResource(): URI | null { return null; } @@ -170,7 +170,7 @@ export class QuickOpenEntry { /** * Allows to return highlight ranges that should show up for the entry label and description. */ - getHighlights(): [IHighlight[] /* Label */, IHighlight[] /* Description */, IHighlight[] /* Detail */] { + getHighlights(): [IHighlight[] /* Label */, IHighlight[] | undefined /* Description */, IHighlight[] | undefined /* Detail */] { return [this.labelHighlights, this.descriptionHighlights, this.detailHighlights]; } @@ -180,7 +180,7 @@ export class QuickOpenEntry { * * The context parameter provides additional context information how the run was triggered. */ - run(mode: Mode, context: IContext): boolean { + run(mode: Mode, context: IEntryRunContext): boolean { return false; } @@ -195,9 +195,9 @@ export class QuickOpenEntry { } export class QuickOpenEntryGroup extends QuickOpenEntry { - private entry: QuickOpenEntry; - private groupLabel: string; - private withBorder: boolean; + private entry?: QuickOpenEntry; + private groupLabel?: string; + private withBorder?: boolean; constructor(entry?: QuickOpenEntry, groupLabel?: string, withBorder?: boolean) { super(); @@ -210,11 +210,11 @@ export class QuickOpenEntryGroup extends QuickOpenEntry { /** * The label of the group or null if none. */ - getGroupLabel(): string { + getGroupLabel(): string | undefined { return this.groupLabel; } - setGroupLabel(groupLabel: string): void { + setGroupLabel(groupLabel: string | undefined): void { this.groupLabel = groupLabel; } @@ -222,18 +222,18 @@ export class QuickOpenEntryGroup extends QuickOpenEntry { * Whether to show a border on top of the group entry or not. */ showBorder(): boolean { - return this.withBorder; + return !!this.withBorder; } setShowBorder(showBorder: boolean): void { this.withBorder = showBorder; } - getLabel(): string { + getLabel(): string | null { return this.entry ? this.entry.getLabel() : super.getLabel(); } - getLabelOptions(): IIconLabelValueOptions { + getLabelOptions(): IIconLabelValueOptions | null { return this.entry ? this.entry.getLabelOptions() : super.getLabelOptions(); } @@ -241,27 +241,27 @@ export class QuickOpenEntryGroup extends QuickOpenEntry { return this.entry ? this.entry.getAriaLabel() : super.getAriaLabel(); } - getDetail(): string { + getDetail(): string | null { return this.entry ? this.entry.getDetail() : super.getDetail(); } - getResource(): URI { + getResource(): URI | null { return this.entry ? this.entry.getResource() : super.getResource(); } - getIcon(): string { + getIcon(): string | null { return this.entry ? this.entry.getIcon() : super.getIcon(); } - getDescription(): string { + getDescription(): string | null { return this.entry ? this.entry.getDescription() : super.getDescription(); } - getEntry(): QuickOpenEntry { + getEntry(): QuickOpenEntry | undefined { return this.entry; } - getHighlights(): [IHighlight[], IHighlight[], IHighlight[]] { + getHighlights(): [IHighlight[], IHighlight[] | undefined, IHighlight[] | undefined] { return this.entry ? this.entry.getHighlights() : super.getHighlights(); } @@ -277,7 +277,7 @@ export class QuickOpenEntryGroup extends QuickOpenEntry { this.entry ? this.entry.setHidden(hidden) : super.setHidden(hidden); } - run(mode: Mode, context: IContext): boolean { + run(mode: Mode, context: IEntryRunContext): boolean { return this.entry ? this.entry.run(mode, context) : super.run(mode, context); } } @@ -288,7 +288,7 @@ class NoActionProvider implements IActionProvider { return false; } - getActions(tree: ITree, element: any): IAction[] { + getActions(tree: ITree, element: any): IAction[] | null { return null; } @@ -296,11 +296,11 @@ class NoActionProvider implements IActionProvider { return false; } - getSecondaryActions(tree: ITree, element: any): IAction[] { + getSecondaryActions(tree: ITree, element: any): IAction[] | null { return null; } - getActionItem(tree: ITree, element: any, action: Action) { + getActionItem(tree: ITree, element: any, action: Action): IActionItem | null { return null; } } @@ -316,7 +316,7 @@ export interface IQuickOpenEntryTemplateData { } export interface IQuickOpenEntryGroupTemplateData extends IQuickOpenEntryTemplateData { - group: HTMLDivElement; + group?: HTMLDivElement; } const templateEntry = 'quickOpenEntry'; @@ -325,9 +325,9 @@ const templateEntryGroup = 'quickOpenEntryGroup'; class Renderer implements IRenderer { private actionProvider: IActionProvider; - private actionRunner: IActionRunner; + private actionRunner?: IActionRunner; - constructor(actionProvider: IActionProvider = new NoActionProvider(), actionRunner: IActionRunner | null = null) { + constructor(actionProvider: IActionProvider = new NoActionProvider(), actionRunner?: IActionRunner) { this.actionProvider = actionProvider; this.actionRunner = actionRunner; } @@ -356,7 +356,7 @@ class Renderer implements IRenderer { // Entry const row1 = DOM.$('.quick-open-row'); const row2 = DOM.$('.quick-open-row'); - const entry = DOM.$('.quick-open-entry', null, row1, row2); + const entry = DOM.$('.quick-open-entry', undefined, row1, row2); entryContainer.appendChild(entry); // Icon @@ -379,7 +379,7 @@ class Renderer implements IRenderer { const detail = new HighlightedLabel(detailContainer, true); // Entry Group - let group: HTMLDivElement; + let group: HTMLDivElement | undefined; if (templateId === templateEntryGroup) { group = document.createElement('div'); DOM.addClass(group, 'results-group'); @@ -442,7 +442,9 @@ class Renderer implements IRenderer { // Border if (group.showBorder()) { DOM.addClass(groupData.container, 'results-group-separator'); - groupData.container.style.borderTopColor = styles.pickerGroupBorder.toString(); + if (styles.pickerGroupBorder) { + groupData.container.style.borderTopColor = styles.pickerGroupBorder.toString(); + } } else { DOM.removeClass(groupData.container, 'results-group-separator'); groupData.container.style.borderTopColor = null; @@ -450,8 +452,12 @@ class Renderer implements IRenderer { // Group Label const groupLabel = group.getGroupLabel() || ''; - groupData.group.textContent = groupLabel; - groupData.group.style.color = styles.pickerGroupForeground.toString(); + if (groupData.group) { + groupData.group.textContent = groupLabel; + if (styles.pickerGroupForeground) { + groupData.group.style.color = styles.pickerGroupForeground.toString(); + } + } } // Normal Entry @@ -465,31 +471,31 @@ class Renderer implements IRenderer { // Label const options: IIconLabelValueOptions = entry.getLabelOptions() || Object.create(null); options.matches = labelHighlights || []; - options.title = entry.getTooltip(); - options.descriptionTitle = entry.getDescriptionTooltip() || entry.getDescription(); // tooltip over description because it could overflow + options.title = types.withNullAsUndefined(entry.getTooltip()); + options.descriptionTitle = entry.getDescriptionTooltip() || types.withNullAsUndefined(entry.getDescription()); // tooltip over description because it could overflow options.descriptionMatches = descriptionHighlights || []; - data.label.setLabel(entry.getLabel(), entry.getDescription(), options); + data.label.setLabel(types.withNullAsUndefined(entry.getLabel()), types.withNullAsUndefined(entry.getDescription()), options); // Meta - data.detail.set(entry.getDetail(), detailHighlights); + data.detail.set(types.withNullAsUndefined(entry.getDetail()), detailHighlights); // Keybinding - data.keybinding.set(entry.getKeybinding()); + data.keybinding.set(entry.getKeybinding()!); } } disposeTemplate(templateId: string, templateData: IQuickOpenEntryGroupTemplateData): void { const data = templateData as IQuickOpenEntryGroupTemplateData; data.actionBar.dispose(); - data.actionBar = null; - data.container = null; - data.entry = null; - data.keybinding = null; - data.detail = null; - data.group = null; - data.icon = null; + data.actionBar = null!; + data.container = null!; + data.entry = null!; + data.keybinding = null!; + data.detail = null!; + data.group = null!; + data.icon = null!; data.label.dispose(); - data.label = null; + data.label = null!; } } @@ -562,7 +568,7 @@ export class QuickOpenModel implements return entry.getId(); } - getLabel(entry: QuickOpenEntry): string { + getLabel(entry: QuickOpenEntry): string | null { return entry.getLabel(); } @@ -579,7 +585,7 @@ export class QuickOpenModel implements return !entry.isHidden(); } - run(entry: QuickOpenEntry, mode: Mode, context: IContext): boolean { + run(entry: QuickOpenEntry, mode: Mode, context: IEntryRunContext): boolean { return entry.run(mode, context); } } @@ -603,8 +609,8 @@ export function compareEntries(elementA: QuickOpenEntry, elementB: QuickOpenEntr } // Fallback to the full path if labels are identical and we have associated resources - let nameA = elementA.getLabel(); - let nameB = elementB.getLabel(); + let nameA = elementA.getLabel()!; + let nameB = elementB.getLabel()!; if (nameA === nameB) { const resourceA = elementA.getResource(); const resourceB = elementB.getResource(); diff --git a/src/vs/base/parts/quickopen/browser/quickOpenViewer.ts b/src/vs/base/parts/quickopen/browser/quickOpenViewer.ts index d80e2a4f5b..2583abc37c 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenViewer.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenViewer.ts @@ -24,7 +24,7 @@ export class DataSource implements IDataSource { getId(tree: ITree, element: any): string { if (!element) { - return null; + return null!; } const model = this.modelProvider.getModel(); @@ -33,7 +33,7 @@ export class DataSource implements IDataSource { hasChildren(tree: ITree, element: any): boolean { const model = this.modelProvider.getModel(); - return model && model === element && model.entries.length > 0; + return !!(model && model === element && model.entries.length > 0); } getChildren(tree: ITree, element: any): Promise { @@ -49,10 +49,10 @@ export class DataSource implements IDataSource { export class AccessibilityProvider implements IAccessibilityProvider { constructor(private modelProvider: IModelProvider) { } - getAriaLabel(tree: ITree, element: any): string { + getAriaLabel(tree: ITree, element: any): string | null { const model = this.modelProvider.getModel(); - return model.accessibilityProvider && model.accessibilityProvider.getAriaLabel(element); + return model.accessibilityProvider ? model.accessibilityProvider.getAriaLabel(element) : null; } getPosInSet(tree: ITree, element: any): string { diff --git a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts index c862671c6c..206b6a03d0 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts @@ -36,7 +36,7 @@ export interface IQuickOpenCallbacks { export interface IQuickOpenOptions extends IQuickOpenStyles { minItemsToShow?: number; maxItemsToShow?: number; - inputPlaceHolder: string; + inputPlaceHolder?: string; inputAriaLabel?: string; actionProvider?: IActionProvider; keyboardSupport?: boolean; @@ -110,12 +110,12 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { private visible: boolean; private isLoosingFocus: boolean; private callbacks: IQuickOpenCallbacks; - private quickNavigateConfiguration: IQuickNavigateConfiguration; + private quickNavigateConfiguration: IQuickNavigateConfiguration | undefined; private container: HTMLElement; private treeElement: HTMLElement; private inputElement: HTMLElement; private layoutDimensions: DOM.Dimension; - private model: IModel; + private model: IModel | null; private inputChangingTimeoutHandle: any; private styles: IQuickOpenStyles; private renderer: Renderer; @@ -137,7 +137,7 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { } getModel(): IModel { - return this.model; + return this.model!; } setCallbacks(callbacks: IQuickOpenCallbacks): void { @@ -181,7 +181,7 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { DOM.addClass(this.inputContainer, 'quick-open-input'); this.element.appendChild(this.inputContainer); - this.inputBox = this._register(new InputBox(this.inputContainer, null, { + this.inputBox = this._register(new InputBox(this.inputContainer, undefined, { placeholder: this.options.inputPlaceHolder || '', ariaLabel: DEFAULT_INPUT_ARIA_LABEL, inputBackground: this.styles.inputBackground, @@ -538,10 +538,15 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { } // ARIA - this.inputElement.setAttribute('aria-activedescendant', this.treeElement.getAttribute('aria-activedescendant')); + const arivaActiveDescendant = this.treeElement.getAttribute('aria-activedescendant'); + if (arivaActiveDescendant) { + this.inputElement.setAttribute('aria-activedescendant', arivaActiveDescendant); + } else { + this.inputElement.removeAttribute('aria-activedescendant'); + } const context: IEntryRunContext = { event: event, keymods: this.extractKeyMods(event), quickNavigateConfiguration: this.quickNavigateConfiguration }; - this.model.runner.run(value, Mode.PREVIEW, context); + this.model!.runner.run(value, Mode.PREVIEW, context); } private elementSelected(value: any, event?: any, preferredMode?: Mode): void { @@ -553,7 +558,7 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { const context: IEntryRunContext = { event, keymods: this.extractKeyMods(event), quickNavigateConfiguration: this.quickNavigateConfiguration }; - hide = this.model.runner.run(value, mode, context); + hide = this.model!.runner.run(value, mode, context); } // Hide if command was run successfully @@ -603,7 +608,7 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { if (types.isString(param)) { this.doShowWithPrefix(param); } else { - if (options.value) { + if (options && options.value) { this.restoreLastInput(options.value); } this.doShowWithInput(param, options && options.autoFocus ? options.autoFocus : {}); @@ -634,7 +639,7 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { this.setInput(input, autoFocus); } - private setInputAndLayout(input: IModel, autoFocus: IAutoFocus): void { + private setInputAndLayout(input: IModel, autoFocus?: IAutoFocus): void { this.treeContainer.style.height = `${this.getHeight(input)}px`; this.tree.setInput(null).then(() => { @@ -676,7 +681,7 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { const prefix = autoFocus.autoFocusPrefixMatch; const lowerCasePrefix = prefix.toLowerCase(); for (const entry of entries) { - const label = input.dataSource.getLabel(entry); + const label = input.dataSource.getLabel(entry) || ''; if (!caseSensitiveMatch && label.indexOf(prefix) === 0) { caseSensitiveMatch = entry; @@ -747,13 +752,13 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { // Indicate entries to tree this.tree.layout(); - const entries = input ? input.entries.filter(e => this.isElementVisible(input, e)) : []; + const entries = input ? input.entries!.filter(e => this.isElementVisible(input!, e)) : []; this.updateResultCount(entries.length); // Handle auto focus if (autoFocus) { if (entries.length) { - this.autoFocus(input, entries, autoFocus); + this.autoFocus(input!, entries, autoFocus); } } }); @@ -770,7 +775,7 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { let height = 0; - let preferredItemsHeight: number; + let preferredItemsHeight: number | undefined; if (this.layoutDimensions && this.layoutDimensions.height) { preferredItemsHeight = (this.layoutDimensions.height - 50 /* subtract height of input field (30px) and some spacing (drop shadow) to fit */) * 0.4 /* max 40% of screen */; } @@ -834,12 +839,12 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { } if (this.callbacks.onHide) { - this.callbacks.onHide(reason); + this.callbacks.onHide(reason!); } } getQuickNavigateConfiguration(): IQuickNavigateConfiguration { - return this.quickNavigateConfiguration; + return this.quickNavigateConfiguration!; } setPlaceHolder(placeHolder: string): void { @@ -868,7 +873,7 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { } } - setInput(input: IModel, autoFocus: IAutoFocus, ariaLabel?: string): void { + setInput(input: IModel, autoFocus?: IAutoFocus, ariaLabel?: string): void { if (!this.isVisible()) { return; } @@ -940,7 +945,7 @@ export class QuickOpenWidget extends Disposable implements IModelProvider { return this.inputBox; } - setExtraClass(clazz: string): void { + setExtraClass(clazz: string | null): void { const previousClass = this.element.getAttribute('quick-open-extra-class'); if (previousClass) { DOM.removeClasses(this.element, previousClass); diff --git a/src/vs/base/parts/quickopen/common/quickOpen.ts b/src/vs/base/parts/quickopen/common/quickOpen.ts index f70ceaadc7..86afadaefd 100644 --- a/src/vs/base/parts/quickopen/common/quickOpen.ts +++ b/src/vs/base/parts/quickopen/common/quickOpen.ts @@ -49,7 +49,7 @@ export const enum Mode { export interface IEntryRunContext { event: any; keymods: IKeyMods; - quickNavigateConfiguration: IQuickNavigateConfiguration; + quickNavigateConfiguration: IQuickNavigateConfiguration | undefined; } export interface IKeyMods { @@ -59,7 +59,7 @@ export interface IKeyMods { export interface IDataSource { getId(entry: T): string; - getLabel(entry: T): string; + getLabel(entry: T): string | null; } /** diff --git a/src/vs/base/parts/quickopen/common/quickOpenScorer.ts b/src/vs/base/parts/quickopen/common/quickOpenScorer.ts index f755cc4bf7..99d743f0c8 100644 --- a/src/vs/base/parts/quickopen/common/quickOpenScorer.ts +++ b/src/vs/base/parts/quickopen/common/quickOpenScorer.ts @@ -5,7 +5,7 @@ import { compareAnything } from 'vs/base/common/comparers'; import { matchesPrefix, IMatch, matchesCamelCase, isUpper } from 'vs/base/common/filters'; -import { nativeSep } from 'vs/base/common/paths'; +import { sep } from 'vs/base/common/path'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { stripWildcards, equalsIgnoreCase } from 'vs/base/common/strings'; import { CharCode } from 'vs/base/common/charCode'; @@ -156,7 +156,7 @@ function doScore(query: string, queryLower: string, queryLength: number, target: return [scores[queryLength * targetLength - 1], positions.reverse()]; } -function computeCharScore(queryCharAtIndex, queryLowerCharAtIndex, target: string, targetLower: string, targetIndex: number, matchesSequenceLength: number): number { +function computeCharScore(queryCharAtIndex: string, queryLowerCharAtIndex: string, target: string, targetLower: string, targetIndex: number, matchesSequenceLength: number): number { let score = 0; if (queryLowerCharAtIndex !== targetLower[targetIndex]) { @@ -285,17 +285,17 @@ export interface IItemAccessor { /** * Just the label of the item to score on. */ - getItemLabel(item: T): string; + getItemLabel(item: T): string | null; /** * The optional description of the item to score on. Can be null. */ - getItemDescription(item: T): string; + getItemDescription(item: T): string | null; /** * If the item is a file, the path of the file to score on. Can be null. */ - getItemPath(file: T): string; + getItemPath(file: T): string | undefined; } const PATH_IDENTITY_SCORE = 1 << 18; @@ -320,11 +320,11 @@ export function prepareQuery(original: string): IPreparedQuery { let value = stripWildcards(original).replace(/\s/g, ''); // get rid of all wildcards and whitespace if (isWindows) { - value = value.replace(/\//g, nativeSep); // Help Windows users to search for paths when using slash + value = value.replace(/\//g, sep); // Help Windows users to search for paths when using slash } const lowercase = value.toLowerCase(); - const containsPathSeparator = value.indexOf(nativeSep) >= 0; + const containsPathSeparator = value.indexOf(sep) >= 0; return { original, value, lowercase, containsPathSeparator }; } @@ -376,10 +376,10 @@ function createMatches(offsets: undefined | number[]): IMatch[] { return ret; } -function doScoreItem(label: string, description: string, path: string, query: IPreparedQuery, fuzzy: boolean): IItemScore { +function doScoreItem(label: string, description: string | null, path: string | undefined, query: IPreparedQuery, fuzzy: boolean): IItemScore { // 1.) treat identity matches on full path highest - if (path && isLinux ? query.original === path : equalsIgnoreCase(query.original, path)) { + if (path && (isLinux ? query.original === path : equalsIgnoreCase(query.original, path))) { return { score: PATH_IDENTITY_SCORE, labelMatch: [{ start: 0, end: label.length }], descriptionMatch: description ? [{ start: 0, end: description.length }] : undefined }; } @@ -410,7 +410,7 @@ function doScoreItem(label: string, description: string, path: string, query: IP if (description) { let descriptionPrefix = description; if (!!path) { - descriptionPrefix = `${description}${nativeSep}`; // assume this is a file path + descriptionPrefix = `${description}${sep}`; // assume this is a file path } const descriptionPrefixLength = descriptionPrefix.length; @@ -469,8 +469,8 @@ export function compareItemsByScore(itemA: T, itemB: T, query: IPreparedQuery return scoreA === LABEL_PREFIX_SCORE ? -1 : 1; } - const labelA = accessor.getItemLabel(itemA); - const labelB = accessor.getItemLabel(itemB); + const labelA = accessor.getItemLabel(itemA) || ''; + const labelB = accessor.getItemLabel(itemB) || ''; // prefer shorter names when both match on label prefix if (labelA.length !== labelB.length) { @@ -484,8 +484,8 @@ export function compareItemsByScore(itemA: T, itemB: T, query: IPreparedQuery return scoreA === LABEL_CAMELCASE_SCORE ? -1 : 1; } - const labelA = accessor.getItemLabel(itemA); - const labelB = accessor.getItemLabel(itemB); + const labelA = accessor.getItemLabel(itemA) || ''; + const labelB = accessor.getItemLabel(itemB) || ''; // prefer more compact camel case matches over longer const comparedByMatchLength = compareByMatchLength(itemScoreA.labelMatch, itemScoreB.labelMatch); @@ -592,8 +592,8 @@ function compareByMatchLength(matchesA?: IMatch[], matchesB?: IMatch[]): number export function fallbackCompare(itemA: T, itemB: T, query: IPreparedQuery, accessor: IItemAccessor): number { // check for label + description length and prefer shorter - const labelA = accessor.getItemLabel(itemA); - const labelB = accessor.getItemLabel(itemB); + const labelA = accessor.getItemLabel(itemA) || ''; + const labelB = accessor.getItemLabel(itemB) || ''; const descriptionA = accessor.getItemDescription(itemA); const descriptionB = accessor.getItemDescription(itemB); diff --git a/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts b/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts index b5cfc62349..9df0ea6512 100644 --- a/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts +++ b/src/vs/base/parts/quickopen/test/common/quickOpenScorer.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as scorer from 'vs/base/parts/quickopen/common/quickOpenScorer'; import { URI } from 'vs/base/common/uri'; -import { basename, dirname, nativeSep } from 'vs/base/common/paths'; +import { basename, dirname, sep } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; class ResourceAccessorClass implements scorer.IItemAccessor { @@ -797,9 +797,9 @@ suite('Quick Open Scorer', () => { }); test('compareFilesByScore - avoid match scattering (bug #12095)', function () { - const resourceA = URI.file('src/vs/workbench/parts/files/common/explorerViewModel.ts'); - const resourceB = URI.file('src/vs/workbench/parts/files/browser/views/explorerView.ts'); - const resourceC = URI.file('src/vs/workbench/parts/files/browser/views/explorerViewer.ts'); + const resourceA = URI.file('src/vs/workbench/contrib/files/common/explorerViewModel.ts'); + const resourceB = URI.file('src/vs/workbench/contrib/files/browser/views/explorerView.ts'); + const resourceC = URI.file('src/vs/workbench/contrib/files/browser/views/explorerViewer.ts'); let query = 'filesexplorerview.ts'; @@ -815,6 +815,6 @@ suite('Quick Open Scorer', () => { assert.equal(scorer.prepareQuery('model Tester.ts').value, 'modelTester.ts'); assert.equal(scorer.prepareQuery('Model Tester.ts').lowercase, 'modeltester.ts'); assert.equal(scorer.prepareQuery('ModelTester.ts').containsPathSeparator, false); - assert.equal(scorer.prepareQuery('Model' + nativeSep + 'Tester.ts').containsPathSeparator, true); + assert.equal(scorer.prepareQuery('Model' + sep + 'Tester.ts').containsPathSeparator, true); }); }); \ No newline at end of file diff --git a/src/vs/base/parts/tree/browser/tree.ts b/src/vs/base/parts/tree/browser/tree.ts index 63388531d6..bf4501dd91 100644 --- a/src/vs/base/parts/tree/browser/tree.ts +++ b/src/vs/base/parts/tree/browser/tree.ts @@ -724,12 +724,12 @@ export interface IActionProvider { /** * Returns whether or not the element has actions. These show up in place right to the element in the tree. */ - hasActions(tree: ITree, element: any): boolean; + hasActions(tree: ITree | null, element: any): boolean; /** * Returns a promise of an array with the actions of the element that should show up in place right to the element in the tree. */ - getActions(tree: ITree, element: any): IAction[]; + getActions(tree: ITree | null, element: any): IAction[] | null; /** * Returns whether or not the element has secondary actions. These show up once the user has expanded the element's action bar. @@ -739,7 +739,7 @@ export interface IActionProvider { /** * Returns a promise of an array with the secondary actions of the element that should show up once the user has expanded the element's action bar. */ - getSecondaryActions(tree: ITree, element: any): IAction[]; + getSecondaryActions(tree: ITree, element: any): IAction[] | null; /** * Returns an action item to render an action. diff --git a/src/vs/base/parts/tree/browser/treeDefaults.ts b/src/vs/base/parts/tree/browser/treeDefaults.ts index 0228661b85..a0a9b90995 100644 --- a/src/vs/base/parts/tree/browser/treeDefaults.ts +++ b/src/vs/base/parts/tree/browser/treeDefaults.ts @@ -13,7 +13,7 @@ import * as mouse from 'vs/base/browser/mouseEvent'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import * as _ from 'vs/base/parts/tree/browser/tree'; import { IDragAndDropData } from 'vs/base/browser/dnd'; -import { KeyCode, KeyMod, Keybinding, SimpleKeybinding, createSimpleKeybinding } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyMod, Keybinding, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; export interface IKeyBindingCallback { (tree: _.ITree, event: IKeyboardEvent): void; @@ -49,7 +49,7 @@ export interface IControllerOptions { } interface IKeybindingDispatcherItem { - keybinding: Keybinding; + keybinding: Keybinding | null; callback: IKeyBindingCallback; } @@ -62,10 +62,12 @@ export class KeybindingDispatcher { } public has(keybinding: KeyCode): boolean { - let target = createSimpleKeybinding(keybinding, platform.OS); - for (const a of this._arr) { - if (target.equals(a.keybinding)) { - return true; + let target = createKeybinding(keybinding, platform.OS); + if (target !== null) { + for (const a of this._arr) { + if (target.equals(a.keybinding)) { + return true; + } } } return false; @@ -73,7 +75,7 @@ export class KeybindingDispatcher { public set(keybinding: number, callback: IKeyBindingCallback) { this._arr.push({ - keybinding: createSimpleKeybinding(keybinding, platform.OS), + keybinding: createKeybinding(keybinding, platform.OS), callback: callback }); } @@ -82,7 +84,7 @@ export class KeybindingDispatcher { // Loop from the last to the first to handle overwrites for (let i = this._arr.length - 1; i >= 0; i--) { let item = this._arr[i]; - if (keybinding.equals(item.keybinding)) { + if (keybinding.toChord().equals(item.keybinding)) { return item.callback; } } @@ -556,7 +558,7 @@ export class DefaultTreestyler implements _.ITreeStyler { export class CollapseAllAction extends Action { constructor(private viewer: _.ITree, enabled: boolean) { - super('vs.tree.collapse', nls.localize('collapse', "Collapse"), 'monaco-tree-action collapse-all', enabled); + super('vs.tree.collapse', nls.localize('collapse all', "Collapse All"), 'monaco-tree-action collapse-all', enabled); } public run(context?: any): Promise { diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts index 60cc6c9324..7f1b7b8101 100644 --- a/src/vs/base/parts/tree/browser/treeView.ts +++ b/src/vs/base/parts/tree/browser/treeView.ts @@ -1286,8 +1286,8 @@ export class TreeView extends HeightMap { element = this.model!.getInput(); position = DOM.getDomNodePagePosition(this.inputItem.element); } else { - let id = this.context.dataSource.getId(this.context.tree, element); - let viewItem = this.items[id]; + const id = this.context.dataSource.getId(this.context.tree, element); + const viewItem = this.items[id!]; position = DOM.getDomNodePagePosition(viewItem.element); } @@ -1422,6 +1422,8 @@ export class TreeView extends HeightMap { } private onDragOver(e: DragEvent): boolean { + e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome) + let event = new Mouse.DragMouseEvent(e); let viewItem = this.getItemAround(event.target); diff --git a/src/vs/base/test/browser/ui/menu/menubar.test.ts b/src/vs/base/test/browser/ui/menu/menubar.test.ts new file mode 100644 index 0000000000..54dba9b398 --- /dev/null +++ b/src/vs/base/test/browser/ui/menu/menubar.test.ts @@ -0,0 +1,81 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { $ } from 'vs/base/browser/dom'; +import { MenuBar } from 'vs/base/browser/ui/menu/menubar'; + +function getButtonElementByAriaLabel(menubarElement: HTMLElement, ariaLabel: string): HTMLElement | null { + let i; + for (i = 0; i < menubarElement.childElementCount; i++) { + + if (menubarElement.children[i].getAttribute('aria-label') === ariaLabel) { + return menubarElement.children[i] as HTMLElement; + } + } + + return null; +} + +function getTitleDivFromButtonDiv(menuButtonElement: HTMLElement): HTMLElement | null { + let i; + for (i = 0; i < menuButtonElement.childElementCount; i++) { + if (menuButtonElement.children[i].classList.contains('menubar-menu-title')) { + return menuButtonElement.children[i] as HTMLElement; + } + } + + return null; +} + +function getMnemonicFromTitleDiv(menuTitleDiv: HTMLElement): string | null { + let i; + for (i = 0; i < menuTitleDiv.childElementCount; i++) { + if (menuTitleDiv.children[i].tagName.toLocaleLowerCase() === 'mnemonic') { + return menuTitleDiv.children[i].textContent; + } + } + + return null; +} + +function validateMenuBarItem(menubar: MenuBar, menubarContainer: HTMLElement, label: string, readableLabel: string, mnemonic: string) { + menubar.push([ + { + actions: [], + label: label + } + ]); + + const buttonElement = getButtonElementByAriaLabel(menubarContainer, readableLabel); + assert(buttonElement !== null, `Button element not found for ${readableLabel} button.`); + + const titleDiv = getTitleDivFromButtonDiv(buttonElement!); + assert(titleDiv !== null, `Title div not found for ${readableLabel} button.`); + + const mnem = getMnemonicFromTitleDiv(titleDiv!); + assert.equal(mnem, mnemonic, 'Mnemonic not correct'); +} + +suite('Menubar', () => { + const container = $('.container'); + + const menubar = new MenuBar(container, { + enableMnemonics: true, + visibility: 'visible' + }); + + test('English File menu renders mnemonics', function () { + validateMenuBarItem(menubar, container, '&File', 'File', 'F'); + }); + + test('Russian File menu renders mnemonics', function () { + validateMenuBarItem(menubar, container, '&Файл', 'Файл', 'Ф'); + }); + + test('Chinese File menu renders mnemonics', function () { + validateMenuBarItem(menubar, container, '文件(&F)', '文件', 'F'); + }); +}); diff --git a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts index fe0b883531..1212895924 100644 --- a/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts +++ b/src/vs/base/test/browser/ui/tree/asyncDataTree.test.ts @@ -101,4 +101,229 @@ suite('AsyncDataTree', function () { await tree.updateChildren(root); assert.equal(container.querySelectorAll('.monaco-list-row').length, 1); }); + + test('issue #68648', async () => { + const container = document.createElement('div'); + container.style.width = '200px'; + container.style.height = '200px'; + + const delegate = new class implements IListVirtualDelegate { + getHeight() { return 20; } + getTemplateId(element: Element): string { return 'default'; } + }; + + const renderer = new class implements ITreeRenderer { + readonly templateId = 'default'; + renderTemplate(container: HTMLElement): HTMLElement { + return container; + } + renderElement(element: ITreeNode, index: number, templateData: HTMLElement): void { + templateData.textContent = element.element.id; + } + disposeTemplate(templateData: HTMLElement): void { + // noop + } + }; + + const getChildrenCalls: string[] = []; + const dataSource = new class implements IAsyncDataSource { + hasChildren(element: Element): boolean { + return !!element.children && element.children.length > 0; + } + getChildren(element: Element): Promise { + getChildrenCalls.push(element.id); + return Promise.resolve(element.children || []); + } + }; + + const identityProvider = new class implements IIdentityProvider { + getId(element: Element) { + return element.id; + } + }; + + const root: Element = { + id: 'root', + children: [{ + id: 'a' + }] + }; + + const _: (id: string) => Element = find.bind(null, root.children); + + const tree = new AsyncDataTree(container, delegate, [renderer], dataSource, { identityProvider }); + tree.layout(200); + + await tree.setInput(root); + assert.deepStrictEqual(getChildrenCalls, ['root']); + + let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; + assert(!hasClass(twistie, 'collapsible')); + assert(!hasClass(twistie, 'collapsed')); + assert(tree.getNode().children[0].collapsed); + + _('a').children = [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }]; + await tree.updateChildren(root); + + assert.deepStrictEqual(getChildrenCalls, ['root', 'root']); + twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; + assert(hasClass(twistie, 'collapsible')); + assert(hasClass(twistie, 'collapsed')); + assert(tree.getNode().children[0].collapsed); + + _('a').children = []; + await tree.updateChildren(root); + + assert.deepStrictEqual(getChildrenCalls, ['root', 'root', 'root']); + twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; + assert(!hasClass(twistie, 'collapsible')); + assert(!hasClass(twistie, 'collapsed')); + assert(tree.getNode().children[0].collapsed); + + _('a').children = [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }]; + await tree.updateChildren(root); + + assert.deepStrictEqual(getChildrenCalls, ['root', 'root', 'root', 'root']); + twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; + assert(hasClass(twistie, 'collapsible')); + assert(hasClass(twistie, 'collapsed')); + assert(tree.getNode().children[0].collapsed); + }); + + test('issue #67722 - once resolved, refreshed collapsed nodes should only get children when expanded', async () => { + const container = document.createElement('div'); + container.style.width = '200px'; + container.style.height = '200px'; + + const delegate = new class implements IListVirtualDelegate { + getHeight() { return 20; } + getTemplateId(element: Element): string { return 'default'; } + }; + + const renderer = new class implements ITreeRenderer { + readonly templateId = 'default'; + renderTemplate(container: HTMLElement): HTMLElement { + return container; + } + renderElement(element: ITreeNode, index: number, templateData: HTMLElement): void { + templateData.textContent = element.element.id; + } + disposeTemplate(templateData: HTMLElement): void { + // noop + } + }; + + const getChildrenCalls: string[] = []; + const dataSource = new class implements IAsyncDataSource { + hasChildren(element: Element): boolean { + return !!element.children && element.children.length > 0; + } + getChildren(element: Element): Promise { + getChildrenCalls.push(element.id); + return Promise.resolve(element.children || []); + } + }; + + const identityProvider = new class implements IIdentityProvider { + getId(element: Element) { + return element.id; + } + }; + + const root: Element = { + id: 'root', + children: [{ + id: 'a', children: [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }] + }] + }; + + const _: (id: string) => Element = find.bind(null, root.children); + + const tree = new AsyncDataTree(container, delegate, [renderer], dataSource, { identityProvider }); + tree.layout(200); + + await tree.setInput(root); + assert(tree.getNode(_('a')).collapsed); + assert.deepStrictEqual(getChildrenCalls, ['root']); + + await tree.expand(_('a')); + assert(!tree.getNode(_('a')).collapsed); + assert.deepStrictEqual(getChildrenCalls, ['root', 'a']); + + tree.collapse(_('a')); + assert(tree.getNode(_('a')).collapsed); + assert.deepStrictEqual(getChildrenCalls, ['root', 'a']); + + await tree.updateChildren(); + assert(tree.getNode(_('a')).collapsed); + assert.deepStrictEqual(getChildrenCalls, ['root', 'a', 'root'], 'a should not be refreshed, since it\' collapsed'); + }); + + test('resolved collapsed nodes which lose children should lose twistie as well', async () => { + const container = document.createElement('div'); + container.style.width = '200px'; + container.style.height = '200px'; + + const delegate = new class implements IListVirtualDelegate { + getHeight() { return 20; } + getTemplateId(element: Element): string { return 'default'; } + }; + + const renderer = new class implements ITreeRenderer { + readonly templateId = 'default'; + renderTemplate(container: HTMLElement): HTMLElement { + return container; + } + renderElement(element: ITreeNode, index: number, templateData: HTMLElement): void { + templateData.textContent = element.element.id; + } + disposeTemplate(templateData: HTMLElement): void { + // noop + } + }; + + const dataSource = new class implements IAsyncDataSource { + hasChildren(element: Element): boolean { + return !!element.children && element.children.length > 0; + } + getChildren(element: Element): Promise { + return Promise.resolve(element.children || []); + } + }; + + const identityProvider = new class implements IIdentityProvider { + getId(element: Element) { + return element.id; + } + }; + + const root: Element = { + id: 'root', + children: [{ + id: 'a', children: [{ id: 'aa' }, { id: 'ab' }, { id: 'ac' }] + }] + }; + + const _: (id: string) => Element = find.bind(null, root.children); + + const tree = new AsyncDataTree(container, delegate, [renderer], dataSource, { identityProvider }); + tree.layout(200); + + await tree.setInput(root); + await tree.expand(_('a')); + + let twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; + assert(hasClass(twistie, 'collapsible')); + assert(!hasClass(twistie, 'collapsed')); + assert(!tree.getNode(_('a')).collapsed); + + tree.collapse(_('a')); + _('a').children = []; + await tree.updateChildren(root); + + twistie = container.querySelector('.monaco-list-row:first-child .monaco-tl-twistie') as HTMLElement; + assert(!hasClass(twistie, 'collapsible')); + assert(!hasClass(twistie, 'collapsed')); + assert(tree.getNode(_('a')).collapsed); + }); }); \ No newline at end of file diff --git a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts index 6668d636fa..79694d62fe 100644 --- a/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts +++ b/src/vs/base/test/browser/ui/tree/objectTreeModel.test.ts @@ -168,4 +168,77 @@ suite('ObjectTreeModel', function () { model.setChildren(null, data); assert.deepEqual(toArray(list), ['father']); }); + + test('sorter', () => { + let compare: (a: string, b: string) => number = (a, b) => a < b ? -1 : 1; + + const list: ITreeNode[] = []; + const model = new ObjectTreeModel(toSpliceable(list), { sorter: { compare(a, b) { return compare(a, b); } } }); + const data = [ + { element: 'cars', children: [{ element: 'sedan' }, { element: 'convertible' }, { element: 'compact' }] }, + { element: 'airplanes', children: [{ element: 'passenger' }, { element: 'jet' }] }, + { element: 'bicycles', children: [{ element: 'dutch' }, { element: 'mountain' }, { element: 'electric' }] }, + ]; + + model.setChildren(null, data); + assert.deepEqual(toArray(list), ['airplanes', 'jet', 'passenger', 'bicycles', 'dutch', 'electric', 'mountain', 'cars', 'compact', 'convertible', 'sedan']); + }); + + test('resort', () => { + let compare: (a: string, b: string) => number = () => 0; + + const list: ITreeNode[] = []; + const model = new ObjectTreeModel(toSpliceable(list), { sorter: { compare(a, b) { return compare(a, b); } } }); + const data = [ + { element: 'cars', children: [{ element: 'sedan' }, { element: 'convertible' }, { element: 'compact' }] }, + { element: 'airplanes', children: [{ element: 'passenger' }, { element: 'jet' }] }, + { element: 'bicycles', children: [{ element: 'dutch' }, { element: 'mountain' }, { element: 'electric' }] }, + ]; + + model.setChildren(null, data); + assert.deepEqual(toArray(list), ['cars', 'sedan', 'convertible', 'compact', 'airplanes', 'passenger', 'jet', 'bicycles', 'dutch', 'mountain', 'electric']); + + // lexicographical + compare = (a, b) => a < b ? -1 : 1; + + // non-recursive + model.resort(null, false); + assert.deepEqual(toArray(list), ['airplanes', 'passenger', 'jet', 'bicycles', 'dutch', 'mountain', 'electric', 'cars', 'sedan', 'convertible', 'compact']); + + // recursive + model.resort(); + assert.deepEqual(toArray(list), ['airplanes', 'jet', 'passenger', 'bicycles', 'dutch', 'electric', 'mountain', 'cars', 'compact', 'convertible', 'sedan']); + + // reverse + compare = (a, b) => a < b ? 1 : -1; + + // scoped + model.resort('cars'); + assert.deepEqual(toArray(list), ['airplanes', 'jet', 'passenger', 'bicycles', 'dutch', 'electric', 'mountain', 'cars', 'sedan', 'convertible', 'compact']); + + // recursive + model.resort(); + assert.deepEqual(toArray(list), ['cars', 'sedan', 'convertible', 'compact', 'bicycles', 'mountain', 'electric', 'dutch', 'airplanes', 'passenger', 'jet']); + }); + + test('expandTo', () => { + const list: ITreeNode[] = []; + const model = new ObjectTreeModel(toSpliceable(list), { collapseByDefault: true }); + + model.setChildren(null, [ + { + element: 0, children: [ + { element: 10, children: [{ element: 100, children: [{ element: 1000 }] }] }, + { element: 11 }, + { element: 12 }, + ] + }, + { element: 1 }, + { element: 2 } + ]); + + assert.deepEqual(toArray(list), [0, 1, 2]); + model.expandTo(1000); + assert.deepEqual(toArray(list), [0, 10, 100, 1000, 11, 12, 1, 2]); + }); }); \ No newline at end of file diff --git a/src/vs/base/test/common/extpath.test.ts b/src/vs/base/test/common/extpath.test.ts new file mode 100644 index 0000000000..5455959936 --- /dev/null +++ b/src/vs/base/test/common/extpath.test.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as assert from 'assert'; +import * as extpath from 'vs/base/common/extpath'; +import * as platform from 'vs/base/common/platform'; + +suite('Paths', () => { + + test('toForwardSlashes', () => { + assert.equal(extpath.toSlashes('\\\\server\\share\\some\\path'), '//server/share/some/path'); + assert.equal(extpath.toSlashes('c:\\test'), 'c:/test'); + assert.equal(extpath.toSlashes('foo\\bar'), 'foo/bar'); + assert.equal(extpath.toSlashes('/user/far'), '/user/far'); + }); + + test('getRoot', () => { + + assert.equal(extpath.getRoot('/user/far'), '/'); + assert.equal(extpath.getRoot('\\\\server\\share\\some\\path'), '//server/share/'); + assert.equal(extpath.getRoot('//server/share/some/path'), '//server/share/'); + assert.equal(extpath.getRoot('//server/share'), '/'); + assert.equal(extpath.getRoot('//server'), '/'); + assert.equal(extpath.getRoot('//server//'), '/'); + assert.equal(extpath.getRoot('c:/user/far'), 'c:/'); + assert.equal(extpath.getRoot('c:user/far'), 'c:'); + assert.equal(extpath.getRoot('http://www'), ''); + assert.equal(extpath.getRoot('http://www/'), 'http://www/'); + assert.equal(extpath.getRoot('file:///foo'), 'file:///'); + assert.equal(extpath.getRoot('file://foo'), ''); + + }); + + test('isUNC', () => { + if (platform.isWindows) { + assert.ok(!extpath.isUNC('foo')); + assert.ok(!extpath.isUNC('/foo')); + assert.ok(!extpath.isUNC('\\foo')); + assert.ok(!extpath.isUNC('\\\\foo')); + assert.ok(extpath.isUNC('\\\\a\\b')); + assert.ok(!extpath.isUNC('//a/b')); + assert.ok(extpath.isUNC('\\\\server\\share')); + assert.ok(extpath.isUNC('\\\\server\\share\\')); + assert.ok(extpath.isUNC('\\\\server\\share\\path')); + } + }); + + test('isValidBasename', () => { + assert.ok(!extpath.isValidBasename(null)); + assert.ok(!extpath.isValidBasename('')); + assert.ok(extpath.isValidBasename('test.txt')); + assert.ok(!extpath.isValidBasename('/test.txt')); + assert.ok(!extpath.isValidBasename('\\test.txt')); + + if (platform.isWindows) { + assert.ok(!extpath.isValidBasename('aux')); + assert.ok(!extpath.isValidBasename('Aux')); + assert.ok(!extpath.isValidBasename('LPT0')); + assert.ok(!extpath.isValidBasename('test.txt.')); + assert.ok(!extpath.isValidBasename('test.txt..')); + assert.ok(!extpath.isValidBasename('test.txt ')); + assert.ok(!extpath.isValidBasename('test.txt\t')); + assert.ok(!extpath.isValidBasename('tes:t.txt')); + assert.ok(!extpath.isValidBasename('tes"t.txt')); + } + }); +}); diff --git a/src/vs/base/test/common/keyCodes.test.ts b/src/vs/base/test/common/keyCodes.test.ts index 672c6c9cee..d141a5dbea 100644 --- a/src/vs/base/test/common/keyCodes.test.ts +++ b/src/vs/base/test/common/keyCodes.test.ts @@ -20,35 +20,35 @@ suite('keyCodes', () => { } test(null, 0); - test(new SimpleKeybinding(false, false, false, false, KeyCode.Enter), KeyCode.Enter); - test(new SimpleKeybinding(true, false, false, false, KeyCode.Enter), KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, false, true, false, KeyCode.Enter), KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(true, false, true, false, KeyCode.Enter), KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, true, false, false, KeyCode.Enter), KeyMod.Shift | KeyCode.Enter); - test(new SimpleKeybinding(true, true, false, false, KeyCode.Enter), KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, true, true, false, KeyCode.Enter), KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(true, true, true, false, KeyCode.Enter), KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, false, false, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyCode.Enter); - test(new SimpleKeybinding(true, false, false, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, false, true, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(true, false, true, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, true, false, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter); - test(new SimpleKeybinding(true, true, false, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, true, true, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(true, true, true, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(false, false, false, false, KeyCode.Enter).toChord(), KeyCode.Enter); + test(new SimpleKeybinding(true, false, false, false, KeyCode.Enter).toChord(), KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(false, false, true, false, KeyCode.Enter).toChord(), KeyMod.Alt | KeyCode.Enter); + test(new SimpleKeybinding(true, false, true, false, KeyCode.Enter).toChord(), KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(false, true, false, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyCode.Enter); + test(new SimpleKeybinding(true, true, false, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(false, true, true, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); + test(new SimpleKeybinding(true, true, true, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(false, false, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyCode.Enter); + test(new SimpleKeybinding(true, false, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(false, false, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter); + test(new SimpleKeybinding(true, false, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(false, true, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter); + test(new SimpleKeybinding(true, true, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(false, true, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); + test(new SimpleKeybinding(true, true, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); test( - new ChordKeybinding( + new ChordKeybinding([ new SimpleKeybinding(false, false, false, false, KeyCode.Enter), new SimpleKeybinding(false, false, false, false, KeyCode.Tab) - ), + ]), KeyChord(KeyCode.Enter, KeyCode.Tab) ); test( - new ChordKeybinding( + new ChordKeybinding([ new SimpleKeybinding(false, false, false, true, KeyCode.KEY_Y), new SimpleKeybinding(false, false, false, false, KeyCode.KEY_Z) - ), + ]), KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_Y, KeyCode.KEY_Z) ); }); @@ -62,35 +62,35 @@ suite('keyCodes', () => { } test(null, 0); - test(new SimpleKeybinding(false, false, false, false, KeyCode.Enter), KeyCode.Enter); - test(new SimpleKeybinding(false, false, false, true, KeyCode.Enter), KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, false, true, false, KeyCode.Enter), KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(false, false, true, true, KeyCode.Enter), KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, true, false, false, KeyCode.Enter), KeyMod.Shift | KeyCode.Enter); - test(new SimpleKeybinding(false, true, false, true, KeyCode.Enter), KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(false, true, true, false, KeyCode.Enter), KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(false, true, true, true, KeyCode.Enter), KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(true, false, false, false, KeyCode.Enter), KeyMod.CtrlCmd | KeyCode.Enter); - test(new SimpleKeybinding(true, false, false, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(true, false, true, false, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(true, false, true, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(true, true, false, false, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter); - test(new SimpleKeybinding(true, true, false, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); - test(new SimpleKeybinding(true, true, true, false, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); - test(new SimpleKeybinding(true, true, true, true, KeyCode.Enter), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(false, false, false, false, KeyCode.Enter).toChord(), KeyCode.Enter); + test(new SimpleKeybinding(false, false, false, true, KeyCode.Enter).toChord(), KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(false, false, true, false, KeyCode.Enter).toChord(), KeyMod.Alt | KeyCode.Enter); + test(new SimpleKeybinding(false, false, true, true, KeyCode.Enter).toChord(), KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(false, true, false, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyCode.Enter); + test(new SimpleKeybinding(false, true, false, true, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(false, true, true, false, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); + test(new SimpleKeybinding(false, true, true, true, KeyCode.Enter).toChord(), KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(true, false, false, false, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyCode.Enter); + test(new SimpleKeybinding(true, false, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(true, false, true, false, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter); + test(new SimpleKeybinding(true, false, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(true, true, false, false, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter); + test(new SimpleKeybinding(true, true, false, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.Enter); + test(new SimpleKeybinding(true, true, true, false, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.Enter); + test(new SimpleKeybinding(true, true, true, true, KeyCode.Enter).toChord(), KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.Enter); test( - new ChordKeybinding( + new ChordKeybinding([ new SimpleKeybinding(false, false, false, false, KeyCode.Enter), new SimpleKeybinding(false, false, false, false, KeyCode.Tab) - ), + ]), KeyChord(KeyCode.Enter, KeyCode.Tab) ); test( - new ChordKeybinding( + new ChordKeybinding([ new SimpleKeybinding(true, false, false, false, KeyCode.KEY_Y), new SimpleKeybinding(false, false, false, false, KeyCode.KEY_Z) - ), + ]), KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_Y, KeyCode.KEY_Z) ); diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 6251373842..e83961f153 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -226,6 +226,50 @@ suite('Map', () => { }); }); + test('LinkedMap - delete Head and Tail', function () { + const map = new LinkedMap(); + + assert.equal(map.size, 0); + + map.set('1', 1); + assert.equal(map.size, 1); + map.delete('1'); + assert.equal(map.get('1'), undefined); + assert.equal(map.size, 0); + assert.equal(map.keys().length, 0); + }); + + test('LinkedMap - delete Head', function () { + const map = new LinkedMap(); + + assert.equal(map.size, 0); + + map.set('1', 1); + map.set('2', 2); + assert.equal(map.size, 2); + map.delete('1'); + assert.equal(map.get('2'), 2); + assert.equal(map.size, 1); + assert.equal(map.keys().length, 1); + assert.equal(map.keys()[0], 2); + }); + + test('LinkedMap - delete Tail', function () { + const map = new LinkedMap(); + + assert.equal(map.size, 0); + + map.set('1', 1); + map.set('2', 2); + assert.equal(map.size, 2); + map.delete('2'); + assert.equal(map.get('1'), 1); + assert.equal(map.size, 1); + assert.equal(map.keys().length, 1); + assert.equal(map.keys()[0], 1); + }); + + test('PathIterator', () => { const iter = new PathIterator(); iter.reset('file:///usr/bin/file.txt'); diff --git a/src/vs/base/test/common/path.test.ts b/src/vs/base/test/common/path.test.ts new file mode 100644 index 0000000000..98da358556 --- /dev/null +++ b/src/vs/base/test/common/path.test.ts @@ -0,0 +1,824 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// NOTE: VSCode's copy of nodejs path library to be usable in common (non-node) namespace +// Copied from: https://github.com/nodejs/node/tree/43dd49c9782848c25e5b03448c8a0f923f13c158 + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +import * as assert from 'assert'; +import * as path from 'vs/base/common/path'; +import { isWindows } from 'vs/base/common/platform'; + +suite('Paths (Node Implementation)', () => { + test('join', () => { + const failures = [] as string[]; + const backslashRE = /\\/g; + + const joinTests: any = [ + [[path.posix.join, path.win32.join], + // arguments result + [[['.', 'x/b', '..', '/b/c.js'], 'x/b/c.js'], + [[], '.'], + [['/.', 'x/b', '..', '/b/c.js'], '/x/b/c.js'], + [['/foo', '../../../bar'], '/bar'], + [['foo', '../../../bar'], '../../bar'], + [['foo/', '../../../bar'], '../../bar'], + [['foo/x', '../../../bar'], '../bar'], + [['foo/x', './bar'], 'foo/x/bar'], + [['foo/x/', './bar'], 'foo/x/bar'], + [['foo/x/', '.', 'bar'], 'foo/x/bar'], + [['./'], './'], + [['.', './'], './'], + [['.', '.', '.'], '.'], + [['.', './', '.'], '.'], + [['.', '/./', '.'], '.'], + [['.', '/////./', '.'], '.'], + [['.'], '.'], + [['', '.'], '.'], + [['', 'foo'], 'foo'], + [['foo', '/bar'], 'foo/bar'], + [['', '/foo'], '/foo'], + [['', '', '/foo'], '/foo'], + [['', '', 'foo'], 'foo'], + [['foo', ''], 'foo'], + [['foo/', ''], 'foo/'], + [['foo', '', '/bar'], 'foo/bar'], + [['./', '..', '/foo'], '../foo'], + [['./', '..', '..', '/foo'], '../../foo'], + [['.', '..', '..', '/foo'], '../../foo'], + [['', '..', '..', '/foo'], '../../foo'], + [['/'], '/'], + [['/', '.'], '/'], + [['/', '..'], '/'], + [['/', '..', '..'], '/'], + [[''], '.'], + [['', ''], '.'], + [[' /foo'], ' /foo'], + [[' ', 'foo'], ' /foo'], + [[' ', '.'], ' '], + [[' ', '/'], ' /'], + [[' ', ''], ' '], + [['/', 'foo'], '/foo'], + [['/', '/foo'], '/foo'], + [['/', '//foo'], '/foo'], + [['/', '', '/foo'], '/foo'], + [['', '/', 'foo'], '/foo'], + [['', '/', '/foo'], '/foo'] + ] + ] + ]; + + // Windows-specific join tests + joinTests.push([ + path.win32.join, + joinTests[0][1].slice(0).concat( + [// arguments result + // UNC path expected + [['//foo/bar'], '\\\\foo\\bar\\'], + [['\\/foo/bar'], '\\\\foo\\bar\\'], + [['\\\\foo/bar'], '\\\\foo\\bar\\'], + // UNC path expected - server and share separate + [['//foo', 'bar'], '\\\\foo\\bar\\'], + [['//foo/', 'bar'], '\\\\foo\\bar\\'], + [['//foo', '/bar'], '\\\\foo\\bar\\'], + // UNC path expected - questionable + [['//foo', '', 'bar'], '\\\\foo\\bar\\'], + [['//foo/', '', 'bar'], '\\\\foo\\bar\\'], + [['//foo/', '', '/bar'], '\\\\foo\\bar\\'], + // UNC path expected - even more questionable + [['', '//foo', 'bar'], '\\\\foo\\bar\\'], + [['', '//foo/', 'bar'], '\\\\foo\\bar\\'], + [['', '//foo/', '/bar'], '\\\\foo\\bar\\'], + // No UNC path expected (no double slash in first component) + [['\\', 'foo/bar'], '\\foo\\bar'], + [['\\', '/foo/bar'], '\\foo\\bar'], + [['', '/', '/foo/bar'], '\\foo\\bar'], + // No UNC path expected (no non-slashes in first component - + // questionable) + [['//', 'foo/bar'], '\\foo\\bar'], + [['//', '/foo/bar'], '\\foo\\bar'], + [['\\\\', '/', '/foo/bar'], '\\foo\\bar'], + [['//'], '\\'], + // No UNC path expected (share name missing - questionable). + [['//foo'], '\\foo'], + [['//foo/'], '\\foo\\'], + [['//foo', '/'], '\\foo\\'], + [['//foo', '', '/'], '\\foo\\'], + // No UNC path expected (too many leading slashes - questionable) + [['///foo/bar'], '\\foo\\bar'], + [['////foo', 'bar'], '\\foo\\bar'], + [['\\\\\\/foo/bar'], '\\foo\\bar'], + // Drive-relative vs drive-absolute paths. This merely describes the + // status quo, rather than being obviously right + [['c:'], 'c:.'], + [['c:.'], 'c:.'], + [['c:', ''], 'c:.'], + [['', 'c:'], 'c:.'], + [['c:.', '/'], 'c:.\\'], + [['c:.', 'file'], 'c:file'], + [['c:', '/'], 'c:\\'], + [['c:', 'file'], 'c:\\file'] + ] + ) + ]); + joinTests.forEach((test) => { + if (!Array.isArray(test[0])) { + test[0] = [test[0]]; + } + test[0].forEach((join) => { + test[1].forEach((test) => { + const actual = join.apply(null, test[0]); + const expected = test[1]; + // For non-Windows specific tests with the Windows join(), we need to try + // replacing the slashes since the non-Windows specific tests' `expected` + // use forward slashes + let actualAlt; + let os; + if (join === path.win32.join) { + actualAlt = actual.replace(backslashRE, '/'); + os = 'win32'; + } else { + os = 'posix'; + } + const message = + `path.${os}.join(${test[0].map(JSON.stringify).join(',')})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected && actualAlt !== expected) { + failures.push(`\n${message}`); + } + }); + }); + }); + assert.strictEqual(failures.length, 0, failures.join('')); + }); + + test('dirname', () => { + assert.strictEqual(path.dirname(path.normalize(__filename)).substr(-11), + isWindows ? 'test\\common' : 'test/common'); + + assert.strictEqual(path.posix.dirname('/a/b/'), '/a'); + assert.strictEqual(path.posix.dirname('/a/b'), '/a'); + assert.strictEqual(path.posix.dirname('/a'), '/'); + assert.strictEqual(path.posix.dirname(''), '.'); + assert.strictEqual(path.posix.dirname('/'), '/'); + assert.strictEqual(path.posix.dirname('////'), '/'); + assert.strictEqual(path.posix.dirname('//a'), '//'); + assert.strictEqual(path.posix.dirname('foo'), '.'); + + assert.strictEqual(path.win32.dirname('c:\\'), 'c:\\'); + assert.strictEqual(path.win32.dirname('c:\\foo'), 'c:\\'); + assert.strictEqual(path.win32.dirname('c:\\foo\\'), 'c:\\'); + assert.strictEqual(path.win32.dirname('c:\\foo\\bar'), 'c:\\foo'); + assert.strictEqual(path.win32.dirname('c:\\foo\\bar\\'), 'c:\\foo'); + assert.strictEqual(path.win32.dirname('c:\\foo\\bar\\baz'), 'c:\\foo\\bar'); + assert.strictEqual(path.win32.dirname('\\'), '\\'); + assert.strictEqual(path.win32.dirname('\\foo'), '\\'); + assert.strictEqual(path.win32.dirname('\\foo\\'), '\\'); + assert.strictEqual(path.win32.dirname('\\foo\\bar'), '\\foo'); + assert.strictEqual(path.win32.dirname('\\foo\\bar\\'), '\\foo'); + assert.strictEqual(path.win32.dirname('\\foo\\bar\\baz'), '\\foo\\bar'); + assert.strictEqual(path.win32.dirname('c:'), 'c:'); + assert.strictEqual(path.win32.dirname('c:foo'), 'c:'); + assert.strictEqual(path.win32.dirname('c:foo\\'), 'c:'); + assert.strictEqual(path.win32.dirname('c:foo\\bar'), 'c:foo'); + assert.strictEqual(path.win32.dirname('c:foo\\bar\\'), 'c:foo'); + assert.strictEqual(path.win32.dirname('c:foo\\bar\\baz'), 'c:foo\\bar'); + assert.strictEqual(path.win32.dirname('file:stream'), '.'); + assert.strictEqual(path.win32.dirname('dir\\file:stream'), 'dir'); + assert.strictEqual(path.win32.dirname('\\\\unc\\share'), + '\\\\unc\\share'); + assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo'), + '\\\\unc\\share\\'); + assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\'), + '\\\\unc\\share\\'); + assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\bar'), + '\\\\unc\\share\\foo'); + assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\bar\\'), + '\\\\unc\\share\\foo'); + assert.strictEqual(path.win32.dirname('\\\\unc\\share\\foo\\bar\\baz'), + '\\\\unc\\share\\foo\\bar'); + assert.strictEqual(path.win32.dirname('/a/b/'), '/a'); + assert.strictEqual(path.win32.dirname('/a/b'), '/a'); + assert.strictEqual(path.win32.dirname('/a'), '/'); + assert.strictEqual(path.win32.dirname(''), '.'); + assert.strictEqual(path.win32.dirname('/'), '/'); + assert.strictEqual(path.win32.dirname('////'), '/'); + assert.strictEqual(path.win32.dirname('foo'), '.'); + + // Tests from VSCode + + function assertDirname(p: string, expected: string, win = false) { + const actual = win ? path.win32.dirname(p) : path.posix.dirname(p); + + if (actual !== expected) { + assert.fail(`${p}: expected: ${expected}, ours: ${actual}`); + } + } + + assertDirname('foo/bar', 'foo'); + assertDirname('foo\\bar', 'foo', true); + assertDirname('/foo/bar', '/foo'); + assertDirname('\\foo\\bar', '\\foo', true); + assertDirname('/foo', '/'); + assertDirname('\\foo', '\\', true); + assertDirname('/', '/'); + assertDirname('\\', '\\', true); + assertDirname('foo', '.'); + assertDirname('f', '.'); + assertDirname('f/', '.'); + assertDirname('/folder/', '/'); + assertDirname('c:\\some\\file.txt', 'c:\\some', true); + assertDirname('c:\\some', 'c:\\', true); + assertDirname('c:\\', 'c:\\', true); + assertDirname('c:', 'c:', true); + assertDirname('\\\\server\\share\\some\\path', '\\\\server\\share\\some', true); + assertDirname('\\\\server\\share\\some', '\\\\server\\share\\', true); + assertDirname('\\\\server\\share\\', '\\\\server\\share\\', true); + }); + + test('extname', () => { + const failures = [] as string[]; + const slashRE = /\//g; + + [ + [__filename, '.js'], + ['', ''], + ['/path/to/file', ''], + ['/path/to/file.ext', '.ext'], + ['/path.to/file.ext', '.ext'], + ['/path.to/file', ''], + ['/path.to/.file', ''], + ['/path.to/.file.ext', '.ext'], + ['/path/to/f.ext', '.ext'], + ['/path/to/..ext', '.ext'], + ['/path/to/..', ''], + ['file', ''], + ['file.ext', '.ext'], + ['.file', ''], + ['.file.ext', '.ext'], + ['/file', ''], + ['/file.ext', '.ext'], + ['/.file', ''], + ['/.file.ext', '.ext'], + ['.path/file.ext', '.ext'], + ['file.ext.ext', '.ext'], + ['file.', '.'], + ['.', ''], + ['./', ''], + ['.file.ext', '.ext'], + ['.file', ''], + ['.file.', '.'], + ['.file..', '.'], + ['..', ''], + ['../', ''], + ['..file.ext', '.ext'], + ['..file', '.file'], + ['..file.', '.'], + ['..file..', '.'], + ['...', '.'], + ['...ext', '.ext'], + ['....', '.'], + ['file.ext/', '.ext'], + ['file.ext//', '.ext'], + ['file/', ''], + ['file//', ''], + ['file./', '.'], + ['file.//', '.'], + ].forEach((test) => { + const expected = test[1]; + [path.posix.extname, path.win32.extname].forEach((extname) => { + let input = test[0]; + let os; + if (extname === path.win32.extname) { + input = input.replace(slashRE, '\\'); + os = 'win32'; + } else { + os = 'posix'; + } + const actual = extname(input); + const message = `path.${os}.extname(${JSON.stringify(input)})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected) { + failures.push(`\n${message}`); + } + }); + { + const input = `C:${test[0].replace(slashRE, '\\')}`; + const actual = path.win32.extname(input); + const message = `path.win32.extname(${JSON.stringify(input)})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected) { + failures.push(`\n${message}`); + } + } + }); + assert.strictEqual(failures.length, 0, failures.join('')); + + // On Windows, backslash is a path separator. + assert.strictEqual(path.win32.extname('.\\'), ''); + assert.strictEqual(path.win32.extname('..\\'), ''); + assert.strictEqual(path.win32.extname('file.ext\\'), '.ext'); + assert.strictEqual(path.win32.extname('file.ext\\\\'), '.ext'); + assert.strictEqual(path.win32.extname('file\\'), ''); + assert.strictEqual(path.win32.extname('file\\\\'), ''); + assert.strictEqual(path.win32.extname('file.\\'), '.'); + assert.strictEqual(path.win32.extname('file.\\\\'), '.'); + + // On *nix, backslash is a valid name component like any other character. + assert.strictEqual(path.posix.extname('.\\'), ''); + assert.strictEqual(path.posix.extname('..\\'), '.\\'); + assert.strictEqual(path.posix.extname('file.ext\\'), '.ext\\'); + assert.strictEqual(path.posix.extname('file.ext\\\\'), '.ext\\\\'); + assert.strictEqual(path.posix.extname('file\\'), ''); + assert.strictEqual(path.posix.extname('file\\\\'), ''); + assert.strictEqual(path.posix.extname('file.\\'), '.\\'); + assert.strictEqual(path.posix.extname('file.\\\\'), '.\\\\'); + + // Tests from VSCode + assert.equal(path.extname('far.boo'), '.boo'); + assert.equal(path.extname('far.b'), '.b'); + assert.equal(path.extname('far.'), '.'); + assert.equal(path.extname('far.boo/boo.far'), '.far'); + assert.equal(path.extname('far.boo/boo'), ''); + }); + + test('resolve', () => { + const failures = [] as string[]; + const slashRE = /\//g; + const backslashRE = /\\/g; + + const resolveTests = [ + [path.win32.resolve, + // arguments result + [[['c:/blah\\blah', 'd:/games', 'c:../a'], 'c:\\blah\\a'], + [['c:/ignore', 'd:\\a/b\\c/d', '\\e.exe'], 'd:\\e.exe'], + [['c:/ignore', 'c:/some/file'], 'c:\\some\\file'], + [['d:/ignore', 'd:some/dir//'], 'd:\\ignore\\some\\dir'], + [['.'], process.cwd()], + [['//server/share', '..', 'relative\\'], '\\\\server\\share\\relative'], + [['c:/', '//'], 'c:\\'], + [['c:/', '//dir'], 'c:\\dir'], + [['c:/', '//server/share'], '\\\\server\\share\\'], + [['c:/', '//server//share'], '\\\\server\\share\\'], + [['c:/', '///some//dir'], 'c:\\some\\dir'], + [['C:\\foo\\tmp.3\\', '..\\tmp.3\\cycles\\root.js'], + 'C:\\foo\\tmp.3\\cycles\\root.js'] + ] + ], + [path.posix.resolve, + // arguments result + [[['/var/lib', '../', 'file/'], '/var/file'], + [['/var/lib', '/../', 'file/'], '/file'], + [['a/b/c/', '../../..'], process.cwd()], + [['.'], process.cwd()], + [['/some/dir', '.', '/absolute/'], '/absolute'], + [['/foo/tmp.3/', '../tmp.3/cycles/root.js'], '/foo/tmp.3/cycles/root.js'] + ] + ] + ]; + resolveTests.forEach((test) => { + const resolve = test[0]; + //@ts-ignore + test[1].forEach((test) => { + //@ts-ignore + const actual = resolve.apply(null, test[0]); + let actualAlt; + const os = resolve === path.win32.resolve ? 'win32' : 'posix'; + if (resolve === path.win32.resolve && !isWindows) { + actualAlt = actual.replace(backslashRE, '/'); + } + else if (resolve !== path.win32.resolve && isWindows) { + actualAlt = actual.replace(slashRE, '\\'); + } + + const expected = test[1]; + const message = + `path.${os}.resolve(${test[0].map(JSON.stringify).join(',')})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected && actualAlt !== expected) { + failures.push(`\n${message}`); + } + }); + }); + assert.strictEqual(failures.length, 0, failures.join('')); + + // if (isWindows) { + // // Test resolving the current Windows drive letter from a spawned process. + // // See https://github.com/nodejs/node/issues/7215 + // const currentDriveLetter = path.parse(process.cwd()).root.substring(0, 2); + // const resolveFixture = fixtures.path('path-resolve.js'); + // const spawnResult = child.spawnSync( + // process.argv[0], [resolveFixture, currentDriveLetter]); + // const resolvedPath = spawnResult.stdout.toString().trim(); + // assert.strictEqual(resolvedPath.toLowerCase(), process.cwd().toLowerCase()); + // } + }); + + test('basename', () => { + assert.strictEqual(path.basename(__filename), 'path.test.js'); + assert.strictEqual(path.basename(__filename, '.js'), 'path.test'); + assert.strictEqual(path.basename('.js', '.js'), ''); + assert.strictEqual(path.basename(''), ''); + assert.strictEqual(path.basename('/dir/basename.ext'), 'basename.ext'); + assert.strictEqual(path.basename('/basename.ext'), 'basename.ext'); + assert.strictEqual(path.basename('basename.ext'), 'basename.ext'); + assert.strictEqual(path.basename('basename.ext/'), 'basename.ext'); + assert.strictEqual(path.basename('basename.ext//'), 'basename.ext'); + assert.strictEqual(path.basename('aaa/bbb', '/bbb'), 'bbb'); + assert.strictEqual(path.basename('aaa/bbb', 'a/bbb'), 'bbb'); + assert.strictEqual(path.basename('aaa/bbb', 'bbb'), 'bbb'); + assert.strictEqual(path.basename('aaa/bbb//', 'bbb'), 'bbb'); + assert.strictEqual(path.basename('aaa/bbb', 'bb'), 'b'); + assert.strictEqual(path.basename('aaa/bbb', 'b'), 'bb'); + assert.strictEqual(path.basename('/aaa/bbb', '/bbb'), 'bbb'); + assert.strictEqual(path.basename('/aaa/bbb', 'a/bbb'), 'bbb'); + assert.strictEqual(path.basename('/aaa/bbb', 'bbb'), 'bbb'); + assert.strictEqual(path.basename('/aaa/bbb//', 'bbb'), 'bbb'); + assert.strictEqual(path.basename('/aaa/bbb', 'bb'), 'b'); + assert.strictEqual(path.basename('/aaa/bbb', 'b'), 'bb'); + assert.strictEqual(path.basename('/aaa/bbb'), 'bbb'); + assert.strictEqual(path.basename('/aaa/'), 'aaa'); + assert.strictEqual(path.basename('/aaa/b'), 'b'); + assert.strictEqual(path.basename('/a/b'), 'b'); + assert.strictEqual(path.basename('//a'), 'a'); + assert.strictEqual(path.basename('a', 'a'), ''); + + // On Windows a backslash acts as a path separator. + assert.strictEqual(path.win32.basename('\\dir\\basename.ext'), 'basename.ext'); + assert.strictEqual(path.win32.basename('\\basename.ext'), 'basename.ext'); + assert.strictEqual(path.win32.basename('basename.ext'), 'basename.ext'); + assert.strictEqual(path.win32.basename('basename.ext\\'), 'basename.ext'); + assert.strictEqual(path.win32.basename('basename.ext\\\\'), 'basename.ext'); + assert.strictEqual(path.win32.basename('foo'), 'foo'); + assert.strictEqual(path.win32.basename('aaa\\bbb', '\\bbb'), 'bbb'); + assert.strictEqual(path.win32.basename('aaa\\bbb', 'a\\bbb'), 'bbb'); + assert.strictEqual(path.win32.basename('aaa\\bbb', 'bbb'), 'bbb'); + assert.strictEqual(path.win32.basename('aaa\\bbb\\\\\\\\', 'bbb'), 'bbb'); + assert.strictEqual(path.win32.basename('aaa\\bbb', 'bb'), 'b'); + assert.strictEqual(path.win32.basename('aaa\\bbb', 'b'), 'bb'); + assert.strictEqual(path.win32.basename('C:'), ''); + assert.strictEqual(path.win32.basename('C:.'), '.'); + assert.strictEqual(path.win32.basename('C:\\'), ''); + assert.strictEqual(path.win32.basename('C:\\dir\\base.ext'), 'base.ext'); + assert.strictEqual(path.win32.basename('C:\\basename.ext'), 'basename.ext'); + assert.strictEqual(path.win32.basename('C:basename.ext'), 'basename.ext'); + assert.strictEqual(path.win32.basename('C:basename.ext\\'), 'basename.ext'); + assert.strictEqual(path.win32.basename('C:basename.ext\\\\'), 'basename.ext'); + assert.strictEqual(path.win32.basename('C:foo'), 'foo'); + assert.strictEqual(path.win32.basename('file:stream'), 'file:stream'); + assert.strictEqual(path.win32.basename('a', 'a'), ''); + + // On unix a backslash is just treated as any other character. + assert.strictEqual(path.posix.basename('\\dir\\basename.ext'), + '\\dir\\basename.ext'); + assert.strictEqual(path.posix.basename('\\basename.ext'), '\\basename.ext'); + assert.strictEqual(path.posix.basename('basename.ext'), 'basename.ext'); + assert.strictEqual(path.posix.basename('basename.ext\\'), 'basename.ext\\'); + assert.strictEqual(path.posix.basename('basename.ext\\\\'), 'basename.ext\\\\'); + assert.strictEqual(path.posix.basename('foo'), 'foo'); + + // POSIX filenames may include control characters + // c.f. http://www.dwheeler.com/essays/fixing-unix-linux-filenames.html + const controlCharFilename = `Icon${String.fromCharCode(13)}`; + assert.strictEqual(path.posix.basename(`/a/b/${controlCharFilename}`), + controlCharFilename); + + // Tests from VSCode + assert.equal(path.basename('foo/bar'), 'bar'); + assert.equal(path.posix.basename('foo\\bar'), 'foo\\bar'); + assert.equal(path.win32.basename('foo\\bar'), 'bar'); + assert.equal(path.basename('/foo/bar'), 'bar'); + assert.equal(path.posix.basename('\\foo\\bar'), '\\foo\\bar'); + assert.equal(path.win32.basename('\\foo\\bar'), 'bar'); + assert.equal(path.basename('./bar'), 'bar'); + assert.equal(path.posix.basename('.\\bar'), '.\\bar'); + assert.equal(path.win32.basename('.\\bar'), 'bar'); + assert.equal(path.basename('/bar'), 'bar'); + assert.equal(path.posix.basename('\\bar'), '\\bar'); + assert.equal(path.win32.basename('\\bar'), 'bar'); + assert.equal(path.basename('bar/'), 'bar'); + assert.equal(path.posix.basename('bar\\'), 'bar\\'); + assert.equal(path.win32.basename('bar\\'), 'bar'); + assert.equal(path.basename('bar'), 'bar'); + assert.equal(path.basename('////////'), ''); + assert.equal(path.posix.basename('\\\\\\\\'), '\\\\\\\\'); + assert.equal(path.win32.basename('\\\\\\\\'), ''); + }); + + test('relative', () => { + const failures = [] as string[]; + + const relativeTests = [ + [path.win32.relative, + // arguments result + [['c:/blah\\blah', 'd:/games', 'd:\\games'], + ['c:/aaaa/bbbb', 'c:/aaaa', '..'], + ['c:/aaaa/bbbb', 'c:/cccc', '..\\..\\cccc'], + ['c:/aaaa/bbbb', 'c:/aaaa/bbbb', ''], + ['c:/aaaa/bbbb', 'c:/aaaa/cccc', '..\\cccc'], + ['c:/aaaa/', 'c:/aaaa/cccc', 'cccc'], + ['c:/', 'c:\\aaaa\\bbbb', 'aaaa\\bbbb'], + ['c:/aaaa/bbbb', 'd:\\', 'd:\\'], + ['c:/AaAa/bbbb', 'c:/aaaa/bbbb', ''], + ['c:/aaaaa/', 'c:/aaaa/cccc', '..\\aaaa\\cccc'], + ['C:\\foo\\bar\\baz\\quux', 'C:\\', '..\\..\\..\\..'], + ['C:\\foo\\test', 'C:\\foo\\test\\bar\\package.json', 'bar\\package.json'], + ['C:\\foo\\bar\\baz-quux', 'C:\\foo\\bar\\baz', '..\\baz'], + ['C:\\foo\\bar\\baz', 'C:\\foo\\bar\\baz-quux', '..\\baz-quux'], + ['\\\\foo\\bar', '\\\\foo\\bar\\baz', 'baz'], + ['\\\\foo\\bar\\baz', '\\\\foo\\bar', '..'], + ['\\\\foo\\bar\\baz-quux', '\\\\foo\\bar\\baz', '..\\baz'], + ['\\\\foo\\bar\\baz', '\\\\foo\\bar\\baz-quux', '..\\baz-quux'], + ['C:\\baz-quux', 'C:\\baz', '..\\baz'], + ['C:\\baz', 'C:\\baz-quux', '..\\baz-quux'], + ['\\\\foo\\baz-quux', '\\\\foo\\baz', '..\\baz'], + ['\\\\foo\\baz', '\\\\foo\\baz-quux', '..\\baz-quux'], + ['C:\\baz', '\\\\foo\\bar\\baz', '\\\\foo\\bar\\baz'], + ['\\\\foo\\bar\\baz', 'C:\\baz', 'C:\\baz'] + ] + ], + [path.posix.relative, + // arguments result + [['/var/lib', '/var', '..'], + ['/var/lib', '/bin', '../../bin'], + ['/var/lib', '/var/lib', ''], + ['/var/lib', '/var/apache', '../apache'], + ['/var/', '/var/lib', 'lib'], + ['/', '/var/lib', 'var/lib'], + ['/foo/test', '/foo/test/bar/package.json', 'bar/package.json'], + ['/Users/a/web/b/test/mails', '/Users/a/web/b', '../..'], + ['/foo/bar/baz-quux', '/foo/bar/baz', '../baz'], + ['/foo/bar/baz', '/foo/bar/baz-quux', '../baz-quux'], + ['/baz-quux', '/baz', '../baz'], + ['/baz', '/baz-quux', '../baz-quux'] + ] + ] + ]; + relativeTests.forEach((test) => { + const relative = test[0]; + //@ts-ignore + test[1].forEach((test) => { + //@ts-ignore + const actual = relative(test[0], test[1]); + const expected = test[2]; + const os = relative === path.win32.relative ? 'win32' : 'posix'; + const message = `path.${os}.relative(${ + test.slice(0, 2).map(JSON.stringify).join(',')})\n expect=${ + JSON.stringify(expected)}\n actual=${JSON.stringify(actual)}`; + if (actual !== expected) { + failures.push(`\n${message}`); + } + }); + }); + assert.strictEqual(failures.length, 0, failures.join('')); + }); + + test('normalize', () => { + assert.strictEqual(path.win32.normalize('./fixtures///b/../b/c.js'), + 'fixtures\\b\\c.js'); + assert.strictEqual(path.win32.normalize('/foo/../../../bar'), '\\bar'); + assert.strictEqual(path.win32.normalize('a//b//../b'), 'a\\b'); + assert.strictEqual(path.win32.normalize('a//b//./c'), 'a\\b\\c'); + assert.strictEqual(path.win32.normalize('a//b//.'), 'a\\b'); + assert.strictEqual(path.win32.normalize('//server/share/dir/file.ext'), + '\\\\server\\share\\dir\\file.ext'); + assert.strictEqual(path.win32.normalize('/a/b/c/../../../x/y/z'), '\\x\\y\\z'); + assert.strictEqual(path.win32.normalize('C:'), 'C:.'); + assert.strictEqual(path.win32.normalize('C:..\\abc'), 'C:..\\abc'); + assert.strictEqual(path.win32.normalize('C:..\\..\\abc\\..\\def'), + 'C:..\\..\\def'); + assert.strictEqual(path.win32.normalize('C:\\.'), 'C:\\'); + assert.strictEqual(path.win32.normalize('file:stream'), 'file:stream'); + assert.strictEqual(path.win32.normalize('bar\\foo..\\..\\'), 'bar\\'); + assert.strictEqual(path.win32.normalize('bar\\foo..\\..'), 'bar'); + assert.strictEqual(path.win32.normalize('bar\\foo..\\..\\baz'), 'bar\\baz'); + assert.strictEqual(path.win32.normalize('bar\\foo..\\'), 'bar\\foo..\\'); + assert.strictEqual(path.win32.normalize('bar\\foo..'), 'bar\\foo..'); + assert.strictEqual(path.win32.normalize('..\\foo..\\..\\..\\bar'), + '..\\..\\bar'); + assert.strictEqual(path.win32.normalize('..\\...\\..\\.\\...\\..\\..\\bar'), + '..\\..\\bar'); + assert.strictEqual(path.win32.normalize('../../../foo/../../../bar'), + '..\\..\\..\\..\\..\\bar'); + assert.strictEqual(path.win32.normalize('../../../foo/../../../bar/../../'), + '..\\..\\..\\..\\..\\..\\'); + assert.strictEqual( + path.win32.normalize('../foobar/barfoo/foo/../../../bar/../../'), + '..\\..\\' + ); + assert.strictEqual( + path.win32.normalize('../.../../foobar/../../../bar/../../baz'), + '..\\..\\..\\..\\baz' + ); + assert.strictEqual(path.win32.normalize('foo/bar\\baz'), 'foo\\bar\\baz'); + + assert.strictEqual(path.posix.normalize('./fixtures///b/../b/c.js'), + 'fixtures/b/c.js'); + assert.strictEqual(path.posix.normalize('/foo/../../../bar'), '/bar'); + assert.strictEqual(path.posix.normalize('a//b//../b'), 'a/b'); + assert.strictEqual(path.posix.normalize('a//b//./c'), 'a/b/c'); + assert.strictEqual(path.posix.normalize('a//b//.'), 'a/b'); + assert.strictEqual(path.posix.normalize('/a/b/c/../../../x/y/z'), '/x/y/z'); + assert.strictEqual(path.posix.normalize('///..//./foo/.//bar'), '/foo/bar'); + assert.strictEqual(path.posix.normalize('bar/foo../../'), 'bar/'); + assert.strictEqual(path.posix.normalize('bar/foo../..'), 'bar'); + assert.strictEqual(path.posix.normalize('bar/foo../../baz'), 'bar/baz'); + assert.strictEqual(path.posix.normalize('bar/foo../'), 'bar/foo../'); + assert.strictEqual(path.posix.normalize('bar/foo..'), 'bar/foo..'); + assert.strictEqual(path.posix.normalize('../foo../../../bar'), '../../bar'); + assert.strictEqual(path.posix.normalize('../.../.././.../../../bar'), + '../../bar'); + assert.strictEqual(path.posix.normalize('../../../foo/../../../bar'), + '../../../../../bar'); + assert.strictEqual(path.posix.normalize('../../../foo/../../../bar/../../'), + '../../../../../../'); + assert.strictEqual( + path.posix.normalize('../foobar/barfoo/foo/../../../bar/../../'), + '../../' + ); + assert.strictEqual( + path.posix.normalize('../.../../foobar/../../../bar/../../baz'), + '../../../../baz' + ); + assert.strictEqual(path.posix.normalize('foo/bar\\baz'), 'foo/bar\\baz'); + }); + + test('isAbsolute', () => { + assert.strictEqual(path.win32.isAbsolute('/'), true); + assert.strictEqual(path.win32.isAbsolute('//'), true); + assert.strictEqual(path.win32.isAbsolute('//server'), true); + assert.strictEqual(path.win32.isAbsolute('//server/file'), true); + assert.strictEqual(path.win32.isAbsolute('\\\\server\\file'), true); + assert.strictEqual(path.win32.isAbsolute('\\\\server'), true); + assert.strictEqual(path.win32.isAbsolute('\\\\'), true); + assert.strictEqual(path.win32.isAbsolute('c'), false); + assert.strictEqual(path.win32.isAbsolute('c:'), false); + assert.strictEqual(path.win32.isAbsolute('c:\\'), true); + assert.strictEqual(path.win32.isAbsolute('c:/'), true); + assert.strictEqual(path.win32.isAbsolute('c://'), true); + assert.strictEqual(path.win32.isAbsolute('C:/Users/'), true); + assert.strictEqual(path.win32.isAbsolute('C:\\Users\\'), true); + assert.strictEqual(path.win32.isAbsolute('C:cwd/another'), false); + assert.strictEqual(path.win32.isAbsolute('C:cwd\\another'), false); + assert.strictEqual(path.win32.isAbsolute('directory/directory'), false); + assert.strictEqual(path.win32.isAbsolute('directory\\directory'), false); + + assert.strictEqual(path.posix.isAbsolute('/home/foo'), true); + assert.strictEqual(path.posix.isAbsolute('/home/foo/..'), true); + assert.strictEqual(path.posix.isAbsolute('bar/'), false); + assert.strictEqual(path.posix.isAbsolute('./baz'), false); + + // Tests from VSCode: + + // Absolute Paths + [ + 'C:/', + 'C:\\', + 'C:/foo', + 'C:\\foo', + 'z:/foo/bar.txt', + 'z:\\foo\\bar.txt', + + '\\\\localhost\\c$\\foo', + + '/', + '/foo' + ].forEach(absolutePath => { + assert.ok(path.win32.isAbsolute(absolutePath), absolutePath); + }); + + [ + '/', + '/foo', + '/foo/bar.txt' + ].forEach(absolutePath => { + assert.ok(path.posix.isAbsolute(absolutePath), absolutePath); + }); + + // Relative Paths + [ + '', + 'foo', + 'foo/bar', + './foo', + 'http://foo.com/bar' + ].forEach(nonAbsolutePath => { + assert.ok(!path.win32.isAbsolute(nonAbsolutePath), nonAbsolutePath); + }); + + [ + '', + 'foo', + 'foo/bar', + './foo', + 'http://foo.com/bar', + 'z:/foo/bar.txt', + ].forEach(nonAbsolutePath => { + assert.ok(!path.posix.isAbsolute(nonAbsolutePath), nonAbsolutePath); + }); + }); + + test('path', () => { + // path.sep tests + // windows + assert.strictEqual(path.win32.sep, '\\'); + // posix + assert.strictEqual(path.posix.sep, '/'); + + // path.delimiter tests + // windows + assert.strictEqual(path.win32.delimiter, ';'); + // posix + assert.strictEqual(path.posix.delimiter, ':'); + + // if (isWindows) { + // assert.strictEqual(path, path.win32); + // } else { + // assert.strictEqual(path, path.posix); + // } + }); + + // test('perf', () => { + // const folderNames = [ + // 'abc', + // 'Users', + // 'reallylongfoldername', + // 's', + // 'reallyreallyreallylongfoldername', + // 'home' + // ]; + + // const basePaths = [ + // 'C:', + // '', + // ]; + + // const separators = [ + // '\\', + // '/' + // ]; + + // function randomInt(ciel: number): number { + // return Math.floor(Math.random() * ciel); + // } + + // let pathsToNormalize = []; + // let pathsToJoin = []; + // let i; + // for (i = 0; i < 1000000; i++) { + // const basePath = basePaths[randomInt(basePaths.length)]; + // let lengthOfPath = randomInt(10) + 2; + + // let pathToNormalize = basePath + separators[randomInt(separators.length)]; + // while (lengthOfPath-- > 0) { + // pathToNormalize = pathToNormalize + folderNames[randomInt(folderNames.length)] + separators[randomInt(separators.length)]; + // } + + // pathsToNormalize.push(pathToNormalize); + + // let pathToJoin = ''; + // lengthOfPath = randomInt(10) + 2; + // while (lengthOfPath-- > 0) { + // pathToJoin = pathToJoin + folderNames[randomInt(folderNames.length)] + separators[randomInt(separators.length)]; + // } + + // pathsToJoin.push(pathToJoin + '.ts'); + // } + + // let newTime = 0; + + // let j; + // for(j = 0; j < pathsToJoin.length; j++) { + // const path1 = pathsToNormalize[j]; + // const path2 = pathsToNormalize[j]; + + // const newStart = performance.now(); + // path.join(path1, path2); + // newTime += performance.now() - newStart; + // } + + // assert.ok(false, `Time: ${newTime}ms.`); + // }); +}); diff --git a/src/vs/base/test/common/paths.test.ts b/src/vs/base/test/common/paths.test.ts deleted file mode 100644 index 0cbe7273a9..0000000000 --- a/src/vs/base/test/common/paths.test.ts +++ /dev/null @@ -1,241 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; -import * as paths from 'vs/base/common/paths'; -import * as platform from 'vs/base/common/platform'; - -suite('Paths', () => { - - function assertDirname(path: string, expected: string, win = false) { - const actual = paths.dirname(path, win ? '\\' : '/'); - - if (actual !== expected) { - assert.fail(`${path}: expected: ${expected}, ours: ${actual}`); - } - } - - test('dirname', () => { - assertDirname('foo/bar', 'foo'); - assertDirname('foo\\bar', 'foo', true); - assertDirname('/foo/bar', '/foo'); - assertDirname('\\foo\\bar', '\\foo', true); - assertDirname('/foo', '/'); - assertDirname('\\foo', '\\', true); - assertDirname('/', '/'); - assertDirname('\\', '\\', true); - assertDirname('foo', '.'); - assertDirname('f', '.'); - assertDirname('f/', '.'); - assertDirname('/folder/', '/'); - assertDirname('c:\\some\\file.txt', 'c:\\some', true); - assertDirname('c:\\some', 'c:\\', true); - assertDirname('c:\\', 'c:\\', true); - assertDirname('c:', 'c:', true); - assertDirname('\\\\server\\share\\some\\path', '\\\\server\\share\\some', true); - assertDirname('\\\\server\\share\\some', '\\\\server\\share\\', true); - assertDirname('\\\\server\\share\\', '\\\\server\\share\\', true); - }); - - test('normalize', () => { - assert.equal(paths.normalize(''), '.'); - assert.equal(paths.normalize('.'), '.'); - assert.equal(paths.normalize('.'), '.'); - assert.equal(paths.normalize('../../far'), '../../far'); - assert.equal(paths.normalize('../bar'), '../bar'); - assert.equal(paths.normalize('../far'), '../far'); - assert.equal(paths.normalize('./'), './'); - assert.equal(paths.normalize('./././'), './'); - assert.equal(paths.normalize('./ff/./'), 'ff/'); - assert.equal(paths.normalize('./foo'), 'foo'); - assert.equal(paths.normalize('/'), '/'); - assert.equal(paths.normalize('/..'), '/'); - assert.equal(paths.normalize('///'), '/'); - assert.equal(paths.normalize('//foo'), '/foo'); - assert.equal(paths.normalize('//foo//'), '/foo/'); - assert.equal(paths.normalize('/foo'), '/foo'); - assert.equal(paths.normalize('/foo/bar.test'), '/foo/bar.test'); - assert.equal(paths.normalize('\\\\\\'), '/'); - assert.equal(paths.normalize('c:/../ff'), 'c:/ff'); - assert.equal(paths.normalize('c:\\./'), 'c:/'); - assert.equal(paths.normalize('foo/'), 'foo/'); - assert.equal(paths.normalize('foo/../../bar'), '../bar'); - assert.equal(paths.normalize('foo/./'), 'foo/'); - assert.equal(paths.normalize('foo/./bar'), 'foo/bar'); - assert.equal(paths.normalize('foo//'), 'foo/'); - assert.equal(paths.normalize('foo//'), 'foo/'); - assert.equal(paths.normalize('foo//bar'), 'foo/bar'); - assert.equal(paths.normalize('foo//bar/far'), 'foo/bar/far'); - assert.equal(paths.normalize('foo/bar/../../far'), 'far'); - assert.equal(paths.normalize('foo/bar/../far'), 'foo/far'); - assert.equal(paths.normalize('foo/far/../../bar'), 'bar'); - assert.equal(paths.normalize('foo/far/../../bar'), 'bar'); - assert.equal(paths.normalize('foo/xxx/..'), 'foo'); - assert.equal(paths.normalize('foo/xxx/../bar'), 'foo/bar'); - assert.equal(paths.normalize('foo/xxx/./..'), 'foo'); - assert.equal(paths.normalize('foo/xxx/./../bar'), 'foo/bar'); - assert.equal(paths.normalize('foo/xxx/./bar'), 'foo/xxx/bar'); - assert.equal(paths.normalize('foo\\bar'), 'foo/bar'); - assert.equal(paths.normalize(null), null); - assert.equal(paths.normalize(undefined), undefined); - - // https://github.com/Microsoft/vscode/issues/7234 - assert.equal(paths.join('/home/aeschli/workspaces/vscode/extensions/css', './syntaxes/css.plist'), '/home/aeschli/workspaces/vscode/extensions/css/syntaxes/css.plist'); - }); - - test('getRootLength', () => { - - assert.equal(paths.getRoot('/user/far'), '/'); - assert.equal(paths.getRoot('\\\\server\\share\\some\\path'), '//server/share/'); - assert.equal(paths.getRoot('//server/share/some/path'), '//server/share/'); - assert.equal(paths.getRoot('//server/share'), '/'); - assert.equal(paths.getRoot('//server'), '/'); - assert.equal(paths.getRoot('//server//'), '/'); - assert.equal(paths.getRoot('c:/user/far'), 'c:/'); - assert.equal(paths.getRoot('c:user/far'), 'c:'); - assert.equal(paths.getRoot('http://www'), ''); - assert.equal(paths.getRoot('http://www/'), 'http://www/'); - assert.equal(paths.getRoot('file:///foo'), 'file:///'); - assert.equal(paths.getRoot('file://foo'), ''); - - }); - - test('basename', () => { - assert.equal(paths.basename('foo/bar'), 'bar'); - assert.equal(paths.basename('foo\\bar'), 'bar'); - assert.equal(paths.basename('/foo/bar'), 'bar'); - assert.equal(paths.basename('\\foo\\bar'), 'bar'); - assert.equal(paths.basename('./bar'), 'bar'); - assert.equal(paths.basename('.\\bar'), 'bar'); - assert.equal(paths.basename('/bar'), 'bar'); - assert.equal(paths.basename('\\bar'), 'bar'); - assert.equal(paths.basename('bar/'), 'bar'); - assert.equal(paths.basename('bar\\'), 'bar'); - assert.equal(paths.basename('bar'), 'bar'); - assert.equal(paths.basename('////////'), ''); - assert.equal(paths.basename('\\\\\\\\'), ''); - }); - - test('join', () => { - assert.equal(paths.join('.', 'bar'), 'bar'); - assert.equal(paths.join('../../foo/bar', '../../foo'), '../../foo'); - assert.equal(paths.join('../../foo/bar', '../bar/foo'), '../../foo/bar/foo'); - assert.equal(paths.join('../foo/bar', '../bar/foo'), '../foo/bar/foo'); - assert.equal(paths.join('/', 'bar'), '/bar'); - assert.equal(paths.join('//server/far/boo', '../file.txt'), '//server/far/file.txt'); - assert.equal(paths.join('/foo/', '/bar'), '/foo/bar'); - assert.equal(paths.join('\\\\server\\far\\boo', '../file.txt'), '//server/far/file.txt'); - assert.equal(paths.join('\\\\server\\far\\boo', './file.txt'), '//server/far/boo/file.txt'); - assert.equal(paths.join('\\\\server\\far\\boo', '.\\file.txt'), '//server/far/boo/file.txt'); - assert.equal(paths.join('\\\\server\\far\\boo', 'file.txt'), '//server/far/boo/file.txt'); - assert.equal(paths.join('file:///c/users/test', 'test'), 'file:///c/users/test/test'); - assert.equal(paths.join('file://localhost/c$/GitDevelopment/express', './settings'), 'file://localhost/c$/GitDevelopment/express/settings'); // unc - assert.equal(paths.join('file://localhost/c$/GitDevelopment/express', '.settings'), 'file://localhost/c$/GitDevelopment/express/.settings'); // unc - assert.equal(paths.join('foo', '/bar'), 'foo/bar'); - assert.equal(paths.join('foo', 'bar'), 'foo/bar'); - assert.equal(paths.join('foo', 'bar/'), 'foo/bar/'); - assert.equal(paths.join('foo/', '/bar'), 'foo/bar'); - assert.equal(paths.join('foo/', '/bar/'), 'foo/bar/'); - assert.equal(paths.join('foo/', 'bar'), 'foo/bar'); - assert.equal(paths.join('foo/bar', '../bar/foo'), 'foo/bar/foo'); - assert.equal(paths.join('foo/bar', './bar/foo'), 'foo/bar/bar/foo'); - assert.equal(paths.join('http://localhost/test', '../next'), 'http://localhost/next'); - assert.equal(paths.join('http://localhost/test', 'test'), 'http://localhost/test/test'); - }); - - test('extname', () => { - assert.equal(paths.extname('far.boo'), '.boo'); - assert.equal(paths.extname('far.b'), '.b'); - assert.equal(paths.extname('far.'), '.'); - assert.equal(paths.extname('far.boo/boo.far'), '.far'); - assert.equal(paths.extname('far.boo/boo'), ''); - }); - - test('isUNC', () => { - if (platform.isWindows) { - assert.ok(!paths.isUNC('foo')); - assert.ok(!paths.isUNC('/foo')); - assert.ok(!paths.isUNC('\\foo')); - assert.ok(!paths.isUNC('\\\\foo')); - assert.ok(paths.isUNC('\\\\a\\b')); - assert.ok(!paths.isUNC('//a/b')); - assert.ok(paths.isUNC('\\\\server\\share')); - assert.ok(paths.isUNC('\\\\server\\share\\')); - assert.ok(paths.isUNC('\\\\server\\share\\path')); - } - }); - - test('isValidBasename', () => { - assert.ok(!paths.isValidBasename(null)); - assert.ok(!paths.isValidBasename('')); - assert.ok(paths.isValidBasename('test.txt')); - assert.ok(!paths.isValidBasename('/test.txt')); - assert.ok(!paths.isValidBasename('\\test.txt')); - - if (platform.isWindows) { - assert.ok(!paths.isValidBasename('aux')); - assert.ok(!paths.isValidBasename('Aux')); - assert.ok(!paths.isValidBasename('LPT0')); - assert.ok(!paths.isValidBasename('test.txt.')); - assert.ok(!paths.isValidBasename('test.txt..')); - assert.ok(!paths.isValidBasename('test.txt ')); - assert.ok(!paths.isValidBasename('test.txt\t')); - assert.ok(!paths.isValidBasename('tes:t.txt')); - assert.ok(!paths.isValidBasename('tes"t.txt')); - } - }); - - test('isAbsolute_win', () => { - // Absolute paths - [ - 'C:/', - 'C:\\', - 'C:/foo', - 'C:\\foo', - 'z:/foo/bar.txt', - 'z:\\foo\\bar.txt', - - '\\\\localhost\\c$\\foo', - - '/', - '/foo' - ].forEach(absolutePath => { - assert.ok(paths.isAbsolute_win32(absolutePath), absolutePath); - }); - - // Not absolute paths - [ - '', - 'foo', - 'foo/bar', - './foo', - 'http://foo.com/bar' - ].forEach(nonAbsolutePath => { - assert.ok(!paths.isAbsolute_win32(nonAbsolutePath), nonAbsolutePath); - }); - }); - - test('isAbsolute_posix', () => { - // Absolute paths - [ - '/', - '/foo', - '/foo/bar.txt' - ].forEach(absolutePath => { - assert.ok(paths.isAbsolute_posix(absolutePath), absolutePath); - }); - - // Not absolute paths - [ - '', - 'foo', - 'foo/bar', - './foo', - 'http://foo.com/bar', - 'z:/foo/bar.txt', - ].forEach(nonAbsolutePath => { - assert.ok(!paths.isAbsolute_posix(nonAbsolutePath), nonAbsolutePath); - }); - }); -}); diff --git a/src/vs/base/test/common/processes.test.ts b/src/vs/base/test/common/processes.test.ts new file mode 100644 index 0000000000..9acf926213 --- /dev/null +++ b/src/vs/base/test/common/processes.test.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as processes from 'vs/base/common/processes'; + +suite('Processes', () => { + test('sanitizeProcessEnvironment', () => { + let env = { + FOO: 'bar', + ELECTRON_ENABLE_STACK_DUMPING: 'x', + ELECTRON_ENABLE_LOGGING: 'x', + ELECTRON_NO_ASAR: 'x', + ELECTRON_NO_ATTACH_CONSOLE: 'x', + ELECTRON_RUN_AS_NODE: 'x', + GOOGLE_API_KEY: 'x', + VSCODE_CLI: 'x', + VSCODE_DEV: 'x', + VSCODE_IPC_HOOK: 'x', + VSCODE_LOGS: 'x', + VSCODE_NLS_CONFIG: 'x', + VSCODE_PORTABLE: 'x', + VSCODE_PID: 'x', + VSCODE_NODE_CACHED_DATA_DIR: 'x', + VSCODE_NEW_VAR: 'x' + }; + processes.sanitizeProcessEnvironment(env); + assert.equal(env['FOO'], 'bar'); + assert.equal(Object.keys(env).length, 1); + }); +}); diff --git a/src/vs/base/test/common/resources.test.ts b/src/vs/base/test/common/resources.test.ts index 1236356ecf..863b125f7b 100644 --- a/src/vs/base/test/common/resources.test.ts +++ b/src/vs/base/test/common/resources.test.ts @@ -3,9 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { dirname, basename, distinctParents, joinPath, isEqual, isEqualOrParent, hasToIgnoreCase, normalizePath, isAbsolutePath, isMalformedFileUri } from 'vs/base/common/resources'; -import { URI, setUriThrowOnMissingScheme } from 'vs/base/common/uri'; +import { dirname, basename, distinctParents, joinPath, isEqual, isEqualOrParent, hasToIgnoreCase, normalizePath, isAbsolutePath, relativePath, removeTrailingPathSeparator, hasTrailingPathSeparator, resolvePath } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; import { isWindows } from 'vs/base/common/platform'; +import { toSlashes } from 'vs/base/common/extpath'; +import { startsWith } from 'vs/base/common/strings'; +import { isAbsolute } from 'vs/base/common/path'; + suite('Resources', () => { @@ -42,23 +46,23 @@ suite('Resources', () => { test('dirname', () => { if (isWindows) { - assert.equal(dirname(URI.file('c:\\some\\file\\test.txt'))!.toString(), 'file:///c%3A/some/file'); - assert.equal(dirname(URI.file('c:\\some\\file'))!.toString(), 'file:///c%3A/some'); - assert.equal(dirname(URI.file('c:\\some\\file\\'))!.toString(), 'file:///c%3A/some'); - assert.equal(dirname(URI.file('c:\\some'))!.toString(), 'file:///c%3A/'); - assert.equal(dirname(URI.file('C:\\some'))!.toString(), 'file:///c%3A/'); - assert.equal(dirname(URI.file('c:\\'))!.toString(), 'file:///c%3A/'); + assert.equal(dirname(URI.file('c:\\some\\file\\test.txt')).toString(), 'file:///c%3A/some/file'); + assert.equal(dirname(URI.file('c:\\some\\file')).toString(), 'file:///c%3A/some'); + assert.equal(dirname(URI.file('c:\\some\\file\\')).toString(), 'file:///c%3A/some'); + assert.equal(dirname(URI.file('c:\\some')).toString(), 'file:///c%3A/'); + assert.equal(dirname(URI.file('C:\\some')).toString(), 'file:///c%3A/'); + assert.equal(dirname(URI.file('c:\\')).toString(), 'file:///c%3A/'); } else { - assert.equal(dirname(URI.file('/some/file/test.txt'))!.toString(), 'file:///some/file'); - assert.equal(dirname(URI.file('/some/file/'))!.toString(), 'file:///some'); - assert.equal(dirname(URI.file('/some/file'))!.toString(), 'file:///some'); + assert.equal(dirname(URI.file('/some/file/test.txt')).toString(), 'file:///some/file'); + assert.equal(dirname(URI.file('/some/file/')).toString(), 'file:///some'); + assert.equal(dirname(URI.file('/some/file')).toString(), 'file:///some'); } - assert.equal(dirname(URI.parse('foo://a/some/file/test.txt'))!.toString(), 'foo://a/some/file'); - assert.equal(dirname(URI.parse('foo://a/some/file/'))!.toString(), 'foo://a/some'); - assert.equal(dirname(URI.parse('foo://a/some/file'))!.toString(), 'foo://a/some'); - assert.equal(dirname(URI.parse('foo://a/some'))!.toString(), 'foo://a/'); - assert.equal(dirname(URI.parse('foo://a/'))!.toString(), 'foo://a/'); - assert.equal(dirname(URI.parse('foo://a'))!.toString(), 'foo://a'); + assert.equal(dirname(URI.parse('foo://a/some/file/test.txt')).toString(), 'foo://a/some/file'); + assert.equal(dirname(URI.parse('foo://a/some/file/')).toString(), 'foo://a/some'); + assert.equal(dirname(URI.parse('foo://a/some/file')).toString(), 'foo://a/some'); + assert.equal(dirname(URI.parse('foo://a/some')).toString(), 'foo://a/'); + assert.equal(dirname(URI.parse('foo://a/')).toString(), 'foo://a/'); + assert.equal(dirname(URI.parse('foo://a')).toString(), 'foo://a'); // does not explode (https://github.com/Microsoft/vscode/issues/41987) dirname(URI.from({ scheme: 'file', authority: '/users/someone/portal.h' })); @@ -166,6 +170,150 @@ suite('Resources', () => { assert.equal(isAbsolutePath(URI.parse('foo://a/foo/.')), true); }); + function assertTrailingSeparator(u1: URI, expected: boolean) { + assert.equal(hasTrailingPathSeparator(u1), expected, u1.toString()); + } + + function assertRemoveTrailingSeparator(u1: URI, expected: URI) { + assertEqualURI(removeTrailingPathSeparator(u1), expected, u1.toString()); + } + + + test('trailingPathSeparator', () => { + assertTrailingSeparator(URI.parse('foo://a/foo'), false); + assertTrailingSeparator(URI.parse('foo://a/foo/'), true); + assertTrailingSeparator(URI.parse('foo://a/'), false); + assertTrailingSeparator(URI.parse('foo://a'), false); + + assertRemoveTrailingSeparator(URI.parse('foo://a/foo'), URI.parse('foo://a/foo')); + assertRemoveTrailingSeparator(URI.parse('foo://a/foo/'), URI.parse('foo://a/foo')); + assertRemoveTrailingSeparator(URI.parse('foo://a/'), URI.parse('foo://a/')); + assertRemoveTrailingSeparator(URI.parse('foo://a'), URI.parse('foo://a')); + + if (isWindows) { + assertTrailingSeparator(URI.file('c:\\a\\foo'), false); + assertTrailingSeparator(URI.file('c:\\a\\foo\\'), true); + assertTrailingSeparator(URI.file('c:\\'), false); + assertTrailingSeparator(URI.file('\\\\server\\share\\some\\'), true); + assertTrailingSeparator(URI.file('\\\\server\\share\\'), false); + + assertRemoveTrailingSeparator(URI.file('c:\\a\\foo'), URI.file('c:\\a\\foo')); + assertRemoveTrailingSeparator(URI.file('c:\\a\\foo\\'), URI.file('c:\\a\\foo')); + assertRemoveTrailingSeparator(URI.file('c:\\'), URI.file('c:\\')); + assertRemoveTrailingSeparator(URI.file('\\\\server\\share\\some\\'), URI.file('\\\\server\\share\\some')); + assertRemoveTrailingSeparator(URI.file('\\\\server\\share\\'), URI.file('\\\\server\\share\\')); + } else { + assertTrailingSeparator(URI.file('/foo/bar'), false); + assertTrailingSeparator(URI.file('/foo/bar/'), true); + assertTrailingSeparator(URI.file('/'), false); + + assertRemoveTrailingSeparator(URI.file('/foo/bar'), URI.file('/foo/bar')); + assertRemoveTrailingSeparator(URI.file('/foo/bar/'), URI.file('/foo/bar')); + assertRemoveTrailingSeparator(URI.file('/'), URI.file('/')); + } + }); + + function assertEqualURI(actual: URI, expected: URI, message?: string) { + if (!isEqual(expected, actual)) { + assert.equal(expected.toString(), actual.toString(), message); + } + } + + function assertRelativePath(u1: URI, u2: URI, expectedPath: string | undefined, ignoreJoin?: boolean) { + assert.equal(relativePath(u1, u2), expectedPath, `from ${u1.toString()} to ${u2.toString()}`); + if (expectedPath !== undefined && !ignoreJoin) { + assertEqualURI(removeTrailingPathSeparator(joinPath(u1, expectedPath)), removeTrailingPathSeparator(u2), 'joinPath on relativePath should be equal'); + } + } + + test('relativePath', () => { + assertRelativePath(URI.parse('foo://a/foo'), URI.parse('foo://a/foo/bar'), 'bar'); + assertRelativePath(URI.parse('foo://a/foo'), URI.parse('foo://a/foo/bar/'), 'bar'); + assertRelativePath(URI.parse('foo://a/foo'), URI.parse('foo://a/foo/bar/goo'), 'bar/goo'); + assertRelativePath(URI.parse('foo://a/'), URI.parse('foo://a/foo/bar/goo'), 'foo/bar/goo'); + assertRelativePath(URI.parse('foo://a/foo/xoo'), URI.parse('foo://a/foo/bar'), '../bar'); + assertRelativePath(URI.parse('foo://a/foo/xoo/yoo'), URI.parse('foo://a'), '../../..'); + assertRelativePath(URI.parse('foo://a/foo'), URI.parse('foo://a/foo/'), ''); + assertRelativePath(URI.parse('foo://a/foo/'), URI.parse('foo://a/foo'), ''); + assertRelativePath(URI.parse('foo://a/foo/'), URI.parse('foo://a/foo/'), ''); + assertRelativePath(URI.parse('foo://a/foo'), URI.parse('foo://a/foo'), ''); + assertRelativePath(URI.parse('foo://a'), URI.parse('foo://a'), ''); + assertRelativePath(URI.parse('foo://a/'), URI.parse('foo://a/'), ''); + assertRelativePath(URI.parse('foo://a/'), URI.parse('foo://a'), ''); + assertRelativePath(URI.parse('foo://a/foo?q'), URI.parse('foo://a/foo/bar#h'), 'bar'); + assertRelativePath(URI.parse('foo://'), URI.parse('foo://a/b'), undefined); + assertRelativePath(URI.parse('foo://a2/b'), URI.parse('foo://a/b'), undefined); + assertRelativePath(URI.parse('goo://a/b'), URI.parse('foo://a/b'), undefined); + + if (isWindows) { + assertRelativePath(URI.file('c:\\foo\\bar'), URI.file('c:\\foo\\bar'), ''); + assertRelativePath(URI.file('c:\\foo\\bar\\huu'), URI.file('c:\\foo\\bar'), '..'); + assertRelativePath(URI.file('c:\\foo\\bar\\a1\\a2'), URI.file('c:\\foo\\bar'), '../..'); + assertRelativePath(URI.file('c:\\foo\\bar\\'), URI.file('c:\\foo\\bar\\a1\\a2'), 'a1/a2'); + assertRelativePath(URI.file('c:\\foo\\bar\\'), URI.file('c:\\foo\\bar\\a1\\a2\\'), 'a1/a2'); + assertRelativePath(URI.file('c:\\'), URI.file('c:\\foo\\bar'), 'foo/bar'); + assertRelativePath(URI.file('\\\\server\\share\\some\\'), URI.file('\\\\server\\share\\some\\path'), 'path'); + assertRelativePath(URI.file('\\\\server\\share\\some\\'), URI.file('\\\\server\\share2\\some\\path'), '../../share2/some/path', true); // ignore joinPath assert: path.join is not root aware + } else { + assertRelativePath(URI.file('/a/foo'), URI.file('/a/foo/bar'), 'bar'); + assertRelativePath(URI.file('/a/foo'), URI.file('/a/foo/bar/'), 'bar'); + assertRelativePath(URI.file('/a/foo'), URI.file('/a/foo/bar/goo'), 'bar/goo'); + assertRelativePath(URI.file('/a/'), URI.file('/a/foo/bar/goo'), 'foo/bar/goo'); + assertRelativePath(URI.file('/'), URI.file('/a/foo/bar/goo'), 'a/foo/bar/goo'); + assertRelativePath(URI.file('/a/foo/xoo'), URI.file('/a/foo/bar'), '../bar'); + assertRelativePath(URI.file('/a/foo/xoo/yoo'), URI.file('/a'), '../../..'); + assertRelativePath(URI.file('/a/foo'), URI.file('/a/foo/'), ''); + assertRelativePath(URI.file('/a/foo'), URI.file('/b/foo/'), '../../b/foo'); + } + }); + + function assertResolve(u1: URI, path: string, expected: URI) { + const actual = resolvePath(u1, path); + assertEqualURI(actual, expected, `from ${u1.toString()} and ${path}`); + + if (!isAbsolute(path)) { + let expectedPath = isWindows ? toSlashes(path) : path; + expectedPath = startsWith(expectedPath, './') ? expectedPath.substr(2) : expectedPath; + assert.equal(relativePath(u1, actual), expectedPath, `relativePath (${u1.toString()}) on actual (${actual.toString()}) should be to path (${expectedPath})`); + } + } + + test('resolve', () => { + if (isWindows) { + assertResolve(URI.file('c:\\foo\\bar'), 'file.js', URI.file('c:\\foo\\bar\\file.js')); + assertResolve(URI.file('c:\\foo\\bar'), 't\\file.js', URI.file('c:\\foo\\bar\\t\\file.js')); + assertResolve(URI.file('c:\\foo\\bar'), '.\\t\\file.js', URI.file('c:\\foo\\bar\\t\\file.js')); + assertResolve(URI.file('c:\\foo\\bar'), 'a1/file.js', URI.file('c:\\foo\\bar\\a1\\file.js')); + assertResolve(URI.file('c:\\foo\\bar'), './a1/file.js', URI.file('c:\\foo\\bar\\a1\\file.js')); + assertResolve(URI.file('c:\\foo\\bar'), '\\b1\\file.js', URI.file('c:\\b1\\file.js')); + assertResolve(URI.file('c:\\foo\\bar'), '/b1/file.js', URI.file('c:\\b1\\file.js')); + assertResolve(URI.file('c:\\foo\\bar\\'), 'file.js', URI.file('c:\\foo\\bar\\file.js')); + + assertResolve(URI.file('c:\\'), 'file.js', URI.file('c:\\file.js')); + assertResolve(URI.file('c:\\'), '\\b1\\file.js', URI.file('c:\\b1\\file.js')); + assertResolve(URI.file('c:\\'), '/b1/file.js', URI.file('c:\\b1\\file.js')); + assertResolve(URI.file('c:\\'), 'd:\\foo\\bar.txt', URI.file('d:\\foo\\bar.txt')); + + assertResolve(URI.file('\\\\server\\share\\some\\'), 'b1\\file.js', URI.file('\\\\server\\share\\some\\b1\\file.js')); + assertResolve(URI.file('\\\\server\\share\\some\\'), '\\file.js', URI.file('\\\\server\\share\\file.js')); + + assertResolve(URI.file('c:\\'), '\\\\server\\share\\some\\', URI.file('\\\\server\\share\\some')); + assertResolve(URI.file('\\\\server\\share\\some\\'), 'c:\\', URI.file('c:\\')); + } else { + assertResolve(URI.file('/foo/bar'), 'file.js', URI.file('/foo/bar/file.js')); + assertResolve(URI.file('/foo/bar'), './file.js', URI.file('/foo/bar/file.js')); + assertResolve(URI.file('/foo/bar'), '/file.js', URI.file('/file.js')); + assertResolve(URI.file('/foo/bar/'), 'file.js', URI.file('/foo/bar/file.js')); + assertResolve(URI.file('/'), 'file.js', URI.file('/file.js')); + assertResolve(URI.file(''), './file.js', URI.file('/file.js')); + assertResolve(URI.file(''), '/file.js', URI.file('/file.js')); + } + + assertResolve(URI.parse('foo://server/foo/bar'), 'file.js', URI.parse('foo://server/foo/bar/file.js')); + assertResolve(URI.parse('foo://server/foo/bar'), './file.js', URI.parse('foo://server/foo/bar/file.js')); + assertResolve(URI.parse('foo://server/foo/bar'), './file.js', URI.parse('foo://server/foo/bar/file.js')); + }); + test('isEqual', () => { let fileURI = isWindows ? URI.file('c:\\foo\\bar') : URI.file('/foo/bar'); let fileURI2 = isWindows ? URI.file('C:\\foo\\Bar') : URI.file('/foo/Bar'); @@ -184,6 +332,8 @@ suite('Resources', () => { assert.equal(isEqual(fileURI3, fileURI4, false), false); assert.equal(isEqual(fileURI, fileURI3, true), false); + + assert.equal(isEqual(URI.parse('foo://server'), URI.parse('foo://server/')), true); }); test('isEqualOrParent', () => { @@ -210,27 +360,4 @@ suite('Resources', () => { assert.equal(isEqualOrParent(fileURI3, fileURI, true), false, '15'); assert.equal(isEqualOrParent(fileURI5, fileURI5, true), true, '16'); }); - - function assertMalformedFileUri(path: string, expected: string | undefined) { - const old = setUriThrowOnMissingScheme(false); - const newURI = isMalformedFileUri(URI.parse(path)); - assert.equal(newURI && newURI.toString(), expected); - setUriThrowOnMissingScheme(old); - } - - test('isMalformedFileUri', () => { - if (isWindows) { - assertMalformedFileUri('c:/foo/bar', 'file:///c%3A/foo/bar'); - assertMalformedFileUri('c:\\foo\\bar', 'file:///c%3A/foo/bar'); - assertMalformedFileUri('C:\\foo\\bar', 'file:///c%3A/foo/bar'); - assertMalformedFileUri('\\\\localhost\\c$\\devel\\test', 'file://localhost/c%24/devel/test'); - } - assertMalformedFileUri('/foo/bar', 'file:///foo/bar'); - - assertMalformedFileUri('file:///foo/bar', undefined); - assertMalformedFileUri('file:///c%3A/foo/bar', undefined); - assertMalformedFileUri('file://localhost/c$/devel/test', undefined); - assertMalformedFileUri('foo://dadie/foo/bar', undefined); - assertMalformedFileUri('foo:///dadie/foo/bar', undefined); - }); }); diff --git a/src/vs/base/test/common/strings.test.ts b/src/vs/base/test/common/strings.test.ts index f548561a0e..1a33054e5f 100644 --- a/src/vs/base/test/common/strings.test.ts +++ b/src/vs/base/test/common/strings.test.ts @@ -403,4 +403,59 @@ suite('Strings', () => { assert.equal(strings.getNLines('foo\nbar\nsomething', 2), 'foo\nbar'); assert.equal(strings.getNLines('foo', 0), ''); }); + + test('removeAccents', function () { + assert.equal(strings.removeAccents('joào'), 'joao'); + assert.equal(strings.removeAccents('joáo'), 'joao'); + assert.equal(strings.removeAccents('joâo'), 'joao'); + assert.equal(strings.removeAccents('joäo'), 'joao'); + // assert.equal(strings.removeAccents('joæo'), 'joao'); // not an accent + assert.equal(strings.removeAccents('joão'), 'joao'); + assert.equal(strings.removeAccents('joåo'), 'joao'); + assert.equal(strings.removeAccents('joåo'), 'joao'); + assert.equal(strings.removeAccents('joāo'), 'joao'); + + assert.equal(strings.removeAccents('fôo'), 'foo'); + assert.equal(strings.removeAccents('föo'), 'foo'); + assert.equal(strings.removeAccents('fòo'), 'foo'); + assert.equal(strings.removeAccents('fóo'), 'foo'); + // assert.equal(strings.removeAccents('fœo'), 'foo'); + // assert.equal(strings.removeAccents('føo'), 'foo'); + assert.equal(strings.removeAccents('fōo'), 'foo'); + assert.equal(strings.removeAccents('fõo'), 'foo'); + + assert.equal(strings.removeAccents('andrè'), 'andre'); + assert.equal(strings.removeAccents('andré'), 'andre'); + assert.equal(strings.removeAccents('andrê'), 'andre'); + assert.equal(strings.removeAccents('andrë'), 'andre'); + assert.equal(strings.removeAccents('andrē'), 'andre'); + assert.equal(strings.removeAccents('andrė'), 'andre'); + assert.equal(strings.removeAccents('andrę'), 'andre'); + + assert.equal(strings.removeAccents('hvîc'), 'hvic'); + assert.equal(strings.removeAccents('hvïc'), 'hvic'); + assert.equal(strings.removeAccents('hvíc'), 'hvic'); + assert.equal(strings.removeAccents('hvīc'), 'hvic'); + assert.equal(strings.removeAccents('hvįc'), 'hvic'); + assert.equal(strings.removeAccents('hvìc'), 'hvic'); + + assert.equal(strings.removeAccents('ûdo'), 'udo'); + assert.equal(strings.removeAccents('üdo'), 'udo'); + assert.equal(strings.removeAccents('ùdo'), 'udo'); + assert.equal(strings.removeAccents('údo'), 'udo'); + assert.equal(strings.removeAccents('ūdo'), 'udo'); + + assert.equal(strings.removeAccents('heÿ'), 'hey'); + + // assert.equal(strings.removeAccents('gruß'), 'grus'); + assert.equal(strings.removeAccents('gruś'), 'grus'); + assert.equal(strings.removeAccents('gruš'), 'grus'); + + assert.equal(strings.removeAccents('çool'), 'cool'); + assert.equal(strings.removeAccents('ćool'), 'cool'); + assert.equal(strings.removeAccents('čool'), 'cool'); + + assert.equal(strings.removeAccents('ñice'), 'nice'); + assert.equal(strings.removeAccents('ńice'), 'nice'); + }); }); diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts index 15d6b6be01..fa3731b241 100644 --- a/src/vs/base/test/common/uri.test.ts +++ b/src/vs/base/test/common/uri.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { normalize } from 'vs/base/common/paths'; import { isWindows } from 'vs/base/common/platform'; @@ -141,7 +140,6 @@ suite('URI', () => { assert.equal(value.scheme, 'http'); assert.equal(value.authority, 'api'); assert.equal(value.path, '/files/test.me'); - assert.equal(value.fsPath, normalize('/files/test.me', true)); assert.equal(value.query, 't=1234'); assert.equal(value.fragment, ''); @@ -151,7 +149,7 @@ suite('URI', () => { assert.equal(value.path, '/c:/test/me'); assert.equal(value.fragment, ''); assert.equal(value.query, ''); - assert.equal(value.fsPath, normalize('c:/test/me', true)); + assert.equal(value.fsPath, isWindows ? 'c:\\test\\me' : 'c:/test/me'); value = URI.parse('file://shares/files/c%23/p.cs'); assert.equal(value.scheme, 'file'); @@ -159,7 +157,7 @@ suite('URI', () => { assert.equal(value.path, '/files/c#/p.cs'); assert.equal(value.fragment, ''); assert.equal(value.query, ''); - assert.equal(value.fsPath, normalize('//shares/files/c#/p.cs', true)); + assert.equal(value.fsPath, isWindows ? '\\\\shares\\files\\c#\\p.cs' : '//shares/files/c#/p.cs'); value = URI.parse('file:///c:/Source/Z%C3%BCrich%20or%20Zurich%20(%CB%88zj%CA%8A%C9%99r%C9%AAk,/Code/resources/app/plugins/c%23/plugin.json'); assert.equal(value.scheme, 'file'); @@ -360,7 +358,6 @@ suite('URI', () => { test('correctFileUriToFilePath2', () => { const test = (input: string, expected: string) => { - expected = normalize(expected, true); const value = URI.parse(input); assert.equal(value.fsPath, expected, 'Result for ' + input); const value2 = URI.file(value.fsPath); @@ -368,10 +365,10 @@ suite('URI', () => { assert.equal(value.toString(), value2.toString()); }; - test('file:///c:/alex.txt', 'c:\\alex.txt'); - test('file:///c:/Source/Z%C3%BCrich%20or%20Zurich%20(%CB%88zj%CA%8A%C9%99r%C9%AAk,/Code/resources/app/plugins', 'c:\\Source\\Zürich or Zurich (ˈzjʊərɪk,\\Code\\resources\\app\\plugins'); - test('file://monacotools/folder/isi.txt', '\\\\monacotools\\folder\\isi.txt'); - test('file://monacotools1/certificates/SSL/', '\\\\monacotools1\\certificates\\SSL\\'); + test('file:///c:/alex.txt', isWindows ? 'c:\\alex.txt' : 'c:/alex.txt'); + test('file:///c:/Source/Z%C3%BCrich%20or%20Zurich%20(%CB%88zj%CA%8A%C9%99r%C9%AAk,/Code/resources/app/plugins', isWindows ? 'c:\\Source\\Zürich or Zurich (ˈzjʊərɪk,\\Code\\resources\\app\\plugins' : 'c:/Source/Zürich or Zurich (ˈzjʊərɪk,/Code/resources/app/plugins'); + test('file://monacotools/folder/isi.txt', isWindows ? '\\\\monacotools\\folder\\isi.txt' : '//monacotools/folder/isi.txt'); + test('file://monacotools1/certificates/SSL/', isWindows ? '\\\\monacotools1\\certificates\\SSL\\' : '//monacotools1/certificates/SSL/'); }); test('URI - http, query & toString', function () { diff --git a/src/vs/base/test/common/utils.ts b/src/vs/base/test/common/utils.ts index aa441f7e9b..950e8750f7 100644 --- a/src/vs/base/test/common/utils.ts +++ b/src/vs/base/test/common/utils.ts @@ -3,9 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as paths from 'vs/base/common/paths'; +import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { canceled } from 'vs/base/common/errors'; +import { isWindows } from 'vs/base/common/platform'; export type ValueCallback = (value: T | Promise) => void; @@ -49,7 +50,11 @@ export class DeferredPromise { } export function toResource(this: any, path: string) { - return URI.file(paths.join('C:\\', Buffer.from(this.test.fullTitle()).toString('base64'), path)); + if (isWindows) { + return URI.file(join('C:\\', Buffer.from(this.test.fullTitle()).toString('base64'), path)); + } + + return URI.file(join('/', Buffer.from(this.test.fullTitle()).toString('base64'), path)); } export function suiteRepeat(n: number, description: string, callback: (this: any) => void): void { diff --git a/src/vs/base/test/node/config.test.ts b/src/vs/base/test/node/config.test.ts index 41267d64ee..eafcfb3fe1 100644 --- a/src/vs/base/test/node/config.test.ts +++ b/src/vs/base/test/node/config.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as os from 'os'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as fs from 'fs'; import * as uuid from 'vs/base/common/uuid'; import { ConfigWatcher } from 'vs/base/node/config'; diff --git a/src/vs/base/test/node/console.test.ts b/src/vs/base/test/node/console.test.ts index 71ad0ca80a..c362f48fe4 100644 --- a/src/vs/base/test/node/console.test.ts +++ b/src/vs/base/test/node/console.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { getFirstFrame } from 'vs/base/node/console'; -import { normalize } from 'path'; +import { normalize } from 'vs/base/common/path'; suite('Console', () => { diff --git a/src/vs/base/test/node/extfs/extfs.test.ts b/src/vs/base/test/node/extfs/extfs.test.ts index 76ed26a315..a86d239dfa 100644 --- a/src/vs/base/test/node/extfs/extfs.test.ts +++ b/src/vs/base/test/node/extfs/extfs.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as os from 'os'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { Readable } from 'stream'; import { canNormalize } from 'vs/base/common/normalization'; import { isLinux, isWindows } from 'vs/base/common/platform'; diff --git a/src/vs/base/test/node/glob.test.ts b/src/vs/base/test/node/glob.test.ts index d566de4a0a..af66975d9c 100644 --- a/src/vs/base/test/node/glob.test.ts +++ b/src/vs/base/test/node/glob.test.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as glob from 'vs/base/common/glob'; import { isWindows } from 'vs/base/common/platform'; @@ -102,6 +102,9 @@ suite('Glob', () => { p = 'C:/DNXConsoleApp/**/*.cs'; assertGlobMatch(p, 'C:\\DNXConsoleApp\\Program.cs'); assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.cs'); + + p = '*'; + assertGlobMatch(p, ''); }); test('dot hidden', function () { @@ -719,7 +722,10 @@ suite('Glob', () => { assert.strictEqual(glob.match(expr, 'bar', hasSibling), '**/bar'); assert.strictEqual(glob.match(expr, 'foo', hasSibling), null); assert.strictEqual(glob.match(expr, 'foo/bar', hasSibling), '**/bar'); - assert.strictEqual(glob.match(expr, 'foo\\bar', hasSibling), '**/bar'); + if (isWindows) { + // backslash is a valid file name character on posix + assert.strictEqual(glob.match(expr, 'foo\\bar', hasSibling), '**/bar'); + } assert.strictEqual(glob.match(expr, 'foo/foo', hasSibling), null); assert.strictEqual(glob.match(expr, 'foo.js', hasSibling), '**/*.js'); assert.strictEqual(glob.match(expr, 'bar.js', hasSibling), null); @@ -948,14 +954,14 @@ suite('Glob', () => { test('relative pattern - glob star', function () { if (isWindows) { - let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: '**/*.cs', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: '**/*.cs' }; assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.cs'); assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\bar\\Program.cs'); assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.ts'); assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\Program.cs'); assertNoGlobMatch(p, 'C:\\other\\DNXConsoleApp\\foo\\Program.ts'); } else { - let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: '**/*.cs', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: '**/*.cs' }; assertGlobMatch(p, '/DNXConsoleApp/foo/Program.cs'); assertGlobMatch(p, '/DNXConsoleApp/foo/bar/Program.cs'); assertNoGlobMatch(p, '/DNXConsoleApp/foo/Program.ts'); @@ -966,14 +972,14 @@ suite('Glob', () => { test('relative pattern - single star', function () { if (isWindows) { - let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: '*.cs', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: '*.cs' }; assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.cs'); assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\bar\\Program.cs'); assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.ts'); assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\Program.cs'); assertNoGlobMatch(p, 'C:\\other\\DNXConsoleApp\\foo\\Program.ts'); } else { - let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: '*.cs', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: '*.cs' }; assertGlobMatch(p, '/DNXConsoleApp/foo/Program.cs'); assertNoGlobMatch(p, '/DNXConsoleApp/foo/bar/Program.cs'); assertNoGlobMatch(p, '/DNXConsoleApp/foo/Program.ts'); @@ -984,11 +990,11 @@ suite('Glob', () => { test('relative pattern - single star with path', function () { if (isWindows) { - let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: 'something/*.cs', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: 'something/*.cs' }; assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\something\\Program.cs'); assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.cs'); } else { - let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: 'something/*.cs', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: 'something/*.cs' }; assertGlobMatch(p, '/DNXConsoleApp/foo/something/Program.cs'); assertNoGlobMatch(p, '/DNXConsoleApp/foo/Program.cs'); } @@ -1000,11 +1006,11 @@ suite('Glob', () => { test('relative pattern - #57475', function () { if (isWindows) { - let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: 'styles/style.css', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: 'C:\\DNXConsoleApp\\foo', pattern: 'styles/style.css' }; assertGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\styles\\style.css'); assertNoGlobMatch(p, 'C:\\DNXConsoleApp\\foo\\Program.cs'); } else { - let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: 'styles/style.css', pathToRelative: (from, to) => path.relative(from, to) }; + let p: glob.IRelativePattern = { base: '/DNXConsoleApp/foo', pattern: 'styles/style.css' }; assertGlobMatch(p, '/DNXConsoleApp/foo/styles/style.css'); assertNoGlobMatch(p, '/DNXConsoleApp/foo/Program.cs'); } diff --git a/src/vs/platform/credentials/test/node/keytar.test.ts b/src/vs/base/test/node/keytar.test.ts similarity index 100% rename from src/vs/platform/credentials/test/node/keytar.test.ts rename to src/vs/base/test/node/keytar.test.ts diff --git a/src/vs/base/test/node/pfs.test.ts b/src/vs/base/test/node/pfs.test.ts index 69c7860640..fa2311935a 100644 --- a/src/vs/base/test/node/pfs.test.ts +++ b/src/vs/base/test/node/pfs.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as os from 'os'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as fs from 'fs'; import * as uuid from 'vs/base/common/uuid'; diff --git a/src/vs/base/test/node/processes/processes.test.ts b/src/vs/base/test/node/processes/processes.test.ts index 5fcb781952..d32e2e5485 100644 --- a/src/vs/base/test/node/processes/processes.test.ts +++ b/src/vs/base/test/node/processes/processes.test.ts @@ -84,29 +84,4 @@ suite('Processes', () => { } }); }); - - - test('sanitizeProcessEnvironment', () => { - let env = { - FOO: 'bar', - ELECTRON_ENABLE_STACK_DUMPING: 'x', - ELECTRON_ENABLE_LOGGING: 'x', - ELECTRON_NO_ASAR: 'x', - ELECTRON_NO_ATTACH_CONSOLE: 'x', - ELECTRON_RUN_AS_NODE: 'x', - GOOGLE_API_KEY: 'x', - VSCODE_CLI: 'x', - VSCODE_DEV: 'x', - VSCODE_IPC_HOOK: 'x', - VSCODE_LOGS: 'x', - VSCODE_NLS_CONFIG: 'x', - VSCODE_PORTABLE: 'x', - VSCODE_PID: 'x', - VSCODE_NODE_CACHED_DATA_DIR: 'x', - VSCODE_NEW_VAR: 'x' - }; - processes.sanitizeProcessEnvironment(env); - assert.equal(env['FOO'], 'bar'); - assert.equal(Object.keys(env).length, 1); - }); }); diff --git a/src/vs/base/test/node/storage/storage.test.ts b/src/vs/base/test/node/storage/storage.test.ts index 40867b4602..5988c34178 100644 --- a/src/vs/base/test/node/storage/storage.test.ts +++ b/src/vs/base/test/node/storage/storage.test.ts @@ -5,7 +5,7 @@ import { Storage, SQLiteStorageDatabase, IStorageDatabase, ISQLiteStorageDatabaseOptions, IStorageItemsChangeEvent } from 'vs/base/node/storage'; import { generateUuid } from 'vs/base/common/uuid'; -import { join } from 'path'; +import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; import { equal, ok } from 'assert'; import { mkdirp, del, writeFile, exists, unlink } from 'vs/base/node/pfs'; @@ -31,7 +31,7 @@ suite('Storage Library', () => { // Empty fallbacks equal(storage.get('foo', 'bar'), 'bar'); - equal(storage.getInteger('foo', 55), 55); + equal(storage.getNumber('foo', 55), 55); equal(storage.getBoolean('foo', true), true); let changes = new Set(); @@ -45,7 +45,7 @@ suite('Storage Library', () => { const set3Promise = storage.set('barBoolean', true); equal(storage.get('bar'), 'foo'); - equal(storage.getInteger('barNumber'), 55); + equal(storage.getNumber('barNumber'), 55); equal(storage.getBoolean('barBoolean'), true); equal(changes.size, 3); @@ -71,7 +71,7 @@ suite('Storage Library', () => { const delete3Promise = storage.delete('barBoolean'); ok(!storage.get('bar')); - ok(!storage.getInteger('barNumber')); + ok(!storage.getNumber('barNumber')); ok(!storage.getBoolean('barBoolean')); equal(changes.size, 3); @@ -539,7 +539,9 @@ suite('SQLite Storage Library', () => { await del(storageDir, tmpdir()); }); - test('real world example', async () => { + test('real world example', async function () { + this.timeout(20000); + const storageDir = uniqueStorageDir(); await mkdirp(storageDir); @@ -553,7 +555,7 @@ suite('SQLite Storage Library', () => { items1.set('debug.actionswidgetposition', '0.6880952380952381'); const items2 = new Map(); - items2.set('workbench.editors.files.textfileeditor', '{"textEditorViewState":[["file:///Users/dummy/Documents/ticino-playground/play.htm",{"0":{"cursorState":[{"inSelectionMode":false,"selectionStart":{"lineNumber":6,"column":16},"position":{"lineNumber":6,"column":16}}],"viewState":{"scrollLeft":0,"firstPosition":{"lineNumber":1,"column":1},"firstPositionDeltaTop":0},"contributionsState":{"editor.contrib.folding":{},"editor.contrib.wordHighlighter":false}}}],["file:///Users/dummy/Documents/ticino-playground/nakefile.js",{"0":{"cursorState":[{"inSelectionMode":false,"selectionStart":{"lineNumber":7,"column":81},"position":{"lineNumber":7,"column":81}}],"viewState":{"scrollLeft":0,"firstPosition":{"lineNumber":1,"column":1},"firstPositionDeltaTop":20},"contributionsState":{"editor.contrib.folding":{},"editor.contrib.wordHighlighter":false}}}],["file:///Users/dummy/Desktop/vscode2/.gitattributes",{"0":{"cursorState":[{"inSelectionMode":false,"selectionStart":{"lineNumber":9,"column":12},"position":{"lineNumber":9,"column":12}}],"viewState":{"scrollLeft":0,"firstPosition":{"lineNumber":1,"column":1},"firstPositionDeltaTop":20},"contributionsState":{"editor.contrib.folding":{},"editor.contrib.wordHighlighter":false}}}],["file:///Users/dummy/Desktop/vscode2/src/vs/workbench/parts/search/browser/openAnythingHandler.ts",{"0":{"cursorState":[{"inSelectionMode":false,"selectionStart":{"lineNumber":1,"column":1},"position":{"lineNumber":1,"column":1}}],"viewState":{"scrollLeft":0,"firstPosition":{"lineNumber":1,"column":1},"firstPositionDeltaTop":0},"contributionsState":{"editor.contrib.folding":{},"editor.contrib.wordHighlighter":false}}}]]}'); + items2.set('workbench.editors.files.textfileeditor', '{"textEditorViewState":[["file:///Users/dummy/Documents/ticino-playground/play.htm",{"0":{"cursorState":[{"inSelectionMode":false,"selectionStart":{"lineNumber":6,"column":16},"position":{"lineNumber":6,"column":16}}],"viewState":{"scrollLeft":0,"firstPosition":{"lineNumber":1,"column":1},"firstPositionDeltaTop":0},"contributionsState":{"editor.contrib.folding":{},"editor.contrib.wordHighlighter":false}}}],["file:///Users/dummy/Documents/ticino-playground/nakefile.js",{"0":{"cursorState":[{"inSelectionMode":false,"selectionStart":{"lineNumber":7,"column":81},"position":{"lineNumber":7,"column":81}}],"viewState":{"scrollLeft":0,"firstPosition":{"lineNumber":1,"column":1},"firstPositionDeltaTop":20},"contributionsState":{"editor.contrib.folding":{},"editor.contrib.wordHighlighter":false}}}],["file:///Users/dummy/Desktop/vscode2/.gitattributes",{"0":{"cursorState":[{"inSelectionMode":false,"selectionStart":{"lineNumber":9,"column":12},"position":{"lineNumber":9,"column":12}}],"viewState":{"scrollLeft":0,"firstPosition":{"lineNumber":1,"column":1},"firstPositionDeltaTop":20},"contributionsState":{"editor.contrib.folding":{},"editor.contrib.wordHighlighter":false}}}],["file:///Users/dummy/Desktop/vscode2/src/vs/workbench/contrib/search/browser/openAnythingHandler.ts",{"0":{"cursorState":[{"inSelectionMode":false,"selectionStart":{"lineNumber":1,"column":1},"position":{"lineNumber":1,"column":1}}],"viewState":{"scrollLeft":0,"firstPosition":{"lineNumber":1,"column":1},"firstPositionDeltaTop":0},"contributionsState":{"editor.contrib.folding":{},"editor.contrib.wordHighlighter":false}}}]]}'); const items3 = new Map(); items3.set('nps/iscandidate', 'false'); @@ -628,7 +630,9 @@ suite('SQLite Storage Library', () => { await del(storageDir, tmpdir()); }); - test('very large item value', async () => { + test('very large item value', async function () { + this.timeout(20000); + const storageDir = uniqueStorageDir(); await mkdirp(storageDir); diff --git a/src/vs/workbench/parts/debug/test/browser/debugANSIHandling.test.ts b/src/vs/base/test/node/testUtils.ts similarity index 60% rename from src/vs/workbench/parts/debug/test/browser/debugANSIHandling.test.ts rename to src/vs/base/test/node/testUtils.ts index f17efc60c9..aebb56a418 100644 --- a/src/vs/workbench/parts/debug/test/browser/debugANSIHandling.test.ts +++ b/src/vs/base/test/node/testUtils.ts @@ -3,10 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import { join } from 'vs/base/common/path'; +import { generateUuid } from 'vs/base/common/uuid'; -suite('Debug - ANSI Handling', () => { - test('appendStylizedStringToContainer', () => { - assert.equal('', ''); - }); -}); +export function getRandomTestPath(tmpdir: string, ...segments: string[]): string { + return join(tmpdir, ...segments, generateUuid()); +} diff --git a/src/vs/base/test/node/utils.ts b/src/vs/base/test/node/utils.ts index d2e92094ad..e79ef08cdf 100644 --- a/src/vs/base/test/node/utils.ts +++ b/src/vs/base/test/node/utils.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { generateUuid } from 'vs/base/common/uuid'; -import { join } from 'path'; +import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; import { mkdirp, del } from 'vs/base/node/pfs'; diff --git a/src/vs/base/worker/defaultWorkerFactory.ts b/src/vs/base/worker/defaultWorkerFactory.ts index c7ff1d29e3..e89f339a3e 100644 --- a/src/vs/base/worker/defaultWorkerFactory.ts +++ b/src/vs/base/worker/defaultWorkerFactory.ts @@ -6,7 +6,7 @@ import { globals } from 'vs/base/common/platform'; import { IWorker, IWorkerCallback, IWorkerFactory, logOnceWebWorkerWarning } from 'vs/base/common/worker/simpleWorker'; -function getWorker(workerId: string, label: string): Worker { +function getWorker(workerId: string, label: string): Worker | Promise { // Option for hosts to overwrite the worker script (used in the standalone editor) if (globals.MonacoEnvironment) { if (typeof globals.MonacoEnvironment.getWorker === 'function') { @@ -18,12 +18,34 @@ function getWorker(workerId: string, label: string): Worker { } // ESM-comment-begin if (typeof require === 'function') { - return new Worker(require.toUrl('./' + workerId) + '#' + label); + // check if the JS lives on a different origin + + const workerMain = require.toUrl('./' + workerId); + if (/^(http:)|(https:)|(file:)/.test(workerMain)) { + const currentUrl = String(window.location); + const currentOrigin = currentUrl.substr(0, currentUrl.length - window.location.hash.length - window.location.search.length - window.location.pathname.length); + if (workerMain.substring(0, currentOrigin.length) !== currentOrigin) { + // this is the cross-origin case + // i.e. the webpage is running at a different origin than where the scripts are loaded from + const workerBaseUrl = workerMain.substr(0, workerMain.length - 'vs/base/worker/workerMain.js'.length); + const js = `/*${label}*/self.MonacoEnvironment={baseUrl: '${workerBaseUrl}'};importScripts('${workerMain}');/*${label}*/`; + const url = `data:text/javascript;charset=utf-8,${encodeURIComponent(js)}`; + return new Worker(url); + } + } + return new Worker(workerMain + '#' + label); } // ESM-comment-end throw new Error(`You must define a function MonacoEnvironment.getWorkerUrl or MonacoEnvironment.getWorker`); } +function isPromiseLike(obj: any): obj is PromiseLike { + if (typeof obj.then === 'function') { + return true; + } + return false; +} + /** * A worker that uses HTML5 web workers so that is has * its own global scope and its own thread. @@ -31,18 +53,26 @@ function getWorker(workerId: string, label: string): Worker { class WebWorker implements IWorker { private id: number; - private worker: Worker | null; + private worker: Promise | null; constructor(moduleId: string, id: number, label: string, onMessageCallback: IWorkerCallback, onErrorCallback: (err: any) => void) { this.id = id; - this.worker = getWorker('workerMain.js', label); - this.postMessage(moduleId); - this.worker.onmessage = function (ev: any) { - onMessageCallback(ev.data); - }; - if (typeof this.worker.addEventListener === 'function') { - this.worker.addEventListener('error', onErrorCallback); + const workerOrPromise = getWorker('workerMain.js', label); + if (isPromiseLike(workerOrPromise)) { + this.worker = workerOrPromise; + } else { + this.worker = Promise.resolve(workerOrPromise); } + this.postMessage(moduleId); + this.worker.then((w) => { + w.onmessage = function (ev: any) { + onMessageCallback(ev.data); + }; + (w).onmessageerror = onErrorCallback; + if (typeof w.addEventListener === 'function') { + w.addEventListener('error', onErrorCallback); + } + }); } public getId(): number { @@ -51,13 +81,13 @@ class WebWorker implements IWorker { public postMessage(msg: string): void { if (this.worker) { - this.worker.postMessage(msg); + this.worker.then(w => w.postMessage(msg)); } } public dispose(): void { if (this.worker) { - this.worker.terminate(); + this.worker.then(w => w.terminate()); } this.worker = null; } diff --git a/src/vs/code/buildfile.js b/src/vs/code/buildfile.js index 296e8e9f1a..1a8085fcc1 100644 --- a/src/vs/code/buildfile.js +++ b/src/vs/code/buildfile.js @@ -5,8 +5,9 @@ 'use strict'; function createModuleDescription(name, exclude) { - var result = {}; - var excludes = ['vs/css', 'vs/nls']; + const result = {}; + + let excludes = ['vs/css', 'vs/nls']; result.name = name; if (Array.isArray(exclude) && exclude.length > 0) { excludes = excludes.concat(exclude); diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index fc486a48dc..0225027b92 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -10,13 +10,12 @@ import { $ } from 'vs/base/browser/dom'; import * as collections from 'vs/base/common/collections'; import * as browser from 'vs/base/browser/browser'; import { escape } from 'vs/base/common/strings'; -import product from 'vs/platform/node/product'; -import pkg from 'vs/platform/node/package'; +import product from 'vs/platform/product/node/product'; +import pkg from 'vs/platform/product/node/package'; import * as os from 'os'; import { debounce } from 'vs/base/common/decorators'; import * as platform from 'vs/base/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; -import { Client as ElectronIPCClient } from 'vs/base/parts/ipc/electron-browser/ipc.electron-browser'; import { getDelayedChannel } from 'vs/base/parts/ipc/node/ipc'; import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -28,7 +27,8 @@ import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; -import { WindowsChannelClient } from 'vs/platform/windows/node/windowsIpc'; +import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; +import { MainProcessService, IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel'; import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/common/issue'; @@ -64,14 +64,14 @@ export class IssueReporter extends Disposable { private environmentService: IEnvironmentService; private telemetryService: ITelemetryService; private logService: ILogService; - private issueReporterModel: IssueReporterModel; + private readonly issueReporterModel: IssueReporterModel; private numberOfSearchResultsDisplayed = 0; private receivedSystemInfo = false; private receivedPerformanceInfo = false; private shouldQueueSearch = false; private hasBeenSubmitted = false; - private previewButton: Button; + private readonly previewButton: Button; constructor(configuration: IssueReporterConfiguration) { super(); @@ -273,14 +273,14 @@ export class IssueReporter extends Disposable { private initServices(configuration: IWindowConfiguration): void { const serviceCollection = new ServiceCollection(); - const mainProcessClient = new ElectronIPCClient(String(`window${configuration.windowId}`)); + const mainProcessService = new MainProcessService(configuration.windowId); + serviceCollection.set(IMainProcessService, mainProcessService); - const windowsChannel = mainProcessClient.getChannel('windows'); - serviceCollection.set(IWindowsService, new WindowsChannelClient(windowsChannel)); + serviceCollection.set(IWindowsService, new WindowsService(mainProcessService)); this.environmentService = new EnvironmentService(configuration, configuration.execPath); const logService = createSpdLogService(`issuereporter${configuration.windowId}`, getLogLevel(this.environmentService), this.environmentService.logsPath); - const logLevelClient = new LogLevelSetterChannelClient(mainProcessClient.getChannel('loglevel')); + const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel')); this.logService = new FollowerLogService(logLevelClient, logService); const sharedProcess = (serviceCollection.get(IWindowsService)).whenSharedProcessReady() @@ -311,6 +311,7 @@ export class IssueReporter extends Disposable { ipcRenderer.send('vscode:issuePerformanceInfoRequest'); } this.updatePreviewButtonState(); + this.setSourceOptions(); this.render(); }); @@ -342,8 +343,20 @@ export class IssueReporter extends Disposable { } this.addEventListener('issue-source', 'change', (e: Event) => { - const fileOnExtension = JSON.parse((e.target).value); - this.issueReporterModel.update({ fileOnExtension: fileOnExtension, includeExtensions: !fileOnExtension }); + const value = (e.target).value; + const problemSourceHelpText = this.getElementById('problem-source-help-text')!; + if (value === '') { + this.issueReporterModel.update({ fileOnExtension: undefined }); + show(problemSourceHelpText); + this.clearSearchResults(); + this.render(); + return; + } else { + hide(problemSourceHelpText); + } + + const fileOnExtension = JSON.parse(value); + this.issueReporterModel.update({ fileOnExtension: fileOnExtension }); this.render(); const title = (this.getElementById('issue-title')).value; @@ -360,7 +373,7 @@ export class IssueReporter extends Disposable { this.issueReporterModel.update({ issueDescription }); // Only search for extension issues on title change - if (!this.issueReporterModel.fileOnExtension()) { + if (this.issueReporterModel.fileOnExtension() === false) { const title = (this.getElementById('issue-title')).value; this.searchVSCodeIssues(title, issueDescription); } @@ -375,7 +388,12 @@ export class IssueReporter extends Disposable { hide(lengthValidationMessage); } - if (this.issueReporterModel.fileOnExtension()) { + const fileOnExtension = this.issueReporterModel.fileOnExtension(); + if (fileOnExtension === undefined) { + return; + } + + if (fileOnExtension) { this.searchExtensionIssues(title); } else { const description = this.issueReporterModel.getData().issueDescription; @@ -663,6 +681,45 @@ export class IssueReporter extends Disposable { } typeSelect.value = issueType.toString(); + + this.setSourceOptions(); + } + + private makeOption(value: string, description: string, disabled: boolean): HTMLOptionElement { + const option: HTMLOptionElement = document.createElement('option'); + option.disabled = disabled; + option.value = value; + option.textContent = description; + + return option; + } + + private setSourceOptions(): void { + const sourceSelect = this.getElementById('issue-source')! as HTMLSelectElement; + const selected = sourceSelect.selectedIndex; + sourceSelect.innerHTML = ''; + const { issueType } = this.issueReporterModel.getData(); + if (issueType === IssueType.FeatureRequest) { + sourceSelect.append(...[ + this.makeOption('', localize('selectSource', "Select source"), true), + this.makeOption('false', localize('vscode', "Visual Studio Code"), false), + this.makeOption('true', localize('extension', "An extension"), false) + ]); + } else { + sourceSelect.append(...[ + this.makeOption('', localize('selectSource', "Select source"), true), + this.makeOption('false', localize('vscode', "Visual Studio Code"), false), + this.makeOption('true', localize('extension', "An extension"), false), + this.makeOption('', localize('unknown', "Don't Know"), false) + ]); + } + + if (selected !== -1 && selected < sourceSelect.options.length) { + sourceSelect.selectedIndex = selected; + } else { + sourceSelect.selectedIndex = 0; + hide(this.getElementById('problem-source-help-text')); + } } private renderBlocks(): void { @@ -677,7 +734,6 @@ export class IssueReporter extends Disposable { const settingsSearchResultsBlock = document.querySelector('.block-settingsSearchResults'); const problemSource = this.getElementById('problem-source')!; - const problemSourceHelpText = this.getElementById('problem-source-help-text')!; const descriptionTitle = this.getElementById('issue-description-label')!; const descriptionSubtitle = this.getElementById('issue-description-subtitle')!; const extensionSelector = this.getElementById('extension-selection')!; @@ -691,7 +747,6 @@ export class IssueReporter extends Disposable { hide(searchedExtensionsBlock); hide(settingsSearchResultsBlock); hide(problemSource); - hide(problemSourceHelpText); hide(extensionSelector); if (issueType === IssueType.Bug) { @@ -703,7 +758,6 @@ export class IssueReporter extends Disposable { show(extensionSelector); } else { show(extensionsBlock); - show(problemSourceHelpText); } descriptionTitle.innerHTML = `${localize('stepsToReproduce', "Steps to Reproduce")} *`; @@ -719,7 +773,6 @@ export class IssueReporter extends Disposable { show(extensionSelector); } else { show(extensionsBlock); - show(problemSourceHelpText); } descriptionTitle.innerHTML = `${localize('stepsToReproduce', "Steps to Reproduce")} *`; @@ -783,6 +836,10 @@ export class IssueReporter extends Disposable { this.validateInput('description'); }); + this.addEventListener('issue-source', 'change', _ => { + this.validateInput('issue-source'); + }); + if (this.issueReporterModel.fileOnExtension()) { this.addEventListener('extension-selector', 'change', _ => { this.validateInput('extension-selector'); diff --git a/src/vs/code/electron-browser/issue/issueReporterModel.ts b/src/vs/code/electron-browser/issue/issueReporterModel.ts index 389c0f6668..78bb06dd93 100644 --- a/src/vs/code/electron-browser/issue/issueReporterModel.ts +++ b/src/vs/code/electron-browser/issue/issueReporterModel.ts @@ -34,7 +34,7 @@ export interface IssueReporterData { } export class IssueReporterModel { - private _data: IssueReporterData; + private readonly _data: IssueReporterData; constructor(initialData?: Partial) { const defaultData = { @@ -73,12 +73,12 @@ ${this.getInfos()} `; } - fileOnExtension(): boolean { + fileOnExtension(): boolean | undefined { const fileOnExtensionSupported = this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue || this._data.issueType === IssueType.FeatureRequest; - return !!(fileOnExtensionSupported && this._data.fileOnExtension); + return fileOnExtensionSupported && this._data.fileOnExtension; } private getExtensionVersion(): string { @@ -122,7 +122,7 @@ ${this.getInfos()} } if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) { - if (this._data.includeExtensions) { + if (!this._data.fileOnExtension && this._data.includeExtensions) { info += this.generateExtensionsMd(); } } @@ -197,7 +197,7 @@ ${this._data.workspaceInfo}; return 'Extensions: none' + themeExclusionStr; } - let tableHeader = `Extension|Author (truncated)|Version + const tableHeader = `Extension|Author (truncated)|Version ---|---|---`; const table = this._data.enabledNonThemeExtesions.map(e => { return `${e.name}|${e.publisher.substr(0, 3)}|${e.version}`; @@ -227,7 +227,7 @@ Literal matches: ${this._data.filterResultCount}`; return `No fuzzy results`; } - let tableHeader = `Setting|Extension|Score + const tableHeader = `Setting|Extension|Score ---|---|---`; const table = this._data.actualSearchResults.map(setting => { return `${setting.key}|${setting.extensionId}|${String(setting.score).slice(0, 5)}`; diff --git a/src/vs/code/electron-browser/issue/issueReporterPage.ts b/src/vs/code/electron-browser/issue/issueReporterPage.ts index ae48c9560a..e8300eabd2 100644 --- a/src/vs/code/electron-browser/issue/issueReporterPage.ts +++ b/src/vs/code/electron-browser/issue/issueReporterPage.ts @@ -19,13 +19,11 @@ export default (): string => `
- - + -
${escape(localize('disableExtensionsLabelText', "Try to reproduce the problem after {0}. If the problem only reproduces when extensions are active, it is likely an issue with an extension.")) + @@ -126,4 +124,4 @@ export default (): string => `
-`; \ No newline at end of file +`; diff --git a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts index 5568d24392..d40780dcc3 100644 --- a/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-browser/processExplorer/processExplorerMain.ts @@ -8,7 +8,7 @@ import { listProcesses, ProcessItem } from 'vs/base/node/ps'; import { webFrame, ipcRenderer, clipboard } from 'electron'; import { repeat } from 'vs/base/common/strings'; import { totalmem } from 'os'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { localize } from 'vs/nls'; import { ProcessExplorerStyles, ProcessExplorerData } from 'vs/platform/issue/common/issue'; import * as browser from 'vs/base/browser/browser'; diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/contributions.ts b/src/vs/code/electron-browser/sharedProcess/contrib/contributions.ts deleted file mode 100644 index 1fba5f0294..0000000000 --- a/src/vs/code/electron-browser/sharedProcess/contrib/contributions.ts +++ /dev/null @@ -1,20 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner'; -import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; -import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner'; -import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner'; - -export function createSharedProcessContributions(service: IInstantiationService): IDisposable { - return combinedDisposable([ - service.createInstance(NodeCachedDataCleaner), - service.createInstance(LanguagePackCachedDataCleaner), - service.createInstance(StorageDataCleaner), - service.createInstance(LogsDataCleaner) - ]); -} diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts index aaa2c18a51..f62e2f8b61 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts @@ -3,11 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { IStringDictionary } from 'vs/base/common/collections'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ILogService } from 'vs/platform/log/common/log'; @@ -57,15 +57,15 @@ export class LanguagePackCachedDataCleaner { ? 1000 * 60 * 60 * 24 * 7 // roughly 1 week : 1000 * 60 * 60 * 24 * 30 * 3; // roughly 3 months try { - let installed: IStringDictionary = Object.create(null); + const installed: IStringDictionary = Object.create(null); const metaData: LanguagePackFile = JSON.parse(await pfs.readFile(path.join(this._environmentService.userDataPath, 'languagepacks.json'), 'utf8')); for (let locale of Object.keys(metaData)) { - let entry = metaData[locale]; + const entry = metaData[locale]; installed[`${entry.hash}.${locale}`] = true; } // Cleanup entries for language packs that aren't installed anymore const cacheDir = path.join(this._environmentService.userDataPath, 'clp'); - let exists = await pfs.exists(cacheDir); + const exists = await pfs.exists(cacheDir); if (!exists) { return; } diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts index a6216f81b9..dcf114a63e 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { join, dirname, basename } from 'path'; +import { join, dirname, basename } from 'vs/base/common/path'; import { readdir, rimraf } from 'vs/base/node/pfs'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts index ec649f9102..3d89b05b3f 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts @@ -3,12 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { basename, dirname, join } from 'path'; +import { basename, dirname, join } from 'vs/base/common/path'; import { onUnexpectedError } from 'vs/base/common/errors'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { readdir, rimraf, stat } from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; export class NodeCachedDataCleaner { diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts index fe40784a8b..0bbde5f5a1 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { join } from 'path'; +import { join } from 'vs/base/common/path'; import { readdir, readFile, rimraf } from 'vs/base/node/pfs'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index fceb246f7a..f4db2ab110 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -5,8 +5,8 @@ import * as fs from 'fs'; import * as platform from 'vs/base/common/platform'; -import product from 'vs/platform/node/product'; -import pkg from 'vs/platform/node/package'; +import product from 'vs/platform/product/node/product'; +import pkg from 'vs/platform/product/node/package'; import { serve, Server, connect } from 'vs/base/parts/ipc/node/ipc.net'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; @@ -28,9 +28,8 @@ import { TelemetryAppenderChannel } from 'vs/platform/telemetry/node/telemetryIp import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { AppInsightsAppender } from 'vs/platform/telemetry/node/appInsightsAppender'; import { IWindowsService, ActiveWindowManager } from 'vs/platform/windows/common/windows'; -import { WindowsChannelClient } from 'vs/platform/windows/node/windowsIpc'; +import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; import { ipcRenderer } from 'electron'; -import { createSharedProcessContributions } from 'vs/code/electron-browser/sharedProcess/contrib/contributions'; import { createSpdLogService } from 'vs/platform/log/node/spdlogService'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc'; @@ -39,10 +38,17 @@ import { ILocalizationsService } from 'vs/platform/localizations/common/localiza import { LocalizationsChannel } from 'vs/platform/localizations/node/localizationsIpc'; import { DialogChannelClient } from 'vs/platform/dialogs/node/dialogIpc'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { DownloadService } from 'vs/platform/download/node/downloadService'; import { IDownloadService } from 'vs/platform/download/common/download'; import { StaticRouter } from 'vs/base/parts/ipc/node/ipc'; +import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner'; +import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner'; +import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner'; +import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner'; +import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; export interface ISharedProcessConfiguration { readonly machineId: string; @@ -60,6 +66,20 @@ interface ISharedProcessInitData { const eventPrefix = 'monacoworkbench'; +class MainProcessService implements IMainProcessService { + constructor(private server: Server, private mainRouter: StaticRouter) { } + + _serviceBrand: ServiceIdentifier; + + getChannel(channelName: string): IChannel { + return this.server.getChannel(channelName, this.mainRouter); + } + + registerChannel(channelName: string, channel: IServerChannel): void { + this.server.registerChannel(channelName, channel); + } +} + function main(server: Server, initData: ISharedProcessInitData, configuration: ISharedProcessConfiguration): void { const services = new ServiceCollection(); @@ -86,8 +106,10 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I services.set(IRequestService, new SyncDescriptor(RequestService)); services.set(IDownloadService, new SyncDescriptor(DownloadService)); - const windowsChannel = server.getChannel('windows', mainRouter); - const windowsService = new WindowsChannelClient(windowsChannel); + const mainProcessService = new MainProcessService(server, mainRouter); + services.set(IMainProcessService, mainProcessService); + + const windowsService = new WindowsService(mainProcessService); services.set(IWindowsService, windowsService); const activeWindowManager = new ActiveWindowManager(windowsService); @@ -135,14 +157,21 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I const channel = new ExtensionManagementChannel(extensionManagementService, () => null); server.registerChannel('extensions', channel); - // clean up deprecated extensions - (extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions(); - const localizationsService = accessor.get(ILocalizationsService); const localizationsChannel = new LocalizationsChannel(localizationsService); server.registerChannel('localizations', localizationsChannel); - createSharedProcessContributions(instantiationService2); + disposables.push(combinedDisposable([ + // clean up deprecated extensions + toDisposable(() => (extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions()), + // update localizations cache + toDisposable(() => (localizationsService as LocalizationsService).update()), + // other cache clean ups + instantiationService2.createInstance(NodeCachedDataCleaner), + instantiationService2.createInstance(LanguagePackCachedDataCleaner), + instantiationService2.createInstance(StorageDataCleaner), + instantiationService2.createInstance(LogsDataCleaner) + ])); disposables.push(extensionManagementService as ExtensionManagementService); }); }); diff --git a/src/vs/code/electron-browser/workbench/workbench.html b/src/vs/code/electron-browser/workbench/workbench.html index 570138824d..35c39d3aca 100644 --- a/src/vs/code/electron-browser/workbench/workbench.html +++ b/src/vs/code/electron-browser/workbench/workbench.html @@ -7,7 +7,7 @@ --> - + diff --git a/src/vs/code/electron-browser/workbench/workbench.js b/src/vs/code/electron-browser/workbench/workbench.js index 60edca868c..49b43ba3fc 100644 --- a/src/vs/code/electron-browser/workbench/workbench.js +++ b/src/vs/code/electron-browser/workbench/workbench.js @@ -42,7 +42,7 @@ bootstrapWindow.load([ perf.mark('main/startup'); // @ts-ignore - return require('vs/workbench/electron-browser/main').startup(configuration); + return require('vs/workbench/electron-browser/main').main(configuration); }); }, { removeDeveloperKeybindingsAfterLoad: true, @@ -57,7 +57,6 @@ bootstrapWindow.load([ onNodeCachedData.push(arguments); }; } - }, beforeRequire: function () { perf.mark('willLoadWorkbenchMain'); @@ -71,10 +70,12 @@ function showPartsSplash(configuration) { perf.mark('willShowPartsSplash'); let data; - try { - data = JSON.parse(configuration.partsSplashData); - } catch (e) { - // ignore + if (typeof configuration.partsSplashPath === 'string') { + try { + data = JSON.parse(require('fs').readFileSync(configuration.partsSplashPath, 'utf8')); + } catch (e) { + // ignore + } } // high contrast mode has been turned on from the outside, e.g OS -> ignore stored colors and layouts diff --git a/src/vs/code/electron-browser/workbench/workbench.nodeless.html b/src/vs/code/electron-browser/workbench/workbench.nodeless.html new file mode 100644 index 0000000000..e37abfdf5a --- /dev/null +++ b/src/vs/code/electron-browser/workbench/workbench.nodeless.html @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/vs/code/electron-browser/workbench/workbench.nodeless.js b/src/vs/code/electron-browser/workbench/workbench.nodeless.js new file mode 100644 index 0000000000..fefcbc7a74 --- /dev/null +++ b/src/vs/code/electron-browser/workbench/workbench.nodeless.js @@ -0,0 +1,67 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check +'use strict'; + +(function () { + + function uriFromPath(_path) { + let pathName = _path.replace(/\\/g, '/'); + if (pathName.length > 0 && pathName.charAt(0) !== '/') { + pathName = '/' + pathName; + } + + let uri; + if (navigator.userAgent.indexOf('Windows') >= 0 && pathName.startsWith('//')) { // specially handle Windows UNC paths + uri = encodeURI('file:' + pathName); + } else { + uri = encodeURI('file://' + pathName); + } + + return uri.replace(/#/g, '%23'); + } + + function parseURLQueryArgs() { + const search = window.location.search || ''; + + return search.split(/[?&]/) + .filter(function (param) { return !!param; }) + .map(function (param) { return param.split('='); }) + .filter(function (param) { return param.length === 2; }) + .reduce(function (r, param) { r[param[0]] = decodeURIComponent(param[1]); return r; }, {}); + } + + function loadScript(path, callback) { + let script = document.createElement('script'); + script.onload = callback; + script.async = true; + script.type = 'text/javascript'; + script.src = path; + document.head.appendChild(script); + } + + loadScript('../../../../../out/vs/loader.js', function () { + + const args = parseURLQueryArgs(); + const configuration = JSON.parse(args['config'] || '{}') || {}; + + // @ts-ignore + require.config({ + baseUrl: uriFromPath(configuration.appRoot) + '/out', + }); + + // @ts-ignore + require([ + 'vs/workbench/workbench.nodeless.main', + 'vs/nls!vs/workbench/workbench.nodeless.main', + 'vs/css!vs/workbench/workbench.nodeless.main' + ], function () { + + // @ts-ignore + require('vs/workbench/browser/nodeless.main').main().then(undefined, console.error); + }); + }); +})(); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 76552b605c..741515aa0c 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -6,7 +6,7 @@ import { app, ipcMain as ipc, systemPreferences, shell, Event, contentTracing, protocol, powerMonitor } from 'electron'; import { IProcessEnvironment, isWindows, isMacintosh } from 'vs/base/common/platform'; import { WindowsManager } from 'vs/code/electron-main/windows'; -import { IWindowsService, OpenContext, ActiveWindowManager } from 'vs/platform/windows/common/windows'; +import { IWindowsService, OpenContext, ActiveWindowManager, IURIToOpen } from 'vs/platform/windows/common/windows'; import { WindowsChannel } from 'vs/platform/windows/node/windowsIpc'; import { WindowsService } from 'vs/platform/windows/electron-main/windowsService'; import { ILifecycleService, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; @@ -16,7 +16,6 @@ import { UpdateChannel } from 'vs/platform/update/node/updateIpc'; import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron-main'; import { Server, connect, Client } from 'vs/base/parts/ipc/node/ipc.net'; import { SharedProcess } from 'vs/code/electron-main/sharedProcess'; -import { Mutex } from 'windows-mutex'; import { LaunchService, LaunchChannel, ILaunchService } from 'vs/platform/launch/electron-main/launchService'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -33,14 +32,14 @@ import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; import { getDelayedChannel, StaticRouter } from 'vs/base/parts/ipc/node/ipc'; -import product from 'vs/platform/node/product'; -import pkg from 'vs/platform/node/package'; +import product from 'vs/platform/product/node/product'; +import pkg from 'vs/platform/product/node/package'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { ConfigurationService } from 'vs/platform/configuration/node/configurationService'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { IHistoryMainService } from 'vs/platform/history/common/history'; -import { isUndefinedOrNull } from 'vs/base/common/types'; +import { isUndefinedOrNull, withUndefinedAsNull } from 'vs/base/common/types'; import { KeyboardLayoutMonitor } from 'vs/code/electron-main/keyboard'; import { URI } from 'vs/base/common/uri'; import { WorkspacesChannel } from 'vs/platform/workspaces/node/workspacesIpc'; @@ -56,7 +55,7 @@ import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc'; import * as errors from 'vs/base/common/errors'; import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener'; import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver'; -import { connectRemoteAgentManagement, RemoteAgentConnectionContext } from 'vs/platform/remote/node/remoteAgentConnection'; +import { connectRemoteAgentManagement } from 'vs/platform/remote/node/remoteAgentConnection'; import { IMenubarService } from 'vs/platform/menubar/common/menubar'; import { MenubarService } from 'vs/platform/menubar/electron-main/menubarService'; import { MenubarChannel } from 'vs/platform/menubar/node/menubarIpc'; @@ -64,8 +63,8 @@ import { hasArgs } from 'vs/platform/environment/node/argv'; import { RunOnceScheduler } from 'vs/base/common/async'; import { registerContextMenuListener } from 'vs/base/parts/contextmenu/electron-main/contextmenu'; import { storeBackgroundColor } from 'vs/code/electron-main/theme'; -import { nativeSep, join } from 'vs/base/common/paths'; import { homedir } from 'os'; +import { join, sep } from 'vs/base/common/path'; import { localize } from 'vs/nls'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { REMOTE_FILE_SYSTEM_CHANNEL_NAME } from 'vs/platform/remote/node/remoteAgentFileSystemChannel'; @@ -73,13 +72,13 @@ import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityReso import { SnapUpdateService } from 'vs/platform/update/electron-main/updateService.snap'; import { IStorageMainService, StorageMainService } from 'vs/platform/storage/node/storageMainService'; import { GlobalStorageDatabaseChannel } from 'vs/platform/storage/node/storageIpc'; -import { generateUuid } from 'vs/base/common/uuid'; import { startsWith } from 'vs/base/common/strings'; import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService'; import { IBackupMainService } from 'vs/platform/backup/common/backup'; import { HistoryMainService } from 'vs/platform/history/electron-main/historyMainService'; import { URLService } from 'vs/platform/url/common/urlService'; import { WorkspacesMainService } from 'vs/platform/workspaces/electron-main/workspacesMainService'; +import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; export class CodeApplication extends Disposable { @@ -93,8 +92,8 @@ export class CodeApplication extends Disposable { private sharedProcessClient: Promise; constructor( - private mainIpcServer: Server, - private userEnv: IProcessEnvironment, + private readonly mainIpcServer: Server, + private readonly userEnv: IProcessEnvironment, @IInstantiationService private readonly instantiationService: IInstantiationService, @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @@ -155,7 +154,7 @@ export class CodeApplication extends Disposable { const srcUri = URI.parse(source).fsPath.toLowerCase(); const rootUri = URI.file(this.environmentService.appRoot).fsPath.toLowerCase(); - return startsWith(srcUri, rootUri + nativeSep); + return startsWith(srcUri, rootUri + sep); }; // Ensure defaults @@ -188,14 +187,14 @@ export class CodeApplication extends Disposable { }); }); - let macOpenFileURIs: URI[] = []; + let macOpenFileURIs: IURIToOpen[] = []; let runningTimeout: any = null; app.on('open-file', (event: Event, path: string) => { this.logService.trace('App#open-file: ', path); event.preventDefault(); // Keep in array because more might come! - macOpenFileURIs.push(URI.file(path)); + macOpenFileURIs.push({ uri: URI.file(path) }); // Clear previous handler if any if (runningTimeout !== null) { @@ -461,6 +460,7 @@ export class CodeApplication extends Disposable { const appInstantiationService = this.instantiationService.createChild(services); + // Init services that require it return appInstantiationService.invokeFunction(accessor => Promise.all([ this.initStorageService(accessor), this.initBackupService(accessor) @@ -473,35 +473,8 @@ export class CodeApplication extends Disposable { // Ensure to close storage on shutdown this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close())); - // Initialize storage service - return storageMainService.initialize().then(undefined, error => { - errors.onUnexpectedError(error); - this.logService.error(error); - }).then(() => { + return Promise.resolve(); - // Apply global telemetry values as part of the initialization - // These are global across all windows and thereby should be - // written from the main process once. - - const telemetryInstanceId = 'telemetry.instanceId'; - const instanceId = storageMainService.get(telemetryInstanceId, null); - if (instanceId === null) { - storageMainService.store(telemetryInstanceId, generateUuid()); - } - - const telemetryFirstSessionDate = 'telemetry.firstSessionDate'; - const firstSessionDate = storageMainService.get(telemetryFirstSessionDate, null); - if (firstSessionDate === null) { - storageMainService.store(telemetryFirstSessionDate, new Date().toUTCString()); - } - - const telemetryCurrentSessionDate = 'telemetry.currentSessionDate'; - const telemetryLastSessionDate = 'telemetry.lastSessionDate'; - const lastSessionDate = storageMainService.get(telemetryCurrentSessionDate, null); // previous session date was the "current" one at that time - const currentSessionDate = new Date().toUTCString(); // current session date is "now" - storageMainService.store(telemetryLastSessionDate, lastSessionDate); - storageMainService.store(telemetryCurrentSessionDate, currentSessionDate); - }); } private initBackupService(accessor: ServicesAccessor): Promise { @@ -545,7 +518,7 @@ export class CodeApplication extends Disposable { this.electronIpcServer.registerChannel('url', urlChannel); const storageMainService = accessor.get(IStorageMainService); - const storageChannel = this._register(new GlobalStorageDatabaseChannel(storageMainService as StorageMainService)); + const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService as StorageMainService)); this.electronIpcServer.registerChannel('storage', storageChannel); // Log level management @@ -584,13 +557,13 @@ export class CodeApplication extends Disposable { }); } - // Register the multiple URL handker + // Register the multiple URL handler urlService.registerHandler(multiplexURLHandler); // Watch Electron URLs and forward them to the UrlService const args = this.environmentService.args; const urls = args['open-url'] ? args._urls : []; - const urlListener = new ElectronURLListener(urls, urlService, this.windowsMainService); + const urlListener = new ElectronURLListener(urls || [], urlService, this.windowsMainService); this._register(urlListener); this.windowsMainService.ready(this.userEnv); @@ -607,7 +580,7 @@ export class CodeApplication extends Disposable { } if (macOpenFiles && macOpenFiles.length && !hasCliArgs && !hasFolderURIs && !hasFileURIs) { - return this.windowsMainService.open({ context: OpenContext.DOCK, cli: args, urisToOpen: macOpenFiles.map(file => URI.file(file)), initialStartup: true }); // mac: open-file event received on startup + return this.windowsMainService.open({ context: OpenContext.DOCK, cli: args, urisToOpen: macOpenFiles.map(file => ({ uri: URI.file(file) })), initialStartup: true }); // mac: open-file event received on startup } return this.windowsMainService.open({ context, cli: args, forceNewWindow: args['new-window'] || (!hasCliArgs && args['unity-launch']), diffMode: args.diff, initialStartup: true }); // default: read paths from cli @@ -617,13 +590,12 @@ export class CodeApplication extends Disposable { const windowsMainService = accessor.get(IWindowsMainService); const historyMainService = accessor.get(IHistoryMainService); - let windowsMutex: Mutex | null = null; if (isWindows) { // Setup Windows mutex try { const Mutex = (require.__$__nodeRequire('windows-mutex') as any).Mutex; - windowsMutex = new Mutex(product.win32MutexName); + const windowsMutex = new Mutex(product.win32MutexName); this._register(toDisposable(() => windowsMutex.release())); } catch (e) { if (!this.environmentService.isBuilt) { @@ -677,17 +649,17 @@ export class CodeApplication extends Disposable { const isBuilt = this.environmentService.isBuilt; class ActiveConnection { - private _authority: string; - private _client: Promise>; - private _disposeRunner: RunOnceScheduler; + private readonly _authority: string; + private readonly _client: Promise>; + private readonly _disposeRunner: RunOnceScheduler; constructor(authority: string, host: string, port: number) { this._authority = authority; this._client = connectRemoteAgentManagement(authority, host, port, `main`, isBuilt); - this._disposeRunner = new RunOnceScheduler(() => this._dispose(), 5000); + this._disposeRunner = new RunOnceScheduler(() => this.dispose(), 5000); } - private _dispose(): void { + dispose(): void { this._disposeRunner.dispose(); connectionPool.delete(this._authority); this._client.then((connection) => { @@ -695,7 +667,7 @@ export class CodeApplication extends Disposable { }); } - public getClient(): Promise> { + getClient(): Promise> { this._disposeRunner.schedule(); return this._client; } @@ -703,42 +675,49 @@ export class CodeApplication extends Disposable { const resolvedAuthorities = new Map(); ipc.on('vscode:remoteAuthorityResolved', (event: any, data: ResolvedAuthority) => { + this.logService.info('Received resolved authority', data.authority); resolvedAuthorities.set(data.authority, data); + // Make sure to close and remove any existing connections + if (connectionPool.has(data.authority)) { + connectionPool.get(data.authority)!.dispose(); + } }); const resolveAuthority = (authority: string): ResolvedAuthority | null => { + this.logService.info('Resolving authority', authority); if (authority.indexOf('+') >= 0) { if (resolvedAuthorities.has(authority)) { - return resolvedAuthorities.get(authority); + return withUndefinedAsNull(resolvedAuthorities.get(authority)); } + this.logService.info('Didnot find resolved authority for', authority); return null; } else { const [host, strPort] = authority.split(':'); const port = parseInt(strPort, 10); - return { authority, host, port, syncExtensions: false }; + return { authority, host, port }; } }; protocol.registerBufferProtocol(REMOTE_HOST_SCHEME, async (request, callback) => { if (request.method !== 'GET') { - return callback(null); + return callback(undefined); } const uri = URI.parse(request.url); - let activeConnection: ActiveConnection = null; + let activeConnection: ActiveConnection | undefined; if (connectionPool.has(uri.authority)) { activeConnection = connectionPool.get(uri.authority); } else { - let resolvedAuthority = resolveAuthority(uri.authority); + const resolvedAuthority = resolveAuthority(uri.authority); if (!resolvedAuthority) { - callback(null); + callback(undefined); return; } activeConnection = new ActiveConnection(uri.authority, resolvedAuthority.host, resolvedAuthority.port); connectionPool.set(uri.authority, activeConnection); } try { - const rawClient = await activeConnection.getClient(); + const rawClient = await activeConnection!.getClient(); if (connectionPool.has(uri.authority)) { // not disposed in the meantime const channel = rawClient.getChannel(REMOTE_FILE_SYSTEM_CHANNEL_NAME); @@ -746,11 +725,11 @@ export class CodeApplication extends Disposable { const fileContents = await channel.call('readFile', [uri]); callback(Buffer.from(fileContents)); } else { - callback(null); + callback(undefined); } } catch (err) { errors.onUnexpectedError(err); - callback(null); + callback(undefined); } }); } diff --git a/src/vs/code/electron-main/logUploader.ts b/src/vs/code/electron-main/logUploader.ts index 38aa609cd0..811718d4ab 100644 --- a/src/vs/code/electron-main/logUploader.ts +++ b/src/vs/code/electron-main/logUploader.ts @@ -6,10 +6,10 @@ import * as os from 'os'; import * as cp from 'child_process'; import * as fs from 'fs'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { localize } from 'vs/nls'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { IRequestService } from 'vs/platform/request/node/request'; import { IRequestContext } from 'vs/base/node/request'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 35539b034e..65bfdbf5c3 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -7,15 +7,16 @@ import 'vs/code/code.main'; import { app, dialog } from 'electron'; import { assign } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; -import product from 'vs/platform/node/product'; -import { parseMainProcessArgv } from 'vs/platform/environment/node/argvHelper'; +import product from 'vs/platform/product/node/product'; +import { parseMainProcessArgv, createWaitMarkerFile } from 'vs/platform/environment/node/argvHelper'; +import { addArg } from 'vs/platform/environment/node/argv'; import { mkdirp } from 'vs/base/node/pfs'; import { validatePaths } from 'vs/code/node/paths'; import { LifecycleService, ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { Server, serve, connect } from 'vs/base/parts/ipc/node/ipc.net'; import { LaunchChannelClient } from 'vs/platform/launch/electron-main/launchService'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { InstantiationService } from 'vs/platform/instantiation/node/instantiationService'; +import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ILogService, ConsoleLogMainService, MultiplexLogService, getLogLevel } from 'vs/platform/log/common/log'; @@ -36,7 +37,6 @@ import { IDiagnosticsService, DiagnosticsService } from 'vs/platform/diagnostics import { BufferLogService } from 'vs/platform/log/common/bufferLog'; import { uploadLogs } from 'vs/code/electron-main/logUploader'; import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; -import { createWaitMarkerFile } from 'vs/code/node/wait'; class ExpectedError extends Error { readonly isExpected = true; @@ -114,7 +114,7 @@ function setupIPC(accessor: ServicesAccessor): Promise { client => { // Tests from CLI require to be the only instance currently - if (environmentService.extensionTestsPath && !environmentService.debugExtensionHost.break) { + if (environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break) { const msg = 'Running extension tests from the command line is currently only supported if no other instance of Code is running.'; logService.error(msg); client.dispose(); @@ -158,7 +158,7 @@ function setupIPC(accessor: ServicesAccessor): Promise { logService.trace('Sending env to running instance...'); return allowSetForegroundWindow(service) - .then(() => service.start(environmentService.args, process.env)) + .then(() => service.start(environmentService.args, process.env as platform.IProcessEnvironment)) .then(() => client.dispose()) .then(() => { @@ -216,7 +216,7 @@ function handleStartupDataDirError(environmentService: IEnvironmentService, erro if (error.code === 'EACCES' || error.code === 'EPERM') { showStartupWarningDialog( localize('startupDataDirError', "Unable to write program user data."), - localize('startupDataDirErrorDetail', "Please make sure the directory {0} is writeable.", environmentService.userDataPath) + localize('startupDataDirErrorDetail', "Please make sure the directories {0} and {1} are writeable.", environmentService.userDataPath, environmentService.extensionsPath) ); } } @@ -316,14 +316,14 @@ function createServices(args: ParsedArgs, bufferLogService: BufferLogService): I function initServices(environmentService: IEnvironmentService, stateService: StateService): Promise { // Ensure paths for environment service exist - const environmentServiceInitialization = Promise.all([ + const environmentServiceInitialization = Promise.all([ environmentService.extensionsPath, environmentService.nodeCachedDataDir, environmentService.logsPath, environmentService.globalStorageHome, environmentService.workspaceStorageHome, environmentService.backupHome - ].map(path => path && mkdirp(path))); + ].map((path): undefined | Promise => path ? mkdirp(path) : undefined)); // State service const stateServiceInitialization = stateService.init(); @@ -359,7 +359,7 @@ function main(): void { if (args.wait && !args.waitMarkerFilePath) { createWaitMarkerFile(args.verbose).then(waitMarkerFilePath => { if (waitMarkerFilePath) { - process.argv.push('--waitMarkerFilePath', waitMarkerFilePath); + addArg(process.argv, '--waitMarkerFilePath', waitMarkerFilePath); args.waitMarkerFilePath = waitMarkerFilePath; } diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 243c0e4fd6..13207f5fc6 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as objects from 'vs/base/common/objects'; import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; @@ -13,7 +13,7 @@ import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/ import { ILogService } from 'vs/platform/log/common/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { parseArgs } from 'vs/platform/environment/node/argv'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { IWindowSettings, MenuBarVisibility, IWindowConfiguration, ReadyState, IRunActionInWindowRequest, getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; @@ -24,8 +24,8 @@ import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import * as perf from 'vs/base/common/performance'; import { resolveMarketplaceHeaders } from 'vs/platform/extensionManagement/node/extensionGalleryService'; import { getBackgroundColor } from 'vs/code/electron-main/theme'; -import { IStorageMainService } from 'vs/platform/storage/node/storageMainService'; import { RunOnceScheduler } from 'vs/base/common/async'; +import { withNullAsUndefined } from 'vs/base/common/types'; export interface IWindowCreationOptions { state: IWindowState; @@ -70,14 +70,16 @@ export class CodeWindow extends Disposable implements ICodeWindow { private currentMenuBarVisibility: MenuBarVisibility; private representedFilename: string; - private whenReadyCallbacks: { (window: ICodeWindow): void }[]; + private readonly whenReadyCallbacks: { (window: ICodeWindow): void }[]; private currentConfig: IWindowConfiguration; - private pendingLoadConfig: IWindowConfiguration; + private pendingLoadConfig?: IWindowConfiguration; private marketplaceHeadersPromise: Promise; - private touchBarGroups: Electron.TouchBarSegmentedControl[]; + private readonly touchBarGroups: Electron.TouchBarSegmentedControl[]; + + private nodeless: boolean; constructor( config: IWindowCreationOptions, @@ -87,7 +89,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { @IStateService private readonly stateService: IStateService, @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @IBackupMainService private readonly backupMainService: IBackupMainService, - @IStorageMainService private readonly storageMainService: IStorageMainService ) { super(); @@ -96,6 +97,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { this._readyState = ReadyState.NONE; this.whenReadyCallbacks = []; + this.nodeless = !!(environmentService.args.nodeless && !environmentService.isBuilt); + // create browser window this.createBrowserWindow(config); @@ -125,7 +128,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { height: this.windowState.height, x: this.windowState.x, y: this.windowState.y, - backgroundColor: getBackgroundColor(this.stateService), + backgroundColor: this.nodeless ? undefined : getBackgroundColor(this.stateService), minWidth: CodeWindow.MIN_WIDTH, minHeight: CodeWindow.MIN_HEIGHT, show: !isFullscreenOrMaximized, @@ -135,10 +138,14 @@ export class CodeWindow extends Disposable implements ICodeWindow { // want to enforce that Code stays in the foreground. This triggers a disable_hidden_ // flag that Electron provides via patch: // https://github.com/electron/libchromiumcontent/blob/master/patches/common/chromium/disable_hidden.patch - 'backgroundThrottling': false + backgroundThrottling: false } }; + if (this.nodeless) { + options.webPreferences!.nodeIntegration = false; // simulate Electron 5 behaviour + } + if (isLinux) { options.icon = path.join(this.environmentService.appRoot, 'resources/linux/code.png'); // Windows and Mac are better off using the embedded icon(s) } @@ -193,6 +200,10 @@ export class CodeWindow extends Disposable implements ICodeWindow { } } + if (this.nodeless) { + this._win.webContents.toggleDevTools(); + } + this._lastFocusTime = Date.now(); // since we show directly, we need to set the last focus time too } @@ -208,7 +219,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { return !!this.config.extensionTestsPath; } - get extensionDevelopmentPath(): string { + get extensionDevelopmentPath(): string | undefined { return this.config.extensionDevelopmentPath; } @@ -256,19 +267,19 @@ export class CodeWindow extends Disposable implements ICodeWindow { return this._lastFocusTime; } - get backupPath(): string { + get backupPath(): string | undefined { return this.currentConfig ? this.currentConfig.backupPath : undefined; } - get openedWorkspace(): IWorkspaceIdentifier { + get openedWorkspace(): IWorkspaceIdentifier | undefined { return this.currentConfig ? this.currentConfig.workspace : undefined; } - get openedFolderUri(): URI { + get openedFolderUri(): URI | undefined { return this.currentConfig ? this.currentConfig.folderUri : undefined; } - get remoteAuthority(): string { + get remoteAuthority(): string | undefined { return this.currentConfig ? this.currentConfig.remoteAuthority : undefined; } @@ -305,7 +316,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { const urls = ['https://marketplace.visualstudio.com/*', 'https://*.vsassets.io/*']; this._win.webContents.session.webRequest.onBeforeSendHeaders({ urls }, (details: any, cb: any) => { this.marketplaceHeadersPromise.then(headers => { - cb({ cancel: false, requestHeaders: objects.assign(details.requestHeaders, headers) }); + const requestHeaders = objects.assign(details.requestHeaders, headers); + if (!this.configurationService.getValue('extensions.disableExperimentalAzureSearch')) { + requestHeaders['Cookie'] = `${requestHeaders['Cookie'] ? requestHeaders['Cookie'] + ';' : ''}EnableExternalSearchForVSCode=true`; + } + cb({ cancel: false, requestHeaders }); }); }); } @@ -313,7 +328,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { private registerListeners(): void { // Prevent loading of svgs - this._win.webContents.session.webRequest.onBeforeRequest(null, (details, callback) => { + this._win.webContents.session.webRequest.onBeforeRequest(null!, (details, callback) => { if (details.url.indexOf('.svg') > 0) { const uri = URI.parse(details.url); if (uri && !uri.scheme.match(/file/i) && (uri.path as any).endsWith('.svg')) { @@ -324,7 +339,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { return callback({}); }); - this._win.webContents.session.webRequest.onHeadersReceived(null, (details: any, callback: any) => { + this._win.webContents.session.webRequest.onHeadersReceived(null!, (details: any, callback: any) => { const contentType: string[] = (details.responseHeaders['content-type'] || details.responseHeaders['Content-Type']) as any; if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) { return callback({ cancel: true }); @@ -341,7 +356,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { if (this.pendingLoadConfig) { this.currentConfig = this.pendingLoadConfig; - this.pendingLoadConfig = null; + this.pendingLoadConfig = undefined; } // To prevent flashing, we set the window visible after the page has finished to load but before Code is loaded @@ -392,7 +407,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { } // Window (Un)Maximize - this._win.on('maximize', e => { + this._win.on('maximize', (e: Event) => { if (this.currentConfig) { this.currentConfig.maximized = true; } @@ -400,7 +415,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { app.emit('browser-window-maximize', e, this._win); }); - this._win.on('unmaximize', e => { + this._win.on('unmaximize', (e: Event) => { if (this.currentConfig) { this.currentConfig.maximized = false; } @@ -522,7 +537,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { // Make window visible if it did not open in N seconds because this indicates an error // Only do this when running out of sources and not when running tests - if (!this.environmentService.isBuilt && !this.environmentService.extensionTestsPath) { + if (!this.environmentService.isBuilt && !this.environmentService.extensionTestsLocationURI) { this.showTimeoutHandle = setTimeout(() => { if (this._win && !this._win.isVisible() && !this._win.isMinimized()) { this._win.show(); @@ -548,8 +563,8 @@ export class CodeWindow extends Disposable implements ICodeWindow { // in extension development mode. These options are all development related. if (this.isExtensionDevelopmentHost && cli) { configuration.verbose = cli.verbose; - configuration.debugPluginHost = cli.debugPluginHost; - configuration.debugBrkPluginHost = cli.debugBrkPluginHost; + configuration['inspect-extensions'] = cli['inspect-extensions']; + configuration['inspect-brk-extensions'] = cli['inspect-brk-extensions']; configuration.debugId = cli.debugId; configuration['extensions-dir'] = cli['extensions-dir']; } @@ -593,7 +608,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { windowConfiguration.perfEntries = perf.exportEntries(); // Parts splash - windowConfiguration.partsSplashData = this.storageMainService.get('parts-splash-data', undefined); + windowConfiguration.partsSplashPath = path.join(this.environmentService.userDataPath, 'rapid_render.json'); // Config (combination of process.argv and window configuration) const environment = parseArgs(process.argv); @@ -623,6 +638,10 @@ export class CodeWindow extends Disposable implements ICodeWindow { } private doGetUrl(config: object): string { + if (this.nodeless) { + return `${require.toUrl('vs/code/electron-browser/workbench/workbench.nodeless.html')}?config=${encodeURIComponent(JSON.stringify(config))}`; + } + return `${require.toUrl('vs/code/electron-browser/workbench/workbench.html')}?config=${encodeURIComponent(JSON.stringify(config))}`; } @@ -688,7 +707,7 @@ export class CodeWindow extends Disposable implements ICodeWindow { private restoreWindowState(state?: IWindowState): IWindowState { if (state) { try { - state = this.validateWindowState(state); + state = withNullAsUndefined(this.validateWindowState(state)); } catch (err) { this.logService.warn(`Unexpected error validating window state: ${err}\n${err.stack}`); // somehow display API can be picky about the state to validate } @@ -706,7 +725,11 @@ export class CodeWindow extends Disposable implements ICodeWindow { return null; } - if ([state.x, state.y, state.width, state.height].some(n => typeof n !== 'number')) { + if (typeof state.x !== 'number' + || typeof state.y !== 'number' + || typeof state.width !== 'number' + || typeof state.height !== 'number' + ) { return null; } @@ -1045,6 +1068,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { clearTimeout(this.showTimeoutHandle); } - this._win = null; // Important to dereference the window object to allow for GC + this._win = null!; // Important to dereference the window object to allow for GC } } diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 81a6eb7f06..9f417ca575 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { basename, normalize, join, dirname } from 'path'; import * as fs from 'fs'; +import { basename, normalize, join, dirname } from 'vs/base/common/path'; import { localize } from 'vs/nls'; import * as arrays from 'vs/base/common/arrays'; import { assign, mixin, equals } from 'vs/base/common/objects'; @@ -14,57 +14,54 @@ import { IStateService } from 'vs/platform/state/common/state'; import { CodeWindow, defaultWindowState } from 'vs/code/electron-main/window'; import { hasArgs, asArray } from 'vs/platform/environment/node/argv'; import { ipcMain as ipc, screen, BrowserWindow, dialog, systemPreferences } from 'electron'; -import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/paths'; -import { ILifecycleService, UnloadReason, IWindowUnloadEvent, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; +import { parseLineAndColumnAware } from 'vs/code/node/paths'; +import { ILifecycleService, UnloadReason, LifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions } from 'vs/platform/windows/common/windows'; +import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions, IURIToOpen, URIType, OpenDialogOptions } from 'vs/platform/windows/common/windows'; import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderUri } from 'vs/code/node/windowsFinder'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow, IWindowState as ISingleWindowState, WindowMode } from 'vs/platform/windows/electron-main/windows'; -import { IHistoryMainService } from 'vs/platform/history/common/history'; -import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; -import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IHistoryMainService, IRecent } from 'vs/platform/history/common/history'; +import { IProcessEnvironment, isMacintosh, isWindows } from 'vs/base/common/platform'; +import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_FILTER, isSingleFolderWorkspaceIdentifier, hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Schemas } from 'vs/base/common/network'; import { normalizeNFC } from 'vs/base/common/normalization'; import { URI } from 'vs/base/common/uri'; -import { Queue, timeout } from 'vs/base/common/async'; +import { Queue } from 'vs/base/common/async'; import { exists } from 'vs/base/node/pfs'; -import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename } from 'vs/base/common/resources'; -import { endsWith } from 'vs/base/common/strings'; +import { getComparisonKey, isEqual, normalizePath, basename as resourcesBasename, originalFSPath, hasTrailingPathSeparator, removeTrailingPathSeparator } from 'vs/base/common/resources'; import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; +import { restoreWindowsState, WindowsStateStorageData, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage'; +import { getWorkspaceIdentifier } from 'vs/platform/workspaces/electron-main/workspacesMainService'; const enum WindowError { UNRESPONSIVE = 1, CRASHED = 2 } -interface INewWindowState extends ISingleWindowState { - hasDefaultState?: boolean; -} - -interface IWindowState { +export interface IWindowState { workspace?: IWorkspaceIdentifier; folderUri?: URI; - backupPath: string; + backupPath?: string; remoteAuthority?: string; uiState: ISingleWindowState; } -interface IBackwardCompatibleWindowState extends IWindowState { - folderPath?: string; -} - -interface IWindowsState { +export interface IWindowsState { lastActiveWindow?: IWindowState; lastPluginDevelopmentHostWindow?: IWindowState; openedWindows: IWindowState[]; } +interface INewWindowState extends ISingleWindowState { + hasDefaultState?: boolean; +} + type RestoreWindowsSetting = 'all' | 'folders' | 'one' | 'none'; interface IOpenBrowserWindowOptions { @@ -74,7 +71,7 @@ interface IOpenBrowserWindowOptions { workspace?: IWorkspaceIdentifier; folderUri?: URI; - remoteAuthority: string; + remoteAuthority?: string; initialStartup?: boolean; @@ -118,6 +115,47 @@ interface IPathToOpen extends IPath { // indicator to create the file path in the Code instance createFilePath?: boolean; + + // optional label for the recent history + label?: string; +} + +function isFolderPathToOpen(path: IPathToOpen): path is IFolderPathToOpen { + return !!path.folderUri; +} + +interface IFolderPathToOpen { + + // the folder path for a Code instance to open + folderUri: URI; + + // the backup path for a Code instance to use + backupPath?: string; + + // the remote authority for the Code instance to open. Undefined if not remote. + remoteAuthority?: string; + + // optional label for the recent history + label?: string; +} + +function isWorkspacePathToOpen(path: IPathToOpen): path is IWorkspacePathToOpen { + return !!path.workspace; +} + +interface IWorkspacePathToOpen { + + // the workspace for a Code instance to open + workspace: IWorkspaceIdentifier; + + // the backup path for a Code instance to use + backupPath?: string; + + // the remote authority for the Code instance to open. Undefined if not remote. + remoteAuthority?: string; + + // optional label for the recent history + label?: string; } export class WindowsManager implements IWindowsMainService { @@ -130,11 +168,11 @@ export class WindowsManager implements IWindowsMainService { private initialUserEnv: IProcessEnvironment; - private windowsState: IWindowsState; - private lastClosedWindowState: IWindowState; + private readonly windowsState: IWindowsState; + private lastClosedWindowState?: IWindowState; - private dialogs: Dialogs; - private workspacesManager: WorkspacesManager; + private readonly dialogs: Dialogs; + private readonly workspacesManager: WorkspacesManager; private _onWindowReady = new Emitter(); onWindowReady: CommonEvent = this._onWindowReady.event; @@ -161,37 +199,15 @@ export class WindowsManager implements IWindowsMainService { @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { - this.windowsState = this.getWindowsState(); + const windowsStateStoreData = this.stateService.getItem(WindowsManager.windowsStateStorageKey); + + this.windowsState = restoreWindowsState(windowsStateStoreData); if (!Array.isArray(this.windowsState.openedWindows)) { this.windowsState.openedWindows = []; } this.dialogs = new Dialogs(environmentService, telemetryService, stateService, this); - this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, environmentService, historyMainService, this); - } - - private getWindowsState(): IWindowsState { - const windowsState = this.stateService.getItem(WindowsManager.windowsStateStorageKey) || { openedWindows: [] }; - if (windowsState.lastActiveWindow) { - windowsState.lastActiveWindow = this.revive(windowsState.lastActiveWindow); - } - if (windowsState.lastPluginDevelopmentHostWindow) { - windowsState.lastPluginDevelopmentHostWindow = this.revive(windowsState.lastPluginDevelopmentHostWindow); - } - if (windowsState.openedWindows) { - windowsState.openedWindows = windowsState.openedWindows.map(windowState => this.revive(windowState)); - } - return windowsState; - } - - private revive(windowState: IWindowState): IWindowState { - if (windowState.folderUri) { - windowState.folderUri = URI.revive(windowState.folderUri); - } - if ((windowState).folderPath) { - windowState.folderUri = URI.file((windowState).folderPath); - } - return windowState; + this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, this); } ready(initialUserEnv: IProcessEnvironment): void { @@ -227,7 +243,6 @@ export class WindowsManager implements IWindowsMainService { } // Handle various lifecycle events around windows - this.lifecycleService.onBeforeWindowUnload(e => this.onBeforeWindowUnload(e)); this.lifecycleService.onBeforeWindowClose(window => this.onBeforeWindowClose(window)); this.lifecycleService.onBeforeShutdown(() => this.onBeforeShutdown()); this.onWindowsCountChanged(e => { @@ -312,7 +327,7 @@ export class WindowsManager implements IWindowsMainService { } // Persist - this.stateService.setItem(WindowsManager.windowsStateStorageKey, currentWindowsState); + this.stateService.setItem(WindowsManager.windowsStateStorageKey, getWindowsStateStoreData(currentWindowsState)); } // See note on #onBeforeShutdown() for details how these events are flowing @@ -362,20 +377,26 @@ export class WindowsManager implements IWindowsMainService { this.logService.trace('windowsManager#open'); openConfig = this.validateOpenConfig(openConfig); - let pathsToOpen = this.getPathsToOpen(openConfig); + const pathsToOpen = this.getPathsToOpen(openConfig); - // When run with --add, take the folders that are to be opened as - // folders that should be added to the currently active window. - let foldersToAdd: URI[] = []; - if (openConfig.addMode) { - foldersToAdd = pathsToOpen.filter(path => !!path.folderUri).map(path => path.folderUri); - pathsToOpen = pathsToOpen.filter(path => !path.folderUri); - } - - // collect all file inputs - let fileInputs: IFileInputs = undefined; + const foldersToAdd: IFolderPathToOpen[] = []; + const foldersToOpen: IFolderPathToOpen[] = []; + const workspacesToOpen: IWorkspacePathToOpen[] = []; + const emptyToRestore: IEmptyWindowBackupInfo[] = []; // empty windows with backupPath + let emptyToOpen: number = 0; + let fileInputs: IFileInputs | undefined; // collect all file inputs for (const path of pathsToOpen) { - if (path.fileUri) { + if (isFolderPathToOpen(path)) { + if (openConfig.addMode) { + // When run with --add, take the folders that are to be opened as + // folders that should be added to the currently active window. + foldersToAdd.push(path); + } else { + foldersToOpen.push(path); + } + } else if (isWorkspacePathToOpen(path)) { + workspacesToOpen.push(path); + } else if (path.fileUri) { if (!fileInputs) { fileInputs = { filesToCreate: [], filesToOpen: [], filesToDiff: [], remoteAuthority: path.remoteAuthority }; } @@ -384,6 +405,10 @@ export class WindowsManager implements IWindowsMainService { } else { fileInputs.filesToCreate.push(path); } + } else if (path.backupPath) { + emptyToRestore.push({ backupFolder: basename(path.backupPath), remoteAuthority: path.remoteAuthority }); + } else { + emptyToOpen++; } } @@ -400,53 +425,39 @@ export class WindowsManager implements IWindowsMainService { fileInputs.filesToWait = { paths: [...fileInputs.filesToDiff, ...fileInputs.filesToOpen, ...fileInputs.filesToCreate], waitMarkerFilePath: openConfig.cli.waitMarkerFilePath }; } - // - // These are windows to open to show workspaces - // - const workspacesToOpen = arrays.distinct(pathsToOpen.filter(win => !!win.workspace).map(win => win.workspace), workspace => workspace.id); // prevent duplicates - - // - // These are windows to open to show either folders or files (including diffing files or creating them) - // - const foldersToOpen = arrays.distinct(pathsToOpen.filter(win => win.folderUri && !win.fileUri).map(win => win.folderUri), folder => getComparisonKey(folder)); // prevent duplicates - // // These are windows to restore because of hot-exit or from previous session (only performed once on startup!) // let foldersToRestore: URI[] = []; - let workspacesToRestore: IWorkspaceIdentifier[] = []; - let emptyToRestore: IEmptyWindowBackupInfo[] = []; + let workspacesToRestore: IWorkspacePathToOpen[] = []; if (openConfig.initialStartup && !openConfig.cli.extensionDevelopmentPath && !openConfig.cli['disable-restore-windows']) { - foldersToRestore = this.backupMainService.getFolderBackupPaths(); + let foldersToRestore = this.backupMainService.getFolderBackupPaths(); + foldersToAdd.push(...foldersToRestore.map(f => ({ folderUri: f, remoteAuhority: getRemoteAuthority(f), isRestored: true }))); - workspacesToRestore = this.backupMainService.getWorkspaceBackups(); // collect from workspaces with hot-exit backups - workspacesToRestore.push(...this.workspacesMainService.getUntitledWorkspacesSync()); // collect from previous window session + // collect from workspaces with hot-exit backups and from previous window session + workspacesToRestore = [...this.backupMainService.getWorkspaceBackups(), ...this.workspacesMainService.getUntitledWorkspacesSync()]; + workspacesToOpen.push(...workspacesToRestore); - emptyToRestore = this.backupMainService.getEmptyWindowBackupPaths(); - emptyToRestore.push(...pathsToOpen.filter(w => !w.workspace && !w.folderUri && w.backupPath).map(w => ({ backupFolder: basename(w.backupPath), remoteAuthority: w.remoteAuthority }))); // add empty windows with backupPath - emptyToRestore = arrays.distinct(emptyToRestore, info => info.backupFolder); // prevent duplicates + emptyToRestore.push(...this.backupMainService.getEmptyWindowBackupPaths()); + } else { + emptyToRestore.length = 0; } - // - // These are empty windows to open - // - const emptyToOpen = pathsToOpen.filter(win => !win.workspace && !win.folderUri && !win.fileUri && !win.backupPath).length; - // Open based on config - const usedWindows = this.doOpen(openConfig, workspacesToOpen, workspacesToRestore, foldersToOpen, foldersToRestore, emptyToRestore, emptyToOpen, fileInputs, foldersToAdd); + const usedWindows = this.doOpen(openConfig, workspacesToOpen, foldersToOpen, emptyToRestore, emptyToOpen, fileInputs, foldersToAdd); // Make sure to pass focus to the most relevant of the windows if we open multiple if (usedWindows.length > 1) { - let focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !hasArgs(openConfig.cli._) && !hasArgs(openConfig.cli['file-uri']) && !hasArgs(openConfig.cli['folder-uri']) && !(openConfig.urisToOpen && openConfig.urisToOpen.length); + const focusLastActive = this.windowsState.lastActiveWindow && !openConfig.forceEmpty && !hasArgs(openConfig.cli._) && !hasArgs(openConfig.cli['file-uri']) && !hasArgs(openConfig.cli['folder-uri']) && !(openConfig.urisToOpen && openConfig.urisToOpen.length); let focusLastOpened = true; let focusLastWindow = true; // 1.) focus last active window if we are not instructed to open any paths if (focusLastActive) { - const lastActiveWindw = usedWindows.filter(w => w.backupPath === this.windowsState.lastActiveWindow.backupPath); - if (lastActiveWindw.length) { - lastActiveWindw[0].focus(); + const lastActiveWindow = usedWindows.filter(w => w.backupPath === this.windowsState.lastActiveWindow!.backupPath); + if (lastActiveWindow.length) { + lastActiveWindow[0].focus(); focusLastOpened = false; focusLastWindow = false; } @@ -457,9 +468,9 @@ export class WindowsManager implements IWindowsMainService { for (let i = usedWindows.length - 1; i >= 0; i--) { const usedWindow = usedWindows[i]; if ( - (usedWindow.openedWorkspace && workspacesToRestore.some(workspace => workspace.id === usedWindow.openedWorkspace.id)) || // skip over restored workspace - (usedWindow.openedFolderUri && foldersToRestore.some(folder => isEqual(folder, usedWindow.openedFolderUri))) || // skip over restored folder - (usedWindow.backupPath && emptyToRestore.some(empty => empty.backupFolder === basename(usedWindow.backupPath))) // skip over restored empty window + (usedWindow.openedWorkspace && workspacesToRestore.some(workspace => workspace.workspace.id === usedWindow.openedWorkspace!.id)) || // skip over restored workspace + (usedWindow.openedFolderUri && foldersToRestore.some(uri => isEqual(uri, usedWindow.openedFolderUri))) || // skip over restored folder + (usedWindow.backupPath && emptyToRestore.some(empty => empty.backupFolder === basename(usedWindow.backupPath!))) // skip over restored empty window ) { continue; } @@ -478,28 +489,25 @@ export class WindowsManager implements IWindowsMainService { // Remember in recent document list (unless this opens for extension development) // Also do not add paths when files are opened for diffing, only if opened individually - if (!usedWindows.some(w => w.isExtensionDevelopmentHost) && !openConfig.diffMode) { - const recentlyOpenedWorkspaces: Array = []; - const recentlyOpenedFiles: URI[] = []; - - pathsToOpen.forEach(win => { - if (win.workspace || win.folderUri) { - recentlyOpenedWorkspaces.push(win.workspace || win.folderUri); - } else if (win.fileUri) { - recentlyOpenedFiles.push(win.fileUri); + if (!usedWindows.some(w => w.isExtensionDevelopmentHost) && !openConfig.diffMode && !this.environmentService.skipAddToRecentlyOpened) { + const recents: IRecent[] = []; + for (let pathToOpen of pathsToOpen) { + if (pathToOpen.workspace) { + recents.push({ label: pathToOpen.label, workspace: pathToOpen.workspace }); + } else if (pathToOpen.folderUri) { + recents.push({ label: pathToOpen.label, folderUri: pathToOpen.folderUri }); + } else if (pathToOpen.fileUri) { + recents.push({ label: pathToOpen.label, fileUri: pathToOpen.fileUri }); } - }); - - if (!this.environmentService.skipAddToRecentlyOpened) { - this.historyMainService.addRecentlyOpened(recentlyOpenedWorkspaces, recentlyOpenedFiles); } + this.historyMainService.addRecentlyOpened(recents); } // If we got started with --wait from the CLI, we need to signal to the outside when the window // used for the edit operation is closed or loaded to a different folder so that the waiting // process can continue. We do this by deleting the waitMarkerFilePath. if (openConfig.context === OpenContext.CLI && openConfig.cli.wait && openConfig.cli.waitMarkerFilePath && usedWindows.length === 1 && usedWindows[0]) { - this.waitForWindowCloseOrLoad(usedWindows[0].id).then(() => fs.unlink(openConfig.cli.waitMarkerFilePath, error => undefined)); + this.waitForWindowCloseOrLoad(usedWindows[0].id).then(() => fs.unlink(openConfig.cli.waitMarkerFilePath!, _error => undefined)); } return usedWindows; @@ -517,14 +525,12 @@ export class WindowsManager implements IWindowsMainService { private doOpen( openConfig: IOpenConfiguration, - workspacesToOpen: IWorkspaceIdentifier[], - workspacesToRestore: IWorkspaceIdentifier[], - foldersToOpen: URI[], - foldersToRestore: URI[], + workspacesToOpen: IWorkspacePathToOpen[], + foldersToOpen: IFolderPathToOpen[], emptyToRestore: IEmptyWindowBackupInfo[], emptyToOpen: number, fileInputs: IFileInputs | undefined, - foldersToAdd: URI[] + foldersToAdd: IFolderPathToOpen[] ) { const usedWindows: ICodeWindow[] = []; @@ -533,29 +539,28 @@ export class WindowsManager implements IWindowsMainService { // Handle folders to add by looking for the last active workspace (not on initial startup) if (!openConfig.initialStartup && foldersToAdd.length > 0) { - const authority = getRemoteAuthority(foldersToAdd[0]); + const authority = foldersToAdd[0].remoteAuthority; const lastActiveWindow = this.getLastActiveWindowForAuthority(authority); if (lastActiveWindow) { - usedWindows.push(this.doAddFoldersToExistingWindow(lastActiveWindow, foldersToAdd)); + usedWindows.push(this.doAddFoldersToExistingWindow(lastActiveWindow, foldersToAdd.map(f => f.folderUri))); } } // Handle files to open/diff or to create when we dont open a folder and we do not restore any folder/untitled from hot-exit - const potentialWindowsCount = foldersToOpen.length + foldersToRestore.length + workspacesToOpen.length + workspacesToRestore.length + emptyToRestore.length; + const potentialWindowsCount = foldersToOpen.length + workspacesToOpen.length + emptyToRestore.length; if (potentialWindowsCount === 0 && fileInputs) { // Find suitable window or folder path to open files in const fileToCheck = fileInputs.filesToOpen[0] || fileInputs.filesToCreate[0] || fileInputs.filesToDiff[0]; // only look at the windows with correct authority - const windows = WindowsManager.WINDOWS.filter(w => w.remoteAuthority === fileInputs.remoteAuthority); + const windows = WindowsManager.WINDOWS.filter(w => w.remoteAuthority === fileInputs!.remoteAuthority); - let bestWindowOrFolder = findBestWindowOrFolderForFile({ + const bestWindowOrFolder = findBestWindowOrFolderForFile({ windows, newWindow: openFilesInNewWindow, - reuseWindow: openConfig.forceReuseWindow, context: openConfig.context, fileUri: fileToCheck && fileToCheck.fileUri, - workspaceResolver: workspace => this.workspacesMainService.resolveWorkspaceSync(workspace.configPath) + localWorkspaceResolver: workspace => workspace.configPath.scheme === Schemas.file ? this.workspacesMainService.resolveLocalWorkspaceSync(workspace.configPath) : null }); // We found a window to open the files in @@ -563,12 +568,12 @@ export class WindowsManager implements IWindowsMainService { // Window is workspace if (bestWindowOrFolder.openedWorkspace) { - workspacesToOpen.push(bestWindowOrFolder.openedWorkspace); + workspacesToOpen.push({ workspace: bestWindowOrFolder.openedWorkspace, remoteAuthority: bestWindowOrFolder.remoteAuthority }); } // Window is single folder else if (bestWindowOrFolder.openedFolderUri) { - foldersToOpen.push(bestWindowOrFolder.openedFolderUri); + foldersToOpen.push({ folderUri: bestWindowOrFolder.openedFolderUri, remoteAuthority: bestWindowOrFolder.remoteAuthority }); } // Window is empty @@ -600,11 +605,11 @@ export class WindowsManager implements IWindowsMainService { } // Handle workspaces to open (instructed and to restore) - const allWorkspacesToOpen = arrays.distinct([...workspacesToRestore, ...workspacesToOpen], workspace => workspace.id); // prevent duplicates + const allWorkspacesToOpen = arrays.distinct(workspacesToOpen, workspace => workspace.workspace.id); // prevent duplicates if (allWorkspacesToOpen.length > 0) { // Check for existing instances - const windowsOnWorkspace = arrays.coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, workspaceToOpen))); + const windowsOnWorkspace = arrays.coalesce(allWorkspacesToOpen.map(workspaceToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, workspaceToOpen.workspace))); if (windowsOnWorkspace.length > 0) { const windowOnWorkspace = windowsOnWorkspace[0]; const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === windowOnWorkspace.remoteAuthority) ? fileInputs : undefined; @@ -622,14 +627,15 @@ export class WindowsManager implements IWindowsMainService { // Open remaining ones allWorkspacesToOpen.forEach(workspaceToOpen => { - if (windowsOnWorkspace.some(win => win.openedWorkspace.id === workspaceToOpen.id)) { + if (windowsOnWorkspace.some(win => win.openedWorkspace!.id === workspaceToOpen.workspace.id)) { return; // ignore folders that are already open } - const fileInputsForWindow = (fileInputs && !fileInputs.remoteAuthority) ? fileInputs : undefined; + const remoteAuthority = workspaceToOpen.remoteAuthority; + const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined; // Do open folder - usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { workspace: workspaceToOpen }, openFolderInNewWindow, fileInputsForWindow)); + usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, workspaceToOpen, openFolderInNewWindow, fileInputsForWindow)); // Reset these because we handled them if (fileInputsForWindow) { @@ -641,12 +647,12 @@ export class WindowsManager implements IWindowsMainService { } // Handle folders to open (instructed and to restore) - const allFoldersToOpen = arrays.distinct([...foldersToRestore, ...foldersToOpen], folder => getComparisonKey(folder)); // prevent duplicates + const allFoldersToOpen = arrays.distinct(foldersToOpen, folder => getComparisonKey(folder.folderUri)); // prevent duplicates if (allFoldersToOpen.length > 0) { // Check for existing instances - const windowsOnFolderPath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, folderToOpen))); + const windowsOnFolderPath = arrays.coalesce(allFoldersToOpen.map(folderToOpen => findWindowOnWorkspace(WindowsManager.WINDOWS, folderToOpen.folderUri))); if (windowsOnFolderPath.length > 0) { const windowOnFolderPath = windowsOnFolderPath[0]; const fileInputsForWindow = fileInputs && fileInputs.remoteAuthority === windowOnFolderPath.remoteAuthority ? fileInputs : undefined; @@ -665,15 +671,15 @@ export class WindowsManager implements IWindowsMainService { // Open remaining ones allFoldersToOpen.forEach(folderToOpen => { - if (windowsOnFolderPath.some(win => isEqual(win.openedFolderUri, folderToOpen))) { + if (windowsOnFolderPath.some(win => isEqual(win.openedFolderUri, folderToOpen.folderUri))) { return; // ignore folders that are already open } - const remoteAuthority = getRemoteAuthority(folderToOpen); + const remoteAuthority = folderToOpen.remoteAuthority; const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined; // Do open folder - usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, { folderUri: folderToOpen, remoteAuthority }, openFolderInNewWindow, fileInputsForWindow)); + usedWindows.push(this.doOpenFolderOrWorkspace(openConfig, folderToOpen, openFolderInNewWindow, fileInputsForWindow)); // Reset these because we handled them if (fileInputsForWindow) { @@ -685,8 +691,9 @@ export class WindowsManager implements IWindowsMainService { } // Handle empty to restore - if (emptyToRestore.length > 0) { - emptyToRestore.forEach(emptyWindowBackupInfo => { + const allEmptyToRestore = arrays.distinct(emptyToRestore, info => info.backupFolder); // prevent duplicates + if (allEmptyToRestore.length > 0) { + allEmptyToRestore.forEach(emptyWindowBackupInfo => { const remoteAuthority = emptyWindowBackupInfo.remoteAuthority; const fileInputsForWindow = (fileInputs && fileInputs.remoteAuthority === remoteAuthority) ? fileInputs : undefined; @@ -764,7 +771,7 @@ export class WindowsManager implements IWindowsMainService { return window; } - private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, forceNewWindow: boolean, fileInputs: IFileInputs, windowToUse?: ICodeWindow): ICodeWindow { + private doOpenFolderOrWorkspace(openConfig: IOpenConfiguration, folderOrWorkspace: IPathToOpen, forceNewWindow: boolean, fileInputs: IFileInputs | undefined, windowToUse?: ICodeWindow): ICodeWindow { if (!forceNewWindow && !windowToUse && typeof openConfig.contextWindowId === 'number') { windowToUse = this.getWindowById(openConfig.contextWindowId); // fix for https://github.com/Microsoft/vscode/issues/49587 } @@ -817,12 +824,15 @@ export class WindowsManager implements IWindowsMainService { // folders should be added to the existing window. if (!openConfig.addMode && isCommandLineOrAPICall) { const foldersToOpen = windowsToOpen.filter(path => !!path.folderUri); - if (foldersToOpen.length > 1 && foldersToOpen.every(f => f.folderUri.scheme === Schemas.file)) { - const workspace = this.workspacesMainService.createUntitledWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.folderUri }))); + if (foldersToOpen.length > 1) { + const remoteAuthority = foldersToOpen[0].remoteAuthority; + if (foldersToOpen.every(f => f.remoteAuthority === remoteAuthority)) { // only if all folder have the same authority + const workspace = this.workspacesMainService.createUntitledWorkspaceSync(foldersToOpen.map(folder => ({ uri: folder.folderUri! }))); - // Add workspace and remove folders thereby - windowsToOpen.push({ workspace, remoteAuthority: foldersToOpen[0].remoteAuthority }); - windowsToOpen = windowsToOpen.filter(path => !path.folderUri); + // Add workspace and remove folders thereby + windowsToOpen.push({ workspace, remoteAuthority }); + windowsToOpen = windowsToOpen.filter(path => !path.folderUri); + } } } @@ -832,25 +842,26 @@ export class WindowsManager implements IWindowsMainService { private doExtractPathsFromAPI(openConfig: IOpenConfiguration): IPathToOpen[] { const pathsToOpen: IPathToOpen[] = []; const cli = openConfig.cli; - let parseOptions: IPathParseOptions = { gotoLineMode: cli && cli.goto, forceOpenWorkspaceAsFile: openConfig.forceOpenWorkspaceAsFile }; - for (const pathToOpen of openConfig.urisToOpen) { + const parseOptions: IPathParseOptions = { gotoLineMode: cli && cli.goto, forceOpenWorkspaceAsFile: openConfig.forceOpenWorkspaceAsFile }; + for (const pathToOpen of openConfig.urisToOpen || []) { if (!pathToOpen) { continue; } - const path = this.parseUri(pathToOpen, openConfig.forceOpenWorkspaceAsFile, parseOptions); + const path = this.parseUri(pathToOpen.uri, pathToOpen.typeHint, parseOptions); if (path) { + path.label = pathToOpen.label; pathsToOpen.push(path); } else { // Warn about the invalid URI or path let message, detail; - if (pathToOpen.scheme === Schemas.file) { + if (pathToOpen.uri.scheme === Schemas.file) { message = localize('pathNotExistTitle', "Path does not exist"); - detail = localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", pathToOpen.fsPath); + detail = localize('pathNotExistDetail', "The path '{0}' does not seem to exist anymore on disk.", pathToOpen.uri.fsPath); } else { message = localize('uriInvalidTitle', "URI can not be opened"); - detail = localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", pathToOpen.toString()); + detail = localize('uriInvalidDetail', "The URI '{0}' is not valid and can not be opened.", pathToOpen.uri.toString()); } const options: Electron.MessageBoxOptions = { title: product.nameLong, @@ -874,7 +885,7 @@ export class WindowsManager implements IWindowsMainService { // folder uris const folderUris = asArray(cli['folder-uri']); for (let folderUri of folderUris) { - const path = this.parseUri(this.argToUri(folderUri), false, parseOptions); + const path = this.parseUri(this.argToUri(folderUri), 'folder', parseOptions); if (path) { pathsToOpen.push(path); } @@ -883,7 +894,7 @@ export class WindowsManager implements IWindowsMainService { // file uris const fileUris = asArray(cli['file-uri']); for (let fileUri of fileUris) { - const path = this.parseUri(this.argToUri(fileUri), true, parseOptions); + const path = this.parseUri(this.argToUri(fileUri), 'file'); if (path) { pathsToOpen.push(path); } @@ -932,12 +943,12 @@ export class WindowsManager implements IWindowsMainService { const windowsToOpen: IPathToOpen[] = []; for (const openedWindow of openedWindows) { if (openedWindow.workspace) { // Workspaces - const pathToOpen = this.parsePath(openedWindow.workspace.configPath, { remoteAuthority: openedWindow.remoteAuthority }); + const pathToOpen = this.parseUri(openedWindow.workspace.configPath, 'file', { remoteAuthority: openedWindow.remoteAuthority }); if (pathToOpen && pathToOpen.workspace) { windowsToOpen.push(pathToOpen); } } else if (openedWindow.folderUri) { // Folders - const pathToOpen = this.parseUri(openedWindow.folderUri, false, { remoteAuthority: openedWindow.remoteAuthority }); + const pathToOpen = this.parseUri(openedWindow.folderUri, 'folder', { remoteAuthority: openedWindow.remoteAuthority }); if (pathToOpen && pathToOpen.folderUri) { windowsToOpen.push(pathToOpen); } @@ -973,39 +984,50 @@ export class WindowsManager implements IWindowsMainService { return restoreWindows; } - private argToUri(arg: string): URI { + private argToUri(arg: string): URI | undefined { try { - let uri = URI.parse(arg); + const uri = URI.parse(arg); if (!uri.scheme) { this.logService.error(`Invalid URI input string, scheme missing: ${arg}`); - return null; + return undefined; } return uri; } catch (e) { this.logService.error(`Invalid URI input string: ${arg}, ${e.message}`); } - return null; + return undefined; } - private parseUri(uri: URI, isFile: boolean, options?: IPathParseOptions): IPathToOpen { + private parseUri(uri: URI | undefined, typeHint?: URIType, options: IPathParseOptions = {}): IPathToOpen | undefined { if (!uri || !uri.scheme) { - return null; + return undefined; } if (uri.scheme === Schemas.file) { return this.parsePath(uri.fsPath, options); } // open remote if either specified in the cli or if it's a remotehost URI - const remoteAuthority = options && options.remoteAuthority || getRemoteAuthority(uri); + const remoteAuthority = options.remoteAuthority || getRemoteAuthority(uri); // normalize URI uri = normalizePath(uri); - const uriPath = uri.path; - if (uriPath.length > 2 && endsWith(uriPath, '/')) { - uri = uri.with({ path: uriPath.substr(0, uriPath.length - 1) }); + + + // remove trailing slash + if (hasTrailingPathSeparator(uri)) { + uri = removeTrailingPathSeparator(uri); + if (!typeHint) { + typeHint = 'folder'; + } } - if (isFile) { - if (options && options.gotoLineMode) { + + // if there's no type hint + if (!typeHint && (hasWorkspaceFileExtension(uri.path) || options.gotoLineMode)) { + typeHint = 'file'; + } + + if (typeHint === 'file') { + if (options.gotoLineMode) { const parsedPath = parseLineAndColumnAware(uri.path); return { fileUri: uri.with({ path: parsedPath.path }), @@ -1014,6 +1036,12 @@ export class WindowsManager implements IWindowsMainService { remoteAuthority }; } + if (hasWorkspaceFileExtension(uri.path) && !options.forceOpenWorkspaceAsFile) { + return { + workspace: getWorkspaceIdentifier(uri), + remoteAuthority + }; + } return { fileUri: uri, remoteAuthority @@ -1025,21 +1053,23 @@ export class WindowsManager implements IWindowsMainService { }; } - private parsePath(anyPath: string, options?: IPathParseOptions): IPathToOpen { + private parsePath(anyPath: string, options: IPathParseOptions): IPathToOpen | undefined { if (!anyPath) { - return null; + return undefined; } - let parsedPath: IPathWithLineAndColumn; + let lineNumber, columnNumber: number | undefined; + + if (options.gotoLineMode) { + const parsedPath = parseLineAndColumnAware(anyPath); + lineNumber = parsedPath.line; + columnNumber = parsedPath.column; - const gotoLineMode = options && options.gotoLineMode; - if (options && options.gotoLineMode) { - parsedPath = parseLineAndColumnAware(anyPath); anyPath = parsedPath.path; } // open remote if either specified in the cli even if it is a local file. TODO: Future idea: resolve in remote host context. - const remoteAuthority = options && options.remoteAuthority; + const remoteAuthority = options.remoteAuthority; const candidate = normalize(anyPath); try { @@ -1048,18 +1078,18 @@ export class WindowsManager implements IWindowsMainService { if (candidateStat.isFile()) { // Workspace (unless disabled via flag) - if (!options || !options.forceOpenWorkspaceAsFile) { - const workspace = this.workspacesMainService.resolveWorkspaceSync(candidate); + if (!options.forceOpenWorkspaceAsFile) { + const workspace = this.workspacesMainService.resolveLocalWorkspaceSync(URI.file(candidate)); if (workspace) { - return { workspace: { id: workspace.id, configPath: workspace.configPath }, remoteAuthority }; + return { workspace: { id: workspace.id, configPath: workspace.configPath }, remoteAuthority: workspace.remoteAuthority }; } } // File return { fileUri: URI.file(candidate), - lineNumber: gotoLineMode ? parsedPath.line : undefined, - columnNumber: gotoLineMode ? parsedPath.column : undefined, + lineNumber, + columnNumber, remoteAuthority }; } @@ -1075,15 +1105,14 @@ export class WindowsManager implements IWindowsMainService { } } } catch (error) { - this.historyMainService.removeFromRecentlyOpened([candidate]); // since file does not seem to exist anymore, remove from recent - const fileUri = URI.file(candidate); + this.historyMainService.removeFromRecentlyOpened([fileUri]); // since file does not seem to exist anymore, remove from recent if (options && options.ignoreFileNotFound) { return { fileUri, createFilePath: true, remoteAuthority }; // assume this is a file that does not yet exist } } - return null; + return undefined; } private shouldOpenNewWindow(openConfig: IOpenConfiguration): { openFolderInNewWindow: boolean; openFilesInNewWindow: boolean; } { @@ -1099,9 +1128,9 @@ export class WindowsManager implements IWindowsMainService { } // let the user settings override how files are open in a new window or same window unless we are forced (not for extension development though) - let openFilesInNewWindow: boolean; + let openFilesInNewWindow: boolean = false; if (openConfig.forceNewWindow || openConfig.forceReuseWindow) { - openFilesInNewWindow = openConfig.forceNewWindow && !openConfig.forceReuseWindow; + openFilesInNewWindow = !!openConfig.forceNewWindow && !openConfig.forceReuseWindow; } else { // macOS: by default we open files in a new window if this is triggered via DOCK context @@ -1124,15 +1153,15 @@ export class WindowsManager implements IWindowsMainService { } } - return { openFolderInNewWindow, openFilesInNewWindow }; + return { openFolderInNewWindow: !!openFolderInNewWindow, openFilesInNewWindow }; } - openExtensionDevelopmentHostWindow(openConfig: IOpenConfiguration): void { + openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string, openConfig: IOpenConfiguration): void { // Reload an existing extension development host window on the same path // We currently do not allow more than one extension development window // on the same extension path. - const existingWindow = findWindowOnExtensionDevelopmentPath(WindowsManager.WINDOWS, openConfig.cli.extensionDevelopmentPath); + const existingWindow = findWindowOnExtensionDevelopmentPath(WindowsManager.WINDOWS, extensionDevelopmentPath); if (existingWindow) { this.reload(existingWindow, openConfig.cli); existingWindow.focus(); // make sure it gets focus and is restored @@ -1155,7 +1184,11 @@ export class WindowsManager implements IWindowsMainService { folderUris = [workspaceToOpen.toString()]; } } else { - cliArgs = [workspaceToOpen.configPath]; + if (workspaceToOpen.configPath.scheme === Schemas.file) { + cliArgs = [originalFSPath(workspaceToOpen.configPath)]; + } else { + fileUris = [workspaceToOpen.configPath.toString()]; + } } } } @@ -1177,6 +1210,11 @@ export class WindowsManager implements IWindowsMainService { openConfig.cli['folder-uri'] = folderUris; openConfig.cli['file-uri'] = fileUris; + const match = extensionDevelopmentPath.match(/^vscode-remote:\/\/([^\/]+)/); + if (match) { + openConfig.cli['remote'] = URI.parse(extensionDevelopmentPath).authority; + } + // Open it this.open({ context: openConfig.context, cli: openConfig.cli, forceNewWindow: true, forceEmpty: !cliArgs.length && !folderUris.length && !fileUris.length, userEnv: openConfig.userEnv }); } @@ -1212,7 +1250,7 @@ export class WindowsManager implements IWindowsMainService { configuration.backupPath = join(this.environmentService.backupHome, options.emptyWindowBackupInfo.backupFolder); } - let window: ICodeWindow; + let window: ICodeWindow | undefined; if (!options.forceNewWindow && !options.forceNewTabbedWindow) { window = options.windowToUse || this.getLastActiveWindow(); if (window) { @@ -1263,10 +1301,10 @@ export class WindowsManager implements IWindowsMainService { // Window Events window.win.webContents.removeAllListeners('devtools-reload-page'); // remove built in listener so we can handle this on our own - window.win.webContents.on('devtools-reload-page', () => this.reload(window)); - window.win.webContents.on('crashed', () => this.onWindowError(window, WindowError.CRASHED)); - window.win.on('unresponsive', () => this.onWindowError(window, WindowError.UNRESPONSIVE)); - window.win.on('closed', () => this.onWindowClosed(window)); + window.win.webContents.on('devtools-reload-page', () => this.reload(window!)); + window.win.webContents.on('crashed', () => this.onWindowError(window!, WindowError.CRASHED)); + window.win.on('unresponsive', () => this.onWindowError(window!, WindowError.UNRESPONSIVE)); + window.win.on('closed', () => this.onWindowClosed(window!)); // Lifecycle (this.lifecycleService as LifecycleService).registerWindow(window); @@ -1281,9 +1319,9 @@ export class WindowsManager implements IWindowsMainService { if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) { configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath; configuration.verbose = currentWindowConfig.verbose; - configuration.debugBrkPluginHost = currentWindowConfig.debugBrkPluginHost; + configuration['inspect-brk-extensions'] = currentWindowConfig['inspect-brk-extensions']; configuration.debugId = currentWindowConfig.debugId; - configuration.debugPluginHost = currentWindowConfig.debugPluginHost; + configuration['inspect-extensions'] = currentWindowConfig['inspect-extensions']; configuration['extensions-dir'] = currentWindowConfig['extensions-dir']; } } @@ -1294,7 +1332,7 @@ export class WindowsManager implements IWindowsMainService { if (window.isReady) { this.lifecycleService.unload(window, UnloadReason.LOAD).then(veto => { if (!veto) { - this.doOpenInBrowserWindow(window, configuration, options); + this.doOpenInBrowserWindow(window!, configuration, options); } }); } else { @@ -1309,12 +1347,12 @@ export class WindowsManager implements IWindowsMainService { // Register window for backups if (!configuration.extensionDevelopmentPath) { if (configuration.workspace) { - configuration.backupPath = this.backupMainService.registerWorkspaceBackupSync(configuration.workspace); + configuration.backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace: configuration.workspace, remoteAuthority: configuration.remoteAuthority }); } else if (configuration.folderUri) { configuration.backupPath = this.backupMainService.registerFolderBackupSync(configuration.folderUri); } else { const backupFolder = options.emptyWindowBackupInfo && options.emptyWindowBackupInfo.backupFolder; - configuration.backupPath = this.backupMainService.registerEmptyWindowBackupSync({ backupFolder, remoteAuthority: configuration.remoteAuthority }); + configuration.backupPath = this.backupMainService.registerEmptyWindowBackupSync(backupFolder, configuration.remoteAuthority); } } @@ -1337,8 +1375,9 @@ export class WindowsManager implements IWindowsMainService { } // Known Workspace - load from stored settings - if (configuration.workspace) { - const stateForWorkspace = this.windowsState.openedWindows.filter(o => o.workspace && o.workspace.id === configuration.workspace.id).map(o => o.uiState); + const workspace = configuration.workspace; + if (workspace) { + const stateForWorkspace = this.windowsState.openedWindows.filter(o => o.workspace && o.workspace.id === workspace.id).map(o => o.uiState); if (stateForWorkspace.length) { return stateForWorkspace[0]; } @@ -1372,7 +1411,7 @@ export class WindowsManager implements IWindowsMainService { // // We want the new window to open on the same display that the last active one is in - let displayToUse: Electron.Display; + let displayToUse: Electron.Display | undefined; const displays = screen.getAllDisplays(); // Single Display @@ -1404,8 +1443,8 @@ export class WindowsManager implements IWindowsMainService { // Note: important to use Math.round() because Electron does not seem to be too happy about // display coordinates that are not absolute numbers. let state = defaultWindowState() as INewWindowState; - state.x = Math.round(displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (state.width / 2)); - state.y = Math.round(displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (state.height / 2)); + state.x = Math.round(displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (state.width! / 2)); + state.y = Math.round(displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (state.height! / 2)); // Check for newWindowDimensions setting and adjust accordingly const windowConfig = this.configurationService.getValue('window'); @@ -1443,6 +1482,9 @@ export class WindowsManager implements IWindowsMainService { return state; } + state.x = typeof state.x === 'number' ? state.x : 0; + state.y = typeof state.y === 'number' ? state.y : 0; + const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds()); while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) { state.x += 30; @@ -1470,14 +1512,14 @@ export class WindowsManager implements IWindowsMainService { }); } - enterWorkspace(win: ICodeWindow, path: URI): Promise { - return this.workspacesManager.enterWorkspace(win, path).then(result => this.doEnterWorkspace(win, result)); + enterWorkspace(win: ICodeWindow, path: URI): Promise { + return this.workspacesManager.enterWorkspace(win, path).then(result => result ? this.doEnterWorkspace(win, result) : undefined); } private doEnterWorkspace(win: ICodeWindow, result: IEnterWorkspaceResult): IEnterWorkspaceResult { // Mark as recently opened - this.historyMainService.addRecentlyOpened([result.workspace], []); + this.historyMainService.addRecentlyOpened([{ workspace: result.workspace }]); // Trigger Eevent to indicate load of workspace into window this._onWindowReady.fire(win); @@ -1489,43 +1531,6 @@ export class WindowsManager implements IWindowsMainService { this.workspacesManager.pickWorkspaceAndOpen(options); } - private onBeforeWindowUnload(e: IWindowUnloadEvent): void { - const windowClosing = (e.reason === UnloadReason.CLOSE); - const windowLoading = (e.reason === UnloadReason.LOAD); - if (!windowClosing && !windowLoading) { - return; // only interested when window is closing or loading - } - - const workspace = e.window.openedWorkspace; - if (!workspace || !this.workspacesMainService.isUntitledWorkspace(workspace)) { - return; // only care about untitled workspaces to ask for saving - } - - if (e.window.config && !!e.window.config.extensionDevelopmentPath) { - // do not ask to save workspace when doing extension development - // but still delete it. - this.workspacesMainService.deleteUntitledWorkspaceSync(workspace); - return; - } - - if (windowClosing && !isMacintosh && this.getWindowCount() === 1) { - return; // Windows/Linux: quits when last window is closed, so do not ask then - } - - // Handle untitled workspaces with prompt as needed - e.veto(this.workspacesManager.promptToSaveUntitledWorkspace(this.getWindowById(e.window.id), workspace).then(veto => { - if (veto) { - return veto; - } - - // Bug in electron: somehow we need this timeout so that the window closes properly. That - // might be related to the fact that the untitled workspace prompt shows up async and this - // code can execute before the dialog is fully closed which then blocks the window from closing. - // Issue: https://github.com/Microsoft/vscode/issues/41989 - return timeout(0).then(() => veto); - })); - } - focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow { const lastActive = this.getLastActiveWindow(); if (lastActive) { @@ -1538,17 +1543,17 @@ export class WindowsManager implements IWindowsMainService { return this.open({ context, cli, forceEmpty: true })[0]; } - getLastActiveWindow(): ICodeWindow { + getLastActiveWindow(): ICodeWindow | undefined { return getLastActiveWindow(WindowsManager.WINDOWS); } - getLastActiveWindowForAuthority(remoteAuthority: string): ICodeWindow { + getLastActiveWindowForAuthority(remoteAuthority: string | undefined): ICodeWindow | undefined { return getLastActiveWindow(WindowsManager.WINDOWS.filter(w => w.remoteAuthority === remoteAuthority)); } openNewWindow(context: OpenContext, options?: INewWindowOptions): ICodeWindow[] { let cli = this.environmentService.args; - let remote = options && options.remoteAuthority || undefined; + const remote = options && options.remoteAuthority || undefined; if (cli && (cli.remote !== remote)) { cli = { ...cli, remote }; } @@ -1566,7 +1571,7 @@ export class WindowsManager implements IWindowsMainService { closeListener.dispose(); loadListener.dispose(); - resolve(null); + resolve(); } } @@ -1593,22 +1598,22 @@ export class WindowsManager implements IWindowsMainService { }); } - getFocusedWindow(): ICodeWindow { + getFocusedWindow(): ICodeWindow | undefined { const win = BrowserWindow.getFocusedWindow(); if (win) { return this.getWindowById(win.id); } - return null; + return undefined; } - getWindowById(windowId: number): ICodeWindow { + getWindowById(windowId: number): ICodeWindow | undefined { const res = WindowsManager.WINDOWS.filter(w => w.id === windowId); if (res && res.length === 1) { return res[0]; } - return null; + return undefined; } getWindows(): ICodeWindow[] { @@ -1719,9 +1724,8 @@ export class WindowsManager implements IWindowsMainService { internalOptions.pickFolders = pickFolders; internalOptions.pickFiles = pickFiles; - if (!internalOptions.dialogOptions) { - internalOptions.dialogOptions = Object.create(null); - } + const dialogOptions: OpenDialogOptions = internalOptions.dialogOptions || Object.create(null); + internalOptions.dialogOptions = dialogOptions; if (!internalOptions.dialogOptions.title) { if (pickFolders && pickFiles) { @@ -1786,14 +1790,14 @@ class Dialogs { private static readonly workingDirPickerStorageKey = 'pickerWorkingDir'; - private mapWindowToDialogQueue: Map>; - private noWindowDialogQueue: Queue; + private readonly mapWindowToDialogQueue: Map>; + private readonly noWindowDialogQueue: Queue; constructor( - private environmentService: IEnvironmentService, - private telemetryService: ITelemetryService, - private stateService: IStateService, - private windowsMainService: IWindowsMainService, + private readonly environmentService: IEnvironmentService, + private readonly telemetryService: ITelemetryService, + private readonly stateService: IStateService, + private readonly windowsMainService: IWindowsMainService, ) { this.mapWindowToDialogQueue = new Map>(); this.noWindowDialogQueue = new Queue(); @@ -1827,45 +1831,49 @@ class Dialogs { }); } - private getFileOrFolderUris(options: IInternalNativeOpenDialogOptions): Promise { + private getFileOrFolderUris(options: IInternalNativeOpenDialogOptions): Promise { // Ensure dialog options - if (!options.dialogOptions) { - options.dialogOptions = Object.create(null); - } + const dialogOptions = options.dialogOptions || Object.create(null); + options.dialogOptions = dialogOptions; // Ensure defaultPath - if (!options.dialogOptions.defaultPath) { - options.dialogOptions.defaultPath = this.stateService.getItem(Dialogs.workingDirPickerStorageKey); + if (!dialogOptions.defaultPath) { + dialogOptions.defaultPath = this.stateService.getItem(Dialogs.workingDirPickerStorageKey); } // Ensure properties if (typeof options.pickFiles === 'boolean' || typeof options.pickFolders === 'boolean') { - options.dialogOptions.properties = undefined; // let it override based on the booleans + dialogOptions.properties = undefined; // let it override based on the booleans if (options.pickFiles && options.pickFolders) { - options.dialogOptions.properties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory']; + dialogOptions.properties = ['multiSelections', 'openDirectory', 'openFile', 'createDirectory']; } } - if (!options.dialogOptions.properties) { - options.dialogOptions.properties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory']; + if (!dialogOptions.properties) { + dialogOptions.properties = ['multiSelections', options.pickFolders ? 'openDirectory' : 'openFile', 'createDirectory']; } if (isMacintosh) { - options.dialogOptions.properties.push('treatPackageAsDirectory'); // always drill into .app files + dialogOptions.properties.push('treatPackageAsDirectory'); // always drill into .app files } // Show Dialog - const focusedWindow = this.windowsMainService.getWindowById(options.windowId) || this.windowsMainService.getFocusedWindow(); + const focusedWindow = (typeof options.windowId === 'number' ? this.windowsMainService.getWindowById(options.windowId) : undefined) || this.windowsMainService.getFocusedWindow(); - return this.showOpenDialog(options.dialogOptions, focusedWindow).then(paths => { + return this.showOpenDialog(dialogOptions, focusedWindow).then(paths => { if (paths && paths.length > 0) { // Remember path in storage for next time this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0])); - return paths.map(path => URI.file(path)); + const result: IURIToOpen[] = []; + for (const path of paths) { + result.push({ uri: URI.file(path) }); + } + + return result; } return undefined; @@ -1889,7 +1897,7 @@ class Dialogs { showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): Promise { return this.getDialogQueue(window).queue(() => { return new Promise(resolve => { - dialog.showMessageBox(window ? window.win : undefined, options, (response: number, checkboxChecked: boolean) => { + dialog.showMessageBox(window ? window.win : undefined!, options, (response: number, checkboxChecked: boolean) => { resolve({ button: response, checkboxChecked }); }); }); @@ -1908,7 +1916,7 @@ class Dialogs { return this.getDialogQueue(window).queue(() => { return new Promise(resolve => { - dialog.showSaveDialog(window ? window.win : undefined, options, path => { + dialog.showSaveDialog(window ? window.win : undefined!, options, path => { resolve(normalizePath(path)); }); }); @@ -1940,7 +1948,7 @@ class Dialogs { // Show dialog and wrap as promise validatePathPromise.then(() => { - dialog.showOpenDialog(window ? window.win : undefined, options, paths => { + dialog.showOpenDialog(window ? window.win : undefined!, options, paths => { resolve(normalizePaths(paths)); }); }); @@ -1952,13 +1960,10 @@ class Dialogs { class WorkspacesManager { constructor( - private workspacesMainService: IWorkspacesMainService, - private backupMainService: IBackupMainService, - private environmentService: IEnvironmentService, - private historyMainService: IHistoryMainService, - private windowsMainService: IWindowsMainService, - ) { - } + private readonly workspacesMainService: IWorkspacesMainService, + private readonly backupMainService: IBackupMainService, + private readonly windowsMainService: IWindowsMainService, + ) { } enterWorkspace(window: ICodeWindow, path: URI): Promise { if (!window || !window.win || !window.isReady) { @@ -1969,7 +1974,7 @@ class WorkspacesManager { if (!isValid) { return null; // return early if the workspace is not valid } - const workspaceIdentifier = this.workspacesMainService.getWorkspaceIdentifier(path); + const workspaceIdentifier = getWorkspaceIdentifier(path); return this.doOpenWorkspace(window, workspaceIdentifier); }); @@ -1980,12 +1985,12 @@ class WorkspacesManager { return Promise.resolve(true); } - if (window.openedWorkspace && isEqual(URI.file(window.openedWorkspace.configPath), path)) { + if (window.openedWorkspace && isEqual(window.openedWorkspace.configPath, path)) { return Promise.resolve(false); // window is already opened on a workspace with that path } // Prevent overwriting a workspace that is currently opened in another window - if (findWindowOnWorkspace(this.windowsMainService.getWindows(), this.workspacesMainService.getWorkspaceIdentifier(path))) { + if (findWindowOnWorkspace(this.windowsMainService.getWindows(), getWorkspaceIdentifier(path))) { const options: Electron.MessageBoxOptions = { title: product.nameLong, type: 'info', @@ -2005,9 +2010,9 @@ class WorkspacesManager { window.focus(); // Register window for backups and migrate current backups over - let backupPath: string; + let backupPath: string | undefined; if (!window.config.extensionDevelopmentPath) { - backupPath = this.backupMainService.registerWorkspaceBackupSync(workspace, window.config.backupPath); + backupPath = this.backupMainService.registerWorkspaceBackupSync({ workspace, remoteAuthority: window.remoteAuthority }, window.config.backupPath); } // if the window was opened on an untitled workspace, delete it. @@ -2024,7 +2029,7 @@ class WorkspacesManager { } pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void { - const window = this.windowsMainService.getWindowById(options.windowId) || this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); + const window = (typeof options.windowId === 'number' ? this.windowsMainService.getWindowById(options.windowId) : undefined) || this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); this.windowsMainService.pickFileAndOpen({ windowId: window ? window.id : undefined, @@ -2040,92 +2045,4 @@ class WorkspacesManager { telemetryExtraData: options.telemetryExtraData }); } - - promptToSaveUntitledWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): Promise { - enum ConfirmResult { - SAVE, - DONT_SAVE, - CANCEL - } - - const save = { label: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")), result: ConfirmResult.SAVE }; - const dontSave = { label: mnemonicButtonLabel(localize({ key: 'doNotSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save")), result: ConfirmResult.DONT_SAVE }; - const cancel = { label: localize('cancel', "Cancel"), result: ConfirmResult.CANCEL }; - - const buttons: { label: string; result: ConfirmResult; }[] = []; - if (isWindows) { - buttons.push(save, dontSave, cancel); - } else if (isLinux) { - buttons.push(dontSave, cancel, save); - } else { - buttons.push(save, cancel, dontSave); - } - - const options: Electron.MessageBoxOptions = { - title: this.environmentService.appNameLong, - message: localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"), - detail: localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."), - noLink: true, - type: 'warning', - buttons: buttons.map(button => button.label), - cancelId: buttons.indexOf(cancel) - }; - - if (isLinux) { - options.defaultId = 2; - } - - return this.windowsMainService.showMessageBox(options, window).then(res => { - switch (buttons[res.button].result) { - - // Cancel: veto unload - case ConfirmResult.CANCEL: - return true; - - // Don't Save: delete workspace - case ConfirmResult.DONT_SAVE: - this.workspacesMainService.deleteUntitledWorkspaceSync(workspace); - return false; - - // Save: save workspace, but do not veto unload - case ConfirmResult.SAVE: { - return this.windowsMainService.showSaveDialog({ - buttonLabel: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")), - title: localize('saveWorkspace', "Save Workspace"), - filters: WORKSPACE_FILTER, - defaultPath: this.getUntitledWorkspaceSaveDialogDefaultPath(workspace) - }, window).then(target => { - if (target) { - return this.workspacesMainService.saveWorkspaceAs(workspace, target).then(savedWorkspace => { - this.historyMainService.addRecentlyOpened([savedWorkspace], []); - this.workspacesMainService.deleteUntitledWorkspaceSync(workspace); - return false; - }, () => false); - } - - return true; // keep veto if no target was provided - }); - } - } - }); - } - - private getUntitledWorkspaceSaveDialogDefaultPath(workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string { - if (workspace) { - if (isSingleFolderWorkspaceIdentifier(workspace)) { - return workspace.scheme === Schemas.file ? dirname(workspace.fsPath) : undefined; - } - - const resolvedWorkspace = this.workspacesMainService.resolveWorkspaceSync(workspace.configPath); - if (resolvedWorkspace && resolvedWorkspace.folders.length > 0) { - for (const folder of resolvedWorkspace.folders) { - if (folder.uri.scheme === Schemas.file) { - return dirname(folder.uri.fsPath); - } - } - } - } - - return undefined; - } } diff --git a/src/vs/code/electron-main/windowsStateStorage.ts b/src/vs/code/electron-main/windowsStateStorage.ts new file mode 100644 index 0000000000..646c42910c --- /dev/null +++ b/src/vs/code/electron-main/windowsStateStorage.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI, UriComponents } from 'vs/base/common/uri'; +import { IWindowState as IWindowUIState } from 'vs/platform/windows/electron-main/windows'; +import { IWindowState, IWindowsState } from 'vs/code/electron-main/windows'; + +export type WindowsStateStorageData = object; + +interface ISerializedWindowsState { + lastActiveWindow?: ISerializedWindowState; + lastPluginDevelopmentHostWindow?: ISerializedWindowState; + openedWindows: ISerializedWindowState[]; +} + +interface ISerializedWindowState { + workspaceIdentifier?: { id: string; configURIPath: string }; + folder?: string; + backupPath?: string; + remoteAuthority?: string; + uiState: IWindowUIState; + + // deprecated + folderUri?: UriComponents; + folderPath?: string; + workspace?: { id: string; configPath: string }; +} + +export function restoreWindowsState(data: WindowsStateStorageData | undefined): IWindowsState { + const result: IWindowsState = { openedWindows: [] }; + const windowsState = data as ISerializedWindowsState || { openedWindows: [] }; + + if (windowsState.lastActiveWindow) { + result.lastActiveWindow = restoreWindowState(windowsState.lastActiveWindow); + } + if (windowsState.lastPluginDevelopmentHostWindow) { + result.lastPluginDevelopmentHostWindow = restoreWindowState(windowsState.lastPluginDevelopmentHostWindow); + } + if (Array.isArray(windowsState.openedWindows)) { + result.openedWindows = windowsState.openedWindows.map(windowState => restoreWindowState(windowState)); + } + return result; +} + +function restoreWindowState(windowState: ISerializedWindowState): IWindowState { + const result: IWindowState = { uiState: windowState.uiState }; + if (windowState.backupPath) { + result.backupPath = windowState.backupPath; + } + if (windowState.remoteAuthority) { + result.remoteAuthority = windowState.remoteAuthority; + } + if (windowState.folder) { + result.folderUri = URI.parse(windowState.folder); + } else if (windowState.folderUri) { + result.folderUri = URI.revive(windowState.folderUri); + } else if (windowState.folderPath) { + result.folderUri = URI.file(windowState.folderPath); + } + if (windowState.workspaceIdentifier) { + result.workspace = { id: windowState.workspaceIdentifier.id, configPath: URI.parse(windowState.workspaceIdentifier.configURIPath) }; + } else if (windowState.workspace) { + result.workspace = { id: windowState.workspace.id, configPath: URI.file(windowState.workspace.configPath) }; + } + return result; +} + +export function getWindowsStateStoreData(windowsState: IWindowsState): WindowsStateStorageData { + return { + lastActiveWindow: windowsState.lastActiveWindow && serializeWindowState(windowsState.lastActiveWindow), + lastPluginDevelopmentHostWindow: windowsState.lastPluginDevelopmentHostWindow && serializeWindowState(windowsState.lastPluginDevelopmentHostWindow), + openedWindows: windowsState.openedWindows.map(ws => serializeWindowState(ws)) + }; +} + +function serializeWindowState(windowState: IWindowState): ISerializedWindowState { + return { + workspaceIdentifier: windowState.workspace && { id: windowState.workspace.id, configURIPath: windowState.workspace.configPath.toString() }, + folder: windowState.folderUri && windowState.folderUri.toString(), + backupPath: windowState.backupPath, + remoteAuthority: windowState.remoteAuthority, + uiState: windowState.uiState + }; +} diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 3b45472ae4..35df10f5e4 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -5,11 +5,12 @@ import { spawn, ChildProcess } from 'child_process'; import { assign } from 'vs/base/common/objects'; -import { buildHelpMessage, buildVersionMessage } from 'vs/platform/environment/node/argv'; +import { buildHelpMessage, buildVersionMessage, addArg } from 'vs/platform/environment/node/argv'; +import { parseCLIProcessArgv, createWaitMarkerFile } from 'vs/platform/environment/node/argvHelper'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; -import product from 'vs/platform/node/product'; -import pkg from 'vs/platform/node/package'; -import * as paths from 'path'; +import product from 'vs/platform/product/node/product'; +import pkg from 'vs/platform/product/node/package'; +import * as paths from 'vs/base/common/path'; import * as os from 'os'; import * as fs from 'fs'; import { whenDeleted } from 'vs/base/node/pfs'; @@ -19,8 +20,6 @@ import * as iconv from 'iconv-lite'; import { writeFileAndFlushSync } from 'vs/base/node/extfs'; import { isWindows } from 'vs/base/common/platform'; import { ProfilingSession, Target } from 'v8-inspect-profiler'; -import { createWaitMarkerFile } from 'vs/code/node/wait'; -import { parseCLIProcessArgv } from 'vs/platform/environment/node/argvHelper'; function shouldSpawnCliProcess(argv: ParsedArgs): boolean { return !!argv['install-source'] @@ -180,11 +179,11 @@ export async function main(argv: string[]): Promise { }); // Make sure to open tmp file - argv.push(stdinFilePath); + addArg(argv, stdinFilePath); // Enable --wait to get all data and ignore adding this to history - argv.push('--wait'); - argv.push('--skip-add-to-recently-opened'); + addArg(argv, '--wait'); + addArg(argv, '--skip-add-to-recently-opened'); args.wait = true; } @@ -232,7 +231,7 @@ export async function main(argv: string[]): Promise { if (args.wait) { waitMarkerFilePath = await createWaitMarkerFile(verbose); if (waitMarkerFilePath) { - argv.push('--waitMarkerFilePath', waitMarkerFilePath); + addArg(argv, '--waitMarkerFilePath', waitMarkerFilePath); } } @@ -252,11 +251,11 @@ export async function main(argv: string[]): Promise { const filenamePrefix = paths.join(os.homedir(), 'prof-' + Math.random().toString(16).slice(-4)); - argv.push(`--inspect-brk=${portMain}`); - argv.push(`--remote-debugging-port=${portRenderer}`); - argv.push(`--inspect-brk-extensions=${portExthost}`); - argv.push(`--prof-startup-prefix`, filenamePrefix); - argv.push(`--no-cached-data`); + addArg(argv, `--inspect-brk=${portMain}`); + addArg(argv, `--remote-debugging-port=${portRenderer}`); + addArg(argv, `--inspect-brk-extensions=${portExthost}`); + addArg(argv, `--prof-startup-prefix`, filenamePrefix); + addArg(argv, `--no-cached-data`); fs.writeFileSync(filenamePrefix, argv.slice(-6).join('|')); @@ -341,7 +340,7 @@ export async function main(argv: string[]): Promise { if (args['js-flags']) { const match = /max_old_space_size=(\d+)/g.exec(args['js-flags']); if (match && !args['max-memory']) { - argv.push(`--max-memory=${match[1]}`); + addArg(argv, `--max-memory=${match[1]}`); } } diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 7b65f611cd..6d325e6a70 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -4,19 +4,18 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import product from 'vs/platform/node/product'; -import pkg from 'vs/platform/node/package'; -import * as path from 'path'; +import product from 'vs/platform/product/node/product'; +import pkg from 'vs/platform/product/node/package'; +import * as path from 'vs/base/common/path'; import * as semver from 'semver'; -import { sequence } from 'vs/base/common/async'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { IExtensionManagementService, IExtensionGalleryService, IGalleryExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementService, IExtensionGalleryService, IGalleryExtension, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -38,7 +37,10 @@ import { isPromiseCanceledError } from 'vs/base/common/errors'; import { areSameExtensions, adoptToGalleryExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { URI } from 'vs/base/common/uri'; import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; -import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest, ExtensionType, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; +import { isUIExtension } from 'vs/platform/extensions/node/extensionsUtil'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; const notFound = (id: string) => localize('notFound', "Extension '{0}' not found.", id); const notInstalled = (id: string) => localize('notInstalled', "Extension '{0}' is not installed.", id); @@ -66,7 +68,10 @@ export function getIdAndVersion(id: string): [string, string | undefined] { export class Main { constructor( + private readonly remote: boolean, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService ) { } @@ -100,38 +105,50 @@ export class Main { } private async installExtensions(extensions: string[], force: boolean): Promise { - let failed: string[] = []; + const failed: string[] = []; + const installedExtensionsManifests: IExtensionManifest[] = []; for (const extension of extensions) { try { - await this.installExtension(extension, force); + const manifest = await this.installExtension(extension, force); + if (manifest) { + installedExtensionsManifests.push(manifest); + } } catch (err) { console.error(err.message || err.stack || err); failed.push(extension); } } + if (installedExtensionsManifests.some(manifest => isLanguagePackExtension(manifest))) { + await this.updateLocalizationsCache(); + } return failed.length ? Promise.reject(localize('installation failed', "Failed Installing Extensions: {0}", failed.join(', '))) : Promise.resolve(); } - private installExtension(extension: string, force: boolean): Promise { + private async installExtension(extension: string, force: boolean): Promise { if (/\.vsix$/i.test(extension)) { extension = path.isAbsolute(extension) ? extension : path.join(process.cwd(), extension); - return this.validate(extension, force) - .then(valid => { - if (valid) { - return this.extensionManagementService.install(URI.file(extension)).then(() => { - console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed!", getBaseLabel(extension))); - }, error => { - if (isPromiseCanceledError(error)) { - console.log(localize('cancelVsixInstall', "Cancelled installing Extension '{0}'.", getBaseLabel(extension))); - return null; - } else { - return Promise.reject(error); - } - }); + const manifest = await getManifest(extension); + if (this.remote && (!isLanguagePackExtension(manifest) && isUIExtension(manifest, [], this.configurationService))) { + console.log(localize('notSupportedUIExtension', "Can't install extension {0} since UI Extensions are not supported", getBaseLabel(extension))); + return null; + } + const valid = await this.validate(manifest, force); + + if (valid) { + return this.extensionManagementService.install(URI.file(extension)).then(id => { + console.log(localize('successVsixInstall', "Extension '{0}' was successfully installed!", getBaseLabel(extension))); + return manifest; + }, error => { + if (isPromiseCanceledError(error)) { + console.log(localize('cancelVsixInstall', "Cancelled installing Extension '{0}'.", getBaseLabel(extension))); + return null; + } else { + return Promise.reject(error); } - return null; }); + } + return null; } const [id, version] = getIdAndVersion(extension); @@ -148,38 +165,37 @@ export class Main { } return Promise.reject(err); }) - .then(extension => { + .then(async extension => { if (!extension) { return Promise.reject(new Error(`${notFound(version ? `${id}@${version}` : id)}\n${useId}`)); } + const manifest = await this.extensionGalleryService.getManifest(extension, CancellationToken.None); + if (this.remote && manifest && (!isLanguagePackExtension(manifest) && isUIExtension(manifest, [], this.configurationService))) { + console.log(localize('notSupportedUIExtension', "Can't install extension {0} since UI Extensions are not supported", extension.identifier.id)); + return null; + } + const [installedExtension] = installed.filter(e => areSameExtensions(e.identifier, { id })); if (installedExtension) { - if (extension.version !== installedExtension.manifest.version) { - if (version || force) { - console.log(localize('updateMessage', "Updating the Extension '{0}' to the version {1}", id, extension.version)); - return this.installFromGallery(id, extension); - } else { - console.log(localize('forceUpdate', "Extension '{0}' v{1} is already installed, but a newer version {2} is available in the marketplace. Use '--force' option to update to newer version.", id, installedExtension.manifest.version, extension.version)); - return Promise.resolve(null); - } - } else { + if (extension.version === installedExtension.manifest.version) { console.log(localize('alreadyInstalled', "Extension '{0}' is already installed.", version ? `${id}@${version}` : id)); return Promise.resolve(null); } + if (!version && !force) { + console.log(localize('forceUpdate', "Extension '{0}' v{1} is already installed, but a newer version {2} is available in the marketplace. Use '--force' option to update to newer version.", id, installedExtension.manifest.version, extension.version)); + return Promise.resolve(null); + } + console.log(localize('updateMessage', "Updating the Extension '{0}' to the version {1}", id, extension.version)); } else { console.log(localize('foundExtension', "Found '{0}' in the marketplace.", id)); - return this.installFromGallery(id, extension); } - + await this.installFromGallery(id, extension); + return manifest; })); } - - - private async validate(vsix: string, force: boolean): Promise { - const manifest = await getManifest(vsix); - + private async validate(manifest: IExtensionManifest, force: boolean): Promise { if (!manifest) { throw new Error('Invalid vsix'); } @@ -211,7 +227,7 @@ export class Main { } } - private uninstallExtension(extensions: string[]): Promise { + private async uninstallExtension(extensions: string[]): Promise { async function getExtensionId(extensionDescription: string): Promise { if (!/\.vsix$/i.test(extensionDescription)) { return extensionDescription; @@ -222,22 +238,29 @@ export class Main { return getId(manifest); } - return sequence(extensions.map(extension => () => { - return getExtensionId(extension).then(id => { - return this.extensionManagementService.getInstalled(ExtensionType.User).then(installed => { - const [extension] = installed.filter(e => areSameExtensions(e.identifier, { id })); + const uninstalledExtensions: ILocalExtension[] = []; + for (const extension of extensions) { + const id = await getExtensionId(extension); + const installed = await this.extensionManagementService.getInstalled(ExtensionType.User); + const [extensionToUninstall] = installed.filter(e => areSameExtensions(e.identifier, { id })); + if (!extensionToUninstall) { + return Promise.reject(new Error(`${notInstalled(id)}\n${useId}`)); + } + console.log(localize('uninstalling', "Uninstalling {0}...", id)); + await this.extensionManagementService.uninstall(extensionToUninstall, true); + uninstalledExtensions.push(extensionToUninstall); + console.log(localize('successUninstall', "Extension '{0}' was successfully uninstalled!", id)); + } - if (!extension) { - return Promise.reject(new Error(`${notInstalled(id)}\n${useId}`)); - } + if (uninstalledExtensions.some(e => isLanguagePackExtension(e.manifest))) { + await this.updateLocalizationsCache(); + } + } - console.log(localize('uninstalling', "Uninstalling {0}...", id)); - - return this.extensionManagementService.uninstall(extension, true) - .then(() => console.log(localize('successUninstall', "Extension '{0}' was successfully uninstalled!", id))); - }); - }); - })); + private async updateLocalizationsCache(): Promise { + const localizationService = this.instantiationService.createInstance(LocalizationsService); + await localizationService.update(); + localizationService.dispose(); } } @@ -290,7 +313,7 @@ export function main(argv: ParsedArgs): Promise { } const instantiationService2 = instantiationService.createChild(services); - const main = instantiationService2.createInstance(Main); + const main = instantiationService2.createInstance(Main, false); return main.run(argv).then(() => { // Dispose the AI adapter so that remaining data gets flushed. diff --git a/src/vs/code/node/paths.ts b/src/vs/code/node/paths.ts index bb0801d689..df64888dc2 100644 --- a/src/vs/code/node/paths.ts +++ b/src/vs/code/node/paths.ts @@ -3,10 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as arrays from 'vs/base/common/arrays'; import * as strings from 'vs/base/common/strings'; -import * as paths from 'vs/base/common/paths'; +import * as extpath from 'vs/base/common/extpath'; import * as platform from 'vs/base/common/platform'; import * as types from 'vs/base/common/types'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; @@ -48,7 +48,7 @@ function doValidatePaths(args: string[], gotoLineMode?: boolean): string[] { const sanitizedFilePath = sanitizeFilePath(pathCandidate, cwd); const basename = path.basename(sanitizedFilePath); - if (basename /* can be empty if code is opened on root */ && !paths.isValidBasename(basename)) { + if (basename /* can be empty if code is opened on root */ && !extpath.isValidBasename(basename)) { return null; // do not allow invalid file names } diff --git a/src/vs/code/node/shellEnv.ts b/src/vs/code/node/shellEnv.ts index 7ea59668f4..d3db137192 100644 --- a/src/vs/code/node/shellEnv.ts +++ b/src/vs/code/node/shellEnv.ts @@ -9,7 +9,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { isWindows } from 'vs/base/common/platform'; function getUnixShellEnvironment(): Promise { - const promise = new Promise((resolve, reject) => { + const promise = new Promise((resolve, reject) => { const runAsNode = process.env['ELECTRON_RUN_AS_NODE']; const noAttach = process.env['ELECTRON_NO_ATTACH_CONSOLE']; const mark = generateUuid().replace(/-/g, '').substr(0, 12); @@ -66,7 +66,7 @@ function getUnixShellEnvironment(): Promise { }); // swallow errors - return promise.then(undefined, () => ({})); + return promise.catch(() => ({})); } diff --git a/src/vs/code/node/wait.ts b/src/vs/code/node/wait.ts deleted file mode 100644 index 30bc4e85b6..0000000000 --- a/src/vs/code/node/wait.ts +++ /dev/null @@ -1,26 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { join } from 'path'; -import { tmpdir } from 'os'; -import { writeFile } from 'vs/base/node/pfs'; - -export function createWaitMarkerFile(verbose?: boolean): Promise { - const randomWaitMarkerPath = join(tmpdir(), Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10)); - - return writeFile(randomWaitMarkerPath, '').then(() => { - if (verbose) { - console.log(`Marker file for --wait created: ${randomWaitMarkerPath}`); - } - - return randomWaitMarkerPath; - }, error => { - if (verbose) { - console.error(`Failed to create marker file for --wait: ${error}`); - } - - return Promise.resolve(); - }); -} \ No newline at end of file diff --git a/src/vs/code/node/windowsFinder.ts b/src/vs/code/node/windowsFinder.ts index e8c5c57077..8a0bb30f95 100644 --- a/src/vs/code/node/windowsFinder.ts +++ b/src/vs/code/node/windowsFinder.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as platform from 'vs/base/common/platform'; -import * as paths from 'vs/base/common/paths'; +import * as extpath from 'vs/base/common/extpath'; import { OpenContext } from 'vs/platform/windows/common/windows'; import { IWorkspaceIdentifier, IResolvedWorkspace, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; @@ -21,32 +21,41 @@ export interface ISimpleWindow { export interface IBestWindowOrFolderOptions { windows: W[]; newWindow: boolean; - reuseWindow: boolean; context: OpenContext; fileUri?: URI; userHome?: string; codeSettingsFolder?: string; - workspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null; + localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null; } -export function findBestWindowOrFolderForFile({ windows, newWindow, reuseWindow, context, fileUri, workspaceResolver }: IBestWindowOrFolderOptions): W | null { +export function findBestWindowOrFolderForFile({ windows, newWindow, context, fileUri, localWorkspaceResolver: workspaceResolver }: IBestWindowOrFolderOptions): W | undefined { if (!newWindow && fileUri && (context === OpenContext.DESKTOP || context === OpenContext.CLI || context === OpenContext.DOCK)) { const windowOnFilePath = findWindowOnFilePath(windows, fileUri, workspaceResolver); if (windowOnFilePath) { return windowOnFilePath; } } - return !newWindow ? getLastActiveWindow(windows) : null; + return !newWindow ? getLastActiveWindow(windows) : undefined; } -function findWindowOnFilePath(windows: W[], fileUri: URI, workspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null): W | null { +function findWindowOnFilePath(windows: W[], fileUri: URI, localWorkspaceResolver: (workspace: IWorkspaceIdentifier) => IResolvedWorkspace | null): W | null { // First check for windows with workspaces that have a parent folder of the provided path opened - const workspaceWindows = windows.filter(window => !!window.openedWorkspace); - for (const window of workspaceWindows) { - const resolvedWorkspace = workspaceResolver(window.openedWorkspace!); - if (resolvedWorkspace && resolvedWorkspace.folders.some(folder => isEqualOrParent(fileUri, folder.uri))) { - return window; + for (const window of windows) { + const workspace = window.openedWorkspace; + if (workspace) { + const resolvedWorkspace = localWorkspaceResolver(workspace); + if (resolvedWorkspace) { + // workspace could be resolved: It's in the local file system + if (resolvedWorkspace.folders.some(folder => isEqualOrParent(fileUri, folder.uri))) { + return window; + } + } else { + // use the config path instead + if (isEqualOrParent(fileUri, workspace.configPath)) { + return window; + } + } } } @@ -59,7 +68,7 @@ function findWindowOnFilePath(windows: W[], fileUri: UR return null; } -export function getLastActiveWindow(windows: W[]): W { +export function getLastActiveWindow(windows: W[]): W | undefined { const lastFocusedDate = Math.max.apply(Math, windows.map(window => window.lastFocusTime)); return windows.filter(window => window.lastFocusTime === lastFocusedDate)[0]; @@ -89,20 +98,20 @@ export function findWindowOnWorkspace(windows: W[], wor export function findWindowOnExtensionDevelopmentPath(windows: W[], extensionDevelopmentPath: string): W | null { for (const window of windows) { // match on extension development path. The path can be a path or uri string, using paths.isEqual is not 100% correct but good enough - if (window.extensionDevelopmentPath && paths.isEqual(window.extensionDevelopmentPath, extensionDevelopmentPath, !platform.isLinux /* ignorecase */)) { + if (window.extensionDevelopmentPath && extpath.isEqual(window.extensionDevelopmentPath, extensionDevelopmentPath, !platform.isLinux /* ignorecase */)) { return window; } } return null; } -export function findWindowOnWorkspaceOrFolderUri(windows: W[], uri: URI): W | null { +export function findWindowOnWorkspaceOrFolderUri(windows: W[], uri: URI | undefined): W | null { if (!uri) { return null; } for (const window of windows) { // check for workspace config path - if (window.openedWorkspace && isEqual(URI.file(window.openedWorkspace.configPath), uri, !platform.isLinux /* ignorecase */)) { + if (window.openedWorkspace && isEqual(window.openedWorkspace.configPath, uri)) { return window; } diff --git a/src/vs/code/test/electron-main/windowsStateStorage.test.ts b/src/vs/code/test/electron-main/windowsStateStorage.test.ts new file mode 100644 index 0000000000..76ad487aa1 --- /dev/null +++ b/src/vs/code/test/electron-main/windowsStateStorage.test.ts @@ -0,0 +1,291 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as assert from 'assert'; +import * as os from 'os'; +import * as path from 'vs/base/common/path'; + +import { restoreWindowsState, getWindowsStateStoreData } from 'vs/code/electron-main/windowsStateStorage'; +import { IWindowState as IWindowUIState, WindowMode } from 'vs/platform/windows/electron-main/windows'; +import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { URI } from 'vs/base/common/uri'; +import { IWindowsState, IWindowState } from 'vs/code/electron-main/windows'; + +function getUIState(): IWindowUIState { + return { + x: 0, + y: 10, + width: 100, + height: 200, + mode: 0 + }; +} + +function toWorkspace(uri: URI): IWorkspaceIdentifier { + return { + id: '1234', + configPath: uri + }; +} +function assertEqualURI(u1: URI | undefined, u2: URI | undefined, message?: string): void { + assert.equal(u1 && u1.toString(), u2 && u2.toString(), message); +} + +function assertEqualWorkspace(w1: IWorkspaceIdentifier | undefined, w2: IWorkspaceIdentifier | undefined, message?: string): void { + if (!w1 || !w2) { + assert.equal(w1, w2, message); + return; + } + assert.equal(w1.id, w2.id, message); + assertEqualURI(w1.configPath, w2.configPath, message); +} + +function assertEqualWindowState(expected: IWindowState | undefined, actual: IWindowState | undefined, message?: string) { + if (!expected || !actual) { + assert.deepEqual(expected, actual, message); + return; + } + assert.equal(expected.backupPath, actual.backupPath, message); + assertEqualURI(expected.folderUri, actual.folderUri, message); + assert.equal(expected.remoteAuthority, actual.remoteAuthority, message); + assertEqualWorkspace(expected.workspace, actual.workspace, message); + assert.deepEqual(expected.uiState, actual.uiState, message); +} + +function assertEqualWindowsState(expected: IWindowsState, actual: IWindowsState, message?: string) { + assertEqualWindowState(expected.lastPluginDevelopmentHostWindow, actual.lastPluginDevelopmentHostWindow, message); + assertEqualWindowState(expected.lastActiveWindow, actual.lastActiveWindow, message); + assert.equal(expected.openedWindows.length, actual.openedWindows.length, message); + for (let i = 0; i < expected.openedWindows.length; i++) { + assertEqualWindowState(expected.openedWindows[i], actual.openedWindows[i], message); + } +} + +function assertRestoring(state: IWindowsState, message?: string) { + const stored = getWindowsStateStoreData(state); + const restored = restoreWindowsState(stored); + assertEqualWindowsState(state, restored, message); +} + +const testBackupPath1 = path.join(os.tmpdir(), 'windowStateTest', 'backupFolder1'); +const testBackupPath2 = path.join(os.tmpdir(), 'windowStateTest', 'backupFolder2'); + +const testWSPath = URI.file(path.join(os.tmpdir(), 'windowStateTest', 'test.code-workspace')); +const testFolderURI = URI.file(path.join(os.tmpdir(), 'windowStateTest', 'testFolder')); + +const testRemoteFolderURI = URI.parse('foo://bar/c/d'); + +suite('Windows State Storing', () => { + test('storing and restoring', () => { + let windowState: IWindowsState; + windowState = { + openedWindows: [] + }; + assertRestoring(windowState, 'no windows'); + windowState = { + openedWindows: [{ backupPath: testBackupPath1, uiState: getUIState() }] + }; + assertRestoring(windowState, 'empty workspace'); + + windowState = { + openedWindows: [{ backupPath: testBackupPath1, uiState: getUIState(), workspace: toWorkspace(testWSPath) }] + }; + assertRestoring(windowState, 'workspace'); + + windowState = { + openedWindows: [{ backupPath: testBackupPath2, uiState: getUIState(), folderUri: testFolderURI }] + }; + assertRestoring(windowState, 'folder'); + + windowState = { + openedWindows: [{ backupPath: testBackupPath1, uiState: getUIState(), folderUri: testFolderURI }, { backupPath: testBackupPath1, uiState: getUIState(), folderUri: testRemoteFolderURI, remoteAuthority: 'bar' }] + }; + assertRestoring(windowState, 'multiple windows'); + + windowState = { + lastActiveWindow: { backupPath: testBackupPath2, uiState: getUIState(), folderUri: testFolderURI }, + openedWindows: [] + }; + assertRestoring(windowState, 'lastActiveWindow'); + + windowState = { + lastPluginDevelopmentHostWindow: { backupPath: testBackupPath2, uiState: getUIState(), folderUri: testFolderURI }, + openedWindows: [] + }; + assertRestoring(windowState, 'lastPluginDevelopmentHostWindow'); + }); + + test('open 1_31', () => { + const v1_31_workspace = `{ + "openedWindows": [], + "lastActiveWindow": { + "workspace": { + "id": "a41787288b5e9cc1a61ba2dd84cd0d80", + "configPath": "/home/user/workspaces/code-and-docs.code-workspace" + }, + "backupPath": "/home/user/.config/Code - Insiders/Backups/a41787288b5e9cc1a61ba2dd84cd0d80", + "uiState": { + "mode": 0, + "x": 0, + "y": 27, + "width": 2560, + "height": 1364 + } + } + }`; + + let windowsState = restoreWindowsState(JSON.parse(v1_31_workspace)); + let expected: IWindowsState = { + openedWindows: [], + lastActiveWindow: { + backupPath: '/home/user/.config/Code - Insiders/Backups/a41787288b5e9cc1a61ba2dd84cd0d80', + uiState: { mode: WindowMode.Maximized, x: 0, y: 27, width: 2560, height: 1364 }, + workspace: { id: 'a41787288b5e9cc1a61ba2dd84cd0d80', configPath: URI.file('/home/user/workspaces/code-and-docs.code-workspace') } + } + }; + + assertEqualWindowsState(expected, windowsState, 'v1_31_workspace'); + + const v1_31_folder = `{ + "openedWindows": [], + "lastPluginDevelopmentHostWindow": { + "folderUri": { + "$mid": 1, + "fsPath": "/home/user/workspaces/testing/customdata", + "external": "file:///home/user/workspaces/testing/customdata", + "path": "/home/user/workspaces/testing/customdata", + "scheme": "file" + }, + "uiState": { + "mode": 1, + "x": 593, + "y": 617, + "width": 1625, + "height": 595 + } + } + }`; + + windowsState = restoreWindowsState(JSON.parse(v1_31_folder)); + expected = { + openedWindows: [], + lastPluginDevelopmentHostWindow: { + uiState: { mode: WindowMode.Normal, x: 593, y: 617, width: 1625, height: 595 }, + folderUri: URI.parse('file:///home/user/workspaces/testing/customdata') + } + }; + assertEqualWindowsState(expected, windowsState, 'v1_31_folder'); + + const v1_31_empty_window = ` { + "openedWindows": [ + ], + "lastActiveWindow": { + "backupPath": "C:\\\\Users\\\\Mike\\\\AppData\\\\Roaming\\\\Code\\\\Backups\\\\1549538599815", + "uiState": { + "mode": 0, + "x": -8, + "y": -8, + "width": 2576, + "height": 1344 + } + } + }`; + + windowsState = restoreWindowsState(JSON.parse(v1_31_empty_window)); + expected = { + openedWindows: [], + lastActiveWindow: { + backupPath: 'C:\\Users\\Mike\\AppData\\Roaming\\Code\\Backups\\1549538599815', + uiState: { mode: WindowMode.Maximized, x: -8, y: -8, width: 2576, height: 1344 } + } + }; + assertEqualWindowsState(expected, windowsState, 'v1_31_empty_window'); + + }); + + test('open 1_32', () => { + const v1_32_workspace = `{ + "openedWindows": [], + "lastActiveWindow": { + "workspaceIdentifier": { + "id": "53b714b46ef1a2d4346568b4f591028c", + "configURIPath": "file:///home/user/workspaces/testing/custom.code-workspace" + }, + "backupPath": "/home/user/.config/code-oss-dev/Backups/53b714b46ef1a2d4346568b4f591028c", + "uiState": { + "mode": 0, + "x": 0, + "y": 27, + "width": 2560, + "height": 1364 + } + } + }`; + + let windowsState = restoreWindowsState(JSON.parse(v1_32_workspace)); + let expected: IWindowsState = { + openedWindows: [], + lastActiveWindow: { + backupPath: '/home/user/.config/code-oss-dev/Backups/53b714b46ef1a2d4346568b4f591028c', + uiState: { mode: WindowMode.Maximized, x: 0, y: 27, width: 2560, height: 1364 }, + workspace: { id: '53b714b46ef1a2d4346568b4f591028c', configPath: URI.parse('file:///home/user/workspaces/testing/custom.code-workspace') } + } + }; + + assertEqualWindowsState(expected, windowsState, 'v1_32_workspace'); + + const v1_32_folder = `{ + "openedWindows": [], + "lastActiveWindow": { + "folder": "file:///home/user/workspaces/testing/folding", + "backupPath": "/home/user/.config/code-oss-dev/Backups/1daac1621c6c06f9e916ac8062e5a1b5", + "uiState": { + "mode": 1, + "x": 625, + "y": 263, + "width": 1718, + "height": 953 + } + } + }`; + + windowsState = restoreWindowsState(JSON.parse(v1_32_folder)); + expected = { + openedWindows: [], + lastActiveWindow: { + backupPath: '/home/user/.config/code-oss-dev/Backups/1daac1621c6c06f9e916ac8062e5a1b5', + uiState: { mode: WindowMode.Normal, x: 625, y: 263, width: 1718, height: 953 }, + folderUri: URI.parse('file:///home/user/workspaces/testing/folding') + } + }; + assertEqualWindowsState(expected, windowsState, 'v1_32_folder'); + + const v1_32_empty_window = ` { + "openedWindows": [ + ], + "lastActiveWindow": { + "backupPath": "/home/user/.config/code-oss-dev/Backups/1549539668998", + "uiState": { + "mode": 1, + "x": 768, + "y": 336, + "width": 1024, + "height": 768 + } + } + }`; + + windowsState = restoreWindowsState(JSON.parse(v1_32_empty_window)); + expected = { + openedWindows: [], + lastActiveWindow: { + backupPath: '/home/user/.config/code-oss-dev/Backups/1549539668998', + uiState: { mode: WindowMode.Normal, x: 768, y: 336, width: 1024, height: 768 } + } + }; + assertEqualWindowsState(expected, windowsState, 'v1_32_empty_window'); + + }); + +}); diff --git a/src/vs/code/test/node/argv.test.ts b/src/vs/code/test/node/argv.test.ts index 0cab7825d0..ea04ed5d5e 100644 --- a/src/vs/code/test/node/argv.test.ts +++ b/src/vs/code/test/node/argv.test.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { formatOptions, Option } from 'vs/platform/environment/node/argv'; +import { formatOptions, Option, addArg } from 'vs/platform/environment/node/argv'; suite('formatOptions', () => { @@ -54,4 +54,13 @@ suite('formatOptions', () => { ' bar bar bar bar bar bar bar bar bar ' ]); }); + + test('addArg', () => { + assert.deepEqual(addArg([], 'foo'), ['foo']); + assert.deepEqual(addArg([], 'foo', 'bar'), ['foo', 'bar']); + assert.deepEqual(addArg(['foo'], 'bar'), ['foo', 'bar']); + assert.deepEqual(addArg(['--wait'], 'bar'), ['--wait', 'bar']); + assert.deepEqual(addArg(['--wait', '--', '--foo'], 'bar'), ['--wait', 'bar', '--', '--foo']); + assert.deepEqual(addArg(['--', '--foo'], 'bar'), ['bar', '--', '--foo']); + }); }); diff --git a/src/vs/code/test/node/windowsFinder.test.ts b/src/vs/code/test/node/windowsFinder.test.ts index c530b6ee62..0fb8ea315b 100644 --- a/src/vs/code/test/node/windowsFinder.test.ts +++ b/src/vs/code/test/node/windowsFinder.test.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { findBestWindowOrFolderForFile, ISimpleWindow, IBestWindowOrFolderOptions } from 'vs/code/node/windowsFinder'; import { OpenContext } from 'vs/platform/windows/common/windows'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; @@ -15,17 +15,16 @@ const fixturesFolder = getPathFromAmdModule(require, './fixtures'); const testWorkspace: IWorkspaceIdentifier = { id: Date.now().toString(), - configPath: path.join(fixturesFolder, 'workspaces.json') + configPath: URI.file(path.join(fixturesFolder, 'workspaces.json')) }; function options(custom?: Partial>): IBestWindowOrFolderOptions { return { windows: [], newWindow: false, - reuseWindow: false, context: OpenContext.CLI, codeSettingsFolder: '_vscode', - workspaceResolver: workspace => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: toWorkspaceFolders([{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }]) } : null!; }, + localWorkspaceResolver: workspace => { return workspace === testWorkspace ? { id: testWorkspace.id, configPath: workspace.configPath, folders: toWorkspaceFolders([{ path: path.join(fixturesFolder, 'vscode_workspace_1_folder') }, { path: path.join(fixturesFolder, 'vscode_workspace_2_folder') }]) } : null!; }, ...custom }; } @@ -52,7 +51,6 @@ suite('WindowsFinder', () => { })), null); assert.equal(findBestWindowOrFolderForFile(options({ fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')), - reuseWindow: true })), null); assert.equal(findBestWindowOrFolderForFile(options({ fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')), @@ -85,7 +83,6 @@ suite('WindowsFinder', () => { assert.equal(findBestWindowOrFolderForFile(options({ windows: [lastActiveWindow, noVscodeFolderWindow], fileUri: URI.file(path.join(fixturesFolder, 'vscode_folder', 'file.txt')), - reuseWindow: true })), lastActiveWindow); assert.equal(findBestWindowOrFolderForFile(options({ windows, diff --git a/src/vs/editor/browser/config/charWidthReader.ts b/src/vs/editor/browser/config/charWidthReader.ts index 811d3d4cb9..af8d09db9d 100644 --- a/src/vs/editor/browser/config/charWidthReader.ts +++ b/src/vs/editor/browser/config/charWidthReader.ts @@ -62,12 +62,12 @@ class DomCharWidthReader { } private _createDomElements(): void { - let container = document.createElement('div'); + const container = document.createElement('div'); container.style.position = 'absolute'; container.style.top = '-50000px'; container.style.width = '50000px'; - let regularDomNode = document.createElement('div'); + const regularDomNode = document.createElement('div'); regularDomNode.style.fontFamily = this._bareFontInfo.getMassagedFontFamily(); regularDomNode.style.fontWeight = this._bareFontInfo.fontWeight; regularDomNode.style.fontSize = this._bareFontInfo.fontSize + 'px'; @@ -75,7 +75,7 @@ class DomCharWidthReader { regularDomNode.style.letterSpacing = this._bareFontInfo.letterSpacing + 'px'; container.appendChild(regularDomNode); - let boldDomNode = document.createElement('div'); + const boldDomNode = document.createElement('div'); boldDomNode.style.fontFamily = this._bareFontInfo.getMassagedFontFamily(); boldDomNode.style.fontWeight = 'bold'; boldDomNode.style.fontSize = this._bareFontInfo.fontSize + 'px'; @@ -83,7 +83,7 @@ class DomCharWidthReader { boldDomNode.style.letterSpacing = this._bareFontInfo.letterSpacing + 'px'; container.appendChild(boldDomNode); - let italicDomNode = document.createElement('div'); + const italicDomNode = document.createElement('div'); italicDomNode.style.fontFamily = this._bareFontInfo.getMassagedFontFamily(); italicDomNode.style.fontWeight = this._bareFontInfo.fontWeight; italicDomNode.style.fontSize = this._bareFontInfo.fontSize + 'px'; @@ -92,7 +92,7 @@ class DomCharWidthReader { italicDomNode.style.fontStyle = 'italic'; container.appendChild(italicDomNode); - let testElements: HTMLSpanElement[] = []; + const testElements: HTMLSpanElement[] = []; for (let i = 0, len = this._requests.length; i < len; i++) { const request = this._requests[i]; @@ -109,7 +109,7 @@ class DomCharWidthReader { parent!.appendChild(document.createElement('br')); - let testElement = document.createElement('span'); + const testElement = document.createElement('span'); DomCharWidthReader._render(testElement, request); parent!.appendChild(testElement); @@ -149,6 +149,6 @@ class DomCharWidthReader { } export function readCharWidths(bareFontInfo: BareFontInfo, requests: CharWidthRequest[]): void { - let reader = new DomCharWidthReader(bareFontInfo, requests); + const reader = new DomCharWidthReader(bareFontInfo, requests); reader.read(); } diff --git a/src/vs/editor/browser/config/configuration.ts b/src/vs/editor/browser/config/configuration.ts index 6c573f45e1..cd2725b670 100644 --- a/src/vs/editor/browser/config/configuration.ts +++ b/src/vs/editor/browser/config/configuration.ts @@ -15,11 +15,12 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; import { IDimension } from 'vs/editor/common/editorCommon'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; class CSSBasedConfigurationCache { - private _keys: { [key: string]: BareFontInfo; }; - private _values: { [key: string]: FontInfo; }; + private readonly _keys: { [key: string]: BareFontInfo; }; + private readonly _values: { [key: string]: FontInfo; }; constructor() { this._keys = Object.create(null); @@ -27,23 +28,23 @@ class CSSBasedConfigurationCache { } public has(item: BareFontInfo): boolean { - let itemId = item.getId(); + const itemId = item.getId(); return !!this._values[itemId]; } public get(item: BareFontInfo): FontInfo { - let itemId = item.getId(); + const itemId = item.getId(); return this._values[itemId]; } public put(item: BareFontInfo, value: FontInfo): void { - let itemId = item.getId(); + const itemId = item.getId(); this._keys[itemId] = item; this._values[itemId] = value; } public remove(item: BareFontInfo): void { - let itemId = item.getId(); + const itemId = item.getId(); delete this._keys[itemId]; delete this._values[itemId]; } @@ -53,12 +54,16 @@ class CSSBasedConfigurationCache { } } +export function clearAllFontInfos(): void { + CSSBasedConfiguration.INSTANCE.clearCache(); +} + export function readFontInfo(bareFontInfo: BareFontInfo): FontInfo { return CSSBasedConfiguration.INSTANCE.readConfiguration(bareFontInfo); } export function restoreFontInfo(storageService: IStorageService): void { - let strStoredFontInfo = storageService.get('editorFontInfo', StorageScope.GLOBAL); + const strStoredFontInfo = storageService.get('editorFontInfo', StorageScope.GLOBAL); if (typeof strStoredFontInfo !== 'string') { return; } @@ -75,7 +80,7 @@ export function restoreFontInfo(storageService: IStorageService): void { } export function saveFontInfo(storageService: IStorageService): void { - let knownFontInfo = CSSBasedConfiguration.INSTANCE.saveFontInfo(); + const knownFontInfo = CSSBasedConfiguration.INSTANCE.saveFontInfo(); if (knownFontInfo.length > 0) { storageService.store('editorFontInfo', JSON.stringify(knownFontInfo), StorageScope.GLOBAL); } @@ -121,6 +126,11 @@ class CSSBasedConfiguration extends Disposable { super.dispose(); } + public clearCache(): void { + this._cache = new CSSBasedConfigurationCache(); + this._onDidChange.fire(); + } + private _writeToCache(item: BareFontInfo, value: FontInfo): void { this._cache.put(item, value); @@ -134,10 +144,10 @@ class CSSBasedConfiguration extends Disposable { } private _evictUntrustedReadings(): void { - let values = this._cache.getValues(); + const values = this._cache.getValues(); let somethingRemoved = false; for (let i = 0, len = values.length; i < len; i++) { - let item = values[i]; + const item = values[i]; if (!item.isTrusted) { somethingRemoved = true; this._cache.remove(item); @@ -157,7 +167,7 @@ class CSSBasedConfiguration extends Disposable { // Take all the saved font info and insert them in the cache without the trusted flag. // The reason for this is that a font might have been installed on the OS in the meantime. for (let i = 0, len = savedFontInfo.length; i < len; i++) { - let fontInfo = new FontInfo(savedFontInfo[i], false); + const fontInfo = new FontInfo(savedFontInfo[i], false); this._writeToCache(fontInfo, fontInfo); } } @@ -190,7 +200,7 @@ class CSSBasedConfiguration extends Disposable { } private static createRequest(chr: string, type: CharWidthRequestType, all: CharWidthRequest[], monospace: CharWidthRequest[] | null): CharWidthRequest { - let result = new CharWidthRequest(chr, type); + const result = new CharWidthRequest(chr, type); all.push(result); if (monospace) { monospace.push(result); @@ -199,8 +209,8 @@ class CSSBasedConfiguration extends Disposable { } private static _actualReadConfiguration(bareFontInfo: BareFontInfo): FontInfo { - let all: CharWidthRequest[] = []; - let monospace: CharWidthRequest[] = []; + const all: CharWidthRequest[] = []; + const monospace: CharWidthRequest[] = []; const typicalHalfwidthCharacter = this.createRequest('n', CharWidthRequestType.Regular, all, monospace); const typicalFullwidthCharacter = this.createRequest('\uff4d', CharWidthRequestType.Regular, all, null); @@ -252,7 +262,7 @@ class CSSBasedConfiguration extends Disposable { const maxDigitWidth = Math.max(digit0.width, digit1.width, digit2.width, digit3.width, digit4.width, digit5.width, digit6.width, digit7.width, digit8.width, digit9.width); let isMonospace = true; - let referenceWidth = monospace[0].width; + const referenceWidth = monospace[0].width; for (let i = 1, len = monospace.length; i < len; i++) { const diff = referenceWidth - monospace[i].width; if (diff < -0.001 || diff > 0.001) { @@ -310,7 +320,7 @@ export class Configuration extends CommonEditorConfiguration { private readonly _elementSizeObserver: ElementSizeObserver; - constructor(options: IEditorOptions, referenceDomElement: HTMLElement | null = null) { + constructor(options: IEditorOptions, referenceDomElement: HTMLElement | null = null, private readonly accessibilityService: IAccessibilityService) { super(options); this._elementSizeObserver = this._register(new ElementSizeObserver(referenceDomElement, () => this._onReferenceDomElementSizeChanged())); @@ -322,7 +332,7 @@ export class Configuration extends CommonEditorConfiguration { } this._register(browser.onDidChangeZoomLevel(_ => this._recomputeOptions())); - this._register(browser.onDidChangeAccessibilitySupport(() => this._recomputeOptions())); + this._register(this.accessibilityService.onDidChangeAccessibilitySupport(() => this._recomputeOptions())); this._recomputeOptions(); } @@ -363,7 +373,7 @@ export class Configuration extends CommonEditorConfiguration { emptySelectionClipboard: browser.isWebKit || browser.isFirefox, pixelRatio: browser.getPixelRatio(), zoomLevel: browser.getZoomLevel(), - accessibilitySupport: browser.getAccessibilitySupport() + accessibilitySupport: this.accessibilityService.getAccessibilitySupport() }; } diff --git a/src/vs/editor/browser/config/elementSizeObserver.ts b/src/vs/editor/browser/config/elementSizeObserver.ts index b4768f8ee7..a372dc700e 100644 --- a/src/vs/editor/browser/config/elementSizeObserver.ts +++ b/src/vs/editor/browser/config/elementSizeObserver.ts @@ -8,9 +8,9 @@ import { IDimension } from 'vs/editor/common/editorCommon'; export class ElementSizeObserver extends Disposable { - private referenceDomElement: HTMLElement | null; + private readonly referenceDomElement: HTMLElement | null; private measureReferenceDomElementToken: any; - private changeCallback: () => void; + private readonly changeCallback: () => void; private width: number; private height: number; diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index 492546780b..39c9edf0e5 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -48,7 +48,7 @@ export namespace EditorScroll_ { return false; } - let scrollArg: RawArguments = arg; + const scrollArg: RawArguments = arg; if (!types.isString(scrollArg.to)) { return false; @@ -86,7 +86,28 @@ export namespace EditorScroll_ { * 'value': Number of units to move. Default is '1'. * 'revealCursor': If 'true' reveals the cursor if it is outside view port. `, - constraint: isEditorScrollArgs + constraint: isEditorScrollArgs, + schema: { + 'type': 'object', + 'required': ['to'], + 'properties': { + 'to': { + 'type': 'string', + 'enum': ['up', 'down'] + }, + 'by': { + 'type': 'string', + 'enum': ['line', 'wrappedLine', 'page', 'halfPage'] + }, + 'value': { + 'type': 'number', + 'default': 1 + }, + 'revealCursor': { + 'type': 'boolean', + } + } + } } ] }; @@ -192,7 +213,7 @@ export namespace RevealLine_ { return false; } - let reveaLineArg: RawArguments = arg; + const reveaLineArg: RawArguments = arg; if (!types.isNumber(reveaLineArg.lineNumber)) { return false; @@ -217,7 +238,20 @@ export namespace RevealLine_ { 'top', 'center', 'bottom' \`\`\` `, - constraint: isRevealLineArgs + constraint: isRevealLineArgs, + schema: { + 'type': 'object', + 'required': ['lineNumber'], + 'properties': { + 'lineNumber': { + 'type': 'number', + }, + 'at': { + 'type': 'string', + 'enum': ['top', 'center', 'bottom'] + } + } + } } ] }; @@ -766,7 +800,7 @@ export namespace CoreNavigationCommands { const lastAddedCursorIndex = cursors.getLastAddedCursorIndex(); const states = cursors.getAll(); - let newStates: PartialCursorState[] = states.slice(0); + const newStates: PartialCursorState[] = states.slice(0); newStates[lastAddedCursorIndex] = CursorMoveCommands.moveTo(context, states[lastAddedCursorIndex], true, args.position, args.viewPosition); cursors.context.model.pushStackElement(); @@ -847,7 +881,7 @@ export namespace CoreNavigationCommands { } private _exec(context: CursorContext, cursors: CursorState[]): PartialCursorState[] { - let result: PartialCursorState[] = []; + const result: PartialCursorState[] = []; for (let i = 0, len = cursors.length; i < len; i++) { const cursor = cursors[i]; const lineNumber = cursor.modelState.position.lineNumber; @@ -926,7 +960,7 @@ export namespace CoreNavigationCommands { } private _exec(context: CursorContext, cursors: CursorState[]): PartialCursorState[] { - let result: PartialCursorState[] = []; + const result: PartialCursorState[] = []; for (let i = 0, len = cursors.length; i < len; i++) { const cursor = cursors[i]; const lineNumber = cursor.modelState.position.lineNumber; @@ -1246,8 +1280,8 @@ export namespace CoreNavigationCommands { const lastAddedCursorIndex = cursors.getLastAddedCursorIndex(); const states = cursors.getAll(); - let newStates: PartialCursorState[] = states.slice(0); - let lastAddedState = states[lastAddedCursorIndex]; + const newStates: PartialCursorState[] = states.slice(0); + const lastAddedState = states[lastAddedCursorIndex]; newStates[lastAddedCursorIndex] = CursorMoveCommands.word(context, lastAddedState, lastAddedState.modelState.hasSelection(), args.position); context.model.pushStackElement(); @@ -1304,7 +1338,7 @@ export namespace CoreNavigationCommands { const lastAddedCursorIndex = cursors.getLastAddedCursorIndex(); const states = cursors.getAll(); - let newStates: PartialCursorState[] = states.slice(0); + const newStates: PartialCursorState[] = states.slice(0); newStates[lastAddedCursorIndex] = CursorMoveCommands.line(cursors.context, states[lastAddedCursorIndex], this._inSelectionMode, args.position, args.viewPosition); cursors.context.model.pushStackElement(); @@ -1336,7 +1370,7 @@ export namespace CoreNavigationCommands { kbOpts: { weight: CORE_WEIGHT, kbExpr: EditorContextKeys.textInputFocus, - primary: KeyMod.CtrlCmd | KeyCode.KEY_I + primary: KeyMod.CtrlCmd | KeyCode.KEY_L } }); } @@ -1651,21 +1685,21 @@ class EditorOrNativeTextInputCommand extends Command { public runCommand(accessor: ServicesAccessor, args: any): void { - let focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); + const focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); // Only if editor text focus (i.e. not if editor has widget focus). if (focusedEditor && focusedEditor.hasTextFocus()) { return this._runEditorHandler(accessor, focusedEditor, args); } // Ignore this action when user is focused on an element that allows for entering text - let activeElement = document.activeElement; + const activeElement = document.activeElement; if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) { document.execCommand(this._inputHandler); return; } // Redirecting to active editor - let activeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor(); + const activeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor(); if (activeEditor) { activeEditor.focus(); return this._runEditorHandler(accessor, activeEditor, args); @@ -1673,7 +1707,7 @@ class EditorOrNativeTextInputCommand extends Command { } private _runEditorHandler(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { - let HANDLER = this._editorHandler; + const HANDLER = this._editorHandler; if (typeof HANDLER === 'string') { editor.trigger('keyboard', HANDLER, args); } else { @@ -1691,10 +1725,11 @@ class EditorHandlerCommand extends Command { private readonly _handlerId: string; - constructor(id: string, handlerId: string) { + constructor(id: string, handlerId: string, description?: ICommandHandlerDescription) { super({ id: id, - precondition: null + precondition: null, + description: description }); this._handlerId = handlerId; } @@ -1769,12 +1804,26 @@ registerCommand(new EditorOrNativeTextInputCommand({ })); registerCommand(new EditorHandlerCommand('default:' + Handler.Redo, Handler.Redo)); -function registerOverwritableCommand(handlerId: string): void { +function registerOverwritableCommand(handlerId: string, description?: ICommandHandlerDescription): void { registerCommand(new EditorHandlerCommand('default:' + handlerId, handlerId)); - registerCommand(new EditorHandlerCommand(handlerId, handlerId)); + registerCommand(new EditorHandlerCommand(handlerId, handlerId, description)); } -registerOverwritableCommand(Handler.Type); +registerOverwritableCommand(Handler.Type, { + description: `Type`, + args: [{ + name: 'args', + schema: { + 'type': 'object', + 'required': ['text'], + 'properties': { + 'text': { + 'type': 'string' + } + }, + } + }] +}); registerOverwritableCommand(Handler.ReplacePreviousChar); registerOverwritableCommand(Handler.CompositionStart); registerOverwritableCommand(Handler.CompositionEnd); diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index ca904999c0..b0adac76e5 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -69,9 +69,9 @@ export class MouseHandler extends ViewEventHandler { protected viewController: ViewController; protected viewHelper: IPointerHandlerHelper; protected mouseTargetFactory: MouseTargetFactory; - private _asyncFocus: RunOnceScheduler; + private readonly _asyncFocus: RunOnceScheduler; - private _mouseDownOperation: MouseDownOperation; + private readonly _mouseDownOperation: MouseDownOperation; private lastMouseLeaveTime: number; constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) { @@ -94,7 +94,7 @@ export class MouseHandler extends ViewEventHandler { this.lastMouseLeaveTime = -1; - let mouseEvents = new EditorMouseEventFactory(this.viewHelper.viewDomNode); + const mouseEvents = new EditorMouseEventFactory(this.viewHelper.viewDomNode); this._register(mouseEvents.onContextMenu(this.viewHelper.viewDomNode, (e) => this._onContextMenu(e, true))); @@ -108,14 +108,14 @@ export class MouseHandler extends ViewEventHandler { this._register(mouseEvents.onMouseDown(this.viewHelper.viewDomNode, (e) => this._onMouseDown(e))); - let onMouseWheel = (browserEvent: IMouseWheelEvent) => { + const onMouseWheel = (browserEvent: IMouseWheelEvent) => { if (!this._context.configuration.editor.viewInfo.mouseWheelZoom) { return; } - let e = new StandardWheelEvent(browserEvent); + const e = new StandardWheelEvent(browserEvent); if (e.browserEvent!.ctrlKey || e.browserEvent!.metaKey) { - let zoomLevel: number = EditorZoom.getZoomLevel(); - let delta = e.deltaY > 0 ? 1 : -1; + const zoomLevel: number = EditorZoom.getZoomLevel(); + const delta = e.deltaY > 0 ? 1 : -1; EditorZoom.setZoomLevel(zoomLevel + delta); e.preventDefault(); e.stopPropagation(); @@ -148,20 +148,20 @@ export class MouseHandler extends ViewEventHandler { // --- end event handlers public getTargetAtClientPoint(clientX: number, clientY: number): editorBrowser.IMouseTarget | null { - let clientPos = new ClientCoordinates(clientX, clientY); - let pos = clientPos.toPageCoordinates(); - let editorPos = createEditorPagePosition(this.viewHelper.viewDomNode); + const clientPos = new ClientCoordinates(clientX, clientY); + const pos = clientPos.toPageCoordinates(); + const editorPos = createEditorPagePosition(this.viewHelper.viewDomNode); if (pos.y < editorPos.y || pos.y > editorPos.y + editorPos.height || pos.x < editorPos.x || pos.x > editorPos.x + editorPos.width) { return null; } - let lastViewCursorsRenderData = this.viewHelper.getLastViewCursorsRenderData(); + const lastViewCursorsRenderData = this.viewHelper.getLastViewCursorsRenderData(); return this.mouseTargetFactory.createMouseTarget(lastViewCursorsRenderData, editorPos, pos, null); } protected _createMouseTarget(e: EditorMouseEvent, testEventTarget: boolean): editorBrowser.IMouseTarget { - let lastViewCursorsRenderData = this.viewHelper.getLastViewCursorsRenderData(); + const lastViewCursorsRenderData = this.viewHelper.getLastViewCursorsRenderData(); return this.mouseTargetFactory.createMouseTarget(lastViewCursorsRenderData, e.editorPos, e.pos, testEventTarget ? e.target : null); } @@ -181,7 +181,7 @@ export class MouseHandler extends ViewEventHandler { // In selection/drag operation return; } - let actualMouseMoveTime = e.timestamp; + const actualMouseMoveTime = e.timestamp; if (actualMouseMoveTime < this.lastMouseLeaveTime) { // Due to throttling, this event occurred before the mouse left the editor, therefore ignore it. return; @@ -209,21 +209,21 @@ export class MouseHandler extends ViewEventHandler { } public _onMouseDown(e: EditorMouseEvent): void { - let t = this._createMouseTarget(e, true); + const t = this._createMouseTarget(e, true); - let targetIsContent = (t.type === editorBrowser.MouseTargetType.CONTENT_TEXT || t.type === editorBrowser.MouseTargetType.CONTENT_EMPTY); - let targetIsGutter = (t.type === editorBrowser.MouseTargetType.GUTTER_GLYPH_MARGIN || t.type === editorBrowser.MouseTargetType.GUTTER_LINE_NUMBERS || t.type === editorBrowser.MouseTargetType.GUTTER_LINE_DECORATIONS); - let targetIsLineNumbers = (t.type === editorBrowser.MouseTargetType.GUTTER_LINE_NUMBERS); - let selectOnLineNumbers = this._context.configuration.editor.viewInfo.selectOnLineNumbers; - let targetIsViewZone = (t.type === editorBrowser.MouseTargetType.CONTENT_VIEW_ZONE || t.type === editorBrowser.MouseTargetType.GUTTER_VIEW_ZONE); - let targetIsWidget = (t.type === editorBrowser.MouseTargetType.CONTENT_WIDGET); + const targetIsContent = (t.type === editorBrowser.MouseTargetType.CONTENT_TEXT || t.type === editorBrowser.MouseTargetType.CONTENT_EMPTY); + const targetIsGutter = (t.type === editorBrowser.MouseTargetType.GUTTER_GLYPH_MARGIN || t.type === editorBrowser.MouseTargetType.GUTTER_LINE_NUMBERS || t.type === editorBrowser.MouseTargetType.GUTTER_LINE_DECORATIONS); + const targetIsLineNumbers = (t.type === editorBrowser.MouseTargetType.GUTTER_LINE_NUMBERS); + const selectOnLineNumbers = this._context.configuration.editor.viewInfo.selectOnLineNumbers; + const targetIsViewZone = (t.type === editorBrowser.MouseTargetType.CONTENT_VIEW_ZONE || t.type === editorBrowser.MouseTargetType.GUTTER_VIEW_ZONE); + const targetIsWidget = (t.type === editorBrowser.MouseTargetType.CONTENT_WIDGET); let shouldHandle = e.leftButton || e.middleButton; if (platform.isMacintosh && e.leftButton && e.ctrlKey) { shouldHandle = false; } - let focus = () => { + const focus = () => { // In IE11, if the focus is in the browser's address bar and // then you click in the editor, calling preventDefault() // will not move focus properly (focus remains the address bar) @@ -243,7 +243,7 @@ export class MouseHandler extends ViewEventHandler { // Do not steal focus e.preventDefault(); } else if (targetIsViewZone) { - let viewZoneData = t.detail; + const viewZoneData = t.detail; if (this.viewHelper.shouldSuppressMouseDownOnViewZone(viewZoneData.viewZoneId)) { focus(); this._mouseDownOperation.start(t.type, e); @@ -312,7 +312,7 @@ class MouseDownOperation extends Disposable { this._lastMouseEvent = e; this._mouseState.setModifiers(e); - let position = this._findMousePosition(e, true); + const position = this._findMousePosition(e, true); if (!position) { // Ignoring because position is unknown return; @@ -334,7 +334,7 @@ class MouseDownOperation extends Disposable { this._mouseState.setStartedOnLineNumbers(targetType === editorBrowser.MouseTargetType.GUTTER_LINE_NUMBERS); this._mouseState.setStartButtons(e); this._mouseState.setModifiers(e); - let position = this._findMousePosition(e, true); + const position = this._findMousePosition(e, true); if (!position || !position.position) { // Ignoring because position is unknown return; @@ -361,7 +361,7 @@ class MouseDownOperation extends Disposable { createMouseMoveEventMerger(null), (e) => this._onMouseDownThenMove(e), () => { - let position = this._findMousePosition(this._lastMouseEvent!, true); + const position = this._findMousePosition(this._lastMouseEvent!, true); this._viewController.emitMouseDrop({ event: this._lastMouseEvent!, @@ -401,7 +401,7 @@ class MouseDownOperation extends Disposable { if (!this._lastMouseEvent) { return; } - let position = this._findMousePosition(this._lastMouseEvent, false); + const position = this._findMousePosition(this._lastMouseEvent, false); if (!position) { // Ignoring because position is unknown return; @@ -435,7 +435,7 @@ class MouseDownOperation extends Disposable { } } - let aboveLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset); + const aboveLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset); return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(aboveLineNumber, 1)); } @@ -449,11 +449,11 @@ class MouseDownOperation extends Disposable { } } - let belowLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset); + const belowLineNumber = viewLayout.getLineNumberAtVerticalOffset(verticalOffset); return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(belowLineNumber, model.getLineMaxColumn(belowLineNumber))); } - let possibleLineNumber = viewLayout.getLineNumberAtVerticalOffset(viewLayout.getCurrentScrollTop() + (e.posy - editorContent.y)); + const possibleLineNumber = viewLayout.getLineNumberAtVerticalOffset(viewLayout.getCurrentScrollTop() + (e.posy - editorContent.y)); if (e.posx < editorContent.x) { return new MouseTarget(null, editorBrowser.MouseTargetType.OUTSIDE_EDITOR, mouseColumn, new Position(possibleLineNumber, 1)); @@ -467,13 +467,13 @@ class MouseDownOperation extends Disposable { } private _findMousePosition(e: EditorMouseEvent, testEventTarget: boolean): MouseTarget | null { - let positionOutsideEditor = this._getPositionOutsideEditor(e); + const positionOutsideEditor = this._getPositionOutsideEditor(e); if (positionOutsideEditor) { return positionOutsideEditor; } - let t = this._createMouseTarget(e, testEventTarget); - let hintedPosition = t.position; + const t = this._createMouseTarget(e, testEventTarget); + const hintedPosition = t.position; if (!hintedPosition) { return null; } @@ -490,9 +490,9 @@ class MouseDownOperation extends Disposable { private _helpPositionJumpOverViewZone(viewZoneData: IViewZoneData): Position | null { // Force position on view zones to go above or below depending on where selection started from - let selectionStart = new Position(this._currentSelection.selectionStartLineNumber, this._currentSelection.selectionStartColumn); - let positionBefore = viewZoneData.positionBefore; - let positionAfter = viewZoneData.positionAfter; + const selectionStart = new Position(this._currentSelection.selectionStartLineNumber, this._currentSelection.selectionStartColumn); + const positionBefore = viewZoneData.positionBefore; + const positionAfter = viewZoneData.positionAfter; if (positionBefore && positionAfter) { if (positionBefore.isBefore(selectionStart)) { @@ -594,7 +594,7 @@ class MouseDownState { public trySetCount(setMouseDownCount: number, newMouseDownPosition: Position): void { // a. Invalidate multiple clicking if too much time has passed (will be hit by IE because the detail field of mouse events contains garbage in IE10) - let currentTime = (new Date()).getTime(); + const currentTime = (new Date()).getTime(); if (currentTime - this._lastSetMouseDownCountTime > MouseDownState.CLEAR_MOUSE_DOWN_COUNT_TIME) { setMouseDownCount = 1; } diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index b2fac66515..84316ef4e1 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -253,7 +253,7 @@ export class HitTestContext { public static getZoneAtCoord(context: ViewContext, mouseVerticalOffset: number): IViewZoneData | null { // The target is either a view zone or the empty space after the last view-line - let viewZoneWhitespace = context.viewLayout.getWhitespaceAtVerticalOffset(mouseVerticalOffset); + const viewZoneWhitespace = context.viewLayout.getWhitespaceAtVerticalOffset(mouseVerticalOffset); if (viewZoneWhitespace) { let viewZoneMiddle = viewZoneWhitespace.verticalOffset + viewZoneWhitespace.height / 2, @@ -295,16 +295,16 @@ export class HitTestContext { public getFullLineRangeAtCoord(mouseVerticalOffset: number): { range: EditorRange; isAfterLines: boolean; } { if (this._context.viewLayout.isAfterLines(mouseVerticalOffset)) { // Below the last line - let lineNumber = this._context.model.getLineCount(); - let maxLineColumn = this._context.model.getLineMaxColumn(lineNumber); + const lineNumber = this._context.model.getLineCount(); + const maxLineColumn = this._context.model.getLineMaxColumn(lineNumber); return { range: new EditorRange(lineNumber, maxLineColumn, lineNumber, maxLineColumn), isAfterLines: true }; } - let lineNumber = this._context.viewLayout.getLineNumberAtVerticalOffset(mouseVerticalOffset); - let maxLineColumn = this._context.model.getLineMaxColumn(lineNumber); + const lineNumber = this._context.viewLayout.getLineNumberAtVerticalOffset(mouseVerticalOffset); + const maxLineColumn = this._context.model.getLineMaxColumn(lineNumber); return { range: new EditorRange(lineNumber, 1, lineNumber, maxLineColumn), isAfterLines: false @@ -430,8 +430,8 @@ function createEmptyContentDataInLines(horizontalDistanceToText: number): IEmpty export class MouseTargetFactory { - private _context: ViewContext; - private _viewHelper: IPointerHandlerHelper; + private readonly _context: ViewContext; + private readonly _viewHelper: IPointerHandlerHelper; constructor(context: ViewContext, viewHelper: IPointerHandlerHelper) { this._context = context; @@ -439,8 +439,8 @@ export class MouseTargetFactory { } public mouseTargetIsWidget(e: EditorMouseEvent): boolean { - let t = e.target; - let path = PartFingerprints.collect(t, this._viewHelper.viewDomNode); + const t = e.target; + const path = PartFingerprints.collect(t, this._viewHelper.viewDomNode); // Is it a content widget? if (ElementPath.isChildOfContentWidgets(path) || ElementPath.isChildOfOverflowingContentWidgets(path)) { @@ -459,7 +459,7 @@ export class MouseTargetFactory { const ctx = new HitTestContext(this._context, this._viewHelper, lastViewCursorsRenderData); const request = new HitTestRequest(ctx, editorPos, pos, target); try { - let r = MouseTargetFactory._createMouseTarget(ctx, request, false); + const r = MouseTargetFactory._createMouseTarget(ctx, request, false); // console.log(r.toString()); return r; } catch (err) { @@ -510,7 +510,7 @@ export class MouseTargetFactory { private static _hitTestContentWidget(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { // Is it a content widget? if (ElementPath.isChildOfContentWidgets(request.targetPath) || ElementPath.isChildOfOverflowingContentWidgets(request.targetPath)) { - let widgetId = ctx.findAttribute(request.target, 'widgetId'); + const widgetId = ctx.findAttribute(request.target, 'widgetId'); if (widgetId) { return request.fulfill(MouseTargetType.CONTENT_WIDGET, null, null, widgetId); } else { @@ -523,7 +523,7 @@ export class MouseTargetFactory { private static _hitTestOverlayWidget(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { // Is it an overlay widget? if (ElementPath.isChildOfOverlayWidgets(request.targetPath)) { - let widgetId = ctx.findAttribute(request.target, 'widgetId'); + const widgetId = ctx.findAttribute(request.target, 'widgetId'); if (widgetId) { return request.fulfill(MouseTargetType.OVERLAY_WIDGET, null, null, widgetId); } else { @@ -583,9 +583,9 @@ export class MouseTargetFactory { } private static _hitTestViewZone(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { - let viewZoneData = ctx.getZoneAtCoord(request.mouseVerticalOffset); + const viewZoneData = ctx.getZoneAtCoord(request.mouseVerticalOffset); if (viewZoneData) { - let mouseTargetType = (request.isInContentArea ? MouseTargetType.CONTENT_VIEW_ZONE : MouseTargetType.GUTTER_VIEW_ZONE); + const mouseTargetType = (request.isInContentArea ? MouseTargetType.CONTENT_VIEW_ZONE : MouseTargetType.GUTTER_VIEW_ZONE); return request.fulfill(mouseTargetType, viewZoneData.position, null, viewZoneData); } @@ -602,8 +602,8 @@ export class MouseTargetFactory { private static _hitTestMargin(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { if (request.isInMarginArea) { - let res = ctx.getFullLineRangeAtCoord(request.mouseVerticalOffset); - let pos = res.range.getStartPosition(); + const res = ctx.getFullLineRangeAtCoord(request.mouseVerticalOffset); + const pos = res.range.getStartPosition(); let offset = Math.abs(request.pos.x - request.editorPos.x); const detail: IMarginData = { isAfterLines: res.isAfterLines, @@ -683,7 +683,7 @@ export class MouseTargetFactory { private static _hitTestScrollbarSlider(ctx: HitTestContext, request: ResolvedHitTestRequest): MouseTarget | null { if (ElementPath.isChildOfScrollableElement(request.targetPath)) { if (request.target && request.target.nodeType === 1) { - let className = request.target.className; + const className = request.target.className; if (className && /\b(slider|scrollbar)\b/.test(className)) { const possibleLineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset); const maxColumn = ctx.model.getLineMaxColumn(possibleLineNumber); @@ -707,8 +707,8 @@ export class MouseTargetFactory { } public getMouseColumn(editorPos: EditorPagePosition, pos: PageCoordinates): number { - let layoutInfo = this._context.configuration.editor.layoutInfo; - let mouseContentHorizontalOffset = this._context.viewLayout.getCurrentScrollLeft() + pos.x - editorPos.x - layoutInfo.contentLeft; + const layoutInfo = this._context.configuration.editor.layoutInfo; + const mouseContentHorizontalOffset = this._context.viewLayout.getCurrentScrollLeft() + pos.x - editorPos.x - layoutInfo.contentLeft; return MouseTargetFactory._getMouseColumn(mouseContentHorizontalOffset, this._context.configuration.editor.fontInfo.typicalHalfwidthCharacterWidth); } @@ -716,14 +716,14 @@ export class MouseTargetFactory { if (mouseContentHorizontalOffset < 0) { return 1; } - let chars = Math.round(mouseContentHorizontalOffset / typicalHalfwidthCharacterWidth); + const chars = Math.round(mouseContentHorizontalOffset / typicalHalfwidthCharacterWidth); return (chars + 1); } private static createMouseTargetFromHitTestPosition(ctx: HitTestContext, request: HitTestRequest, lineNumber: number, column: number): MouseTarget { - let pos = new Position(lineNumber, column); + const pos = new Position(lineNumber, column); - let lineWidth = ctx.getLineWidth(lineNumber); + const lineWidth = ctx.getLineWidth(lineNumber); if (request.mouseContentHorizontalOffset > lineWidth) { if (browser.isEdge && pos.column === 1) { @@ -741,7 +741,7 @@ export class MouseTargetFactory { return request.fulfill(MouseTargetType.UNKNOWN, pos); } - let columnHorizontalOffset = visibleRange.left; + const columnHorizontalOffset = visibleRange.left; if (request.mouseContentHorizontalOffset === columnHorizontalOffset) { return request.fulfill(MouseTargetType.CONTENT_TEXT, pos); @@ -750,7 +750,7 @@ export class MouseTargetFactory { // Let's define a, b, c and check if the offset is in between them... interface OffsetColumn { offset: number; column: number; } - let points: OffsetColumn[] = []; + const points: OffsetColumn[] = []; points.push({ offset: visibleRange.left, column: column }); if (column > 1) { const visibleRange = ctx.visibleRangeForPosition2(lineNumber, column - 1); @@ -786,9 +786,9 @@ export class MouseTargetFactory { // In Chrome, especially on Linux it is possible to click between lines, // so try to adjust the `hity` below so that it lands in the center of a line - let lineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset); - let lineVerticalOffset = ctx.getVerticalOffsetForLineNumber(lineNumber); - let lineCenteredVerticalOffset = lineVerticalOffset + Math.floor(ctx.lineHeight / 2); + const lineNumber = ctx.getLineNumberAtVerticalOffset(request.mouseVerticalOffset); + const lineVerticalOffset = ctx.getVerticalOffsetForLineNumber(lineNumber); + const lineCenteredVerticalOffset = lineVerticalOffset + Math.floor(ctx.lineHeight / 2); let adjustedPageY = request.pos.y + (lineCenteredVerticalOffset - request.mouseVerticalOffset); if (adjustedPageY <= request.editorPos.y) { @@ -798,9 +798,9 @@ export class MouseTargetFactory { adjustedPageY = request.editorPos.y + ctx.layoutInfo.height - 1; } - let adjustedPage = new PageCoordinates(request.pos.x, adjustedPageY); + const adjustedPage = new PageCoordinates(request.pos.x, adjustedPageY); - let r = this._actualDoHitTestWithCaretRangeFromPoint(ctx, adjustedPage.toClientCoordinates()); + const r = this._actualDoHitTestWithCaretRangeFromPoint(ctx, adjustedPage.toClientCoordinates()); if (r.position) { return r; } @@ -811,7 +811,7 @@ export class MouseTargetFactory { private static _actualDoHitTestWithCaretRangeFromPoint(ctx: HitTestContext, coords: ClientCoordinates): IHitTestResult { - let range: Range = document.caretRangeFromPoint(coords.clientX, coords.clientY); + const range: Range = document.caretRangeFromPoint(coords.clientX, coords.clientY); if (!range || !range.startContainer) { return { @@ -821,18 +821,18 @@ export class MouseTargetFactory { } // Chrome always hits a TEXT_NODE, while Edge sometimes hits a token span - let startContainer = range.startContainer; + const startContainer = range.startContainer; let hitTarget: HTMLElement | null = null; if (startContainer.nodeType === startContainer.TEXT_NODE) { // startContainer is expected to be the token text - let parent1 = startContainer.parentNode; // expected to be the token span - let parent2 = parent1 ? parent1.parentNode : null; // expected to be the view line container span - let parent3 = parent2 ? parent2.parentNode : null; // expected to be the view line div - let parent3ClassName = parent3 && parent3.nodeType === parent3.ELEMENT_NODE ? (parent3).className : null; + const parent1 = startContainer.parentNode; // expected to be the token span + const parent2 = parent1 ? parent1.parentNode : null; // expected to be the view line container span + const parent3 = parent2 ? parent2.parentNode : null; // expected to be the view line div + const parent3ClassName = parent3 && parent3.nodeType === parent3.ELEMENT_NODE ? (parent3).className : null; if (parent3ClassName === ViewLine.CLASS_NAME) { - let p = ctx.getPositionFromDOMInfo(parent1, range.startOffset); + const p = ctx.getPositionFromDOMInfo(parent1, range.startOffset); return { position: p, hitTarget: null @@ -842,12 +842,12 @@ export class MouseTargetFactory { } } else if (startContainer.nodeType === startContainer.ELEMENT_NODE) { // startContainer is expected to be the token span - let parent1 = startContainer.parentNode; // expected to be the view line container span - let parent2 = parent1 ? parent1.parentNode : null; // expected to be the view line div - let parent2ClassName = parent2 && parent2.nodeType === parent2.ELEMENT_NODE ? (parent2).className : null; + const parent1 = startContainer.parentNode; // expected to be the view line container span + const parent2 = parent1 ? parent1.parentNode : null; // expected to be the view line div + const parent2ClassName = parent2 && parent2.nodeType === parent2.ELEMENT_NODE ? (parent2).className : null; if (parent2ClassName === ViewLine.CLASS_NAME) { - let p = ctx.getPositionFromDOMInfo(startContainer, (startContainer).textContent!.length); + const p = ctx.getPositionFromDOMInfo(startContainer, (startContainer).textContent!.length); return { position: p, hitTarget: null @@ -867,17 +867,17 @@ export class MouseTargetFactory { * Most probably Gecko */ private static _doHitTestWithCaretPositionFromPoint(ctx: HitTestContext, coords: ClientCoordinates): IHitTestResult { - let hitResult: { offsetNode: Node; offset: number; } = (document).caretPositionFromPoint(coords.clientX, coords.clientY); + const hitResult: { offsetNode: Node; offset: number; } = (document).caretPositionFromPoint(coords.clientX, coords.clientY); if (hitResult.offsetNode.nodeType === hitResult.offsetNode.TEXT_NODE) { // offsetNode is expected to be the token text - let parent1 = hitResult.offsetNode.parentNode; // expected to be the token span - let parent2 = parent1 ? parent1.parentNode : null; // expected to be the view line container span - let parent3 = parent2 ? parent2.parentNode : null; // expected to be the view line div - let parent3ClassName = parent3 && parent3.nodeType === parent3.ELEMENT_NODE ? (parent3).className : null; + const parent1 = hitResult.offsetNode.parentNode; // expected to be the token span + const parent2 = parent1 ? parent1.parentNode : null; // expected to be the view line container span + const parent3 = parent2 ? parent2.parentNode : null; // expected to be the view line div + const parent3ClassName = parent3 && parent3.nodeType === parent3.ELEMENT_NODE ? (parent3).className : null; if (parent3ClassName === ViewLine.CLASS_NAME) { - let p = ctx.getPositionFromDOMInfo(hitResult.offsetNode.parentNode, hitResult.offset); + const p = ctx.getPositionFromDOMInfo(hitResult.offsetNode.parentNode, hitResult.offset); return { position: p, hitTarget: null @@ -903,7 +903,7 @@ export class MouseTargetFactory { let resultPosition: Position | null = null; let resultHitTarget: Element | null = null; - let textRange: IETextRange = (document.body).createTextRange(); + const textRange: IETextRange = (document.body).createTextRange(); try { textRange.moveToPoint(coords.clientX, coords.clientY); } catch (err) { @@ -916,14 +916,14 @@ export class MouseTargetFactory { textRange.collapse(true); // Now, let's do our best to figure out what we hit :) - let parentElement = textRange ? textRange.parentElement() : null; - let parent1 = parentElement ? parentElement.parentNode : null; - let parent2 = parent1 ? parent1.parentNode : null; + const parentElement = textRange ? textRange.parentElement() : null; + const parent1 = parentElement ? parentElement.parentNode : null; + const parent2 = parent1 ? parent1.parentNode : null; - let parent2ClassName = parent2 && parent2.nodeType === parent2.ELEMENT_NODE ? (parent2).className : ''; + const parent2ClassName = parent2 && parent2.nodeType === parent2.ELEMENT_NODE ? (parent2).className : ''; if (parent2ClassName === ViewLine.CLASS_NAME) { - let rangeToContainEntireSpan = textRange.duplicate(); + const rangeToContainEntireSpan = textRange.duplicate(); rangeToContainEntireSpan.moveToElementText(parentElement!); rangeToContainEntireSpan.setEndPoint('EndToStart', textRange); diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts index f3ee18724a..a4f73d442e 100644 --- a/src/vs/editor/browser/controller/pointerHandler.ts +++ b/src/vs/editor/browser/controller/pointerHandler.ts @@ -18,7 +18,7 @@ interface IThrottledGestureEvent { } function gestureChangeEventMerger(lastEvent: IThrottledGestureEvent, currentEvent: MSGestureEvent): IThrottledGestureEvent { - let r = { + const r = { translationY: currentEvent.translationY, translationX: currentEvent.translationX }; @@ -48,13 +48,13 @@ class MsPointerHandler extends MouseHandler implements IDisposable { this._installGestureHandlerTimeout = window.setTimeout(() => { this._installGestureHandlerTimeout = -1; if ((window).MSGesture) { - let touchGesture = new MSGesture(); - let penGesture = new MSGesture(); + const touchGesture = new MSGesture(); + const penGesture = new MSGesture(); touchGesture.target = this.viewHelper.linesContentDomNode; penGesture.target = this.viewHelper.linesContentDomNode; this.viewHelper.linesContentDomNode.addEventListener('MSPointerDown', (e: MSPointerEvent) => { // Circumvent IE11 breaking change in e.pointerType & TypeScript's stale definitions - let pointerType = e.pointerType; + const pointerType = e.pointerType; if (pointerType === ((e).MSPOINTER_TYPE_MOUSE || 'mouse')) { this._lastPointerType = 'mouse'; return; @@ -80,8 +80,8 @@ class MsPointerHandler extends MouseHandler implements IDisposable { } private _onCaptureGestureTap(rawEvent: MSGestureEvent): void { - let e = new EditorMouseEvent(rawEvent, this.viewHelper.viewDomNode); - let t = this._createMouseTarget(e, false); + const e = new EditorMouseEvent(rawEvent, this.viewHelper.viewDomNode); + const t = this._createMouseTarget(e, false); if (t.position) { this.viewController.moveTo(t.position); } @@ -127,12 +127,12 @@ class StandardPointerHandler extends MouseHandler implements IDisposable { // TODO@Alex: replace the usage of MSGesture here with something that works across all browsers if ((window).MSGesture) { - let touchGesture = new MSGesture(); - let penGesture = new MSGesture(); + const touchGesture = new MSGesture(); + const penGesture = new MSGesture(); touchGesture.target = this.viewHelper.linesContentDomNode; penGesture.target = this.viewHelper.linesContentDomNode; this.viewHelper.linesContentDomNode.addEventListener('pointerdown', (e: MSPointerEvent) => { - let pointerType = e.pointerType; + const pointerType = e.pointerType; if (pointerType === 'mouse') { this._lastPointerType = 'mouse'; return; @@ -158,8 +158,8 @@ class StandardPointerHandler extends MouseHandler implements IDisposable { } private _onCaptureGestureTap(rawEvent: MSGestureEvent): void { - let e = new EditorMouseEvent(rawEvent, this.viewHelper.viewDomNode); - let t = this._createMouseTarget(e, false); + const e = new EditorMouseEvent(rawEvent, this.viewHelper.viewDomNode); + const t = this._createMouseTarget(e, false); if (t.position) { this.viewController.moveTo(t.position); } @@ -207,7 +207,7 @@ class TouchHandler extends MouseHandler { this.viewHelper.focusTextArea(); - let target = this._createMouseTarget(new EditorMouseEvent(event, this.viewHelper.viewDomNode), false); + const target = this._createMouseTarget(new EditorMouseEvent(event, this.viewHelper.viewDomNode), false); if (target.position) { this.viewController.moveTo(target.position); @@ -220,7 +220,7 @@ class TouchHandler extends MouseHandler { } export class PointerHandler implements IDisposable { - private handler: MouseHandler; + private readonly handler: MouseHandler; constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) { if (window.navigator.msPointerEnabled) { diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 0916686444..c1d7217028 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -27,6 +27,7 @@ import { EndOfLinePreference } from 'vs/editor/common/model'; import { HorizontalRange, RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/view/renderingContext'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; +import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; export interface ITextAreaHandlerHelper { visibleRangeForPositionRelativeToEditor(lineNumber: number, column: number): HorizontalRange | null; @@ -90,7 +91,7 @@ export class TextAreaHandler extends ViewPart { private readonly _viewController: ViewController; private readonly _viewHelper: ITextAreaHandlerHelper; - private _accessibilitySupport: platform.AccessibilitySupport; + private _accessibilitySupport: AccessibilitySupport; private _contentLeft: number; private _contentWidth: number; private _contentHeight: number; @@ -206,7 +207,7 @@ export class TextAreaHandler extends ViewPart { return TextAreaState.EMPTY; } - if (this._accessibilitySupport === platform.AccessibilitySupport.Disabled) { + if (this._accessibilitySupport === AccessibilitySupport.Disabled) { // We know for a fact that a screen reader is not attached // On OSX, we write the character before the cursor to allow for "long-press" composition // Also on OSX, we write the word before the cursor to allow for the Accessibility Keyboard to give good hints @@ -228,7 +229,7 @@ export class TextAreaHandler extends ViewPart { return TextAreaState.EMPTY; } - return PagedScreenReaderStrategy.fromEditorSelection(currentState, simpleModel, this._selections[0], this._accessibilitySupport === platform.AccessibilitySupport.Unknown); + return PagedScreenReaderStrategy.fromEditorSelection(currentState, simpleModel, this._selections[0], this._accessibilitySupport === AccessibilitySupport.Unknown); }, deduceModelPosition: (viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position => { @@ -445,7 +446,7 @@ export class TextAreaHandler extends ViewPart { private _primaryCursorVisibleRange: HorizontalRange | null = null; public prepareRender(ctx: RenderingContext): void { - if (this._accessibilitySupport === platform.AccessibilitySupport.Enabled) { + if (this._accessibilitySupport === AccessibilitySupport.Enabled) { // Do not move the textarea with the cursor, as this generates accessibility events that might confuse screen readers // See https://github.com/Microsoft/vscode/issues/26730 this._primaryCursorVisibleRange = null; diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index 598642b2cd..971c6071dc 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -10,7 +10,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import * as platform from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { ITextAreaWrapper, ITypeData, TextAreaState } from 'vs/editor/browser/controller/textAreaState'; @@ -105,6 +105,7 @@ export class TextAreaInput extends Disposable { private readonly _asyncTriggerCut: RunOnceScheduler; private _textAreaState: TextAreaState; + private _selectionChangeListener: IDisposable | null; private _hasFocus: boolean; private _isDoingComposition: boolean; @@ -330,8 +331,9 @@ export class TextAreaInput extends Disposable { this._lastTextAreaEvent = TextAreaInputEventType.blur; this._setHasFocus(false); })); + } - + private _installSelectionChangeListener(): IDisposable { // See https://github.com/Microsoft/vscode/issues/27216 // When using a Braille display, it is possible for users to reposition the // system caret. This is reflected in Chrome as a `selectionchange` event. @@ -351,7 +353,7 @@ export class TextAreaInput extends Disposable { // `selectionchange` events often come multiple times for a single logical change // so throttle multiple `selectionchange` events that burst in a short period of time. let previousSelectionChangeEventTime = 0; - this._register(dom.addDisposableListener(document, 'selectionchange', (e) => { + return dom.addDisposableListener(document, 'selectionchange', (e) => { if (!this._hasFocus) { return; } @@ -411,11 +413,15 @@ export class TextAreaInput extends Disposable { ); this._onSelectionChangeRequest.fire(newSelection); - })); + }); } public dispose(): void { super.dispose(); + if (this._selectionChangeListener) { + this._selectionChangeListener.dispose(); + this._selectionChangeListener = null; + } } public focusTextArea(): void { @@ -435,6 +441,14 @@ export class TextAreaInput extends Disposable { } this._hasFocus = newHasFocus; + if (this._selectionChangeListener) { + this._selectionChangeListener.dispose(); + this._selectionChangeListener = null; + } + if (this._hasFocus) { + this._selectionChangeListener = this._installSelectionChangeListener(); + } + if (this._hasFocus) { if (browser.isEdge) { // Edge has a bug where setting the selection range while the focus event diff --git a/src/vs/editor/browser/controller/textAreaState.ts b/src/vs/editor/browser/controller/textAreaState.ts index 7b41b750ce..2f7730da8a 100644 --- a/src/vs/editor/browser/controller/textAreaState.ts +++ b/src/vs/editor/browser/controller/textAreaState.ts @@ -233,26 +233,26 @@ export class PagedScreenReaderStrategy { } private static _getRangeForPage(page: number): Range { - let offset = page * PagedScreenReaderStrategy._LINES_PER_PAGE; - let startLineNumber = offset + 1; - let endLineNumber = offset + PagedScreenReaderStrategy._LINES_PER_PAGE; + const offset = page * PagedScreenReaderStrategy._LINES_PER_PAGE; + const startLineNumber = offset + 1; + const endLineNumber = offset + PagedScreenReaderStrategy._LINES_PER_PAGE; return new Range(startLineNumber, 1, endLineNumber + 1, 1); } public static fromEditorSelection(previousState: TextAreaState, model: ISimpleModel, selection: Range, trimLongText: boolean): TextAreaState { - let selectionStartPage = PagedScreenReaderStrategy._getPageOfLine(selection.startLineNumber); - let selectionStartPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionStartPage); + const selectionStartPage = PagedScreenReaderStrategy._getPageOfLine(selection.startLineNumber); + const selectionStartPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionStartPage); - let selectionEndPage = PagedScreenReaderStrategy._getPageOfLine(selection.endLineNumber); - let selectionEndPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionEndPage); + const selectionEndPage = PagedScreenReaderStrategy._getPageOfLine(selection.endLineNumber); + const selectionEndPageRange = PagedScreenReaderStrategy._getRangeForPage(selectionEndPage); - let pretextRange = selectionStartPageRange.intersectRanges(new Range(1, 1, selection.startLineNumber, selection.startColumn))!; + const pretextRange = selectionStartPageRange.intersectRanges(new Range(1, 1, selection.startLineNumber, selection.startColumn))!; let pretext = model.getValueInRange(pretextRange, EndOfLinePreference.LF); - let lastLine = model.getLineCount(); - let lastLineMaxColumn = model.getLineMaxColumn(lastLine); - let posttextRange = selectionEndPageRange.intersectRanges(new Range(selection.endLineNumber, selection.endColumn, lastLine, lastLineMaxColumn))!; + const lastLine = model.getLineCount(); + const lastLineMaxColumn = model.getLineMaxColumn(lastLine); + const posttextRange = selectionEndPageRange.intersectRanges(new Range(selection.endLineNumber, selection.endColumn, lastLine, lastLineMaxColumn))!; let posttext = model.getValueInRange(posttextRange, EndOfLinePreference.LF); @@ -261,8 +261,8 @@ export class PagedScreenReaderStrategy { // take full selection text = model.getValueInRange(selection, EndOfLinePreference.LF); } else { - let selectionRange1 = selectionStartPageRange.intersectRanges(selection)!; - let selectionRange2 = selectionEndPageRange.intersectRanges(selection)!; + const selectionRange1 = selectionStartPageRange.intersectRanges(selection)!; + const selectionRange2 = selectionEndPageRange.intersectRanges(selection)!; text = ( model.getValueInRange(selectionRange1, EndOfLinePreference.LF) + String.fromCharCode(8230) diff --git a/src/vs/editor/browser/core/editorState.ts b/src/vs/editor/browser/core/editorState.ts index b9c006730a..ada53b59a2 100644 --- a/src/vs/editor/browser/core/editorState.ts +++ b/src/vs/editor/browser/core/editorState.ts @@ -29,7 +29,7 @@ export class EditorState { this.flags = flags; if ((this.flags & CodeEditorStateFlag.Value) !== 0) { - let model = editor.getModel(); + const model = editor.getModel(); this.modelVersionId = model ? strings.format('{0}#{1}', model.uri.toString(), model.getVersionId()) : null; } if ((this.flags & CodeEditorStateFlag.Position) !== 0) { @@ -49,7 +49,7 @@ export class EditorState { if (!(other instanceof EditorState)) { return false; } - let state = other; + const state = other; if (this.modelVersionId !== state.modelVersionId) { return false; diff --git a/src/vs/editor/browser/editorDom.ts b/src/vs/editor/browser/editorDom.ts index 856171e11e..acbb8a7612 100644 --- a/src/vs/editor/browser/editorDom.ts +++ b/src/vs/editor/browser/editorDom.ts @@ -59,7 +59,7 @@ export class EditorPagePosition { } export function createEditorPagePosition(editorViewDomNode: HTMLElement): EditorPagePosition { - let editorPos = dom.getDomNodePagePosition(editorViewDomNode); + const editorPos = dom.getDomNodePagePosition(editorViewDomNode); return new EditorPagePosition(editorPos.left, editorPos.top, editorPos.width, editorPos.height); } @@ -89,7 +89,7 @@ export interface EditorMouseEventMerger { export class EditorMouseEventFactory { - private _editorViewDomNode: HTMLElement; + private readonly _editorViewDomNode: HTMLElement; constructor(editorViewDomNode: HTMLElement) { this._editorViewDomNode = editorViewDomNode; @@ -124,7 +124,7 @@ export class EditorMouseEventFactory { } public onMouseMoveThrottled(target: HTMLElement, callback: (e: EditorMouseEvent) => void, merger: EditorMouseEventMerger, minimumTimeMs: number): IDisposable { - let myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent, currentEvent: MouseEvent): EditorMouseEvent => { + const myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent, currentEvent: MouseEvent): EditorMouseEvent => { return merger(lastEvent, this._create(currentEvent)); }; return dom.addDisposableThrottledListener(target, 'mousemove', callback, myMerger, minimumTimeMs); @@ -133,8 +133,8 @@ export class EditorMouseEventFactory { export class GlobalEditorMouseMoveMonitor extends Disposable { - private _editorViewDomNode: HTMLElement; - private _globalMouseMoveMonitor: GlobalMouseMoveMonitor; + private readonly _editorViewDomNode: HTMLElement; + private readonly _globalMouseMoveMonitor: GlobalMouseMoveMonitor; private _keydownListener: IDisposable | null; constructor(editorViewDomNode: HTMLElement) { @@ -157,7 +157,7 @@ export class GlobalEditorMouseMoveMonitor extends Disposable { this._globalMouseMoveMonitor.stopMonitoring(true); }, true); - let myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent, currentEvent: MouseEvent): EditorMouseEvent => { + const myMerger: dom.IEventMerger = (lastEvent: EditorMouseEvent, currentEvent: MouseEvent): EditorMouseEvent => { return merger(lastEvent, new EditorMouseEvent(currentEvent, this._editorViewDomNode)); }; diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 16c1e74a5a..e38e71c17f 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -127,7 +127,7 @@ export abstract class EditorCommand extends Command { */ public static bindToContribution(controllerGetter: (editor: ICodeEditor) => T): EditorControllerCommand { return class EditorControllerCommandImpl extends EditorCommand { - private _callback: (controller: T, args: any) => void; + private readonly _callback: (controller: T, args: any) => void; constructor(opts: IContributionCommandOptions) { super(opts); @@ -136,7 +136,7 @@ export abstract class EditorCommand extends Command { } public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void { - let controller = controllerGetter(editor); + const controller = controllerGetter(editor); if (controller) { this._callback(controllerGetter(editor), args); } @@ -148,7 +148,7 @@ export abstract class EditorCommand extends Command { const codeEditorService = accessor.get(ICodeEditorService); // Find the editor with text focus or active - let editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor(); + const editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor(); if (!editor) { // well, at least we tried... return; @@ -184,9 +184,9 @@ export interface IActionOptions extends ICommandOptions { } export abstract class EditorAction extends EditorCommand { - public label: string; - public alias: string; - private menuOpts: IEditorCommandMenuOptions | undefined; + public readonly label: string; + public readonly alias: string; + private readonly menuOpts: IEditorCommandMenuOptions | undefined; constructor(opts: IActionOptions) { super(opts); @@ -267,7 +267,7 @@ export function registerDefaultLanguageCommand(id: string, handler: (model: ITex return accessor.get(ITextModelService).createModelReference(resource).then(reference => { return new Promise((resolve, reject) => { try { - let result = handler(reference.object.textEditorModel, Position.lift(position), args); + const result = handler(reference.object.textEditorModel, Position.lift(position), args); resolve(result); } catch (err) { reject(err); @@ -320,9 +320,9 @@ class EditorContributionRegistry { public static readonly INSTANCE = new EditorContributionRegistry(); - private editorContributions: IEditorContributionCtor[]; - private editorActions: EditorAction[]; - private editorCommands: { [commandId: string]: EditorCommand; }; + private readonly editorContributions: IEditorContributionCtor[]; + private readonly editorActions: EditorAction[]; + private readonly editorCommands: { [commandId: string]: EditorCommand; }; constructor() { this.editorContributions = []; diff --git a/src/vs/editor/browser/services/abstractCodeEditorService.ts b/src/vs/editor/browser/services/abstractCodeEditorService.ts index 966514d5ac..fab31e80c7 100644 --- a/src/vs/editor/browser/services/abstractCodeEditorService.ts +++ b/src/vs/editor/browser/services/abstractCodeEditorService.ts @@ -31,8 +31,8 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC public readonly onDidChangeTransientModelProperty: Event = this._onDidChangeTransientModelProperty.event; - private _codeEditors: { [editorId: string]: ICodeEditor; }; - private _diffEditors: { [editorId: string]: IDiffEditor; }; + private readonly _codeEditors: { [editorId: string]: ICodeEditor; }; + private readonly _diffEditors: { [editorId: string]: IDiffEditor; }; constructor() { super(); @@ -73,7 +73,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC getFocusedCodeEditor(): ICodeEditor | null { let editorWithWidgetFocus: ICodeEditor | null = null; - let editors = this.listCodeEditors(); + const editors = this.listCodeEditors(); for (const editor of editors) { if (editor.hasTextFocus()) { @@ -93,7 +93,7 @@ export abstract class AbstractCodeEditorService extends Disposable implements IC abstract removeDecorationType(key: string): void; abstract resolveDecorationOptions(decorationTypeKey: string | undefined, writable: boolean): IModelDecorationOptions; - private _transientWatchers: { [uri: string]: ModelTransientSettingWatcher; } = {}; + private readonly _transientWatchers: { [uri: string]: ModelTransientSettingWatcher; } = {}; public setTransientModelProperty(model: ITextModel, key: string, value: any): void { const uri = model.uri.toString(); diff --git a/src/vs/editor/browser/services/bulkEditService.ts b/src/vs/editor/browser/services/bulkEditService.ts index 4ad5f4bd7c..18d758279c 100644 --- a/src/vs/editor/browser/services/bulkEditService.ts +++ b/src/vs/editor/browser/services/bulkEditService.ts @@ -23,6 +23,6 @@ export interface IBulkEditResult { export interface IBulkEditService { _serviceBrand: any; - apply(edit: WorkspaceEdit, options: IBulkEditOptions): Promise; + apply(edit: WorkspaceEdit, options?: IBulkEditOptions): Promise; } diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index 5653dbbd48..7a987f43dc 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -16,9 +16,9 @@ import { ITheme, IThemeService, ThemeColor } from 'vs/platform/theme/common/them export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { - private _styleSheet: HTMLStyleElement; - private _decorationOptionProviders: { [key: string]: IModelDecorationOptionsProvider }; - private _themeService: IThemeService; + private readonly _styleSheet: HTMLStyleElement; + private readonly _decorationOptionProviders: { [key: string]: IModelDecorationOptionsProvider }; + private readonly _themeService: IThemeService; constructor(@IThemeService themeService: IThemeService, styleSheet = dom.createStyleSheet()) { super(); @@ -30,7 +30,7 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { public registerDecorationType(key: string, options: IDecorationRenderOptions, parentTypeKey?: string): void { let provider = this._decorationOptionProviders[key]; if (!provider) { - let providerArgs: ProviderArguments = { + const providerArgs: ProviderArguments = { styleSheet: this._styleSheet, key: key, parentTypeKey: parentTypeKey, @@ -47,7 +47,7 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { } public removeDecorationType(key: string): void { - let provider = this._decorationOptionProviders[key]; + const provider = this._decorationOptionProviders[key]; if (provider) { provider.refCount--; if (provider.refCount <= 0) { @@ -59,7 +59,7 @@ export abstract class CodeEditorServiceImpl extends AbstractCodeEditorService { } public resolveDecorationOptions(decorationTypeKey: string, writable: boolean): IModelDecorationOptions { - let provider = this._decorationOptionProviders[decorationTypeKey]; + const provider = this._decorationOptionProviders[decorationTypeKey]; if (!provider) { throw new Error('Unknown decoration type key: ' + decorationTypeKey); } @@ -79,7 +79,7 @@ class DecorationSubTypeOptionsProvider implements IModelDecorationOptionsProvide public refCount: number; - private _parentTypeKey: string | undefined; + private readonly _parentTypeKey: string | undefined; private _beforeContentRules: DecorationCSSRules | null; private _afterContentRules: DecorationCSSRules | null; @@ -92,7 +92,7 @@ class DecorationSubTypeOptionsProvider implements IModelDecorationOptionsProvide } public getOptions(codeEditorService: AbstractCodeEditorService, writable: boolean): IModelDecorationOptions { - let options = codeEditorService.resolveDecorationOptions(this._parentTypeKey, true); + const options = codeEditorService.resolveDecorationOptions(this._parentTypeKey, true); if (this._beforeContentRules) { options.beforeContentClassName = this._beforeContentRules.className; } @@ -168,12 +168,12 @@ class DecorationTypeOptionsProvider implements IModelDecorationOptionsProvider { this.afterContentClassName = createCSSRules(ModelDecorationCSSRuleType.AfterContentClassName); this.glyphMarginClassName = createCSSRules(ModelDecorationCSSRuleType.GlyphMarginClassName); - let options = providerArgs.options; + const options = providerArgs.options; this.isWholeLine = Boolean(options.isWholeLine); this.stickiness = options.rangeBehavior; - let lightOverviewRulerColor = options.light && options.light.overviewRulerColor || options.overviewRulerColor; - let darkOverviewRulerColor = options.dark && options.dark.overviewRulerColor || options.overviewRulerColor; + const lightOverviewRulerColor = options.light && options.light.overviewRulerColor || options.overviewRulerColor; + const darkOverviewRulerColor = options.dark && options.dark.overviewRulerColor || options.overviewRulerColor; if ( typeof lightOverviewRulerColor !== 'undefined' || typeof darkOverviewRulerColor !== 'undefined' @@ -245,13 +245,13 @@ const _CSS_MAP: { [prop: string]: string; } = { class DecorationCSSRules { private _theme: ITheme; - private _className: string; - private _unThemedSelector: string; + private readonly _className: string; + private readonly _unThemedSelector: string; private _hasContent: boolean; private _hasLetterSpacing: boolean; - private _ruleType: ModelDecorationCSSRuleType; + private readonly _ruleType: ModelDecorationCSSRuleType; private _themeListener: IDisposable | null; - private _providerArgs: ProviderArguments; + private readonly _providerArgs: ProviderArguments; private _usesThemeColors: boolean; public constructor(ruleType: ModelDecorationCSSRuleType, providerArgs: ProviderArguments, themeService: IThemeService) { @@ -307,7 +307,7 @@ class DecorationCSSRules { } private _buildCSS(): void { - let options = this._providerArgs.options; + const options = this._providerArgs.options; let unthemedCSS: string, lightCSS: string, darkCSS: string; switch (this._ruleType) { case ModelDecorationCSSRuleType.ClassName: @@ -338,7 +338,7 @@ class DecorationCSSRules { default: throw new Error('Unknown rule type: ' + this._ruleType); } - let sheet = this._providerArgs.styleSheet.sheet; + const sheet = this._providerArgs.styleSheet.sheet; let hasContent = false; if (unthemedCSS.length > 0) { @@ -367,7 +367,7 @@ class DecorationCSSRules { if (!opts) { return ''; } - let cssTextArr: string[] = []; + const cssTextArr: string[] = []; this.collectCSSText(opts, ['backgroundColor'], cssTextArr); this.collectCSSText(opts, ['outline', 'outlineColor', 'outlineStyle', 'outlineWidth'], cssTextArr); this.collectBorderSettingsCSSText(opts, cssTextArr); @@ -381,7 +381,7 @@ class DecorationCSSRules { if (!opts) { return ''; } - let cssTextArr: string[] = []; + const cssTextArr: string[] = []; this.collectCSSText(opts, ['fontStyle', 'fontWeight', 'textDecoration', 'cursor', 'color', 'opacity', 'letterSpacing'], cssTextArr); if (opts.letterSpacing) { this._hasLetterSpacing = true; @@ -396,7 +396,7 @@ class DecorationCSSRules { if (!opts) { return ''; } - let cssTextArr: string[] = []; + const cssTextArr: string[] = []; if (typeof opts !== 'undefined') { this.collectBorderSettingsCSSText(opts, cssTextArr); @@ -425,7 +425,7 @@ class DecorationCSSRules { if (!opts) { return ''; } - let cssTextArr: string[] = []; + const cssTextArr: string[] = []; if (typeof opts.gutterIconPath !== 'undefined') { cssTextArr.push(strings.format(_CSS_MAP.gutterIconPath, URI.revive(opts.gutterIconPath).toString(true).replace(/'/g, '%27'))); @@ -446,9 +446,9 @@ class DecorationCSSRules { } private collectCSSText(opts: any, properties: string[], cssTextArr: string[]): boolean { - let lenBefore = cssTextArr.length; + const lenBefore = cssTextArr.length; for (let property of properties) { - let value = this.resolveValue(opts[property]); + const value = this.resolveValue(opts[property]); if (typeof value === 'string') { cssTextArr.push(strings.format(_CSS_MAP[property], value)); } @@ -459,7 +459,7 @@ class DecorationCSSRules { private resolveValue(value: string | ThemeColor): string { if (isThemeColor(value)) { this._usesThemeColors = true; - let color = this._theme.getColor(value.id); + const color = this._theme.getColor(value.id); if (color) { return color.toString(); } diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index e38965ad70..f87f37b539 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -11,6 +11,7 @@ import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { equalsIgnoreCase } from 'vs/base/common/strings'; export class OpenerService implements IOpenerService { @@ -30,13 +31,14 @@ export class OpenerService implements IOpenerService { if (!scheme) { // no scheme ?!? return Promise.resolve(false); + } - } else if (scheme === Schemas.http || scheme === Schemas.https || scheme === Schemas.mailto) { + if (equalsIgnoreCase(scheme, Schemas.http) || equalsIgnoreCase(scheme, Schemas.https) || equalsIgnoreCase(scheme, Schemas.mailto)) { // open http or default mail application dom.windowOpenNoOpener(resource.toString(true)); return Promise.resolve(true); - } else if (scheme === Schemas.command) { + } else if (equalsIgnoreCase(scheme, Schemas.command)) { // run command or bail out if command isn't known if (!CommandsRegistry.getCommand(path)) { return Promise.reject(`command '${path}' NOT known`); diff --git a/src/vs/editor/browser/view/viewController.ts b/src/vs/editor/browser/view/viewController.ts index d1cd4f7fbb..f6a7aad032 100644 --- a/src/vs/editor/browser/view/viewController.ts +++ b/src/vs/editor/browser/view/viewController.ts @@ -98,7 +98,7 @@ export class ViewController { } private _validateViewColumn(viewPosition: Position): Position { - let minColumn = this.viewModel.getLineMinColumn(viewPosition.lineNumber); + const minColumn = this.viewModel.getLineMinColumn(viewPosition.lineNumber); if (viewPosition.column < minColumn) { return new Position(viewPosition.lineNumber, minColumn); } @@ -132,7 +132,7 @@ export class ViewController { public dispatchMouse(data: IMouseDispatchData): void { if (data.middleButton) { if (data.inSelectionMode) { - this.columnSelect(data.position, data.mouseColumn); + this._columnSelect(data.position, data.mouseColumn); } else { this.moveTo(data.position); } @@ -140,60 +140,64 @@ export class ViewController { // If the dragging started on the gutter, then have operations work on the entire line if (this._hasMulticursorModifier(data)) { if (data.inSelectionMode) { - this.lastCursorLineSelect(data.position); + this._lastCursorLineSelect(data.position); } else { - this.createCursor(data.position, true); + this._createCursor(data.position, true); } } else { if (data.inSelectionMode) { - this.lineSelectDrag(data.position); + this._lineSelectDrag(data.position); } else { - this.lineSelect(data.position); + this._lineSelect(data.position); } } } else if (data.mouseDownCount >= 4) { - this.selectAll(); + this._selectAll(); } else if (data.mouseDownCount === 3) { if (this._hasMulticursorModifier(data)) { if (data.inSelectionMode) { - this.lastCursorLineSelectDrag(data.position); + this._lastCursorLineSelectDrag(data.position); } else { - this.lastCursorLineSelect(data.position); + this._lastCursorLineSelect(data.position); } } else { if (data.inSelectionMode) { - this.lineSelectDrag(data.position); + this._lineSelectDrag(data.position); } else { - this.lineSelect(data.position); + this._lineSelect(data.position); } } } else if (data.mouseDownCount === 2) { if (this._hasMulticursorModifier(data)) { - this.lastCursorWordSelect(data.position); + this._lastCursorWordSelect(data.position); } else { if (data.inSelectionMode) { - this.wordSelectDrag(data.position); + this._wordSelectDrag(data.position); } else { - this.wordSelect(data.position); + this._wordSelect(data.position); } } } else { if (this._hasMulticursorModifier(data)) { if (!this._hasNonMulticursorModifier(data)) { if (data.shiftKey) { - this.columnSelect(data.position, data.mouseColumn); + this._columnSelect(data.position, data.mouseColumn); } else { // Do multi-cursor operations only when purely alt is pressed if (data.inSelectionMode) { - this.lastCursorMoveToSelect(data.position); + this._lastCursorMoveToSelect(data.position); } else { - this.createCursor(data.position, false); + this._createCursor(data.position, false); } } } } else { if (data.inSelectionMode) { - this.moveToSelect(data.position); + if (data.altKey) { + this._columnSelect(data.position, data.mouseColumn); + } else { + this._moveToSelect(data.position); + } } else { this.moveTo(data.position); } @@ -204,7 +208,7 @@ export class ViewController { private _usualArgs(viewPosition: Position) { viewPosition = this._validateViewColumn(viewPosition); return { - position: this.convertViewToModelPosition(viewPosition), + position: this._convertViewToModelPosition(viewPosition), viewPosition: viewPosition }; } @@ -213,67 +217,67 @@ export class ViewController { this._execMouseCommand(CoreNavigationCommands.MoveTo, this._usualArgs(viewPosition)); } - private moveToSelect(viewPosition: Position): void { + private _moveToSelect(viewPosition: Position): void { this._execMouseCommand(CoreNavigationCommands.MoveToSelect, this._usualArgs(viewPosition)); } - private columnSelect(viewPosition: Position, mouseColumn: number): void { + private _columnSelect(viewPosition: Position, mouseColumn: number): void { viewPosition = this._validateViewColumn(viewPosition); this._execMouseCommand(CoreNavigationCommands.ColumnSelect, { - position: this.convertViewToModelPosition(viewPosition), + position: this._convertViewToModelPosition(viewPosition), viewPosition: viewPosition, mouseColumn: mouseColumn }); } - private createCursor(viewPosition: Position, wholeLine: boolean): void { + private _createCursor(viewPosition: Position, wholeLine: boolean): void { viewPosition = this._validateViewColumn(viewPosition); this._execMouseCommand(CoreNavigationCommands.CreateCursor, { - position: this.convertViewToModelPosition(viewPosition), + position: this._convertViewToModelPosition(viewPosition), viewPosition: viewPosition, wholeLine: wholeLine }); } - private lastCursorMoveToSelect(viewPosition: Position): void { + private _lastCursorMoveToSelect(viewPosition: Position): void { this._execMouseCommand(CoreNavigationCommands.LastCursorMoveToSelect, this._usualArgs(viewPosition)); } - private wordSelect(viewPosition: Position): void { + private _wordSelect(viewPosition: Position): void { this._execMouseCommand(CoreNavigationCommands.WordSelect, this._usualArgs(viewPosition)); } - private wordSelectDrag(viewPosition: Position): void { + private _wordSelectDrag(viewPosition: Position): void { this._execMouseCommand(CoreNavigationCommands.WordSelectDrag, this._usualArgs(viewPosition)); } - private lastCursorWordSelect(viewPosition: Position): void { + private _lastCursorWordSelect(viewPosition: Position): void { this._execMouseCommand(CoreNavigationCommands.LastCursorWordSelect, this._usualArgs(viewPosition)); } - private lineSelect(viewPosition: Position): void { + private _lineSelect(viewPosition: Position): void { this._execMouseCommand(CoreNavigationCommands.LineSelect, this._usualArgs(viewPosition)); } - private lineSelectDrag(viewPosition: Position): void { + private _lineSelectDrag(viewPosition: Position): void { this._execMouseCommand(CoreNavigationCommands.LineSelectDrag, this._usualArgs(viewPosition)); } - private lastCursorLineSelect(viewPosition: Position): void { + private _lastCursorLineSelect(viewPosition: Position): void { this._execMouseCommand(CoreNavigationCommands.LastCursorLineSelect, this._usualArgs(viewPosition)); } - private lastCursorLineSelectDrag(viewPosition: Position): void { + private _lastCursorLineSelectDrag(viewPosition: Position): void { this._execMouseCommand(CoreNavigationCommands.LastCursorLineSelectDrag, this._usualArgs(viewPosition)); } - private selectAll(): void { + private _selectAll(): void { this._execMouseCommand(CoreNavigationCommands.SelectAll, {}); } // ---------------------- - private convertViewToModelPosition(viewPosition: Position): Position { + private _convertViewToModelPosition(viewPosition: Position): Position { return this.viewModel.coordinatesConverter.convertViewPositionToModelPosition(viewPosition); } diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index d4b64c9099..32bc7cff95 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -63,11 +63,11 @@ const invalidFunc = () => { throw new Error(`Invalid change accessor`); }; export class View extends ViewEventHandler { - private eventDispatcher: ViewEventDispatcher; + private readonly eventDispatcher: ViewEventDispatcher; private _scrollbar: EditorScrollbar; - private _context: ViewContext; - private _cursor: Cursor; + private readonly _context: ViewContext; + private readonly _cursor: Cursor; // The view lines private viewLines: ViewLines; @@ -105,7 +105,7 @@ export class View extends ViewEventHandler { this._renderAnimationFrame = null; this.outgoingEvents = outgoingEvents; - let viewController = new ViewController(configuration, model, this.outgoingEvents, commandDelegate); + const viewController = new ViewController(configuration, model, this.outgoingEvents, commandDelegate); // The event dispatcher will always go through _renderOnce before dispatching any events this.eventDispatcher = new ViewEventDispatcher((callback: () => void) => this._renderOnce(callback)); @@ -167,21 +167,21 @@ export class View extends ViewEventHandler { this.viewParts.push(this.viewZones); // Decorations overview ruler - let decorationsOverviewRuler = new DecorationsOverviewRuler(this._context); + const decorationsOverviewRuler = new DecorationsOverviewRuler(this._context); this.viewParts.push(decorationsOverviewRuler); - let scrollDecoration = new ScrollDecorationViewPart(this._context); + const scrollDecoration = new ScrollDecorationViewPart(this._context); this.viewParts.push(scrollDecoration); - let contentViewOverlays = new ContentViewOverlays(this._context); + const contentViewOverlays = new ContentViewOverlays(this._context); this.viewParts.push(contentViewOverlays); contentViewOverlays.addDynamicOverlay(new CurrentLineHighlightOverlay(this._context)); contentViewOverlays.addDynamicOverlay(new SelectionsOverlay(this._context)); contentViewOverlays.addDynamicOverlay(new IndentGuidesOverlay(this._context)); contentViewOverlays.addDynamicOverlay(new DecorationsOverlay(this._context)); - let marginViewOverlays = new MarginViewOverlays(this._context); + const marginViewOverlays = new MarginViewOverlays(this._context); this.viewParts.push(marginViewOverlays); marginViewOverlays.addDynamicOverlay(new CurrentLineMarginHighlightOverlay(this._context)); marginViewOverlays.addDynamicOverlay(new GlyphMarginOverlay(this._context)); @@ -189,7 +189,7 @@ export class View extends ViewEventHandler { marginViewOverlays.addDynamicOverlay(new LinesDecorationsOverlay(this._context)); marginViewOverlays.addDynamicOverlay(new LineNumbersOverlay(this._context)); - let margin = new Margin(this._context); + const margin = new Margin(this._context); margin.getDomNode().appendChild(this.viewZones.marginDomNode); margin.getDomNode().appendChild(marginViewOverlays.getDomNode()); this.viewParts.push(margin); @@ -205,16 +205,16 @@ export class View extends ViewEventHandler { this.overlayWidgets = new ViewOverlayWidgets(this._context); this.viewParts.push(this.overlayWidgets); - let rulers = new Rulers(this._context); + const rulers = new Rulers(this._context); this.viewParts.push(rulers); - let minimap = new Minimap(this._context); + const minimap = new Minimap(this._context); this.viewParts.push(minimap); // -------------- Wire dom nodes up if (decorationsOverviewRuler) { - let overviewRulerData = this._scrollbar.getOverviewRulerLayoutInfo(); + const overviewRulerData = this._scrollbar.getOverviewRulerLayoutInfo(); overviewRulerData.parent.insertBefore(decorationsOverviewRuler.getDomNode(), overviewRulerData.insertBefore); } @@ -297,7 +297,7 @@ export class View extends ViewEventHandler { } private getEditorClassName() { - let focused = this._textAreaHandler.isFocused() ? ' focused' : ''; + const focused = this._textAreaHandler.isFocused() ? ' focused' : ''; return this._context.configuration.editor.editorClassName + ' ' + getThemeTypeSelector(this._context.theme.type) + focused; } @@ -356,7 +356,7 @@ export class View extends ViewEventHandler { } private _renderOnce(callback: () => any): any { - let r = safeInvokeNoArg(callback); + const r = safeInvokeNoArg(callback); this._scheduleRender(); return r; } @@ -379,7 +379,7 @@ export class View extends ViewEventHandler { private _getViewPartsToRender(): ViewPart[] { let result: ViewPart[] = [], resultLen = 0; for (let i = 0, len = this.viewParts.length; i < len; i++) { - let viewPart = this.viewParts[i]; + const viewPart = this.viewParts[i]; if (viewPart.shouldRender()) { result[resultLen++] = viewPart; } @@ -402,7 +402,7 @@ export class View extends ViewEventHandler { const partialViewportData = this._context.viewLayout.getLinesViewportData(); this._context.model.setViewport(partialViewportData.startLineNumber, partialViewportData.endLineNumber, partialViewportData.centeredLineNumber); - let viewportData = new ViewportData( + const viewportData = new ViewportData( this._cursor.getViewSelections(), partialViewportData, this._context.viewLayout.getWhitespaceViewportData(), @@ -422,16 +422,16 @@ export class View extends ViewEventHandler { viewPartsToRender = this._getViewPartsToRender(); } - let renderingContext = new RenderingContext(this._context.viewLayout, viewportData, this.viewLines); + const renderingContext = new RenderingContext(this._context.viewLayout, viewportData, this.viewLines); // Render the rest of the parts for (let i = 0, len = viewPartsToRender.length; i < len; i++) { - let viewPart = viewPartsToRender[i]; + const viewPart = viewPartsToRender[i]; viewPart.prepareRender(renderingContext); } for (let i = 0, len = viewPartsToRender.length; i < len; i++) { - let viewPart = viewPartsToRender[i]; + const viewPart = viewPartsToRender[i]; viewPart.render(renderingContext); viewPart.onDidRender(); } @@ -452,11 +452,11 @@ export class View extends ViewEventHandler { } public getOffsetForColumn(modelLineNumber: number, modelColumn: number): number { - let modelPosition = this._context.model.validateModelPosition({ + const modelPosition = this._context.model.validateModelPosition({ lineNumber: modelLineNumber, column: modelColumn }); - let viewPosition = this._context.model.coordinatesConverter.convertModelPositionToViewPosition(modelPosition); + const viewPosition = this._context.model.coordinatesConverter.convertModelPositionToViewPosition(modelPosition); this._flushAccumulatedAndRenderNow(); const visibleRange = this.viewLines.visibleRangeForPosition(new Position(viewPosition.lineNumber, viewPosition.column)); if (!visibleRange) { @@ -477,7 +477,7 @@ export class View extends ViewEventHandler { let zonesHaveChanged = false; this._renderOnce(() => { - let changeAccessor: editorBrowser.IViewZoneChangeAccessor = { + const changeAccessor: editorBrowser.IViewZoneChangeAccessor = { addZone: (zone: editorBrowser.IViewZone): number => { zonesHaveChanged = true; return this.viewZones.addZone(zone); @@ -516,7 +516,7 @@ export class View extends ViewEventHandler { // Force everything to render... this.viewLines.forceShouldRender(); for (let i = 0, len = this.viewParts.length; i < len; i++) { - let viewPart = this.viewParts[i]; + const viewPart = this.viewParts[i]; viewPart.forceShouldRender(); } } @@ -542,9 +542,9 @@ export class View extends ViewEventHandler { } public layoutContentWidget(widgetData: IContentWidgetData): void { - let newPosition = widgetData.position ? widgetData.position.position : null; - let newRange = widgetData.position ? widgetData.position.range : null; - let newPreference = widgetData.position ? widgetData.position.preference : null; + const newPosition = widgetData.position ? widgetData.position.position : null; + const newRange = widgetData.position ? widgetData.position.range : null; + const newPreference = widgetData.position ? widgetData.position.preference : null; this.contentWidgets.setWidgetPosition(widgetData.widget, newPosition, newRange, newPreference); this._scheduleRender(); } @@ -561,8 +561,8 @@ export class View extends ViewEventHandler { } public layoutOverlayWidget(widgetData: IOverlayWidgetData): void { - let newPreference = widgetData.position ? widgetData.position.preference : null; - let shouldRender = this.overlayWidgets.setWidgetPosition(widgetData.widget, newPreference); + const newPreference = widgetData.position ? widgetData.position.preference : null; + const shouldRender = this.overlayWidgets.setWidgetPosition(widgetData.widget, newPreference); if (shouldRender) { this._scheduleRender(); } diff --git a/src/vs/editor/browser/view/viewLayer.ts b/src/vs/editor/browser/view/viewLayer.ts index 92aa15e76e..aee0bcb49f 100644 --- a/src/vs/editor/browser/view/viewLayer.ts +++ b/src/vs/editor/browser/view/viewLayer.ts @@ -77,7 +77,7 @@ export class RenderedLinesCollection { } public getLine(lineNumber: number): T { - let lineIndex = lineNumber - this._rendLineNumberStart; + const lineIndex = lineNumber - this._rendLineNumberStart; if (lineIndex < 0 || lineIndex >= this._lines.length) { throw new Error('Illegal value for lineNumber'); } @@ -93,12 +93,12 @@ export class RenderedLinesCollection { return null; } - let startLineNumber = this.getStartLineNumber(); - let endLineNumber = this.getEndLineNumber(); + const startLineNumber = this.getStartLineNumber(); + const endLineNumber = this.getEndLineNumber(); if (deleteToLineNumber < startLineNumber) { // deleting above the viewport - let deleteCnt = deleteToLineNumber - deleteFromLineNumber + 1; + const deleteCnt = deleteToLineNumber - deleteFromLineNumber + 1; this._rendLineNumberStart -= deleteCnt; return null; } @@ -112,7 +112,7 @@ export class RenderedLinesCollection { let deleteStartIndex = 0; let deleteCount = 0; for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { - let lineIndex = lineNumber - this._rendLineNumberStart; + const lineIndex = lineNumber - this._rendLineNumberStart; if (deleteFromLineNumber <= lineNumber && lineNumber <= deleteToLineNumber) { // this is a line to be deleted @@ -141,7 +141,7 @@ export class RenderedLinesCollection { this._rendLineNumberStart -= deleteAboveCount; } - let deleted = this._lines.splice(deleteStartIndex, deleteCount); + const deleted = this._lines.splice(deleteStartIndex, deleteCount); return deleted; } @@ -151,8 +151,8 @@ export class RenderedLinesCollection { return false; } - let startLineNumber = this.getStartLineNumber(); - let endLineNumber = this.getEndLineNumber(); + const startLineNumber = this.getStartLineNumber(); + const endLineNumber = this.getEndLineNumber(); let someoneNotified = false; @@ -173,9 +173,9 @@ export class RenderedLinesCollection { return null; } - let insertCnt = insertToLineNumber - insertFromLineNumber + 1; - let startLineNumber = this.getStartLineNumber(); - let endLineNumber = this.getEndLineNumber(); + const insertCnt = insertToLineNumber - insertFromLineNumber + 1; + const startLineNumber = this.getStartLineNumber(); + const endLineNumber = this.getEndLineNumber(); if (insertFromLineNumber <= startLineNumber) { // inserting above the viewport @@ -190,19 +190,19 @@ export class RenderedLinesCollection { if (insertCnt + insertFromLineNumber > endLineNumber) { // insert inside the viewport in such a way that all remaining lines are pushed outside - let deleted = this._lines.splice(insertFromLineNumber - this._rendLineNumberStart, endLineNumber - insertFromLineNumber + 1); + const deleted = this._lines.splice(insertFromLineNumber - this._rendLineNumberStart, endLineNumber - insertFromLineNumber + 1); return deleted; } // insert inside the viewport, push out some lines, but not all remaining lines - let newLines: T[] = []; + const newLines: T[] = []; for (let i = 0; i < insertCnt; i++) { newLines[i] = this._createLine(); } - let insertIndex = insertFromLineNumber - this._rendLineNumberStart; - let beforeLines = this._lines.slice(0, insertIndex); - let afterLines = this._lines.slice(insertIndex, this._lines.length - insertCnt); - let deletedLines = this._lines.slice(this._lines.length - insertCnt, this._lines.length); + const insertIndex = insertFromLineNumber - this._rendLineNumberStart; + const beforeLines = this._lines.slice(0, insertIndex); + const afterLines = this._lines.slice(insertIndex, this._lines.length - insertCnt); + const deletedLines = this._lines.slice(this._lines.length - insertCnt, this._lines.length); this._lines = beforeLines.concat(newLines).concat(afterLines); @@ -215,23 +215,23 @@ export class RenderedLinesCollection { return false; } - let startLineNumber = this.getStartLineNumber(); - let endLineNumber = this.getEndLineNumber(); + const startLineNumber = this.getStartLineNumber(); + const endLineNumber = this.getEndLineNumber(); let notifiedSomeone = false; for (let i = 0, len = ranges.length; i < len; i++) { - let rng = ranges[i]; + const rng = ranges[i]; if (rng.toLineNumber < startLineNumber || rng.fromLineNumber > endLineNumber) { // range outside viewport continue; } - let from = Math.max(startLineNumber, rng.fromLineNumber); - let to = Math.min(endLineNumber, rng.toLineNumber); + const from = Math.max(startLineNumber, rng.fromLineNumber); + const to = Math.min(endLineNumber, rng.toLineNumber); for (let lineNumber = from; lineNumber <= to; lineNumber++) { - let lineIndex = lineNumber - this._rendLineNumberStart; + const lineIndex = lineNumber - this._rendLineNumberStart; this._lines[lineIndex].onTokensChanged(); notifiedSomeone = true; } @@ -258,7 +258,7 @@ export class VisibleLinesCollection { } private _createDomNode(): FastDomNode { - let domNode = createFastDomNode(document.createElement('div')); + const domNode = createFastDomNode(document.createElement('div')); domNode.setClassName('view-layer'); domNode.setPosition('absolute'); domNode.domNode.setAttribute('role', 'presentation'); @@ -283,11 +283,11 @@ export class VisibleLinesCollection { } public onLinesDeleted(e: viewEvents.ViewLinesDeletedEvent): boolean { - let deleted = this._linesCollection.onLinesDeleted(e.fromLineNumber, e.toLineNumber); + const deleted = this._linesCollection.onLinesDeleted(e.fromLineNumber, e.toLineNumber); if (deleted) { // Remove from DOM for (let i = 0, len = deleted.length; i < len; i++) { - let lineDomNode = deleted[i].getDomNode(); + const lineDomNode = deleted[i].getDomNode(); if (lineDomNode) { this.domNode.domNode.removeChild(lineDomNode); } @@ -298,11 +298,11 @@ export class VisibleLinesCollection { } public onLinesInserted(e: viewEvents.ViewLinesInsertedEvent): boolean { - let deleted = this._linesCollection.onLinesInserted(e.fromLineNumber, e.toLineNumber); + const deleted = this._linesCollection.onLinesInserted(e.fromLineNumber, e.toLineNumber); if (deleted) { // Remove from DOM for (let i = 0, len = deleted.length; i < len; i++) { - let lineDomNode = deleted[i].getDomNode(); + const lineDomNode = deleted[i].getDomNode(); if (lineDomNode) { this.domNode.domNode.removeChild(lineDomNode); } @@ -340,18 +340,18 @@ export class VisibleLinesCollection { public renderLines(viewportData: ViewportData): void { - let inp = this._linesCollection._get(); + const inp = this._linesCollection._get(); - let renderer = new ViewLayerRenderer(this.domNode.domNode, this._host, viewportData); + const renderer = new ViewLayerRenderer(this.domNode.domNode, this._host, viewportData); - let ctx: IRendererContext = { + const ctx: IRendererContext = { rendLineNumberStart: inp.rendLineNumberStart, lines: inp.lines, linesLength: inp.lines.length }; // Decide if this render will do a single update (single large .innerHTML) or many updates (inserting/removing dom nodes) - let resCtx = renderer.render(ctx, viewportData.startLineNumber, viewportData.endLineNumber, viewportData.relativeVerticalOffset); + const resCtx = renderer.render(ctx, viewportData.startLineNumber, viewportData.endLineNumber, viewportData.relativeVerticalOffset); this._linesCollection._set(resCtx.rendLineNumberStart, resCtx.lines); } @@ -377,7 +377,7 @@ class ViewLayerRenderer { public render(inContext: IRendererContext, startLineNumber: number, stopLineNumber: number, deltaTop: number[]): IRendererContext { - let ctx: IRendererContext = { + const ctx: IRendererContext = { rendLineNumberStart: inContext.rendLineNumberStart, lines: inContext.lines.slice(0), linesLength: inContext.linesLength @@ -406,15 +406,15 @@ class ViewLayerRenderer { if (ctx.rendLineNumberStart > startLineNumber) { // Insert lines before - let fromLineNumber = startLineNumber; - let toLineNumber = Math.min(stopLineNumber, ctx.rendLineNumberStart - 1); + const fromLineNumber = startLineNumber; + const toLineNumber = Math.min(stopLineNumber, ctx.rendLineNumberStart - 1); if (fromLineNumber <= toLineNumber) { this._insertLinesBefore(ctx, fromLineNumber, toLineNumber, deltaTop, startLineNumber); ctx.linesLength += toLineNumber - fromLineNumber + 1; } } else if (ctx.rendLineNumberStart < startLineNumber) { // Remove lines before - let removeCnt = Math.min(ctx.linesLength, startLineNumber - ctx.rendLineNumberStart); + const removeCnt = Math.min(ctx.linesLength, startLineNumber - ctx.rendLineNumberStart); if (removeCnt > 0) { this._removeLinesBefore(ctx, removeCnt); ctx.linesLength -= removeCnt; @@ -425,8 +425,8 @@ class ViewLayerRenderer { if (ctx.rendLineNumberStart + ctx.linesLength - 1 < stopLineNumber) { // Insert lines after - let fromLineNumber = ctx.rendLineNumberStart + ctx.linesLength; - let toLineNumber = stopLineNumber; + const fromLineNumber = ctx.rendLineNumberStart + ctx.linesLength; + const toLineNumber = stopLineNumber; if (fromLineNumber <= toLineNumber) { this._insertLinesAfter(ctx, fromLineNumber, toLineNumber, deltaTop, startLineNumber); @@ -435,9 +435,9 @@ class ViewLayerRenderer { } else if (ctx.rendLineNumberStart + ctx.linesLength - 1 > stopLineNumber) { // Remove lines after - let fromLineNumber = Math.max(0, stopLineNumber - ctx.rendLineNumberStart + 1); - let toLineNumber = ctx.linesLength - 1; - let removeCnt = toLineNumber - fromLineNumber + 1; + const fromLineNumber = Math.max(0, stopLineNumber - ctx.rendLineNumberStart + 1); + const toLineNumber = ctx.linesLength - 1; + const removeCnt = toLineNumber - fromLineNumber + 1; if (removeCnt > 0) { this._removeLinesAfter(ctx, removeCnt); @@ -455,13 +455,13 @@ class ViewLayerRenderer { const lines = ctx.lines; for (let i = startIndex; i <= endIndex; i++) { - let lineNumber = rendLineNumberStart + i; + const lineNumber = rendLineNumberStart + i; lines[i].layoutLine(lineNumber, deltaTop[lineNumber - deltaLN]); } } private _insertLinesBefore(ctx: IRendererContext, fromLineNumber: number, toLineNumber: number, deltaTop: number[], deltaLN: number): void { - let newLines: T[] = []; + const newLines: T[] = []; let newLinesLen = 0; for (let lineNumber = fromLineNumber; lineNumber <= toLineNumber; lineNumber++) { newLines[newLinesLen++] = this.host.createVisibleLine(); @@ -471,7 +471,7 @@ class ViewLayerRenderer { private _removeLinesBefore(ctx: IRendererContext, removeCount: number): void { for (let i = 0; i < removeCount; i++) { - let lineDomNode = ctx.lines[i].getDomNode(); + const lineDomNode = ctx.lines[i].getDomNode(); if (lineDomNode) { this.domNode.removeChild(lineDomNode); } @@ -480,7 +480,7 @@ class ViewLayerRenderer { } private _insertLinesAfter(ctx: IRendererContext, fromLineNumber: number, toLineNumber: number, deltaTop: number[], deltaLN: number): void { - let newLines: T[] = []; + const newLines: T[] = []; let newLinesLen = 0; for (let lineNumber = fromLineNumber; lineNumber <= toLineNumber; lineNumber++) { newLines[newLinesLen++] = this.host.createVisibleLine(); @@ -489,10 +489,10 @@ class ViewLayerRenderer { } private _removeLinesAfter(ctx: IRendererContext, removeCount: number): void { - let removeIndex = ctx.linesLength - removeCount; + const removeIndex = ctx.linesLength - removeCount; for (let i = 0; i < removeCount; i++) { - let lineDomNode = ctx.lines[removeIndex + i].getDomNode(); + const lineDomNode = ctx.lines[removeIndex + i].getDomNode(); if (lineDomNode) { this.domNode.removeChild(lineDomNode); } @@ -501,7 +501,7 @@ class ViewLayerRenderer { } private _finishRenderingNewLines(ctx: IRendererContext, domNodeIsEmpty: boolean, newLinesHTML: string, wasNew: boolean[]): void { - let lastChild = this.domNode.lastChild; + const lastChild = this.domNode.lastChild; if (domNodeIsEmpty || !lastChild) { this.domNode.innerHTML = newLinesHTML; } else { @@ -510,7 +510,7 @@ class ViewLayerRenderer { let currChild = this.domNode.lastChild; for (let i = ctx.linesLength - 1; i >= 0; i--) { - let line = ctx.lines[i]; + const line = ctx.lines[i]; if (wasNew[i]) { line.setDomNode(currChild); currChild = currChild.previousSibling; @@ -519,15 +519,15 @@ class ViewLayerRenderer { } private _finishRenderingInvalidLines(ctx: IRendererContext, invalidLinesHTML: string, wasInvalid: boolean[]): void { - let hugeDomNode = document.createElement('div'); + const hugeDomNode = document.createElement('div'); hugeDomNode.innerHTML = invalidLinesHTML; for (let i = 0; i < ctx.linesLength; i++) { - let line = ctx.lines[i]; + const line = ctx.lines[i]; if (wasInvalid[i]) { - let source = hugeDomNode.firstChild; - let lineDomNode = line.getDomNode()!; + const source = hugeDomNode.firstChild; + const lineDomNode = line.getDomNode()!; lineDomNode.parentNode!.replaceChild(source, lineDomNode); line.setDomNode(source); } @@ -543,7 +543,7 @@ class ViewLayerRenderer { const lines = ctx.lines; const rendLineNumberStart = ctx.rendLineNumberStart; - let wasNew: boolean[] = []; + const wasNew: boolean[] = []; { sb.reset(); let hadNewLine = false; @@ -577,10 +577,10 @@ class ViewLayerRenderer { sb.reset(); let hadInvalidLine = false; - let wasInvalid: boolean[] = []; + const wasInvalid: boolean[] = []; for (let i = 0; i < linesLength; i++) { - let line = lines[i]; + const line = lines[i]; wasInvalid[i] = false; if (wasNew[i]) { diff --git a/src/vs/editor/browser/view/viewOutgoingEvents.ts b/src/vs/editor/browser/view/viewOutgoingEvents.ts index 2cd32771ab..d1ed8e2e2a 100644 --- a/src/vs/editor/browser/view/viewOutgoingEvents.ts +++ b/src/vs/editor/browser/view/viewOutgoingEvents.ts @@ -32,7 +32,7 @@ export class ViewOutgoingEvents extends Disposable { public onMouseDrag: EventCallback | null = null; public onMouseDrop: EventCallback | null = null; - private _viewModel: IViewModel; + private readonly _viewModel: IViewModel; constructor(viewModel: IViewModel) { super(); diff --git a/src/vs/editor/browser/view/viewOverlays.ts b/src/vs/editor/browser/view/viewOverlays.ts index cd22582d56..3c4e457ae5 100644 --- a/src/vs/editor/browser/view/viewOverlays.ts +++ b/src/vs/editor/browser/view/viewOverlays.ts @@ -40,7 +40,7 @@ export class ViewOverlays extends ViewPart implements IVisibleLinesHost overlay.shouldRender()); + const toRender = this._dynamicOverlays.filter(overlay => overlay.shouldRender()); for (let i = 0, len = toRender.length; i < len; i++) { - let dynamicOverlay = toRender[i]; + const dynamicOverlay = toRender[i]; dynamicOverlay.prepareRender(ctx); dynamicOverlay.onDidRender(); } @@ -139,8 +139,8 @@ export class ViewOverlays extends ViewPart implements IVisibleLinesHost | null; private _renderedContent: string | null; private _lineHeight: number; @@ -179,7 +179,7 @@ export class ViewOverlayLine implements IVisibleLine { public renderLine(lineNumber: number, deltaTop: number, viewportData: ViewportData, sb: IStringBuilder): boolean { let result = ''; for (let i = 0, len = this._dynamicOverlays.length; i < len; i++) { - let dynamicOverlay = this._dynamicOverlays[i]; + const dynamicOverlay = this._dynamicOverlays[i]; result += dynamicOverlay.render(viewportData.startLineNumber, lineNumber); } @@ -276,7 +276,7 @@ export class MarginViewOverlays extends ViewOverlays { _viewOverlaysRender(ctx: RestrictedRenderingContext): void { super._viewOverlaysRender(ctx); - let height = Math.min(ctx.scrollHeight, 1000000); + const height = Math.min(ctx.scrollHeight, 1000000); this.domNode.setHeight(height); this.domNode.setWidth(this._contentLeft); } diff --git a/src/vs/editor/browser/view/viewPart.ts b/src/vs/editor/browser/view/viewPart.ts index 7fc7843549..92afff19b3 100644 --- a/src/vs/editor/browser/view/viewPart.ts +++ b/src/vs/editor/browser/view/viewPart.ts @@ -50,7 +50,7 @@ export class PartFingerprints { } public static read(target: Element): PartFingerprint { - let r = target.getAttribute('data-mprt'); + const r = target.getAttribute('data-mprt'); if (r === null) { return PartFingerprint.None; } @@ -70,7 +70,7 @@ export class PartFingerprints { child = child.parentElement; } - let r = new Uint8Array(resultLen); + const r = new Uint8Array(resultLen); for (let i = 0; i < resultLen; i++) { r[i] = result[resultLen - i - 1]; } diff --git a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts index b72971b8a2..f8feb04fcb 100644 --- a/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts +++ b/src/vs/editor/browser/viewParts/contentWidgets/contentWidgets.ts @@ -14,6 +14,7 @@ import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/v import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; +import { withUndefinedAsNull } from 'vs/base/common/types'; class Coordinate { _coordinateBrand: void; @@ -29,7 +30,7 @@ class Coordinate { export class ViewContentWidgets extends ViewPart { - private _viewDomNode: FastDomNode; + private readonly _viewDomNode: FastDomNode; private _widgets: { [key: string]: Widget; }; public domNode: FastDomNode; @@ -59,7 +60,7 @@ export class ViewContentWidgets extends ViewPart { // --- begin event handlers public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { - let keys = Object.keys(this._widgets); + const keys = Object.keys(this._widgets); for (const widgetId of keys) { this._widgets[widgetId].onConfigurationChanged(e); } @@ -73,7 +74,7 @@ export class ViewContentWidgets extends ViewPart { return true; } public onLineMappingChanged(e: viewEvents.ViewLineMappingChangedEvent): boolean { - let keys = Object.keys(this._widgets); + const keys = Object.keys(this._widgets); for (const widgetId of keys) { this._widgets[widgetId].onLineMappingChanged(e); } @@ -139,21 +140,21 @@ export class ViewContentWidgets extends ViewPart { } public onBeforeRender(viewportData: ViewportData): void { - let keys = Object.keys(this._widgets); + const keys = Object.keys(this._widgets); for (const widgetId of keys) { this._widgets[widgetId].onBeforeRender(viewportData); } } public prepareRender(ctx: RenderingContext): void { - let keys = Object.keys(this._widgets); + const keys = Object.keys(this._widgets); for (const widgetId of keys) { this._widgets[widgetId].prepareRender(ctx); } } public render(ctx: RestrictedRenderingContext): void { - let keys = Object.keys(this._widgets); + const keys = Object.keys(this._widgets); for (const widgetId of keys) { this._widgets[widgetId].render(ctx); } @@ -180,7 +181,7 @@ class Widget { public readonly allowEditorOverflow: boolean; public readonly suppressMouseDown: boolean; - private _fixedOverflowWidgets: boolean; + private readonly _fixedOverflowWidgets: boolean; private _contentWidth: number; private _contentLeft: number; private _lineHeight: number; @@ -242,8 +243,8 @@ class Widget { } private _setPosition(position: IPosition | null | undefined, range: IRange | null | undefined): void { - this._position = position || null; - this._range = range || null; + this._position = withUndefinedAsNull(position); + this._range = withUndefinedAsNull(range); this._viewPosition = null; this._viewRange = null; @@ -280,17 +281,17 @@ class Widget { // Our visible box is split horizontally by the current line => 2 boxes // a) the box above the line - let aboveLineTop = topLeft.top; - let heightAboveLine = aboveLineTop; + const aboveLineTop = topLeft.top; + const heightAboveLine = aboveLineTop; // b) the box under the line - let underLineTop = bottomLeft.top + this._lineHeight; - let heightUnderLine = ctx.viewportHeight - underLineTop; + const underLineTop = bottomLeft.top + this._lineHeight; + const heightUnderLine = ctx.viewportHeight - underLineTop; - let aboveTop = aboveLineTop - height; - let fitsAbove = (heightAboveLine >= height); - let belowTop = underLineTop; - let fitsBelow = (heightUnderLine >= height); + const aboveTop = aboveLineTop - height; + const fitsAbove = (heightAboveLine >= height); + const belowTop = underLineTop; + const fitsBelow = (heightUnderLine >= height); // And its left let actualAboveLeft = topLeft.left; @@ -320,8 +321,8 @@ class Widget { } private _layoutBoxInPage(topLeft: Coordinate, bottomLeft: Coordinate, width: number, height: number, ctx: RenderingContext): IBoxLayoutResult | null { - let aboveLeft0 = topLeft.left - ctx.scrollLeft; - let belowLeft0 = bottomLeft.left - ctx.scrollLeft; + const aboveLeft0 = topLeft.left - ctx.scrollLeft; + const belowLeft0 = bottomLeft.left - ctx.scrollLeft; if (aboveLeft0 < 0 || aboveLeft0 > this._contentWidth) { // Don't render if position is scrolled outside viewport @@ -333,39 +334,39 @@ class Widget { let aboveLeft = aboveLeft0 + this._contentLeft; let belowLeft = belowLeft0 + this._contentLeft; - let domNodePosition = dom.getDomNodePagePosition(this._viewDomNode.domNode); - let absoluteAboveTop = domNodePosition.top + aboveTop - dom.StandardWindow.scrollY; - let absoluteBelowTop = domNodePosition.top + belowTop - dom.StandardWindow.scrollY; + const domNodePosition = dom.getDomNodePagePosition(this._viewDomNode.domNode); + const absoluteAboveTop = domNodePosition.top + aboveTop - dom.StandardWindow.scrollY; + const absoluteBelowTop = domNodePosition.top + belowTop - dom.StandardWindow.scrollY; let absoluteAboveLeft = domNodePosition.left + aboveLeft - dom.StandardWindow.scrollX; let absoluteBelowLeft = domNodePosition.left + belowLeft - dom.StandardWindow.scrollX; - let INNER_WIDTH = window.innerWidth || document.documentElement!.clientWidth || document.body.clientWidth; - let INNER_HEIGHT = window.innerHeight || document.documentElement!.clientHeight || document.body.clientHeight; + const INNER_WIDTH = window.innerWidth || document.documentElement!.clientWidth || document.body.clientWidth; + const INNER_HEIGHT = window.innerHeight || document.documentElement!.clientHeight || document.body.clientHeight; // Leave some clearance to the bottom - let TOP_PADDING = 22; - let BOTTOM_PADDING = 22; + const TOP_PADDING = 22; + const BOTTOM_PADDING = 22; - let fitsAbove = (absoluteAboveTop >= TOP_PADDING), + const fitsAbove = (absoluteAboveTop >= TOP_PADDING), fitsBelow = (absoluteBelowTop + height <= INNER_HEIGHT - BOTTOM_PADDING); if (absoluteAboveLeft + width + 20 > INNER_WIDTH) { - let delta = absoluteAboveLeft - (INNER_WIDTH - width - 20); + const delta = absoluteAboveLeft - (INNER_WIDTH - width - 20); absoluteAboveLeft -= delta; aboveLeft -= delta; } if (absoluteBelowLeft + width + 20 > INNER_WIDTH) { - let delta = absoluteBelowLeft - (INNER_WIDTH - width - 20); + const delta = absoluteBelowLeft - (INNER_WIDTH - width - 20); absoluteBelowLeft -= delta; belowLeft -= delta; } if (absoluteAboveLeft < 0) { - let delta = absoluteAboveLeft; + const delta = absoluteAboveLeft; absoluteAboveLeft -= delta; aboveLeft -= delta; } if (absoluteBelowLeft < 0) { - let delta = absoluteBelowLeft; + const delta = absoluteBelowLeft; absoluteBelowLeft -= delta; belowLeft -= delta; } diff --git a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts index eeabefb3dd..734a75e418 100644 --- a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts +++ b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts @@ -12,7 +12,7 @@ import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; export class CurrentLineHighlightOverlay extends DynamicViewOverlay { - private _context: ViewContext; + private readonly _context: ViewContext; private _lineHeight: number; private _renderLineHighlight: 'none' | 'gutter' | 'line' | 'all'; private _selectionIsEmpty: boolean; diff --git a/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.ts b/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.ts index 944d7dca69..f471467f90 100644 --- a/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.ts +++ b/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.ts @@ -12,7 +12,7 @@ import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; export class CurrentLineMarginHighlightOverlay extends DynamicViewOverlay { - private _context: ViewContext; + private readonly _context: ViewContext; private _lineHeight: number; private _renderLineHighlight: 'none' | 'gutter' | 'line' | 'all'; private _selectionIsEmpty: boolean; diff --git a/src/vs/editor/browser/viewParts/decorations/decorations.ts b/src/vs/editor/browser/viewParts/decorations/decorations.ts index ff33154ce1..1c44aabdca 100644 --- a/src/vs/editor/browser/viewParts/decorations/decorations.ts +++ b/src/vs/editor/browser/viewParts/decorations/decorations.ts @@ -13,7 +13,7 @@ import { ViewModelDecoration } from 'vs/editor/common/viewModel/viewModel'; export class DecorationsOverlay extends DynamicViewOverlay { - private _context: ViewContext; + private readonly _context: ViewContext; private _lineHeight: number; private _typicalHalfwidthCharacterWidth: number; private _renderResult: string[] | null; @@ -69,12 +69,12 @@ export class DecorationsOverlay extends DynamicViewOverlay { // --- end event handlers public prepareRender(ctx: RenderingContext): void { - let _decorations = ctx.getDecorationsInViewport(); + const _decorations = ctx.getDecorationsInViewport(); // Keep only decorations with `className` let decorations: ViewModelDecoration[] = [], decorationsLen = 0; for (let i = 0, len = _decorations.length; i < len; i++) { - let d = _decorations[i]; + const d = _decorations[i]; if (d.options.className) { decorations[decorationsLen++] = d; } @@ -101,11 +101,11 @@ export class DecorationsOverlay extends DynamicViewOverlay { return Range.compareRangesUsingStarts(a.range, b.range); }); - let visibleStartLineNumber = ctx.visibleRange.startLineNumber; - let visibleEndLineNumber = ctx.visibleRange.endLineNumber; - let output: string[] = []; + const visibleStartLineNumber = ctx.visibleRange.startLineNumber; + const visibleEndLineNumber = ctx.visibleRange.endLineNumber; + const output: string[] = []; for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { - let lineIndex = lineNumber - visibleStartLineNumber; + const lineIndex = lineNumber - visibleStartLineNumber; output[lineIndex] = ''; } @@ -116,18 +116,18 @@ export class DecorationsOverlay extends DynamicViewOverlay { } private _renderWholeLineDecorations(ctx: RenderingContext, decorations: ViewModelDecoration[], output: string[]): void { - let lineHeight = String(this._lineHeight); - let visibleStartLineNumber = ctx.visibleRange.startLineNumber; - let visibleEndLineNumber = ctx.visibleRange.endLineNumber; + const lineHeight = String(this._lineHeight); + const visibleStartLineNumber = ctx.visibleRange.startLineNumber; + const visibleEndLineNumber = ctx.visibleRange.endLineNumber; for (let i = 0, lenI = decorations.length; i < lenI; i++) { - let d = decorations[i]; + const d = decorations[i]; if (!d.options.isWholeLine) { continue; } - let decorationOutput = ( + const decorationOutput = ( '
' ); - let startLineNumber = Math.max(d.range.startLineNumber, visibleStartLineNumber); - let endLineNumber = Math.min(d.range.endLineNumber, visibleEndLineNumber); + const startLineNumber = Math.max(d.range.startLineNumber, visibleStartLineNumber); + const endLineNumber = Math.min(d.range.endLineNumber, visibleEndLineNumber); for (let j = startLineNumber; j <= endLineNumber; j++) { - let lineIndex = j - visibleStartLineNumber; + const lineIndex = j - visibleStartLineNumber; output[lineIndex] += decorationOutput; } } @@ -189,13 +189,13 @@ export class DecorationsOverlay extends DynamicViewOverlay { } private _renderNormalDecoration(ctx: RenderingContext, range: Range, className: string, showIfCollapsed: boolean, lineHeight: string, visibleStartLineNumber: number, output: string[]): void { - let linesVisibleRanges = ctx.linesVisibleRangesForRange(range, /*TODO@Alex*/className === 'findMatch'); + const linesVisibleRanges = ctx.linesVisibleRangesForRange(range, /*TODO@Alex*/className === 'findMatch'); if (!linesVisibleRanges) { return; } for (let j = 0, lenJ = linesVisibleRanges.length; j < lenJ; j++) { - let lineVisibleRanges = linesVisibleRanges[j]; + const lineVisibleRanges = linesVisibleRanges[j]; const lineIndex = lineVisibleRanges.lineNumber - visibleStartLineNumber; if (showIfCollapsed && lineVisibleRanges.ranges.length === 1) { @@ -228,7 +228,7 @@ export class DecorationsOverlay extends DynamicViewOverlay { if (!this._renderResult) { return ''; } - let lineIndex = lineNumber - startLineNumber; + const lineIndex = lineNumber - startLineNumber; if (lineIndex < 0 || lineIndex >= this._renderResult.length) { return ''; } diff --git a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts index 668f114038..06f25850ef 100644 --- a/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts +++ b/src/vs/editor/browser/viewParts/editorScrollbar/editorScrollbar.ts @@ -17,8 +17,8 @@ import { getThemeTypeSelector } from 'vs/platform/theme/common/themeService'; export class EditorScrollbar extends ViewPart { - private scrollbar: SmoothScrollableElement; - private scrollbarDomNode: FastDomNode; + private readonly scrollbar: SmoothScrollableElement; + private readonly scrollbarDomNode: FastDomNode; constructor( context: ViewContext, @@ -31,7 +31,7 @@ export class EditorScrollbar extends ViewPart { const editor = this._context.configuration.editor; const configScrollbarOpts = editor.viewInfo.scrollbar; - let scrollbarOptions: ScrollableElementCreationOptions = { + const scrollbarOptions: ScrollableElementCreationOptions = { listenOnDomNode: viewDomNode.domNode, className: 'editor-scrollable' + ' ' + getThemeTypeSelector(context.theme.type), useShadows: false, @@ -62,11 +62,11 @@ export class EditorScrollbar extends ViewPart { // the browser will try desperately to reveal that dom node, unexpectedly // changing the .scrollTop of this.linesContent - let onBrowserDesperateReveal = (domNode: HTMLElement, lookAtScrollTop: boolean, lookAtScrollLeft: boolean) => { - let newScrollPosition: INewScrollPosition = {}; + const onBrowserDesperateReveal = (domNode: HTMLElement, lookAtScrollTop: boolean, lookAtScrollLeft: boolean) => { + const newScrollPosition: INewScrollPosition = {}; if (lookAtScrollTop) { - let deltaTop = domNode.scrollTop; + const deltaTop = domNode.scrollTop; if (deltaTop) { newScrollPosition.scrollTop = this._context.viewLayout.getCurrentScrollTop() + deltaTop; domNode.scrollTop = 0; @@ -74,7 +74,7 @@ export class EditorScrollbar extends ViewPart { } if (lookAtScrollLeft) { - let deltaLeft = domNode.scrollLeft; + const deltaLeft = domNode.scrollLeft; if (deltaLeft) { newScrollPosition.scrollLeft = this._context.viewLayout.getCurrentScrollLeft() + deltaLeft; domNode.scrollLeft = 0; @@ -126,7 +126,7 @@ export class EditorScrollbar extends ViewPart { public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { if (e.viewInfo) { const editor = this._context.configuration.editor; - let newOpts: ScrollableElementChangeOptions = { + const newOpts: ScrollableElementChangeOptions = { handleMouseWheel: editor.viewInfo.scrollbar.handleMouseWheel, mouseWheelScrollSensitivity: editor.viewInfo.scrollbar.mouseWheelScrollSensitivity, fastScrollSensitivity: editor.viewInfo.scrollbar.fastScrollSensitivity diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts index d273ad0855..43a4a8a826 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts +++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts @@ -27,9 +27,9 @@ export abstract class DedupOverlay extends DynamicViewOverlay { protected _render(visibleStartLineNumber: number, visibleEndLineNumber: number, decorations: DecorationToRender[]): string[][] { - let output: string[][] = []; + const output: string[][] = []; for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { - let lineIndex = lineNumber - visibleStartLineNumber; + const lineIndex = lineNumber - visibleStartLineNumber; output[lineIndex] = []; } @@ -50,10 +50,10 @@ export abstract class DedupOverlay extends DynamicViewOverlay { let prevClassName: string | null = null; let prevEndLineIndex = 0; for (let i = 0, len = decorations.length; i < len; i++) { - let d = decorations[i]; - let className = d.className; + const d = decorations[i]; + const className = d.className; let startLineIndex = Math.max(d.startLineNumber, visibleStartLineNumber) - visibleStartLineNumber; - let endLineIndex = Math.min(d.endLineNumber, visibleEndLineNumber) - visibleStartLineNumber; + const endLineIndex = Math.min(d.endLineNumber, visibleEndLineNumber) - visibleStartLineNumber; if (prevClassName === className) { startLineIndex = Math.max(prevEndLineIndex + 1, startLineIndex); @@ -74,7 +74,7 @@ export abstract class DedupOverlay extends DynamicViewOverlay { export class GlyphMarginOverlay extends DedupOverlay { - private _context: ViewContext; + private readonly _context: ViewContext; private _lineHeight: number; private _glyphMargin: boolean; private _glyphMarginLeft: number; @@ -138,11 +138,11 @@ export class GlyphMarginOverlay extends DedupOverlay { // --- end event handlers protected _getDecorations(ctx: RenderingContext): DecorationToRender[] { - let decorations = ctx.getDecorationsInViewport(); + const decorations = ctx.getDecorationsInViewport(); let r: DecorationToRender[] = [], rLen = 0; for (let i = 0, len = decorations.length; i < len; i++) { - let d = decorations[i]; - let glyphMarginClassName = d.options.glyphMarginClassName; + const d = decorations[i]; + const glyphMarginClassName = d.options.glyphMarginClassName; if (glyphMarginClassName) { r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, glyphMarginClassName); } @@ -156,19 +156,19 @@ export class GlyphMarginOverlay extends DedupOverlay { return; } - let visibleStartLineNumber = ctx.visibleRange.startLineNumber; - let visibleEndLineNumber = ctx.visibleRange.endLineNumber; - let toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, this._getDecorations(ctx)); + const visibleStartLineNumber = ctx.visibleRange.startLineNumber; + const visibleEndLineNumber = ctx.visibleRange.endLineNumber; + const toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, this._getDecorations(ctx)); - let lineHeight = this._lineHeight.toString(); - let left = this._glyphMarginLeft.toString(); - let width = this._glyphMarginWidth.toString(); - let common = '" style="left:' + left + 'px;width:' + width + 'px' + ';height:' + lineHeight + 'px;">'; + const lineHeight = this._lineHeight.toString(); + const left = this._glyphMarginLeft.toString(); + const width = this._glyphMarginWidth.toString(); + const common = '" style="left:' + left + 'px;width:' + width + 'px' + ';height:' + lineHeight + 'px;">'; - let output: string[] = []; + const output: string[] = []; for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { - let lineIndex = lineNumber - visibleStartLineNumber; - let classNames = toRender[lineIndex]; + const lineIndex = lineNumber - visibleStartLineNumber; + const classNames = toRender[lineIndex]; if (classNames.length === 0) { output[lineIndex] = ''; @@ -188,7 +188,7 @@ export class GlyphMarginOverlay extends DedupOverlay { if (!this._renderResult) { return ''; } - let lineIndex = lineNumber - startLineNumber; + const lineIndex = lineNumber - startLineNumber; if (lineIndex < 0 || lineIndex >= this._renderResult.length) { return ''; } diff --git a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts index 12b3cbb479..6fcf3a3084 100644 --- a/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts +++ b/src/vs/editor/browser/viewParts/indentGuides/indentGuides.ts @@ -14,7 +14,7 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic export class IndentGuidesOverlay extends DynamicViewOverlay { - private _context: ViewContext; + private readonly _context: ViewContext; private _primaryLineNumber: number; private _lineHeight: number; private _spaceWidth: number; @@ -103,11 +103,10 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { const visibleStartLineNumber = ctx.visibleRange.startLineNumber; const visibleEndLineNumber = ctx.visibleRange.endLineNumber; - const tabSize = this._context.model.getTabSize(); - const tabWidth = tabSize * this._spaceWidth; + const { indentSize } = this._context.model.getOptions(); + const indentWidth = indentSize * this._spaceWidth; const scrollWidth = ctx.scrollWidth; const lineHeight = this._lineHeight; - const indentGuideWidth = tabWidth; const indents = this._context.model.getLinesIndentGuides(visibleStartLineNumber, visibleEndLineNumber); @@ -121,19 +120,19 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { activeIndentLevel = activeIndentInfo.indent; } - let output: string[] = []; + const output: string[] = []; for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { const containsActiveIndentGuide = (activeIndentStartLineNumber <= lineNumber && lineNumber <= activeIndentEndLineNumber); const lineIndex = lineNumber - visibleStartLineNumber; const indent = indents[lineIndex]; let result = ''; - let leftMostVisiblePosition = ctx.visibleRangeForPosition(new Position(lineNumber, 1)); + const leftMostVisiblePosition = ctx.visibleRangeForPosition(new Position(lineNumber, 1)); let left = leftMostVisiblePosition ? leftMostVisiblePosition.left : 0; for (let i = 1; i <= indent; i++) { - let className = (containsActiveIndentGuide && i === activeIndentLevel ? 'cigra' : 'cigr'); - result += `
`; - left += tabWidth; + const className = (containsActiveIndentGuide && i === activeIndentLevel ? 'cigra' : 'cigr'); + result += `
`; + left += indentWidth; if (left > scrollWidth) { break; } @@ -148,7 +147,7 @@ export class IndentGuidesOverlay extends DynamicViewOverlay { if (!this._renderResult) { return ''; } - let lineIndex = lineNumber - startLineNumber; + const lineIndex = lineNumber - startLineNumber; if (lineIndex < 0 || lineIndex >= this._renderResult.length) { return ''; } diff --git a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-2x.svg b/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-2x.svg index 094c6acfbe..1d1cda0e93 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-2x.svg +++ b/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-2x.svg @@ -1 +1 @@ - + diff --git a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac-2x.svg b/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac-2x.svg index 4dee276170..7f64457f78 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac-2x.svg +++ b/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac-2x.svg @@ -1 +1,2 @@ -flipped-cursor-mac-2x \ No newline at end of file + + flipped-cursor-mac diff --git a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac.svg b/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac.svg index 706d2e2ba2..da79a9547d 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac.svg +++ b/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor-mac.svg @@ -1 +1 @@ -flipped-cursor-mac \ No newline at end of file +flipped-cursor-mac diff --git a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor.svg b/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor.svg index 6753e02878..0add3031fb 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor.svg +++ b/src/vs/editor/browser/viewParts/lineNumbers/flipped-cursor.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts index cf1bcc2b6f..d8db7d48f0 100644 --- a/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts +++ b/src/vs/editor/browser/viewParts/lineNumbers/lineNumbers.ts @@ -18,11 +18,12 @@ export class LineNumbersOverlay extends DynamicViewOverlay { public static readonly CLASS_NAME = 'line-numbers'; - private _context: ViewContext; + private readonly _context: ViewContext; private _lineHeight: number; private _renderLineNumbers: RenderLineNumbersType; private _renderCustomLineNumbers: ((lineNumber: number) => string) | null; + private _renderFinalNewline: boolean; private _lineNumbersLeft: number; private _lineNumbersWidth: number; private _lastCursorModelPosition: Position; @@ -44,6 +45,7 @@ export class LineNumbersOverlay extends DynamicViewOverlay { this._lineHeight = config.lineHeight; this._renderLineNumbers = config.viewInfo.renderLineNumbers; this._renderCustomLineNumbers = config.viewInfo.renderCustomLineNumbers; + this._renderFinalNewline = config.viewInfo.renderFinalNewline; this._lineNumbersLeft = config.layoutInfo.lineNumbersLeft; this._lineNumbersWidth = config.layoutInfo.lineNumbersWidth; } @@ -95,14 +97,23 @@ export class LineNumbersOverlay extends DynamicViewOverlay { if (modelPosition.column !== 1) { return ''; } - let modelLineNumber = modelPosition.lineNumber; + const modelLineNumber = modelPosition.lineNumber; + + if (!this._renderFinalNewline) { + const lineCount = this._context.model.getLineCount(); + const lineContent = this._context.model.getLineContent(modelLineNumber); + + if (modelLineNumber === lineCount && lineContent === '') { + return ''; + } + } if (this._renderCustomLineNumbers) { return this._renderCustomLineNumbers(modelLineNumber); } if (this._renderLineNumbers === RenderLineNumbersType.Relative) { - let diff = Math.abs(this._lastCursorModelPosition.lineNumber - modelLineNumber); + const diff = Math.abs(this._lastCursorModelPosition.lineNumber - modelLineNumber); if (diff === 0) { return '' + modelLineNumber + ''; } @@ -128,16 +139,16 @@ export class LineNumbersOverlay extends DynamicViewOverlay { return; } - let lineHeightClassName = (platform.isLinux ? (this._lineHeight % 2 === 0 ? ' lh-even' : ' lh-odd') : ''); - let visibleStartLineNumber = ctx.visibleRange.startLineNumber; - let visibleEndLineNumber = ctx.visibleRange.endLineNumber; - let common = '
'; + const lineHeightClassName = (platform.isLinux ? (this._lineHeight % 2 === 0 ? ' lh-even' : ' lh-odd') : ''); + const visibleStartLineNumber = ctx.visibleRange.startLineNumber; + const visibleEndLineNumber = ctx.visibleRange.endLineNumber; + const common = '
'; - let output: string[] = []; + const output: string[] = []; for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { - let lineIndex = lineNumber - visibleStartLineNumber; + const lineIndex = lineNumber - visibleStartLineNumber; - let renderLineNumber = this._getLineRenderLineNumber(lineNumber); + const renderLineNumber = this._getLineRenderLineNumber(lineNumber); if (renderLineNumber) { output[lineIndex] = ( @@ -157,7 +168,7 @@ export class LineNumbersOverlay extends DynamicViewOverlay { if (!this._renderResult) { return ''; } - let lineIndex = lineNumber - startLineNumber; + const lineIndex = lineNumber - startLineNumber; if (lineIndex < 0 || lineIndex >= this._renderResult.length) { return ''; } diff --git a/src/vs/editor/browser/viewParts/lines/rangeUtil.ts b/src/vs/editor/browser/viewParts/lines/rangeUtil.ts index 8170eba082..a4dffc940c 100644 --- a/src/vs/editor/browser/viewParts/lines/rangeUtil.ts +++ b/src/vs/editor/browser/viewParts/lines/rangeUtil.ts @@ -49,7 +49,7 @@ export class RangeUtil { } private static _readClientRects(startElement: Node, startOffset: number, endElement: Node, endOffset: number, endNode: HTMLElement): ClientRectList | DOMRectList | null { - let range = this._createRange(); + const range = this._createRange(); try { range.setStart(startElement, startOffset); range.setEnd(endElement, endOffset); @@ -102,7 +102,7 @@ export class RangeUtil { // We go through FloatHorizontalRange because it has been observed in bi-di text // that the clientRects are not coming in sorted from the browser - let result: FloatHorizontalRange[] = []; + const result: FloatHorizontalRange[] = []; for (let i = 0, len = clientRects.length; i < len; i++) { const clientRect = clientRects[i]; result[i] = new FloatHorizontalRange(Math.max(0, clientRect.left - clientRectDeltaLeft), clientRect.width); @@ -113,8 +113,8 @@ export class RangeUtil { public static readHorizontalRanges(domNode: HTMLElement, startChildIndex: number, startOffset: number, endChildIndex: number, endOffset: number, clientRectDeltaLeft: number, endNode: HTMLElement): HorizontalRange[] | null { // Panic check - let min = 0; - let max = domNode.children.length - 1; + const min = 0; + const max = domNode.children.length - 1; if (min > max) { return null; } @@ -152,7 +152,7 @@ export class RangeUtil { startOffset = Math.min(startElement.textContent!.length, Math.max(0, startOffset)); endOffset = Math.min(endElement.textContent!.length, Math.max(0, endOffset)); - let clientRects = this._readClientRects(startElement, startOffset, endElement, endOffset, endNode); + const clientRects = this._readClientRects(startElement, startOffset, endElement, endOffset, endNode); return this._createHorizontalRangesFromClientRects(clientRects, clientRectDeltaLeft); } } diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 0cfd27c7f3..f297bf66af 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -180,8 +180,8 @@ export class ViewLine implements IVisibleLine { continue; } - let startColumn = (selection.startLineNumber === lineNumber ? selection.startColumn : lineData.minColumn); - let endColumn = (selection.endLineNumber === lineNumber ? selection.endColumn : lineData.maxColumn); + const startColumn = (selection.startLineNumber === lineNumber ? selection.startColumn : lineData.minColumn); + const endColumn = (selection.endLineNumber === lineNumber ? selection.endColumn : lineData.maxColumn); if (startColumn < endColumn) { actualInlineDecorations.push(new LineDecoration(startColumn, endColumn, 'inline-selected-text', InlineDecorationType.Regular)); @@ -189,7 +189,7 @@ export class ViewLine implements IVisibleLine { } } - let renderLineInput = new RenderLineInput( + const renderLineInput = new RenderLineInput( options.useMonospaceOptimizations, options.canUseHalfwidthRightwardsArrow, lineData.content, @@ -369,7 +369,7 @@ class FastRenderedViewLine implements IRenderedViewLine { } public getColumnOfNodeOffset(lineNumber: number, spanNode: HTMLElement, offset: number): number { - let spanNodeTextContentLength = spanNode.textContent!.length; + const spanNodeTextContentLength = spanNode.textContent!.length; let spanIndex = -1; while (spanNode) { @@ -377,7 +377,7 @@ class FastRenderedViewLine implements IRenderedViewLine { spanIndex++; } - let charOffset = this._characterMapping.partDataToCharOffset(spanIndex, spanNodeTextContentLength, offset); + const charOffset = this._characterMapping.partDataToCharOffset(spanIndex, spanNodeTextContentLength, offset); return charOffset + 1; } } @@ -398,7 +398,7 @@ class RenderedViewLine implements IRenderedViewLine { /** * This is a map that is used only when the line is guaranteed to have no RTL text. */ - private _pixelOffsetCache: Int32Array | null; + private readonly _pixelOffsetCache: Int32Array | null; constructor(domNode: FastDomNode, renderLineInput: RenderLineInput, characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: ForeignElementType) { this.domNode = domNode; @@ -446,12 +446,12 @@ class RenderedViewLine implements IRenderedViewLine { public getVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { if (this._pixelOffsetCache !== null) { // the text is LTR - let startOffset = this._readPixelOffset(startColumn, context); + const startOffset = this._readPixelOffset(startColumn, context); if (startOffset === -1) { return null; } - let endOffset = this._readPixelOffset(endColumn, context); + const endOffset = this._readPixelOffset(endColumn, context); if (endOffset === -1) { return null; } @@ -464,7 +464,7 @@ class RenderedViewLine implements IRenderedViewLine { protected _readVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { if (startColumn === endColumn) { - let pixelOffset = this._readPixelOffset(startColumn, context); + const pixelOffset = this._readPixelOffset(startColumn, context); if (pixelOffset === -1) { return null; } else { @@ -495,12 +495,12 @@ class RenderedViewLine implements IRenderedViewLine { if (this._pixelOffsetCache !== null) { // the text is LTR - let cachedPixelOffset = this._pixelOffsetCache[column]; + const cachedPixelOffset = this._pixelOffsetCache[column]; if (cachedPixelOffset !== -1) { return cachedPixelOffset; } - let result = this._actualReadPixelOffset(column, context); + const result = this._actualReadPixelOffset(column, context); this._pixelOffsetCache[column] = result; return result; } @@ -511,7 +511,7 @@ class RenderedViewLine implements IRenderedViewLine { private _actualReadPixelOffset(column: number, context: DomReadingContext): number { if (this._characterMapping.length === 0) { // This line has no content - let r = RangeUtil.readHorizontalRanges(this._getReadingTarget(), 0, 0, 0, 0, context.clientRectDeltaLeft, context.endNode); + const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(), 0, 0, 0, 0, context.clientRectDeltaLeft, context.endNode); if (!r || r.length === 0) { return -1; } @@ -523,11 +523,11 @@ class RenderedViewLine implements IRenderedViewLine { return this.getWidth(); } - let partData = this._characterMapping.charOffsetToPartData(column - 1); - let partIndex = CharacterMapping.getPartIndex(partData); - let charOffsetInPart = CharacterMapping.getCharIndex(partData); + const partData = this._characterMapping.charOffsetToPartData(column - 1); + const partIndex = CharacterMapping.getPartIndex(partData); + const charOffsetInPart = CharacterMapping.getCharIndex(partData); - let r = RangeUtil.readHorizontalRanges(this._getReadingTarget(), partIndex, charOffsetInPart, partIndex, charOffsetInPart, context.clientRectDeltaLeft, context.endNode); + const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(), partIndex, charOffsetInPart, partIndex, charOffsetInPart, context.clientRectDeltaLeft, context.endNode); if (!r || r.length === 0) { return -1; } @@ -542,13 +542,13 @@ class RenderedViewLine implements IRenderedViewLine { return [new HorizontalRange(0, this.getWidth())]; } - let startPartData = this._characterMapping.charOffsetToPartData(startColumn - 1); - let startPartIndex = CharacterMapping.getPartIndex(startPartData); - let startCharOffsetInPart = CharacterMapping.getCharIndex(startPartData); + const startPartData = this._characterMapping.charOffsetToPartData(startColumn - 1); + const startPartIndex = CharacterMapping.getPartIndex(startPartData); + const startCharOffsetInPart = CharacterMapping.getCharIndex(startPartData); - let endPartData = this._characterMapping.charOffsetToPartData(endColumn - 1); - let endPartIndex = CharacterMapping.getPartIndex(endPartData); - let endCharOffsetInPart = CharacterMapping.getCharIndex(endPartData); + const endPartData = this._characterMapping.charOffsetToPartData(endColumn - 1); + const endPartIndex = CharacterMapping.getPartIndex(endPartData); + const endCharOffsetInPart = CharacterMapping.getCharIndex(endPartData); return RangeUtil.readHorizontalRanges(this._getReadingTarget(), startPartIndex, startCharOffsetInPart, endPartIndex, endCharOffsetInPart, context.clientRectDeltaLeft, context.endNode); } @@ -557,7 +557,7 @@ class RenderedViewLine implements IRenderedViewLine { * Returns the column for the text found at a specific offset inside a rendered dom node */ public getColumnOfNodeOffset(lineNumber: number, spanNode: HTMLElement, offset: number): number { - let spanNodeTextContentLength = spanNode.textContent!.length; + const spanNodeTextContentLength = spanNode.textContent!.length; let spanIndex = -1; while (spanNode) { @@ -565,14 +565,14 @@ class RenderedViewLine implements IRenderedViewLine { spanIndex++; } - let charOffset = this._characterMapping.partDataToCharOffset(spanIndex, spanNodeTextContentLength, offset); + const charOffset = this._characterMapping.partDataToCharOffset(spanIndex, spanNodeTextContentLength, offset); return charOffset + 1; } } class WebKitRenderedViewLine extends RenderedViewLine { protected _readVisibleRangesForRange(startColumn: number, endColumn: number, context: DomReadingContext): HorizontalRange[] | null { - let output = super._readVisibleRangesForRange(startColumn, endColumn, context); + const output = super._readVisibleRangesForRange(startColumn, endColumn, context); if (!output || output.length === 0 || startColumn === endColumn || (startColumn === 1 && endColumn === this._characterMapping.length)) { return output; @@ -583,9 +583,9 @@ class WebKitRenderedViewLine extends RenderedViewLine { if (!this.input.containsRTL) { // This is an attempt to patch things up // Find position of last column - let endPixelOffset = this._readPixelOffset(endColumn, context); + const endPixelOffset = this._readPixelOffset(endColumn, context); if (endPixelOffset !== -1) { - let lastRange = output[output.length - 1]; + const lastRange = output[output.length - 1]; if (lastRange.left < endPixelOffset) { // Trim down the width of the last visible range to not go after the last column's position lastRange.width = endPixelOffset - lastRange.left; diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index 12702f509b..6f37e66207 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -76,10 +76,10 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // --- width private _maxLineWidth: number; - private _asyncUpdateLineWidths: RunOnceScheduler; + private readonly _asyncUpdateLineWidths: RunOnceScheduler; private _horizontalRevealRequest: HorizontalRevealRequest | null; - private _lastRenderedData: LastRenderedData; + private readonly _lastRenderedData: LastRenderedData; constructor(context: ViewContext, linesContent: FastDomNode) { super(context); @@ -169,14 +169,14 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, private _onOptionsMaybeChanged(): boolean { const conf = this._context.configuration; - let newViewLineOptions = new ViewLineOptions(conf, this._context.theme.type); + const newViewLineOptions = new ViewLineOptions(conf, this._context.theme.type); if (!this._viewLineOptions.equals(newViewLineOptions)) { this._viewLineOptions = newViewLineOptions; - let startLineNumber = this._visibleLines.getStartLineNumber(); - let endLineNumber = this._visibleLines.getEndLineNumber(); + const startLineNumber = this._visibleLines.getStartLineNumber(); + const endLineNumber = this._visibleLines.getEndLineNumber(); for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { - let line = this._visibleLines.getVisibleLine(lineNumber); + const line = this._visibleLines.getVisibleLine(lineNumber); line.onOptionsChanged(this._viewLineOptions); } return true; @@ -185,8 +185,8 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return false; } public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { - let rendStartLineNumber = this._visibleLines.getStartLineNumber(); - let rendEndLineNumber = this._visibleLines.getEndLineNumber(); + const rendStartLineNumber = this._visibleLines.getStartLineNumber(); + const rendEndLineNumber = this._visibleLines.getEndLineNumber(); let r = false; for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) { r = this._visibleLines.getVisibleLine(lineNumber).onSelectionChanged() || r; @@ -195,8 +195,8 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, } public onDecorationsChanged(e: viewEvents.ViewDecorationsChangedEvent): boolean { if (true/*e.inlineDecorationsChanged*/) { - let rendStartLineNumber = this._visibleLines.getStartLineNumber(); - let rendEndLineNumber = this._visibleLines.getEndLineNumber(); + const rendStartLineNumber = this._visibleLines.getStartLineNumber(); + const rendEndLineNumber = this._visibleLines.getEndLineNumber(); for (let lineNumber = rendStartLineNumber; lineNumber <= rendEndLineNumber; lineNumber++) { this._visibleLines.getVisibleLine(lineNumber).onDecorationsChanged(); } @@ -204,7 +204,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return true; } public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { - let shouldRender = this._visibleLines.onFlushed(e); + const shouldRender = this._visibleLines.onFlushed(e); this._maxLineWidth = 0; return shouldRender; } @@ -282,12 +282,12 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // ----------- HELPERS FOR OTHERS public getPositionFromDOMInfo(spanNode: HTMLElement, offset: number): Position | null { - let viewLineDomNode = this._getViewLineDomNode(spanNode); + const viewLineDomNode = this._getViewLineDomNode(spanNode); if (viewLineDomNode === null) { // Couldn't find view line node return null; } - let lineNumber = this._getLineNumberFor(viewLineDomNode); + const lineNumber = this._getLineNumberFor(viewLineDomNode); if (lineNumber === -1) { // Couldn't find view line node @@ -304,15 +304,15 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return new Position(lineNumber, 1); } - let rendStartLineNumber = this._visibleLines.getStartLineNumber(); - let rendEndLineNumber = this._visibleLines.getEndLineNumber(); + const rendStartLineNumber = this._visibleLines.getStartLineNumber(); + const rendEndLineNumber = this._visibleLines.getEndLineNumber(); if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) { // Couldn't find line return null; } let column = this._visibleLines.getVisibleLine(lineNumber).getColumnOfNodeOffset(lineNumber, spanNode, offset); - let minColumn = this._context.model.getLineMinColumn(lineNumber); + const minColumn = this._context.model.getLineMinColumn(lineNumber); if (column < minColumn) { column = minColumn; } @@ -333,10 +333,10 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, * @returns the line number of this view line dom node. */ private _getLineNumberFor(domNode: HTMLElement): number { - let startLineNumber = this._visibleLines.getStartLineNumber(); - let endLineNumber = this._visibleLines.getEndLineNumber(); + const startLineNumber = this._visibleLines.getStartLineNumber(); + const endLineNumber = this._visibleLines.getEndLineNumber(); for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { - let line = this._visibleLines.getVisibleLine(lineNumber); + const line = this._visibleLines.getVisibleLine(lineNumber); if (domNode === line.getDomNode()) { return lineNumber; } @@ -345,8 +345,8 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, } public getLineWidth(lineNumber: number): number { - let rendStartLineNumber = this._visibleLines.getStartLineNumber(); - let rendEndLineNumber = this._visibleLines.getEndLineNumber(); + const rendStartLineNumber = this._visibleLines.getStartLineNumber(); + const rendEndLineNumber = this._visibleLines.getEndLineNumber(); if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) { // Couldn't find line return -1; @@ -362,38 +362,38 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, return null; } - let originalEndLineNumber = _range.endLineNumber; + const originalEndLineNumber = _range.endLineNumber; const range = Range.intersectRanges(_range, this._lastRenderedData.getCurrentVisibleRange()); if (!range) { return null; } let visibleRanges: LineVisibleRanges[] = [], visibleRangesLen = 0; - let domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot); + const domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot); let nextLineModelLineNumber: number = 0; if (includeNewLines) { nextLineModelLineNumber = this._context.model.coordinatesConverter.convertViewPositionToModelPosition(new Position(range.startLineNumber, 1)).lineNumber; } - let rendStartLineNumber = this._visibleLines.getStartLineNumber(); - let rendEndLineNumber = this._visibleLines.getEndLineNumber(); + const rendStartLineNumber = this._visibleLines.getStartLineNumber(); + const rendEndLineNumber = this._visibleLines.getEndLineNumber(); for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) { if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) { continue; } - let startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1; - let endColumn = lineNumber === range.endLineNumber ? range.endColumn : this._context.model.getLineMaxColumn(lineNumber); - let visibleRangesForLine = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(startColumn, endColumn, domReadingContext); + const startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1; + const endColumn = lineNumber === range.endLineNumber ? range.endColumn : this._context.model.getLineMaxColumn(lineNumber); + const visibleRangesForLine = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(startColumn, endColumn, domReadingContext); if (!visibleRangesForLine || visibleRangesForLine.length === 0) { continue; } if (includeNewLines && lineNumber < originalEndLineNumber) { - let currentLineModelLineNumber = nextLineModelLineNumber; + const currentLineModelLineNumber = nextLineModelLineNumber; nextLineModelLineNumber = this._context.model.coordinatesConverter.convertViewPositionToModelPosition(new Position(lineNumber + 1, 1)).lineNumber; if (currentLineModelLineNumber !== nextLineModelLineNumber) { @@ -425,19 +425,19 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, } let result: HorizontalRange[] = []; - let domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot); + const domReadingContext = new DomReadingContext(this.domNode.domNode, this._textRangeRestingSpot); - let rendStartLineNumber = this._visibleLines.getStartLineNumber(); - let rendEndLineNumber = this._visibleLines.getEndLineNumber(); + const rendStartLineNumber = this._visibleLines.getStartLineNumber(); + const rendEndLineNumber = this._visibleLines.getEndLineNumber(); for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) { if (lineNumber < rendStartLineNumber || lineNumber > rendEndLineNumber) { continue; } - let startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1; - let endColumn = lineNumber === range.endLineNumber ? range.endColumn : this._context.model.getLineMaxColumn(lineNumber); - let visibleRangesForLine = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(startColumn, endColumn, domReadingContext); + const startColumn = lineNumber === range.startLineNumber ? range.startColumn : 1; + const endColumn = lineNumber === range.endLineNumber ? range.endColumn : this._context.model.getLineMaxColumn(lineNumber); + const visibleRangesForLine = this._visibleLines.getVisibleLine(lineNumber).getVisibleRangesForRange(startColumn, endColumn, domReadingContext); if (!visibleRangesForLine || visibleRangesForLine.length === 0) { continue; @@ -542,9 +542,9 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, this.onDidRender(); // compute new scroll position - let newScrollLeft = this._computeScrollLeftToRevealRange(revealLineNumber, revealStartColumn, revealEndColumn); + const newScrollLeft = this._computeScrollLeftToRevealRange(revealLineNumber, revealStartColumn, revealEndColumn); - let isViewportWrapping = this._isViewportWrapping; + const isViewportWrapping = this._isViewportWrapping; if (!isViewportWrapping) { // ensure `scrollWidth` is large enough this._ensureMaxLineWidth(newScrollLeft.maxHorizontalOffset); @@ -579,7 +579,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // --- width private _ensureMaxLineWidth(lineWidth: number): void { - let iLineWidth = Math.ceil(lineWidth); + const iLineWidth = Math.ceil(lineWidth); if (this._maxLineWidth < iLineWidth) { this._maxLineWidth = iLineWidth; this._context.viewLayout.onMaxLineWidthChanged(this._maxLineWidth); @@ -587,9 +587,9 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, } private _computeScrollTopToRevealRange(viewport: Viewport, range: Range, verticalType: viewEvents.VerticalRevealType): number { - let viewportStartY = viewport.top; - let viewportHeight = viewport.height; - let viewportEndY = viewportStartY + viewportHeight; + const viewportStartY = viewport.top; + const viewportHeight = viewport.height; + const viewportEndY = viewportStartY + viewportHeight; let boxStartY: number; let boxEndY: number; @@ -609,7 +609,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, newScrollTop = viewportStartY; } else { // Box is outside the viewport... center it - let boxMiddleY = (boxStartY + boxEndY) / 2; + const boxMiddleY = (boxStartY + boxEndY) / 2; newScrollTop = Math.max(0, boxMiddleY - viewportHeight / 2); } } else { @@ -623,11 +623,11 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, let maxHorizontalOffset = 0; - let viewport = this._context.viewLayout.getCurrentViewport(); - let viewportStartX = viewport.left; - let viewportEndX = viewportStartX + viewport.width; + const viewport = this._context.viewLayout.getCurrentViewport(); + const viewportStartX = viewport.left; + const viewportEndX = viewportStartX + viewport.width; - let visibleRanges = this.visibleRangesForRange2(new Range(lineNumber, startColumn, lineNumber, endColumn)); + const visibleRanges = this.visibleRangesForRange2(new Range(lineNumber, startColumn, lineNumber, endColumn)); let boxStartX = Number.MAX_VALUE; let boxEndX = 0; @@ -653,7 +653,7 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, boxStartX = Math.max(0, boxStartX - ViewLines.HORIZONTAL_EXTRA_PX); boxEndX += this._revealHorizontalRightPadding; - let newScrollLeft = this._computeMinimumScrolling(viewportStartX, viewportEndX, boxStartX, boxEndX); + const newScrollLeft = this._computeMinimumScrolling(viewportStartX, viewportEndX, boxStartX, boxEndX); return { scrollLeft: newScrollLeft, maxHorizontalOffset: maxHorizontalOffset @@ -668,8 +668,8 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, revealAtStart = !!revealAtStart; revealAtEnd = !!revealAtEnd; - let viewportLength = viewportEnd - viewportStart; - let boxLength = boxEnd - boxStart; + const viewportLength = viewportEnd - viewportStart; + const boxLength = boxEnd - boxStart; if (boxLength < viewportLength) { // The box would fit in the viewport diff --git a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts index 23262f4bd9..2398b61155 100644 --- a/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts +++ b/src/vs/editor/browser/viewParts/linesDecorations/linesDecorations.ts @@ -11,7 +11,7 @@ import * as viewEvents from 'vs/editor/common/view/viewEvents'; export class LinesDecorationsOverlay extends DedupOverlay { - private _context: ViewContext; + private readonly _context: ViewContext; private _decorationsLeft: number; private _decorationsWidth: number; @@ -66,11 +66,11 @@ export class LinesDecorationsOverlay extends DedupOverlay { // --- end event handlers protected _getDecorations(ctx: RenderingContext): DecorationToRender[] { - let decorations = ctx.getDecorationsInViewport(); + const decorations = ctx.getDecorationsInViewport(); let r: DecorationToRender[] = [], rLen = 0; for (let i = 0, len = decorations.length; i < len; i++) { - let d = decorations[i]; - let linesDecorationsClassName = d.options.linesDecorationsClassName; + const d = decorations[i]; + const linesDecorationsClassName = d.options.linesDecorationsClassName; if (linesDecorationsClassName) { r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, linesDecorationsClassName); } @@ -79,18 +79,18 @@ export class LinesDecorationsOverlay extends DedupOverlay { } public prepareRender(ctx: RenderingContext): void { - let visibleStartLineNumber = ctx.visibleRange.startLineNumber; - let visibleEndLineNumber = ctx.visibleRange.endLineNumber; - let toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, this._getDecorations(ctx)); + const visibleStartLineNumber = ctx.visibleRange.startLineNumber; + const visibleEndLineNumber = ctx.visibleRange.endLineNumber; + const toRender = this._render(visibleStartLineNumber, visibleEndLineNumber, this._getDecorations(ctx)); - let left = this._decorationsLeft.toString(); - let width = this._decorationsWidth.toString(); - let common = '" style="left:' + left + 'px;width:' + width + 'px;">
'; + const left = this._decorationsLeft.toString(); + const width = this._decorationsWidth.toString(); + const common = '" style="left:' + left + 'px;width:' + width + 'px;">
'; - let output: string[] = []; + const output: string[] = []; for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { - let lineIndex = lineNumber - visibleStartLineNumber; - let classNames = toRender[lineIndex]; + const lineIndex = lineNumber - visibleStartLineNumber; + const classNames = toRender[lineIndex]; let lineOutput = ''; for (let i = 0, len = classNames.length; i < len; i++) { lineOutput += '
'; diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 9e4bfd43fb..98158ce394 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -200,7 +200,7 @@ class MinimapLayout { * Compute a desired `scrollPosition` such that the slider moves by `delta`. */ public getDesiredScrollTopFromDelta(delta: number): number { - let desiredSliderPosition = this.sliderTop + delta; + const desiredSliderPosition = this.sliderTop + delta; return Math.round(desiredSliderPosition / this._computedSliderRatio); } @@ -350,7 +350,7 @@ class RenderData { } _get(): { imageData: ImageData; rendLineNumberStart: number; lines: MinimapLine[]; } { - let tmp = this._renderedLines._get(); + const tmp = this._renderedLines._get(); return { imageData: this._imageData, rendLineNumberStart: tmp.rendLineNumberStart, @@ -396,7 +396,7 @@ class MinimapBuffers { public getBuffer(): ImageData { // rotate buffers this._lastUsedBuffer = 1 - this._lastUsedBuffer; - let result = this._buffers[this._lastUsedBuffer]; + const result = this._buffers[this._lastUsedBuffer]; // fill with background color result.data.set(this._backgroundFillData); @@ -409,7 +409,7 @@ class MinimapBuffers { const backgroundG = background.g; const backgroundB = background.b; - let result = new Uint8ClampedArray(WIDTH * HEIGHT * 4); + const result = new Uint8ClampedArray(WIDTH * HEIGHT * 4); let offset = 0; for (let i = 0; i < HEIGHT; i++) { for (let j = 0; j < WIDTH; j++) { @@ -584,7 +584,7 @@ export class Minimap extends ViewPart { } private _onOptionsMaybeChanged(): boolean { - let opts = new MinimapOptions(this._context.configuration); + const opts = new MinimapOptions(this._context.configuration); if (this._options.equals(opts)) { return false; } @@ -721,7 +721,7 @@ export class Minimap extends ViewPart { // Render the rest of lines let dy = 0; - let renderedLines: MinimapLine[] = []; + const renderedLines: MinimapLine[] = []; for (let lineIndex = 0, lineCount = endLineNumber - startLineNumber + 1; lineIndex < lineCount; lineIndex++) { if (needed[lineIndex]) { Minimap._renderLine( @@ -764,7 +764,7 @@ export class Minimap extends ViewPart { lastRenderData: RenderData | null, ): [number, number, boolean[]] { - let needed: boolean[] = []; + const needed: boolean[] = []; if (!lastRenderData) { for (let i = 0, len = endLineNumber - startLineNumber + 1; i < len; i++) { needed[i] = true; @@ -801,10 +801,10 @@ export class Minimap extends ViewPart { continue; } - let sourceStart = source_dy * WIDTH * 4; - let sourceEnd = (source_dy + minimapLineHeight) * WIDTH * 4; - let destStart = dest_dy * WIDTH * 4; - let destEnd = (dest_dy + minimapLineHeight) * WIDTH * 4; + const sourceStart = source_dy * WIDTH * 4; + const sourceEnd = (source_dy + minimapLineHeight) * WIDTH * 4; + const destStart = dest_dy * WIDTH * 4; + const destEnd = (dest_dy + minimapLineHeight) * WIDTH * 4; if (copySourceEnd === sourceStart && copyDestEnd === destStart) { // contiguous zone => extend copy request @@ -881,7 +881,7 @@ export class Minimap extends ViewPart { const charCode = content.charCodeAt(charIndex); if (charCode === CharCode.Tab) { - let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize; + const insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize; tabsCharDelta += insertSpacesCount - 1; // No need to render anything since tab is invisible dx += insertSpacesCount * charWidth; @@ -890,7 +890,7 @@ export class Minimap extends ViewPart { dx += charWidth; } else { // Render twice for a full width character - let count = strings.isFullWidthCharacter(charCode) ? 2 : 1; + const count = strings.isFullWidthCharacter(charCode) ? 2 : 1; for (let i = 0; i < count; i++) { if (renderMinimap === RenderMinimap.Large) { diff --git a/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts b/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts index 693f8c522d..18f46a26dd 100644 --- a/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts +++ b/src/vs/editor/browser/viewParts/overlayWidgets/overlayWidgets.ts @@ -24,7 +24,7 @@ interface IWidgetMap { export class ViewOverlayWidgets extends ViewPart { private _widgets: IWidgetMap; - private _domNode: FastDomNode; + private readonly _domNode: FastDomNode; private _verticalScrollbarWidth: number; private _minimapWidth: number; @@ -90,7 +90,7 @@ export class ViewOverlayWidgets extends ViewPart { } public setWidgetPosition(widget: IOverlayWidget, preference: OverlayWidgetPositionPreference | null): boolean { - let widgetData = this._widgets[widget.getId()]; + const widgetData = this._widgets[widget.getId()]; if (widgetData.preference === preference) { return false; } @@ -102,7 +102,7 @@ export class ViewOverlayWidgets extends ViewPart { } public removeWidget(widget: IOverlayWidget): void { - let widgetId = widget.getId(); + const widgetId = widget.getId(); if (this._widgets.hasOwnProperty(widgetId)) { const widgetData = this._widgets[widgetId]; const domNode = widgetData.domNode.domNode; @@ -125,7 +125,7 @@ export class ViewOverlayWidgets extends ViewPart { domNode.setTop(0); domNode.setRight((2 * this._verticalScrollbarWidth) + this._minimapWidth); } else if (widgetData.preference === OverlayWidgetPositionPreference.BOTTOM_RIGHT_CORNER) { - let widgetHeight = domNode.domNode.clientHeight; + const widgetHeight = domNode.domNode.clientHeight; domNode.setTop((this._editorHeight - widgetHeight - 2 * this._horizontalScrollbarHeight)); domNode.setRight((2 * this._verticalScrollbarWidth) + this._minimapWidth); } else if (widgetData.preference === OverlayWidgetPositionPreference.TOP_CENTER) { @@ -141,9 +141,9 @@ export class ViewOverlayWidgets extends ViewPart { public render(ctx: RestrictedRenderingContext): void { this._domNode.setWidth(this._editorWidth); - let keys = Object.keys(this._widgets); + const keys = Object.keys(this._widgets); for (let i = 0, len = keys.length; i < len; i++) { - let widgetId = keys[i]; + const widgetId = keys[i]; this._renderWidget(this._widgets[widgetId]); } } diff --git a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts index 200fdb88bd..782ae11ebf 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/decorationsOverviewRuler.ts @@ -340,7 +340,7 @@ export class DecorationsOverviewRuler extends ViewPart { let y1 = (viewLayout.getVerticalOffsetForLineNumber(startLineNumber) * heightRatio) | 0; let y2 = ((viewLayout.getVerticalOffsetForLineNumber(endLineNumber) + lineHeight) * heightRatio) | 0; - let height = y2 - y1; + const height = y2 - y1; if (height < minDecorationHeight) { let yCenter = ((y1 + y2) / 2) | 0; if (yCenter < halfMinDecorationHeight) { diff --git a/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts index 7377c9ad93..1076832c7d 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts @@ -13,9 +13,9 @@ import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; export class OverviewRuler extends ViewEventHandler implements IOverviewRuler { - private _context: ViewContext; - private _domNode: FastDomNode; - private _zoneManager: OverviewZoneManager; + private readonly _context: ViewContext; + private readonly _domNode: FastDomNode; + private readonly _zoneManager: OverviewZoneManager; constructor(context: ViewContext, cssClassName: string) { super(); @@ -114,10 +114,10 @@ export class OverviewRuler extends ViewEventHandler implements IOverviewRuler { const width = this._zoneManager.getCanvasWidth(); const height = this._zoneManager.getCanvasHeight(); - let colorZones = this._zoneManager.resolveColorZones(); - let id2Color = this._zoneManager.getId2Color(); + const colorZones = this._zoneManager.resolveColorZones(); + const id2Color = this._zoneManager.getId2Color(); - let ctx = this._domNode.domNode.getContext('2d')!; + const ctx = this._domNode.domNode.getContext('2d')!; ctx.clearRect(0, 0, width, height); if (colorZones.length > 0) { this._renderOneLane(ctx, colorZones, id2Color, width); diff --git a/src/vs/editor/browser/viewParts/rulers/rulers.ts b/src/vs/editor/browser/viewParts/rulers/rulers.ts index 61785b1530..f0d24bc82e 100644 --- a/src/vs/editor/browser/viewParts/rulers/rulers.ts +++ b/src/vs/editor/browser/viewParts/rulers/rulers.ts @@ -15,7 +15,7 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic export class Rulers extends ViewPart { public domNode: FastDomNode; - private _renderedRulers: FastDomNode[]; + private readonly _renderedRulers: FastDomNode[]; private _rulers: number[]; private _typicalHalfwidthCharacterWidth: number; @@ -64,10 +64,11 @@ export class Rulers extends ViewPart { } if (currentCount < desiredCount) { - const rulerWidth = this._context.model.getTabSize(); + const { tabSize } = this._context.model.getOptions(); + const rulerWidth = tabSize; let addCount = desiredCount - currentCount; while (addCount > 0) { - let node = createFastDomNode(document.createElement('div')); + const node = createFastDomNode(document.createElement('div')); node.setClassName('view-ruler'); node.setWidth(rulerWidth); this.domNode.appendChild(node); @@ -79,7 +80,7 @@ export class Rulers extends ViewPart { let removeCount = currentCount - desiredCount; while (removeCount > 0) { - let node = this._renderedRulers.pop()!; + const node = this._renderedRulers.pop()!; this.domNode.removeChild(node); removeCount--; } @@ -90,7 +91,7 @@ export class Rulers extends ViewPart { this._ensureRulersCount(); for (let i = 0, len = this._rulers.length; i < len; i++) { - let node = this._renderedRulers[i]; + const node = this._renderedRulers[i]; node.setHeight(Math.min(ctx.scrollHeight, 1000000)); node.setLeft(this._rulers[i] * this._typicalHalfwidthCharacterWidth); diff --git a/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts b/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts index 674b1a6e8f..7b496da70f 100644 --- a/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts +++ b/src/vs/editor/browser/viewParts/scrollDecoration/scrollDecoration.ts @@ -14,7 +14,7 @@ import { registerThemingParticipant } from 'vs/platform/theme/common/themeServic export class ScrollDecorationViewPart extends ViewPart { - private _domNode: FastDomNode; + private readonly _domNode: FastDomNode; private _scrollTop: number; private _width: number; private _shouldShow: boolean; @@ -38,7 +38,7 @@ export class ScrollDecorationViewPart extends ViewPart { } private _updateShouldShow(): boolean { - let newShouldShow = (this._useShadows && this._scrollTop > 0); + const newShouldShow = (this._useShadows && this._scrollTop > 0); if (this._shouldShow !== newShouldShow) { this._shouldShow = newShouldShow; return true; diff --git a/src/vs/editor/browser/viewParts/selections/selections.ts b/src/vs/editor/browser/viewParts/selections/selections.ts index b5759a29c2..cf52b8c68d 100644 --- a/src/vs/editor/browser/viewParts/selections/selections.ts +++ b/src/vs/editor/browser/viewParts/selections/selections.ts @@ -73,7 +73,7 @@ export class SelectionsOverlay extends DynamicViewOverlay { private static readonly ROUNDED_PIECE_WIDTH = 10; - private _context: ViewContext; + private readonly _context: ViewContext; private _lineHeight: number; private _roundedSelection: boolean; private _typicalHalfwidthCharacterWidth: number; @@ -143,7 +143,7 @@ export class SelectionsOverlay extends DynamicViewOverlay { private _visibleRangesHaveGaps(linesVisibleRanges: LineVisibleRangesWithStyle[]): boolean { for (let i = 0, len = linesVisibleRanges.length; i < len; i++) { - let lineVisibleRanges = linesVisibleRanges[i]; + const lineVisibleRanges = linesVisibleRanges[i]; if (lineVisibleRanges.ranges.length > 1) { // There are two ranges on the same line @@ -161,7 +161,7 @@ export class SelectionsOverlay extends DynamicViewOverlay { if (previousFrame && previousFrame.length > 0 && linesVisibleRanges.length > 0) { - let topLineNumber = linesVisibleRanges[0].lineNumber; + const topLineNumber = linesVisibleRanges[0].lineNumber; if (topLineNumber === viewport.startLineNumber) { for (let i = 0; !previousFrameTop && i < previousFrame.length; i++) { if (previousFrame[i].lineNumber === topLineNumber) { @@ -170,7 +170,7 @@ export class SelectionsOverlay extends DynamicViewOverlay { } } - let bottomLineNumber = linesVisibleRanges[linesVisibleRanges.length - 1].lineNumber; + const bottomLineNumber = linesVisibleRanges[linesVisibleRanges.length - 1].lineNumber; if (bottomLineNumber === viewport.endLineNumber) { for (let i = previousFrame.length - 1; !previousFrameBottom && i >= 0; i--) { if (previousFrame[i].lineNumber === bottomLineNumber) { @@ -189,24 +189,24 @@ export class SelectionsOverlay extends DynamicViewOverlay { for (let i = 0, len = linesVisibleRanges.length; i < len; i++) { // We know for a fact that there is precisely one range on each line - let curLineRange = linesVisibleRanges[i].ranges[0]; - let curLeft = curLineRange.left; - let curRight = curLineRange.left + curLineRange.width; + const curLineRange = linesVisibleRanges[i].ranges[0]; + const curLeft = curLineRange.left; + const curRight = curLineRange.left + curLineRange.width; - let startStyle = { + const startStyle = { top: CornerStyle.EXTERN, bottom: CornerStyle.EXTERN }; - let endStyle = { + const endStyle = { top: CornerStyle.EXTERN, bottom: CornerStyle.EXTERN }; if (i > 0) { // Look above - let prevLeft = linesVisibleRanges[i - 1].ranges[0].left; - let prevRight = linesVisibleRanges[i - 1].ranges[0].left + linesVisibleRanges[i - 1].ranges[0].width; + const prevLeft = linesVisibleRanges[i - 1].ranges[0].left; + const prevRight = linesVisibleRanges[i - 1].ranges[0].left + linesVisibleRanges[i - 1].ranges[0].width; if (abs(curLeft - prevLeft) < epsilon) { startStyle.top = CornerStyle.FLAT; @@ -227,8 +227,8 @@ export class SelectionsOverlay extends DynamicViewOverlay { if (i + 1 < len) { // Look below - let nextLeft = linesVisibleRanges[i + 1].ranges[0].left; - let nextRight = linesVisibleRanges[i + 1].ranges[0].left + linesVisibleRanges[i + 1].ranges[0].width; + const nextLeft = linesVisibleRanges[i + 1].ranges[0].left; + const nextRight = linesVisibleRanges[i + 1].ranges[0].left + linesVisibleRanges[i + 1].ranges[0].width; if (abs(curLeft - nextLeft) < epsilon) { startStyle.bottom = CornerStyle.FLAT; @@ -253,9 +253,9 @@ export class SelectionsOverlay extends DynamicViewOverlay { } private _getVisibleRangesWithStyle(selection: Range, ctx: RenderingContext, previousFrame: LineVisibleRangesWithStyle[] | null): LineVisibleRangesWithStyle[] { - let _linesVisibleRanges = ctx.linesVisibleRangesForRange(selection, true) || []; - let linesVisibleRanges = _linesVisibleRanges.map(toStyled); - let visibleRangesHaveGaps = this._visibleRangesHaveGaps(linesVisibleRanges); + const _linesVisibleRanges = ctx.linesVisibleRangesForRange(selection, true) || []; + const linesVisibleRanges = _linesVisibleRanges.map(toStyled); + const visibleRangesHaveGaps = this._visibleRangesHaveGaps(linesVisibleRanges); if (!isIEWithZoomingIssuesNearRoundedBorders && !visibleRangesHaveGaps && this._roundedSelection) { this._enrichVisibleRangesWithStyle(ctx.visibleRange, linesVisibleRanges, previousFrame); @@ -282,25 +282,25 @@ export class SelectionsOverlay extends DynamicViewOverlay { } private _actualRenderOneSelection(output2: string[], visibleStartLineNumber: number, hasMultipleSelections: boolean, visibleRanges: LineVisibleRangesWithStyle[]): void { - let visibleRangesHaveStyle = (visibleRanges.length > 0 && visibleRanges[0].ranges[0].startStyle); - let fullLineHeight = (this._lineHeight).toString(); - let reducedLineHeight = (this._lineHeight - 1).toString(); + const visibleRangesHaveStyle = (visibleRanges.length > 0 && visibleRanges[0].ranges[0].startStyle); + const fullLineHeight = (this._lineHeight).toString(); + const reducedLineHeight = (this._lineHeight - 1).toString(); - let firstLineNumber = (visibleRanges.length > 0 ? visibleRanges[0].lineNumber : 0); - let lastLineNumber = (visibleRanges.length > 0 ? visibleRanges[visibleRanges.length - 1].lineNumber : 0); + const firstLineNumber = (visibleRanges.length > 0 ? visibleRanges[0].lineNumber : 0); + const lastLineNumber = (visibleRanges.length > 0 ? visibleRanges[visibleRanges.length - 1].lineNumber : 0); for (let i = 0, len = visibleRanges.length; i < len; i++) { - let lineVisibleRanges = visibleRanges[i]; - let lineNumber = lineVisibleRanges.lineNumber; - let lineIndex = lineNumber - visibleStartLineNumber; + const lineVisibleRanges = visibleRanges[i]; + const lineNumber = lineVisibleRanges.lineNumber; + const lineIndex = lineNumber - visibleStartLineNumber; - let lineHeight = hasMultipleSelections ? (lineNumber === lastLineNumber || lineNumber === firstLineNumber ? reducedLineHeight : fullLineHeight) : fullLineHeight; - let top = hasMultipleSelections ? (lineNumber === firstLineNumber ? 1 : 0) : 0; + const lineHeight = hasMultipleSelections ? (lineNumber === lastLineNumber || lineNumber === firstLineNumber ? reducedLineHeight : fullLineHeight) : fullLineHeight; + const top = hasMultipleSelections ? (lineNumber === firstLineNumber ? 1 : 0) : 0; let lineOutput = ''; for (let j = 0, lenJ = lineVisibleRanges.ranges.length; j < lenJ; j++) { - let visibleRange = lineVisibleRanges.ranges[j]; + const visibleRange = lineVisibleRanges.ranges[j]; if (visibleRangesHaveStyle) { const startStyle = visibleRange.startStyle!; @@ -366,23 +366,23 @@ export class SelectionsOverlay extends DynamicViewOverlay { private _previousFrameVisibleRangesWithStyle: (LineVisibleRangesWithStyle[] | null)[] = []; public prepareRender(ctx: RenderingContext): void { - let output: string[] = []; - let visibleStartLineNumber = ctx.visibleRange.startLineNumber; - let visibleEndLineNumber = ctx.visibleRange.endLineNumber; + const output: string[] = []; + const visibleStartLineNumber = ctx.visibleRange.startLineNumber; + const visibleEndLineNumber = ctx.visibleRange.endLineNumber; for (let lineNumber = visibleStartLineNumber; lineNumber <= visibleEndLineNumber; lineNumber++) { - let lineIndex = lineNumber - visibleStartLineNumber; + const lineIndex = lineNumber - visibleStartLineNumber; output[lineIndex] = ''; } - let thisFrameVisibleRangesWithStyle: (LineVisibleRangesWithStyle[] | null)[] = []; + const thisFrameVisibleRangesWithStyle: (LineVisibleRangesWithStyle[] | null)[] = []; for (let i = 0, len = this._selections.length; i < len; i++) { - let selection = this._selections[i]; + const selection = this._selections[i]; if (selection.isEmpty()) { thisFrameVisibleRangesWithStyle[i] = null; continue; } - let visibleRangesWithStyle = this._getVisibleRangesWithStyle(selection, ctx, this._previousFrameVisibleRangesWithStyle[i]); + const visibleRangesWithStyle = this._getVisibleRangesWithStyle(selection, ctx, this._previousFrameVisibleRangesWithStyle[i]); thisFrameVisibleRangesWithStyle[i] = visibleRangesWithStyle; this._actualRenderOneSelection(output, visibleStartLineNumber, this._selections.length > 1, visibleRangesWithStyle); } @@ -395,7 +395,7 @@ export class SelectionsOverlay extends DynamicViewOverlay { if (!this._renderResult) { return ''; } - let lineIndex = lineNumber - startLineNumber; + const lineIndex = lineNumber - startLineNumber; if (lineIndex < 0 || lineIndex >= this._renderResult.length) { return ''; } diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts index 8eb1c489d0..390f00c6b1 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts @@ -28,16 +28,16 @@ export class ViewCursors extends ViewPart { private _isVisible: boolean; - private _domNode: FastDomNode; + private readonly _domNode: FastDomNode; - private _startCursorBlinkAnimation: TimeoutTimer; - private _cursorFlatBlinkInterval: IntervalTimer; + private readonly _startCursorBlinkAnimation: TimeoutTimer; + private readonly _cursorFlatBlinkInterval: IntervalTimer; private _blinkingEnabled: boolean; private _editorHasFocus: boolean; - private _primaryCursor: ViewCursor; - private _secondaryCursors: ViewCursor[]; + private readonly _primaryCursor: ViewCursor; + private readonly _secondaryCursors: ViewCursor[]; private _renderData: IViewCursorRenderData[]; constructor(context: ViewContext) { @@ -108,15 +108,15 @@ export class ViewCursors extends ViewPart { if (this._secondaryCursors.length < secondaryPositions.length) { // Create new cursors - let addCnt = secondaryPositions.length - this._secondaryCursors.length; + const addCnt = secondaryPositions.length - this._secondaryCursors.length; for (let i = 0; i < addCnt; i++) { - let newCursor = new ViewCursor(this._context); + const newCursor = new ViewCursor(this._context); this._domNode.domNode.insertBefore(newCursor.getDomNode().domNode, this._primaryCursor.getDomNode().domNode.nextSibling); this._secondaryCursors.push(newCursor); } } else if (this._secondaryCursors.length > secondaryPositions.length) { // Remove some cursors - let removeCnt = this._secondaryCursors.length - secondaryPositions.length; + const removeCnt = this._secondaryCursors.length - secondaryPositions.length; for (let i = 0; i < removeCnt; i++) { this._domNode.removeChild(this._secondaryCursors[0].getDomNode()); this._secondaryCursors.splice(0, 1); @@ -129,7 +129,7 @@ export class ViewCursors extends ViewPart { } public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { - let positions: Position[] = []; + const positions: Position[] = []; for (let i = 0, len = e.selections.length; i < len; i++) { positions[i] = e.selections[i].getPosition(); } @@ -169,7 +169,7 @@ export class ViewCursors extends ViewPart { return true; } public onTokensChanged(e: viewEvents.ViewTokensChangedEvent): boolean { - let shouldRender = (position: Position) => { + const shouldRender = (position: Position) => { for (let i = 0, len = e.ranges.length; i < len; i++) { if (e.ranges[i].fromLineNumber <= position.lineNumber && position.lineNumber <= e.ranges[i].toLineNumber) { return true; @@ -209,11 +209,11 @@ export class ViewCursors extends ViewPart { this._startCursorBlinkAnimation.cancel(); this._cursorFlatBlinkInterval.cancel(); - let blinkingStyle = this._getCursorBlinking(); + const blinkingStyle = this._getCursorBlinking(); // hidden and solid are special as they involve no animations - let isHidden = (blinkingStyle === TextEditorCursorBlinkingStyle.Hidden); - let isSolid = (blinkingStyle === TextEditorCursorBlinkingStyle.Solid); + const isHidden = (blinkingStyle === TextEditorCursorBlinkingStyle.Hidden); + const isSolid = (blinkingStyle === TextEditorCursorBlinkingStyle.Solid); if (isHidden) { this._hide(); diff --git a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts index d5c88714a9..b86eabea57 100644 --- a/src/vs/editor/browser/viewParts/viewZones/viewZones.ts +++ b/src/vs/editor/browser/viewParts/viewZones/viewZones.ts @@ -69,11 +69,11 @@ export class ViewZones extends ViewPart { private _recomputeWhitespacesProps(): boolean { let hadAChange = false; - let keys = Object.keys(this._zones); + const keys = Object.keys(this._zones); for (let i = 0, len = keys.length; i < len; i++) { - let id = keys[i]; - let zone = this._zones[id]; - let props = this._computeWhitespaceProps(zone.delegate); + const id = keys[i]; + const zone = this._zones[id]; + const props = this._computeWhitespaceProps(zone.delegate); if (this._context.viewLayout.changeWhitespace(parseInt(id, 10), props.afterViewLineNumber, props.heightInPx)) { this._safeCallOnComputedHeight(zone.delegate, props.heightInPx); hadAChange = true; @@ -150,7 +150,7 @@ export class ViewZones extends ViewPart { column: zone.afterColumn }); } else { - let validAfterLineNumber = this._context.model.validateModelPosition({ + const validAfterLineNumber = this._context.model.validateModelPosition({ lineNumber: zone.afterLineNumber, column: 1 }).lineNumber; @@ -174,8 +174,8 @@ export class ViewZones extends ViewPart { }); } - let viewPosition = this._context.model.coordinatesConverter.convertModelPositionToViewPosition(zoneAfterModelPosition); - let isVisible = this._context.model.coordinatesConverter.modelPositionIsVisible(zoneBeforeModelPosition); + const viewPosition = this._context.model.coordinatesConverter.convertModelPositionToViewPosition(zoneAfterModelPosition); + const isVisible = this._context.model.coordinatesConverter.modelPositionIsVisible(zoneBeforeModelPosition); return { afterViewLineNumber: viewPosition.lineNumber, heightInPx: (isVisible ? this._heightInPixels(zone) : 0), @@ -184,10 +184,10 @@ export class ViewZones extends ViewPart { } public addZone(zone: IViewZone): number { - let props = this._computeWhitespaceProps(zone); - let whitespaceId = this._context.viewLayout.addWhitespace(props.afterViewLineNumber, this._getZoneOrdinal(zone), props.heightInPx, props.minWidthInPx); + const props = this._computeWhitespaceProps(zone); + const whitespaceId = this._context.viewLayout.addWhitespace(props.afterViewLineNumber, this._getZoneOrdinal(zone), props.heightInPx, props.minWidthInPx); - let myZone: IMyViewZone = { + const myZone: IMyViewZone = { whitespaceId: whitespaceId, delegate: zone, isVisible: false, @@ -221,7 +221,7 @@ export class ViewZones extends ViewPart { public removeZone(id: number): boolean { if (this._zones.hasOwnProperty(id.toString())) { - let zone = this._zones[id.toString()]; + const zone = this._zones[id.toString()]; delete this._zones[id.toString()]; this._context.viewLayout.removeWhitespace(zone.whitespaceId); @@ -245,9 +245,9 @@ export class ViewZones extends ViewPart { public layoutZone(id: number): boolean { let changed = false; if (this._zones.hasOwnProperty(id.toString())) { - let zone = this._zones[id.toString()]; - let props = this._computeWhitespaceProps(zone.delegate); - // let newOrdinal = this._getZoneOrdinal(zone.delegate); + const zone = this._zones[id.toString()]; + const props = this._computeWhitespaceProps(zone.delegate); + // const newOrdinal = this._getZoneOrdinal(zone.delegate); changed = this._context.viewLayout.changeWhitespace(zone.whitespaceId, props.afterViewLineNumber, props.heightInPx) || changed; // TODO@Alex: change `newOrdinal` too @@ -261,7 +261,7 @@ export class ViewZones extends ViewPart { public shouldSuppressMouseDownOnViewZone(id: number): boolean { if (this._zones.hasOwnProperty(id.toString())) { - let zone = this._zones[id.toString()]; + const zone = this._zones[id.toString()]; return Boolean(zone.delegate.suppressMouseDown); } return false; @@ -310,7 +310,7 @@ export class ViewZones extends ViewPart { public render(ctx: RestrictedRenderingContext): void { const visibleWhitespaces = ctx.viewportData.whitespaceViewportData; - let visibleZones: { [id: string]: IViewWhitespaceViewportData; } = {}; + const visibleZones: { [id: string]: IViewWhitespaceViewportData; } = {}; let hasVisibleZone = false; for (let i = 0, len = visibleWhitespaces.length; i < len; i++) { @@ -318,10 +318,10 @@ export class ViewZones extends ViewPart { hasVisibleZone = true; } - let keys = Object.keys(this._zones); + const keys = Object.keys(this._zones); for (let i = 0, len = keys.length; i < len; i++) { - let id = keys[i]; - let zone = this._zones[id]; + const id = keys[i]; + const zone = this._zones[id]; let newTop = 0; let newHeight = 0; diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index f4d01e67a2..7f77744330 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -48,6 +48,7 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; let EDITOR_ID = 0; @@ -140,11 +141,11 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE private readonly _onDidLayoutChange: Emitter = this._register(new Emitter()); public readonly onDidLayoutChange: Event = this._onDidLayoutChange.event; - private _editorTextFocus: BooleanEventEmitter = this._register(new BooleanEventEmitter()); + private readonly _editorTextFocus: BooleanEventEmitter = this._register(new BooleanEventEmitter()); public readonly onDidFocusEditorText: Event = this._editorTextFocus.onDidChangeToTrue; public readonly onDidBlurEditorText: Event = this._editorTextFocus.onDidChangeToFalse; - private _editorWidgetFocus: BooleanEventEmitter = this._register(new BooleanEventEmitter()); + private readonly _editorWidgetFocus: BooleanEventEmitter = this._register(new BooleanEventEmitter()); public readonly onDidFocusEditorWidget: Event = this._editorWidgetFocus.onDidChangeToTrue; public readonly onDidBlurEditorWidget: Event = this._editorWidgetFocus.onDidChangeToFalse; @@ -237,7 +238,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE @ICommandService commandService: ICommandService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @INotificationService notificationService: INotificationService + @INotificationService notificationService: INotificationService, + @IAccessibilityService accessibilityService: IAccessibilityService ) { super(); this._domElement = domElement; @@ -248,7 +250,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._telemetryData = codeEditorWidgetOptions.telemetryData; options = options || {}; - this._configuration = this._register(this._createConfiguration(options)); + this._configuration = this._register(this._createConfiguration(options, accessibilityService)); this._register(this._configuration.onDidChange((e) => { this._onDidChangeConfiguration.fire(e); @@ -292,9 +294,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE contributions = EditorExtensionsRegistry.getEditorContributions(); } for (let i = 0, len = contributions.length; i < len; i++) { - let ctor = contributions[i]; + const ctor = contributions[i]; try { - let contribution = this._instantiationService.createInstance(ctor, this); + const contribution = this._instantiationService.createInstance(ctor, this); this._contributions[contribution.getId()] = contribution; } catch (err) { onUnexpectedError(err); @@ -320,8 +322,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._codeEditorService.addCodeEditor(this); } - protected _createConfiguration(options: editorOptions.IEditorOptions): editorCommon.IConfiguration { - return new Configuration(options, this._domElement); + protected _createConfiguration(options: editorOptions.IEditorOptions, accessibilityService: IAccessibilityService): editorCommon.IConfiguration { + return new Configuration(options, this._domElement, accessibilityService); } public getId(): string { @@ -337,9 +339,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._focusTracker.dispose(); - let keys = Object.keys(this._contributions); + const keys = Object.keys(this._contributions); for (let i = 0, len = keys.length; i < len; i++) { - let contributionId = keys[i]; + const contributionId = keys[i]; this._contributions[contributionId].dispose(); } @@ -372,7 +374,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return ''; } - let preserveBOM: boolean = (options && options.preserveBOM) ? true : false; + const preserveBOM: boolean = (options && options.preserveBOM) ? true : false; let eolPreference = EndOfLinePreference.TextDefined; if (options && options.lineEnding && options.lineEnding === '\n') { eolPreference = EndOfLinePreference.LF; @@ -407,10 +409,10 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return; } - let detachedModel = this._detachModel(); + const detachedModel = this._detachModel(); this._attachModel(model); - let e: editorCommon.IModelChangedEvent = { + const e: editorCommon.IModelChangedEvent = { oldModelUrl: detachedModel ? detachedModel.uri : null, newModelUrl: model ? model.uri : null }; @@ -424,7 +426,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._decorationTypeKeysToIds = {}; if (this._decorationTypeSubtypes) { for (let decorationType in this._decorationTypeSubtypes) { - let subTypes = this._decorationTypeSubtypes[decorationType]; + const subTypes = this._decorationTypeSubtypes[decorationType]; for (let subType in subTypes) { this._removeDecorationType(decorationType + '-' + subType); } @@ -448,11 +450,11 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } private static _getVerticalOffsetForPosition(modelData: ModelData, modelLineNumber: number, modelColumn: number): number { - let modelPosition = modelData.model.validatePosition({ + const modelPosition = modelData.model.validatePosition({ lineNumber: modelLineNumber, column: modelColumn }); - let viewPosition = modelData.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition); + const viewPosition = modelData.viewModel.coordinatesConverter.convertModelPositionToViewPosition(modelPosition); return modelData.viewModel.viewLayout.getVerticalOffsetForLineNumber(viewPosition.lineNumber); } @@ -481,8 +483,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return rawPosition.column; } - let position = this._modelData.model.validatePosition(rawPosition); - let tabSize = this._modelData.model.getOptions().tabSize; + const position = this._modelData.model.validatePosition(rawPosition); + const tabSize = this._modelData.model.getOptions().tabSize; return CursorColumns.visibleColumnFromColumn(this._modelData.model.getLineContent(position.lineNumber), position.column, tabSize) + 1; } @@ -606,8 +608,8 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE public setSelection(selection: ISelection): void; public setSelection(editorSelection: Selection): void; public setSelection(something: any): void { - let isSelection = Selection.isISelection(something); - let isRange = Range.isIRange(something); + const isSelection = Selection.isISelection(something); + const isRange = Range.isIRange(something); if (!isSelection && !isRange) { throw new Error('Invalid arguments'); @@ -617,7 +619,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._setSelectionImpl(something); } else if (isRange) { // act as if it was an IRange - let selection: ISelection = { + const selection: ISelection = { selectionStartLineNumber: something.startLineNumber, selectionStartColumn: something.startColumn, positionLineNumber: something.endLineNumber, @@ -631,7 +633,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE if (!this._modelData) { return; } - let selection = new Selection(sel.selectionStartLineNumber, sel.selectionStartColumn, sel.positionLineNumber, sel.positionColumn); + const selection = new Selection(sel.selectionStartLineNumber, sel.selectionStartColumn, sel.positionLineNumber, sel.positionColumn); this._modelData.cursor.setSelections('api', [selection]); } @@ -823,7 +825,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } const codeEditorState = s as editorCommon.ICodeEditorViewState | null; if (codeEditorState && codeEditorState.cursorState && codeEditorState.viewState) { - let cursorState = codeEditorState.cursorState; + const cursorState = codeEditorState.cursorState; if (Array.isArray(cursorState)) { this._modelData.cursor.restoreState(cursorState); } else { @@ -831,11 +833,11 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._modelData.cursor.restoreState([cursorState]); } - let contributionsState = codeEditorState.contributionsState || {}; - let keys = Object.keys(this._contributions); + const contributionsState = codeEditorState.contributionsState || {}; + const keys = Object.keys(this._contributions); for (let i = 0, len = keys.length; i < len; i++) { - let id = keys[i]; - let contribution = this._contributions[id]; + const id = keys[i]; + const contribution = this._contributions[id]; if (typeof contribution.restoreViewState === 'function') { contribution.restoreViewState(contributionsState[id]); } @@ -857,11 +859,11 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } public getActions(): editorCommon.IEditorAction[] { - let result: editorCommon.IEditorAction[] = []; + const result: editorCommon.IEditorAction[] = []; - let keys = Object.keys(this._actions); + const keys = Object.keys(this._actions); for (let i = 0, len = keys.length; i < len; i++) { - let id = keys[i]; + const id = keys[i]; result.push(this._actions[id]); } @@ -1036,18 +1038,18 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE public setDecorations(decorationTypeKey: string, decorationOptions: editorCommon.IDecorationOptions[]): void { - let newDecorationsSubTypes: { [key: string]: boolean } = {}; - let oldDecorationsSubTypes = this._decorationTypeSubtypes[decorationTypeKey] || {}; + const newDecorationsSubTypes: { [key: string]: boolean } = {}; + const oldDecorationsSubTypes = this._decorationTypeSubtypes[decorationTypeKey] || {}; this._decorationTypeSubtypes[decorationTypeKey] = newDecorationsSubTypes; - let newModelDecorations: IModelDeltaDecoration[] = []; + const newModelDecorations: IModelDeltaDecoration[] = []; for (let decorationOption of decorationOptions) { let typeKey = decorationTypeKey; if (decorationOption.renderOptions) { // identify custom reder options by a hash code over all keys and values // For custom render options register a decoration type if necessary - let subType = hash(decorationOption.renderOptions).toString(16); + const subType = hash(decorationOption.renderOptions).toString(16); // The fact that `decorationTypeKey` appears in the typeKey has no influence // it is just a mechanism to get predictable and unique keys (repeatable for the same options and unique across clients) typeKey = decorationTypeKey + '-' + subType; @@ -1057,7 +1059,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } newDecorationsSubTypes[subType] = true; } - let opts = this._resolveDecorationOptions(typeKey, !!decorationOption.hoverMessage); + const opts = this._resolveDecorationOptions(typeKey, !!decorationOption.hoverMessage); if (decorationOption.hoverMessage) { opts.hoverMessage = decorationOption.hoverMessage; } @@ -1072,33 +1074,33 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } // update all decorations - let oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey] || []; + const oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey] || []; this._decorationTypeKeysToIds[decorationTypeKey] = this.deltaDecorations(oldDecorationsIds, newModelDecorations); } public setDecorationsFast(decorationTypeKey: string, ranges: IRange[]): void { // remove decoration sub types that are no longer used, deregister decoration type if necessary - let oldDecorationsSubTypes = this._decorationTypeSubtypes[decorationTypeKey] || {}; + const oldDecorationsSubTypes = this._decorationTypeSubtypes[decorationTypeKey] || {}; for (let subType in oldDecorationsSubTypes) { this._removeDecorationType(decorationTypeKey + '-' + subType); } this._decorationTypeSubtypes[decorationTypeKey] = {}; const opts = ModelDecorationOptions.createDynamic(this._resolveDecorationOptions(decorationTypeKey, false)); - let newModelDecorations: IModelDeltaDecoration[] = new Array(ranges.length); + const newModelDecorations: IModelDeltaDecoration[] = new Array(ranges.length); for (let i = 0, len = ranges.length; i < len; i++) { newModelDecorations[i] = { range: ranges[i], options: opts }; } // update all decorations - let oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey] || []; + const oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey] || []; this._decorationTypeKeysToIds[decorationTypeKey] = this.deltaDecorations(oldDecorationsIds, newModelDecorations); } public removeDecorations(decorationTypeKey: string): void { // remove decorations for type and sub type - let oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey]; + const oldDecorationsIds = this._decorationTypeKeysToIds[decorationTypeKey]; if (oldDecorationsIds) { this.deltaDecorations(oldDecorationsIds, []); } @@ -1159,7 +1161,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } public addContentWidget(widget: editorBrowser.IContentWidget): void { - let widgetData: IContentWidgetData = { + const widgetData: IContentWidgetData = { widget: widget, position: widget.getPosition() }; @@ -1176,9 +1178,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } public layoutContentWidget(widget: editorBrowser.IContentWidget): void { - let widgetId = widget.getId(); + const widgetId = widget.getId(); if (this._contentWidgets.hasOwnProperty(widgetId)) { - let widgetData = this._contentWidgets[widgetId]; + const widgetData = this._contentWidgets[widgetId]; widgetData.position = widget.getPosition(); if (this._modelData && this._modelData.hasRealView) { this._modelData.view.layoutContentWidget(widgetData); @@ -1187,9 +1189,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } public removeContentWidget(widget: editorBrowser.IContentWidget): void { - let widgetId = widget.getId(); + const widgetId = widget.getId(); if (this._contentWidgets.hasOwnProperty(widgetId)) { - let widgetData = this._contentWidgets[widgetId]; + const widgetData = this._contentWidgets[widgetId]; delete this._contentWidgets[widgetId]; if (this._modelData && this._modelData.hasRealView) { this._modelData.view.removeContentWidget(widgetData); @@ -1198,7 +1200,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } public addOverlayWidget(widget: editorBrowser.IOverlayWidget): void { - let widgetData: IOverlayWidgetData = { + const widgetData: IOverlayWidgetData = { widget: widget, position: widget.getPosition() }; @@ -1215,9 +1217,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } public layoutOverlayWidget(widget: editorBrowser.IOverlayWidget): void { - let widgetId = widget.getId(); + const widgetId = widget.getId(); if (this._overlayWidgets.hasOwnProperty(widgetId)) { - let widgetData = this._overlayWidgets[widgetId]; + const widgetData = this._overlayWidgets[widgetId]; widgetData.position = widget.getPosition(); if (this._modelData && this._modelData.hasRealView) { this._modelData.view.layoutOverlayWidget(widgetData); @@ -1226,9 +1228,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } public removeOverlayWidget(widget: editorBrowser.IOverlayWidget): void { - let widgetId = widget.getId(); + const widgetId = widget.getId(); if (this._overlayWidgets.hasOwnProperty(widgetId)) { - let widgetData = this._overlayWidgets[widgetId]; + const widgetData = this._overlayWidgets[widgetId]; delete this._overlayWidgets[widgetId]; if (this._modelData && this._modelData.hasRealView) { this._modelData.view.removeOverlayWidget(widgetData); @@ -1240,7 +1242,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE if (!this._modelData || !this._modelData.hasRealView) { return; } - let hasChanges = this._modelData.view.change(callback); + const hasChanges = this._modelData.view.change(callback); if (hasChanges) { this._onDidChangeViewZones.fire(); } @@ -1258,11 +1260,11 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE return null; } - let position = this._modelData.model.validatePosition(rawPosition); - let layoutInfo = this._configuration.editor.layoutInfo; + const position = this._modelData.model.validatePosition(rawPosition); + const layoutInfo = this._configuration.editor.layoutInfo; - let top = CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, position.lineNumber, position.column) - this.getScrollTop(); - let left = this._modelData.view.getOffsetForColumn(position.lineNumber, position.column) + layoutInfo.glyphMarginWidth + layoutInfo.lineNumbersWidth + layoutInfo.decorationsWidth - this.getScrollLeft(); + const top = CodeEditorWidget._getVerticalOffsetForPosition(this._modelData, position.lineNumber, position.column) - this.getScrollTop(); + const left = this._modelData.view.getOffsetForColumn(position.lineNumber, position.column) + layoutInfo.glyphMarginWidth + layoutInfo.lineNumbersWidth + layoutInfo.decorationsWidth - this.getScrollLeft(); return { top: top, @@ -1327,7 +1329,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE })); listenersToRemove.push(cursor.onDidChange((e: CursorStateChangedEvent) => { - let positions: Position[] = []; + const positions: Position[] = []; for (let i = 0, len = e.selections.length; i < len; i++) { positions[i] = e.selections[i].getPosition(); } @@ -1355,13 +1357,13 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE let keys = Object.keys(this._contentWidgets); for (let i = 0, len = keys.length; i < len; i++) { - let widgetId = keys[i]; + const widgetId = keys[i]; view.addContentWidget(this._contentWidgets[widgetId]); } keys = Object.keys(this._overlayWidgets); for (let i = 0, len = keys.length; i < len; i++) { - let widgetId = keys[i]; + const widgetId = keys[i]; view.addOverlayWidget(this._overlayWidgets[widgetId]); } @@ -1532,7 +1534,7 @@ export class BooleanEventEmitter extends Disposable { } public setValue(_value: boolean) { - let value = (_value ? BooleanEventValue.True : BooleanEventValue.False); + const value = (_value ? BooleanEventValue.True : BooleanEventValue.False); if (this._value === value) { return; } @@ -1547,16 +1549,16 @@ export class BooleanEventEmitter extends Disposable { class EditorContextKeysManager extends Disposable { - private _editor: CodeEditorWidget; - private _editorFocus: IContextKey; - private _textInputFocus: IContextKey; - private _editorTextFocus: IContextKey; - private _editorTabMovesFocus: IContextKey; - private _editorReadonly: IContextKey; - private _hasMultipleSelections: IContextKey; - private _hasNonEmptySelection: IContextKey; - private _canUndo: IContextKey; - private _canRedo: IContextKey; + private readonly _editor: CodeEditorWidget; + private readonly _editorFocus: IContextKey; + private readonly _textInputFocus: IContextKey; + private readonly _editorTextFocus: IContextKey; + private readonly _editorTabMovesFocus: IContextKey; + private readonly _editorReadonly: IContextKey; + private readonly _hasMultipleSelections: IContextKey; + private readonly _hasNonEmptySelection: IContextKey; + private readonly _canUndo: IContextKey; + private readonly _canRedo: IContextKey; constructor( editor: CodeEditorWidget, @@ -1593,14 +1595,14 @@ class EditorContextKeysManager extends Disposable { } private _updateFromConfig(): void { - let config = this._editor.getConfiguration(); + const config = this._editor.getConfiguration(); this._editorTabMovesFocus.set(config.tabFocusMode); this._editorReadonly.set(config.readOnly); } private _updateFromSelection(): void { - let selections = this._editor.getSelections(); + const selections = this._editor.getSelections(); if (!selections) { this._hasMultipleSelections.reset(); this._hasNonEmptySelection.reset(); @@ -1625,25 +1627,25 @@ class EditorContextKeysManager extends Disposable { export class EditorModeContext extends Disposable { - private _editor: CodeEditorWidget; + private readonly _editor: CodeEditorWidget; - private _langId: IContextKey; - private _hasCompletionItemProvider: IContextKey; - private _hasCodeActionsProvider: IContextKey; - private _hasCodeLensProvider: IContextKey; - private _hasDefinitionProvider: IContextKey; - private _hasDeclarationProvider: IContextKey; - private _hasImplementationProvider: IContextKey; - private _hasTypeDefinitionProvider: IContextKey; - private _hasHoverProvider: IContextKey; - private _hasDocumentHighlightProvider: IContextKey; - private _hasDocumentSymbolProvider: IContextKey; - private _hasReferenceProvider: IContextKey; - private _hasRenameProvider: IContextKey; - private _hasDocumentFormattingProvider: IContextKey; - private _hasDocumentSelectionFormattingProvider: IContextKey; - private _hasSignatureHelpProvider: IContextKey; - private _isInWalkThrough: IContextKey; + private readonly _langId: IContextKey; + private readonly _hasCompletionItemProvider: IContextKey; + private readonly _hasCodeActionsProvider: IContextKey; + private readonly _hasCodeLensProvider: IContextKey; + private readonly _hasDefinitionProvider: IContextKey; + private readonly _hasDeclarationProvider: IContextKey; + private readonly _hasImplementationProvider: IContextKey; + private readonly _hasTypeDefinitionProvider: IContextKey; + private readonly _hasHoverProvider: IContextKey; + private readonly _hasDocumentHighlightProvider: IContextKey; + private readonly _hasDocumentSymbolProvider: IContextKey; + private readonly _hasReferenceProvider: IContextKey; + private readonly _hasRenameProvider: IContextKey; + private readonly _hasDocumentFormattingProvider: IContextKey; + private readonly _hasDocumentSelectionFormattingProvider: IContextKey; + private readonly _hasSignatureHelpProvider: IContextKey; + private readonly _isInWalkThrough: IContextKey; constructor( editor: CodeEditorWidget, @@ -1749,7 +1751,7 @@ export class EditorModeContext extends Disposable { class CodeEditorWidgetFocusTracker extends Disposable { private _hasFocus: boolean; - private _domFocusTracker: dom.IFocusTracker; + private readonly _domFocusTracker: dom.IFocusTracker; private readonly _onChange: Emitter = this._register(new Emitter()); public readonly onChange: Event = this._onChange.event; diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 74d1527912..37a767aa6f 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -148,7 +148,7 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private readonly id: number; - private _domElement: HTMLElement; + private readonly _domElement: HTMLElement; protected readonly _containerDomElement: HTMLElement; private readonly _overviewDomElement: HTMLElement; private readonly _overviewViewportDomElement: FastDomNode; @@ -160,12 +160,12 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private originalEditor: CodeEditorWidget; private _originalDomNode: HTMLElement; - private _originalEditorState: VisualEditorState; + private readonly _originalEditorState: VisualEditorState; private _originalOverviewRuler: editorBrowser.IOverviewRuler; private modifiedEditor: CodeEditorWidget; private _modifiedDomNode: HTMLElement; - private _modifiedEditorState: VisualEditorState; + private readonly _modifiedEditorState: VisualEditorState; private _modifiedOverviewRuler: editorBrowser.IOverviewRuler; private _currentlyChangingViewZones: boolean; @@ -184,15 +184,15 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE private _enableSplitViewResizing: boolean; private _strategy: IDiffEditorWidgetStyle; - private _updateDecorationsRunner: RunOnceScheduler; + private readonly _updateDecorationsRunner: RunOnceScheduler; - private _editorWorkerService: IEditorWorkerService; + private readonly _editorWorkerService: IEditorWorkerService; protected _contextKeyService: IContextKeyService; - private _codeEditorService: ICodeEditorService; - private _themeService: IThemeService; - private _notificationService: INotificationService; + private readonly _codeEditorService: ICodeEditorService; + private readonly _themeService: IThemeService; + private readonly _notificationService: INotificationService; - private _reviewPane: DiffReview; + private readonly _reviewPane: DiffReview; constructor( domElement: HTMLElement, @@ -1251,7 +1251,7 @@ interface IMyViewZone { class ForeignViewZonesIterator { private _index: number; - private _source: IEditorWhitespace[]; + private readonly _source: IEditorWhitespace[]; public current: IEditorWhitespace | null; constructor(source: IEditorWhitespace[]) { @@ -1272,9 +1272,9 @@ class ForeignViewZonesIterator { abstract class ViewZonesComputer { - private lineChanges: editorCommon.ILineChange[]; - private originalForeignVZ: IEditorWhitespace[]; - private modifiedForeignVZ: IEditorWhitespace[]; + private readonly lineChanges: editorCommon.ILineChange[]; + private readonly originalForeignVZ: IEditorWhitespace[]; + private readonly modifiedForeignVZ: IEditorWhitespace[]; constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[]) { this.lineChanges = lineChanges; @@ -1532,7 +1532,7 @@ class DiffEdtorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEd static MINIMUM_EDITOR_WIDTH = 100; private _disableSash: boolean; - private _sash: Sash; + private readonly _sash: Sash; private _sashRatio: number | null; private _sashPosition: number | null; private _startSashPosition: number; @@ -1911,10 +1911,10 @@ class DiffEdtorWidgetInline extends DiffEditorWidgetStyle implements IDiffEditor class InlineViewZonesComputer extends ViewZonesComputer { - private originalModel: ITextModel; - private modifiedEditorConfiguration: editorOptions.InternalEditorOptions; - private modifiedEditorTabSize: number; - private renderIndicators: boolean; + private readonly originalModel: ITextModel; + private readonly modifiedEditorConfiguration: editorOptions.InternalEditorOptions; + private readonly modifiedEditorTabSize: number; + private readonly renderIndicators: boolean; constructor(lineChanges: editorCommon.ILineChange[], originalForeignVZ: IEditorWhitespace[], modifiedForeignVZ: IEditorWhitespace[], originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor, renderIndicators: boolean) { super(lineChanges, originalForeignVZ, modifiedForeignVZ); diff --git a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts index 4736cefd9c..50c9d6696f 100644 --- a/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts +++ b/src/vs/editor/browser/widget/embeddedCodeEditorWidget.ts @@ -15,11 +15,12 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; export class EmbeddedCodeEditorWidget extends CodeEditorWidget { - private _parentEditor: ICodeEditor; - private _overwriteOptions: IEditorOptions; + private readonly _parentEditor: ICodeEditor; + private readonly _overwriteOptions: IEditorOptions; constructor( domElement: HTMLElement, @@ -30,9 +31,10 @@ export class EmbeddedCodeEditorWidget extends CodeEditorWidget { @ICommandService commandService: ICommandService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @INotificationService notificationService: INotificationService + @INotificationService notificationService: INotificationService, + @IAccessibilityService accessibilityService: IAccessibilityService ) { - super(domElement, parentEditor.getRawConfiguration(), {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService); + super(domElement, parentEditor.getRawConfiguration(), {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService); this._parentEditor = parentEditor; this._overwriteOptions = options; @@ -60,8 +62,8 @@ export class EmbeddedCodeEditorWidget extends CodeEditorWidget { export class EmbeddedDiffEditorWidget extends DiffEditorWidget { - private _parentEditor: ICodeEditor; - private _overwriteOptions: IDiffEditorOptions; + private readonly _parentEditor: ICodeEditor; + private readonly _overwriteOptions: IDiffEditorOptions; constructor( domElement: HTMLElement, diff --git a/src/vs/editor/common/commands/replaceCommand.ts b/src/vs/editor/common/commands/replaceCommand.ts index fea8e8ff9d..08cbf3958d 100644 --- a/src/vs/editor/common/commands/replaceCommand.ts +++ b/src/vs/editor/common/commands/replaceCommand.ts @@ -98,9 +98,9 @@ export class ReplaceCommandWithOffsetCursorState implements ICommand { export class ReplaceCommandThatPreservesSelection implements ICommand { - private _range: Range; - private _text: string; - private _initialSelection: Selection; + private readonly _range: Range; + private readonly _text: string; + private readonly _initialSelection: Selection; private _selectionId: string; constructor(editRange: Range, text: string, initialSelection: Selection) { diff --git a/src/vs/editor/common/commands/shiftCommand.ts b/src/vs/editor/common/commands/shiftCommand.ts index 0f3f0d9bad..b9e33100e1 100644 --- a/src/vs/editor/common/commands/shiftCommand.ts +++ b/src/vs/editor/common/commands/shiftCommand.ts @@ -15,34 +15,61 @@ import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageCo export interface IShiftCommandOpts { isUnshift: boolean; tabSize: number; - oneIndent: string; + indentSize: number; + insertSpaces: boolean; useTabStops: boolean; } +const repeatCache: { [str: string]: string[]; } = Object.create(null); +export function cachedStringRepeat(str: string, count: number): string { + if (!repeatCache[str]) { + repeatCache[str] = ['', str]; + } + const cache = repeatCache[str]; + for (let i = cache.length; i <= count; i++) { + cache[i] = cache[i - 1] + str; + } + return cache[count]; +} + export class ShiftCommand implements ICommand { - public static unshiftIndentCount(line: string, column: number, tabSize: number): number { + public static unshiftIndent(line: string, column: number, tabSize: number, indentSize: number, insertSpaces: boolean): string { // Determine the visible column where the content starts - let contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(line, column, tabSize); + const contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(line, column, tabSize); - let desiredTabStop = CursorColumns.prevTabStop(contentStartVisibleColumn, tabSize); - - // The `desiredTabStop` is a multiple of `tabSize` => determine the number of indents - return desiredTabStop / tabSize; + if (insertSpaces) { + const indent = cachedStringRepeat(' ', indentSize); + const desiredTabStop = CursorColumns.prevIndentTabStop(contentStartVisibleColumn, indentSize); + const indentCount = desiredTabStop / indentSize; // will be an integer + return cachedStringRepeat(indent, indentCount); + } else { + const indent = '\t'; + const desiredTabStop = CursorColumns.prevRenderTabStop(contentStartVisibleColumn, tabSize); + const indentCount = desiredTabStop / tabSize; // will be an integer + return cachedStringRepeat(indent, indentCount); + } } - public static shiftIndentCount(line: string, column: number, tabSize: number): number { + public static shiftIndent(line: string, column: number, tabSize: number, indentSize: number, insertSpaces: boolean): string { // Determine the visible column where the content starts - let contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(line, column, tabSize); + const contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(line, column, tabSize); - let desiredTabStop = CursorColumns.nextTabStop(contentStartVisibleColumn, tabSize); - - // The `desiredTabStop` is a multiple of `tabSize` => determine the number of indents - return desiredTabStop / tabSize; + if (insertSpaces) { + const indent = cachedStringRepeat(' ', indentSize); + const desiredTabStop = CursorColumns.nextIndentTabStop(contentStartVisibleColumn, indentSize); + const indentCount = desiredTabStop / indentSize; // will be an integer + return cachedStringRepeat(indent, indentCount); + } else { + const indent = '\t'; + const desiredTabStop = CursorColumns.nextRenderTabStop(contentStartVisibleColumn, tabSize); + const indentCount = desiredTabStop / tabSize; // will be an integer + return cachedStringRepeat(indent, indentCount); + } } - private _opts: IShiftCommandOpts; - private _selection: Selection; + private readonly _opts: IShiftCommandOpts; + private readonly _selection: Selection; private _selectionId: string; private _useLastEditRangeForCursorEndPosition: boolean; private _selectionStartColumnStaysPut: boolean; @@ -70,8 +97,7 @@ export class ShiftCommand implements ICommand { endLine = endLine - 1; } - const tabSize = this._opts.tabSize; - const oneIndent = this._opts.oneIndent; + const { tabSize, indentSize, insertSpaces } = this._opts; const shouldIndentEmptyLines = (startLine === endLine); // if indenting or outdenting on a whitespace only line @@ -82,9 +108,6 @@ export class ShiftCommand implements ICommand { } if (this._opts.useTabStops) { - // indents[i] represents i * oneIndent - let indents: string[] = ['', oneIndent]; - // keep track of previous line's "miss-alignment" let previousLineExtraSpaces = 0, extraSpaces = 0; for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++ , previousLineExtraSpaces = extraSpaces) { @@ -109,7 +132,7 @@ export class ShiftCommand implements ICommand { if (lineNumber > 1) { let contentStartVisibleColumn = CursorColumns.visibleColumnFromColumn(lineText, indentationEndIndex + 1, tabSize); - if (contentStartVisibleColumn % tabSize !== 0) { + if (contentStartVisibleColumn % indentSize !== 0) { // The current line is "miss-aligned", so let's see if this is expected... // This can only happen when it has trailing commas in the indent if (model.isCheapToTokenize(lineNumber - 1)) { @@ -117,7 +140,7 @@ export class ShiftCommand implements ICommand { if (enterAction) { extraSpaces = previousLineExtraSpaces; if (enterAction.appendText) { - for (let j = 0, lenJ = enterAction.appendText.length; j < lenJ && extraSpaces < tabSize; j++) { + for (let j = 0, lenJ = enterAction.appendText.length; j < lenJ && extraSpaces < indentSize; j++) { if (enterAction.appendText.charCodeAt(j) === CharCode.Space) { extraSpaces++; } else { @@ -147,19 +170,14 @@ export class ShiftCommand implements ICommand { continue; } - let desiredIndentCount: number; + let desiredIndent: string; if (this._opts.isUnshift) { - desiredIndentCount = ShiftCommand.unshiftIndentCount(lineText, indentationEndIndex + 1, tabSize); + desiredIndent = ShiftCommand.unshiftIndent(lineText, indentationEndIndex + 1, tabSize, indentSize, insertSpaces); } else { - desiredIndentCount = ShiftCommand.shiftIndentCount(lineText, indentationEndIndex + 1, tabSize); + desiredIndent = ShiftCommand.shiftIndent(lineText, indentationEndIndex + 1, tabSize, indentSize, insertSpaces); } - // Fill `indents`, as needed - for (let j = indents.length; j <= desiredIndentCount; j++) { - indents[j] = indents[j - 1] + oneIndent; - } - - this._addEditOperation(builder, new Range(lineNumber, 1, lineNumber, indentationEndIndex + 1), indents[desiredIndentCount]); + this._addEditOperation(builder, new Range(lineNumber, 1, lineNumber, indentationEndIndex + 1), desiredIndent); if (lineNumber === startLine) { // Force the startColumn to stay put because we're inserting after it this._selectionStartColumnStaysPut = (this._selection.startColumn <= indentationEndIndex + 1); @@ -167,6 +185,8 @@ export class ShiftCommand implements ICommand { } } else { + const oneIndent = (insertSpaces ? cachedStringRepeat(' ', indentSize) : '\t'); + for (let lineNumber = startLine; lineNumber <= endLine; lineNumber++) { const lineText = model.getLineContent(lineNumber); let indentationEndIndex = strings.firstNonWhitespaceIndex(lineText); @@ -193,7 +213,7 @@ export class ShiftCommand implements ICommand { if (this._opts.isUnshift) { - indentationEndIndex = Math.min(indentationEndIndex, tabSize); + indentationEndIndex = Math.min(indentationEndIndex, indentSize); for (let i = 0; i < indentationEndIndex; i++) { const chr = lineText.charCodeAt(i); if (chr === CharCode.Tab) { diff --git a/src/vs/editor/common/commands/surroundSelectionCommand.ts b/src/vs/editor/common/commands/surroundSelectionCommand.ts index cdcb5d27cd..e1b7c50559 100644 --- a/src/vs/editor/common/commands/surroundSelectionCommand.ts +++ b/src/vs/editor/common/commands/surroundSelectionCommand.ts @@ -9,9 +9,9 @@ import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/ed import { ITextModel } from 'vs/editor/common/model'; export class SurroundSelectionCommand implements ICommand { - private _range: Selection; - private _charBeforeSelection: string; - private _charAfterSelection: string; + private readonly _range: Selection; + private readonly _charBeforeSelection: string; + private readonly _charAfterSelection: string; constructor(range: Selection, charBeforeSelection: string, charAfterSelection: string) { this._range = range; diff --git a/src/vs/editor/common/commands/trimTrailingWhitespaceCommand.ts b/src/vs/editor/common/commands/trimTrailingWhitespaceCommand.ts index 6bf6ff6498..f3f72949a3 100644 --- a/src/vs/editor/common/commands/trimTrailingWhitespaceCommand.ts +++ b/src/vs/editor/common/commands/trimTrailingWhitespaceCommand.ts @@ -13,9 +13,9 @@ import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/mod export class TrimTrailingWhitespaceCommand implements ICommand { - private selection: Selection; + private readonly selection: Selection; private selectionId: string; - private cursors: Position[]; + private readonly cursors: Position[]; constructor(selection: Selection, cursors: Position[]) { this.selection = selection; diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 8d764182d3..891c32de14 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -17,6 +17,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import EDITOR_DEFAULTS = editorOptions.EDITOR_DEFAULTS; import EDITOR_FONT_DEFAULTS = editorOptions.EDITOR_FONT_DEFAULTS; import EDITOR_MODEL_DEFAULTS = editorOptions.EDITOR_MODEL_DEFAULTS; +import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; /** * Control what pressing Tab does. @@ -57,7 +58,7 @@ export interface IEnvConfiguration { emptySelectionClipboard: boolean; pixelRatio: number; zoomLevel: number; - accessibilitySupport: platform.AccessibilitySupport; + accessibilitySupport: AccessibilitySupport; } const hasOwnProperty = Object.hasOwnProperty; @@ -264,6 +265,11 @@ const editorConfiguration: IConfigurationNode = { 'default': 'on', 'description': nls.localize('lineNumbers', "Controls the display of line numbers.") }, + 'editor.renderFinalNewline': { + 'type': 'boolean', + 'default': EDITOR_DEFAULTS.viewInfo.renderFinalNewline, + 'description': nls.localize('renderFinalNewline', "Render last line number when the file ends with a newline.") + }, 'editor.rulers': { 'type': 'array', 'items': { @@ -283,6 +289,20 @@ const editorConfiguration: IConfigurationNode = { 'minimum': 1, 'markdownDescription': nls.localize('tabSize', "The number of spaces a tab is equal to. This setting is overridden based on the file contents when `#editor.detectIndentation#` is on.") }, + // 'editor.indentSize': { + // 'anyOf': [ + // { + // 'type': 'string', + // 'enum': ['tabSize'] + // }, + // { + // 'type': 'number', + // 'minimum': 1 + // } + // ], + // 'default': 'tabSize', + // 'markdownDescription': nls.localize('indentSize', "The number of spaces used for indentation or 'tabSize' to use the value from `#editor.tabSize#`. This setting is overridden based on the file contents when `#editor.detectIndentation#` is on.") + // }, 'editor.insertSpaces': { 'type': 'boolean', 'default': EDITOR_MODEL_DEFAULTS.insertSpaces, @@ -371,6 +391,11 @@ const editorConfiguration: IConfigurationNode = { 'description': nls.localize('find.globalFindClipboard', "Controls whether the Find Widget should read or modify the shared find clipboard on macOS."), 'included': platform.isMacintosh }, + 'editor.find.addExtraSpaceOnTop': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('find.addExtraSpaceOnTop', "Controls whether the Find Widget should add extra lines on top of the editor. When true, you can scroll beyond the first line when the Find Widget is visible.") + }, 'editor.wordWrap': { 'type': 'string', 'enum': ['off', 'on', 'wordWrapColumn', 'bounded'], @@ -654,6 +679,166 @@ const editorConfiguration: IConfigurationNode = { default: true, description: nls.localize('suggest.snippetsPreventQuickSuggestions', "Control whether an active snippet prevents quick suggestions.") }, + 'editor.suggest.showIcons': { + type: 'boolean', + default: EDITOR_DEFAULTS.contribInfo.suggest.showIcons, + description: nls.localize('suggest.showIcons', "Controls whether to show or hide icons in suggestions.") + }, + 'editor.suggest.maxVisibleSuggestions': { + type: 'number', + default: EDITOR_DEFAULTS.contribInfo.suggest.maxVisibleSuggestions, + minimum: 1, + maximum: 12, + description: nls.localize('suggest.maxVisibleSuggestions', "Controls how many suggestions IntelliSense will show before showing a scrollbar.") + }, + 'editor.suggest.filteredTypes': { + type: 'object', + default: { keyword: true }, + markdownDescription: nls.localize('suggest.filtered', "Controls whether some suggestion types should be filtered from IntelliSense. A list of suggestion types can be found here: https://code.visualstudio.com/docs/editor/intellisense#_types-of-completions."), + properties: { + method: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.method', "When set to `false` IntelliSense never shows `method` suggestions.") + }, + function: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.function', "When set to `false` IntelliSense never shows `function` suggestions.") + }, + constructor: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.constructor', "When set to `false` IntelliSense never shows `constructor` suggestions.") + }, + field: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.field', "When set to `false` IntelliSense never shows `field` suggestions.") + }, + variable: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.variable', "When set to `false` IntelliSense never shows `variable` suggestions.") + }, + class: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.class', "When set to `false` IntelliSense never shows `class` suggestions.") + }, + struct: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.struct', "When set to `false` IntelliSense never shows `struct` suggestions.") + }, + interface: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.interface', "When set to `false` IntelliSense never shows `interface` suggestions.") + }, + module: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.module', "When set to `false` IntelliSense never shows `module` suggestions.") + }, + property: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.property', "When set to `false` IntelliSense never shows `property` suggestions.") + }, + event: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.event', "When set to `false` IntelliSense never shows `event` suggestions.") + }, + operator: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.operator', "When set to `false` IntelliSense never shows `operator` suggestions.") + }, + unit: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.unit', "When set to `false` IntelliSense never shows `unit` suggestions.") + }, + value: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.value', "When set to `false` IntelliSense never shows `value` suggestions.") + }, + constant: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.constant', "When set to `false` IntelliSense never shows `constant` suggestions.") + }, + enum: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.enum', "When set to `false` IntelliSense never shows `enum` suggestions.") + }, + enumMember: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.enumMember', "When set to `false` IntelliSense never shows `enumMember` suggestions.") + }, + keyword: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.keyword', "When set to `false` IntelliSense never shows `keyword` suggestions.") + }, + text: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.text', "When set to `false` IntelliSense never shows `text` suggestions.") + }, + color: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.color', "When set to `false` IntelliSense never shows `color` suggestions.") + }, + file: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.file', "When set to `false` IntelliSense never shows `file` suggestions.") + }, + reference: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.reference', "When set to `false` IntelliSense never shows `reference` suggestions.") + }, + customcolor: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.customcolor', "When set to `false` IntelliSense never shows `customcolor` suggestions.") + }, + folder: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.folder', "When set to `false` IntelliSense never shows `folder` suggestions.") + }, + typeParameter: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.typeParameter', "When set to `false` IntelliSense never shows `typeParameter` suggestions.") + }, + snippet: { + type: 'boolean', + default: true, + markdownDescription: nls.localize('suggest.filtered.snippet', "When set to `false` IntelliSense never shows `snippet` suggestions.") + }, + } + }, + 'editor.gotoLocation.many': { + description: nls.localize('editor.gotoLocation.many', "Controls the behaviour of 'go to'-commands, like go to definition, when multiple target locations exist."), + type: 'string', + enum: ['peek', 'revealAndPeek', 'reveal'], + default: 'peek', + enumDescriptions: [ + nls.localize('editor.gotoLocation.many.peek', 'Show peek view of the results at the request location'), + nls.localize('editor.gotoLocation.many.revealAndPeek', 'Reveal the first result and show peek view at its location'), + nls.localize('editor.gotoLocation.many.reveal', 'Reveal the first result and ignore others') + ] + }, 'editor.selectionHighlight': { 'type': 'boolean', 'default': EDITOR_DEFAULTS.contribInfo.selectionHighlight, @@ -832,6 +1017,11 @@ const editorConfiguration: IConfigurationNode = { 'default': EDITOR_DEFAULTS.contribInfo.lightbulbEnabled, 'description': nls.localize('codeActions', "Enables the code action lightbulb in the editor.") }, + 'editor.maxTokenizationLineLength': { + 'type': 'integer', + 'default': 20_000, + 'description': nls.localize('maxTokenizationLineLength', "Lines above this length will not be tokenized for performance reasons") + }, 'editor.codeActionsOnSave': { 'type': 'object', 'properties': { diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index df96f01cb3..779156151b 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -11,6 +11,8 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; import { Constants } from 'vs/editor/common/core/uint'; import { USUAL_WORD_SEPARATORS } from 'vs/editor/common/model/wordHelper'; +import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { isObject } from 'vs/base/common/types'; /** * Configuration options for editor scrollbars @@ -85,6 +87,10 @@ export interface IEditorFindOptions { * Controls if Find in Selection flag is turned on when multiple lines of text are selected in the editor. */ autoFindInSelection: boolean; + /* + * Controls whether the Find Widget should add extra lines on top of the editor. + */ + addExtraSpaceOnTop?: boolean; /** * @internal * Controls if the Find Widget should read or modify the shared find clipboard on macOS @@ -194,11 +200,29 @@ export interface ISuggestOptions { * Favours words that appear close to the cursor. */ localityBonus?: boolean; - /** * Enable using global storage for remembering suggestions. */ shareSuggestSelections?: boolean; + /** + * Enable or disable icons in suggestions. Defaults to true. + */ + showIcons?: boolean; + /** + * Max suggestions to show in suggestions. Defaults to 12. + */ + maxVisibleSuggestions?: boolean; + /** + * Names of suggestion types to filter. + */ + filteredTypes?: Record; +} + +export interface IGotoLocationOptions { + /** + * Control how goto-command work when having multiple results. + */ + many?: 'peek' | 'revealAndPeek' | 'reveal'; } /** @@ -244,6 +268,11 @@ export interface IEditorOptions { * Defaults to true. */ lineNumbers?: 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); + /** + * Render last line number when the file ends with a newline. + * Defaults to true on Windows/Mac and to false on Linux. + */ + renderFinalNewline?: boolean; /** * Should the corresponding line be selected when clicking on the line number? * Defaults to true. @@ -482,6 +511,10 @@ export interface IEditorOptions { * Suggest options. */ suggest?: ISuggestOptions; + /** + * + */ + gotoLocation?: IGotoLocationOptions; /** * Enable quick suggestions (shadow suggestions) * Defaults to true. @@ -496,11 +529,6 @@ export interface IEditorOptions { * Parameter hint options. */ parameterHints?: IEditorParameterHintOptions; - /** - * Render icons in suggestions box. - * Defaults to true. - */ - iconsInSuggestions?: boolean; /** * Options for auto closing brackets. * Defaults to language defined behavior. @@ -894,6 +922,7 @@ export interface InternalEditorMinimapOptions { export interface InternalEditorFindOptions { readonly seedSearchStringFromSelection: boolean; readonly autoFindInSelection: boolean; + readonly addExtraSpaceOnTop: boolean; /** * @internal */ @@ -906,12 +935,19 @@ export interface InternalEditorHoverOptions { readonly sticky: boolean; } +export interface InternalGoToLocationOptions { + readonly many: 'peek' | 'revealAndPeek' | 'reveal'; +} + export interface InternalSuggestOptions { readonly filterGraceful: boolean; readonly snippets: 'top' | 'bottom' | 'inline' | 'none'; readonly snippetsPreventQuickSuggestions: boolean; readonly localityBonus: boolean; readonly shareSuggestSelections: boolean; + readonly showIcons: boolean; + readonly maxVisibleSuggestions: number; + readonly filteredTypes: Record; } export interface InternalParameterHintOptions { @@ -946,6 +982,7 @@ export interface InternalEditorViewOptions { readonly ariaLabel: string; readonly renderLineNumbers: RenderLineNumbersType; readonly renderCustomLineNumbers: ((lineNumber: number) => string) | null; + readonly renderFinalNewline: boolean; readonly selectOnLineNumbers: boolean; readonly glyphMargin: boolean; readonly revealHorizontalRightPadding: number; @@ -981,7 +1018,6 @@ export interface EditorContribOptions { readonly quickSuggestions: boolean | { other: boolean, comments: boolean, strings: boolean }; readonly quickSuggestionsDelay: number; readonly parameterHints: InternalParameterHintOptions; - readonly iconsInSuggestions: boolean; readonly formatOnType: boolean; readonly formatOnPaste: boolean; readonly suggestOnTriggerCharacters: boolean; @@ -993,6 +1029,7 @@ export interface EditorContribOptions { readonly suggestLineHeight: number; readonly tabCompletion: 'on' | 'off' | 'onlySnippets'; readonly suggest: InternalSuggestOptions; + readonly gotoLocation: InternalGoToLocationOptions; readonly selectionHighlight: boolean; readonly occurrencesHighlight: boolean; readonly codeLens: boolean; @@ -1059,7 +1096,7 @@ export class InternalEditorOptions { /** * @internal */ - readonly accessibilitySupport: platform.AccessibilitySupport; + readonly accessibilitySupport: AccessibilitySupport; readonly multiCursorModifier: 'altKey' | 'ctrlKey' | 'metaKey'; readonly multiCursorMergeOverlapping: boolean; readonly showUnused: boolean; @@ -1092,7 +1129,7 @@ export class InternalEditorOptions { editorClassName: string; lineHeight: number; readOnly: boolean; - accessibilitySupport: platform.AccessibilitySupport; + accessibilitySupport: AccessibilitySupport; multiCursorModifier: 'altKey' | 'ctrlKey' | 'metaKey'; multiCursorMergeOverlapping: boolean; wordSeparators: string; @@ -1253,6 +1290,7 @@ export class InternalEditorOptions { && a.ariaLabel === b.ariaLabel && a.renderLineNumbers === b.renderLineNumbers && a.renderCustomLineNumbers === b.renderCustomLineNumbers + && a.renderFinalNewline === b.renderFinalNewline && a.selectOnLineNumbers === b.selectOnLineNumbers && a.glyphMargin === b.glyphMargin && a.revealHorizontalRightPadding === b.revealHorizontalRightPadding @@ -1323,6 +1361,7 @@ export class InternalEditorOptions { a.seedSearchStringFromSelection === b.seedSearchStringFromSelection && a.autoFindInSelection === b.autoFindInSelection && a.globalFindClipboard === b.globalFindClipboard + && a.addExtraSpaceOnTop === b.addExtraSpaceOnTop ); } @@ -1360,7 +1399,19 @@ export class InternalEditorOptions { && a.snippets === b.snippets && a.snippetsPreventQuickSuggestions === b.snippetsPreventQuickSuggestions && a.localityBonus === b.localityBonus - && a.shareSuggestSelections === b.shareSuggestSelections; + && a.shareSuggestSelections === b.shareSuggestSelections + && a.showIcons === b.showIcons + && a.maxVisibleSuggestions === b.maxVisibleSuggestions; + } + } + + private static _equalsGotoLocationOptions(a: InternalGoToLocationOptions | undefined, b: InternalGoToLocationOptions | undefined): boolean { + if (a === b) { + return true; + } else if (!a || !b) { + return false; + } else { + return a.many === b.many; } } @@ -1393,7 +1444,6 @@ export class InternalEditorOptions { && InternalEditorOptions._equalsQuickSuggestions(a.quickSuggestions, b.quickSuggestions) && a.quickSuggestionsDelay === b.quickSuggestionsDelay && this._equalsParameterHintOptions(a.parameterHints, b.parameterHints) - && a.iconsInSuggestions === b.iconsInSuggestions && a.formatOnType === b.formatOnType && a.formatOnPaste === b.formatOnPaste && a.suggestOnTriggerCharacters === b.suggestOnTriggerCharacters @@ -1405,6 +1455,7 @@ export class InternalEditorOptions { && a.suggestLineHeight === b.suggestLineHeight && a.tabCompletion === b.tabCompletion && this._equalsSuggestOptions(a.suggest, b.suggest) + && InternalEditorOptions._equalsGotoLocationOptions(a.gotoLocation, b.gotoLocation) && a.selectionHighlight === b.selectionHighlight && a.occurrencesHighlight === b.occurrencesHighlight && a.codeLens === b.codeLens @@ -1602,7 +1653,7 @@ export interface IEnvironmentalOptions { readonly emptySelectionClipboard: boolean; readonly pixelRatio: number; readonly tabFocusMode: boolean; - readonly accessibilitySupport: platform.AccessibilitySupport; + readonly accessibilitySupport: AccessibilitySupport; } function _boolean(value: any, defaultValue: T): boolean | T { @@ -1851,7 +1902,8 @@ export class EditorOptionsValidator { return { seedSearchStringFromSelection: _boolean(opts.seedSearchStringFromSelection, defaults.seedSearchStringFromSelection), autoFindInSelection: _boolean(opts.autoFindInSelection, defaults.autoFindInSelection), - globalFindClipboard: _boolean(opts.globalFindClipboard, defaults.globalFindClipboard) + globalFindClipboard: _boolean(opts.globalFindClipboard, defaults.globalFindClipboard), + addExtraSpaceOnTop: _boolean(opts.addExtraSpaceOnTop, defaults.addExtraSpaceOnTop) }; } @@ -1892,7 +1944,17 @@ export class EditorOptionsValidator { snippets: _stringSet<'top' | 'bottom' | 'inline' | 'none'>(opts.snippetSuggestions, defaults.snippets, ['top', 'bottom', 'inline', 'none']), snippetsPreventQuickSuggestions: _boolean(suggestOpts.snippetsPreventQuickSuggestions, defaults.filterGraceful), localityBonus: _boolean(suggestOpts.localityBonus, defaults.localityBonus), - shareSuggestSelections: _boolean(suggestOpts.shareSuggestSelections, defaults.shareSuggestSelections) + shareSuggestSelections: _boolean(suggestOpts.shareSuggestSelections, defaults.shareSuggestSelections), + showIcons: _boolean(suggestOpts.showIcons, defaults.showIcons), + maxVisibleSuggestions: _clampedInt(suggestOpts.maxVisibleSuggestions, defaults.maxVisibleSuggestions, 1, 12), + filteredTypes: isObject(suggestOpts.filteredTypes) ? suggestOpts.filteredTypes : Object.create(null) + }; + } + + private static _santizeGotoLocationOpts(opts: IEditorOptions, defaults: InternalGoToLocationOptions): InternalGoToLocationOptions { + const gotoOpts = opts.gotoLocation || {}; + return { + many: _stringSet<'peek' | 'revealAndPeek' | 'reveal'>(gotoOpts.many, defaults.many, ['peek', 'revealAndPeek', 'reveal']) }; } @@ -1988,6 +2050,7 @@ export class EditorOptionsValidator { ariaLabel: _string(opts.ariaLabel, defaults.ariaLabel), renderLineNumbers: renderLineNumbers, renderCustomLineNumbers: renderCustomLineNumbers, + renderFinalNewline: _boolean(opts.renderFinalNewline, defaults.renderFinalNewline), selectOnLineNumbers: _boolean(opts.selectOnLineNumbers, defaults.selectOnLineNumbers), glyphMargin: _boolean(opts.glyphMargin, defaults.glyphMargin), revealHorizontalRightPadding: _clampedInt(opts.revealHorizontalRightPadding, defaults.revealHorizontalRightPadding, 0, 1000), @@ -2036,7 +2099,6 @@ export class EditorOptionsValidator { quickSuggestions: quickSuggestions, quickSuggestionsDelay: _clampedInt(opts.quickSuggestionsDelay, defaults.quickSuggestionsDelay, Constants.MIN_SAFE_SMALL_INTEGER, Constants.MAX_SAFE_SMALL_INTEGER), parameterHints: this._sanitizeParameterHintOpts(opts.parameterHints, defaults.parameterHints), - iconsInSuggestions: _boolean(opts.iconsInSuggestions, defaults.iconsInSuggestions), formatOnType: _boolean(opts.formatOnType, defaults.formatOnType), formatOnPaste: _boolean(opts.formatOnPaste, defaults.formatOnPaste), suggestOnTriggerCharacters: _boolean(opts.suggestOnTriggerCharacters, defaults.suggestOnTriggerCharacters), @@ -2048,6 +2110,7 @@ export class EditorOptionsValidator { suggestLineHeight: _clampedInt(opts.suggestLineHeight, defaults.suggestLineHeight, 0, 1000), tabCompletion: this._sanitizeTabCompletionOpts(opts.tabCompletion, defaults.tabCompletion), suggest: this._sanitizeSuggestOpts(opts, defaults.suggest), + gotoLocation: this._santizeGotoLocationOpts(opts, defaults.gotoLocation), selectionHighlight: _boolean(opts.selectionHighlight, defaults.selectionHighlight), occurrencesHighlight: _boolean(opts.occurrencesHighlight, defaults.occurrencesHighlight), codeLens: _boolean(opts.codeLens, defaults.codeLens), @@ -2069,9 +2132,9 @@ export class EditorOptionsValidator { */ export class InternalEditorOptionsFactory { - private static _tweakValidatedOptions(opts: IValidatedEditorOptions, accessibilitySupport: platform.AccessibilitySupport): IValidatedEditorOptions { - const accessibilityIsOn = (accessibilitySupport === platform.AccessibilitySupport.Enabled); - const accessibilityIsOff = (accessibilitySupport === platform.AccessibilitySupport.Disabled); + private static _tweakValidatedOptions(opts: IValidatedEditorOptions, accessibilitySupport: AccessibilitySupport): IValidatedEditorOptions { + const accessibilityIsOn = (accessibilitySupport === AccessibilitySupport.Enabled); + const accessibilityIsOff = (accessibilitySupport === AccessibilitySupport.Disabled); return { inDiffEditor: opts.inDiffEditor, wordSeparators: opts.wordSeparators, @@ -2108,6 +2171,7 @@ export class InternalEditorOptionsFactory { ariaLabel: (accessibilityIsOff ? nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. Press Alt+F1 for options.") : opts.viewInfo.ariaLabel), renderLineNumbers: opts.viewInfo.renderLineNumbers, renderCustomLineNumbers: opts.viewInfo.renderCustomLineNumbers, + renderFinalNewline: opts.viewInfo.renderFinalNewline, selectOnLineNumbers: opts.viewInfo.selectOnLineNumbers, glyphMargin: opts.viewInfo.glyphMargin, revealHorizontalRightPadding: opts.viewInfo.revealHorizontalRightPadding, @@ -2149,7 +2213,6 @@ export class InternalEditorOptionsFactory { quickSuggestions: opts.contribInfo.quickSuggestions, quickSuggestionsDelay: opts.contribInfo.quickSuggestionsDelay, parameterHints: opts.contribInfo.parameterHints, - iconsInSuggestions: opts.contribInfo.iconsInSuggestions, formatOnType: opts.contribInfo.formatOnType, formatOnPaste: opts.contribInfo.formatOnPaste, suggestOnTriggerCharacters: opts.contribInfo.suggestOnTriggerCharacters, @@ -2161,6 +2224,7 @@ export class InternalEditorOptionsFactory { suggestLineHeight: opts.contribInfo.suggestLineHeight, tabCompletion: opts.contribInfo.tabCompletion, suggest: opts.contribInfo.suggest, + gotoLocation: opts.contribInfo.gotoLocation, selectionHighlight: (accessibilityIsOn ? false : opts.contribInfo.selectionHighlight), // DISABLED WHEN SCREEN READER IS ATTACHED occurrencesHighlight: (accessibilityIsOn ? false : opts.contribInfo.occurrencesHighlight), // DISABLED WHEN SCREEN READER IS ATTACHED codeLens: (accessibilityIsOn ? false : opts.contribInfo.codeLens), // DISABLED WHEN SCREEN READER IS ATTACHED @@ -2179,14 +2243,14 @@ export class InternalEditorOptionsFactory { public static createInternalEditorOptions(env: IEnvironmentalOptions, _opts: IValidatedEditorOptions) { - let accessibilitySupport: platform.AccessibilitySupport; + let accessibilitySupport: AccessibilitySupport; if (_opts.accessibilitySupport === 'auto') { // The editor reads the `accessibilitySupport` from the environment accessibilitySupport = env.accessibilitySupport; } else if (_opts.accessibilitySupport === 'on') { - accessibilitySupport = platform.AccessibilitySupport.Enabled; + accessibilitySupport = AccessibilitySupport.Enabled; } else { - accessibilitySupport = platform.AccessibilitySupport.Disabled; + accessibilitySupport = AccessibilitySupport.Disabled; } // Disable some non critical features to get as best performance as possible @@ -2232,7 +2296,7 @@ export class InternalEditorOptionsFactory { const wordWrapColumn = opts.wordWrapColumn; const wordWrapMinified = opts.wordWrapMinified; - if (accessibilitySupport === platform.AccessibilitySupport.Enabled) { + if (accessibilitySupport === AccessibilitySupport.Enabled) { // See https://github.com/Microsoft/vscode/issues/27766 // Never enable wrapping when a screen reader is attached // because arrow down etc. will not move the cursor in the way @@ -2525,6 +2589,7 @@ export const EDITOR_FONT_DEFAULTS = { */ export const EDITOR_MODEL_DEFAULTS = { tabSize: 4, + indentSize: 4, insertSpaces: true, detectIndentation: true, trimAutoWhitespace: true, @@ -2570,6 +2635,7 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { ariaLabel: nls.localize('editorViewAccessibleLabel', "Editor content"), renderLineNumbers: RenderLineNumbersType.On, renderCustomLineNumbers: null, + renderFinalNewline: (platform.isLinux ? false : true), selectOnLineNumbers: true, glyphMargin: true, revealHorizontalRightPadding: 30, @@ -2633,7 +2699,6 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { enabled: true, cycle: false }, - iconsInSuggestions: true, formatOnType: false, formatOnPaste: false, suggestOnTriggerCharacters: true, @@ -2649,7 +2714,13 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { snippets: 'inline', snippetsPreventQuickSuggestions: true, localityBonus: false, - shareSuggestSelections: false + shareSuggestSelections: false, + showIcons: true, + maxVisibleSuggestions: 12, + filteredTypes: Object.create(null) + }, + gotoLocation: { + many: 'peek' }, selectionHighlight: true, occurrencesHighlight: true, @@ -2661,7 +2732,8 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { find: { seedSearchStringFromSelection: true, autoFindInSelection: false, - globalFindClipboard: false + globalFindClipboard: false, + addExtraSpaceOnTop: true }, colorDecorators: true, lightbulbEnabled: true, diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index a6802c2273..5dcf817a31 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import * as strings from 'vs/base/common/strings'; @@ -828,7 +827,8 @@ class CommandExecutor { try { command.getEditOperations(ctx.model, editOperationBuilder); } catch (e) { - e.friendlyMessage = nls.localize('corrupt.commands', "Unexpected exception while executing command."); + // TODO@Alex use notification service if this should be user facing + // e.friendlyMessage = nls.localize('corrupt.commands', "Unexpected exception while executing command."); onUnexpectedError(e); return { operations: [], diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index ce6c885e4d..05d0c902fc 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -74,8 +74,8 @@ export class CursorConfiguration { public readonly readOnly: boolean; public readonly tabSize: number; + public readonly indentSize: number; public readonly insertSpaces: boolean; - public readonly oneIndent: string; public readonly pageSize: number; public readonly lineHeight: number; public readonly useTabStops: boolean; @@ -112,7 +112,6 @@ export class CursorConfiguration { constructor( languageIdentifier: LanguageIdentifier, - oneIndent: string, modelOptions: TextModelResolvedOptions, configuration: IConfiguration ) { @@ -122,8 +121,8 @@ export class CursorConfiguration { this.readOnly = c.readOnly; this.tabSize = modelOptions.tabSize; + this.indentSize = modelOptions.indentSize; this.insertSpaces = modelOptions.insertSpaces; - this.oneIndent = oneIndent; this.pageSize = Math.max(1, Math.floor(c.layoutInfo.height / c.fontInfo.lineHeight) - 2); this.lineHeight = c.lineHeight; this.useTabStops = c.useTabStops; @@ -176,7 +175,7 @@ export class CursorConfiguration { } public normalizeIndentation(str: string): string { - return TextModel.normalizeIndentation(str, this.tabSize, this.insertSpaces); + return TextModel.normalizeIndentation(str, this.indentSize, this.insertSpaces); } private static _getElectricCharacters(languageIdentifier: LanguageIdentifier): string[] | null { @@ -342,7 +341,6 @@ export class CursorContext { this.viewModel = viewModel; this.config = new CursorConfiguration( this.model.getLanguageIdentifier(), - this.model.getOneIndent(), this.model.getOptions(), configuration ); @@ -518,7 +516,7 @@ export class CursorColumns { for (let i = 0; i < endOffset; i++) { let charCode = lineContent.charCodeAt(i); if (charCode === CharCode.Tab) { - result = this.nextTabStop(result, tabSize); + result = this.nextRenderTabStop(result, tabSize); } else if (strings.isFullWidthCharacter(charCode)) { result = result + 2; } else { @@ -545,7 +543,7 @@ export class CursorColumns { let afterVisibleColumn: number; if (charCode === CharCode.Tab) { - afterVisibleColumn = this.nextTabStop(beforeVisibleColumn, tabSize); + afterVisibleColumn = this.nextRenderTabStop(beforeVisibleColumn, tabSize); } else if (strings.isFullWidthCharacter(charCode)) { afterVisibleColumn = beforeVisibleColumn + 2; } else { @@ -588,16 +586,30 @@ export class CursorColumns { /** * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns) */ - public static nextTabStop(visibleColumn: number, tabSize: number): number { + public static nextRenderTabStop(visibleColumn: number, tabSize: number): number { return visibleColumn + tabSize - visibleColumn % tabSize; } /** * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns) */ - public static prevTabStop(column: number, tabSize: number): number { + public static nextIndentTabStop(visibleColumn: number, indentSize: number): number { + return visibleColumn + indentSize - visibleColumn % indentSize; + } + + /** + * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns) + */ + public static prevRenderTabStop(column: number, tabSize: number): number { return column - 1 - (column - 1) % tabSize; } + + /** + * ATTENTION: This works with 0-based columns (as oposed to the regular 1-based columns) + */ + public static prevIndentTabStop(column: number, indentSize: number): number { + return column - 1 - (column - 1) % indentSize; + } } export function isQuote(ch: string): boolean { diff --git a/src/vs/editor/common/controller/cursorDeleteOperations.ts b/src/vs/editor/common/controller/cursorDeleteOperations.ts index e372ccb769..7c40f5454f 100644 --- a/src/vs/editor/common/controller/cursorDeleteOperations.ts +++ b/src/vs/editor/common/controller/cursorDeleteOperations.ts @@ -131,7 +131,7 @@ export class DeleteOperations { if (position.column <= lastIndentationColumn) { let fromVisibleColumn = CursorColumns.visibleColumnFromColumn2(config, model, position); - let toVisibleColumn = CursorColumns.prevTabStop(fromVisibleColumn, config.tabSize); + let toVisibleColumn = CursorColumns.prevIndentTabStop(fromVisibleColumn, config.indentSize); let toColumn = CursorColumns.columnFromVisibleColumn2(config, model, position.lineNumber, toVisibleColumn); deleteSelection = new Range(position.lineNumber, toColumn, position.lineNumber, position.column); } else { diff --git a/src/vs/editor/common/controller/cursorMoveCommands.ts b/src/vs/editor/common/controller/cursorMoveCommands.ts index 16cde097cd..f4c04d7e77 100644 --- a/src/vs/editor/common/controller/cursorMoveCommands.ts +++ b/src/vs/editor/common/controller/cursorMoveCommands.ts @@ -616,7 +616,29 @@ export namespace CursorMove { * 'value': Number of units to move. Default is '1'. * 'select': If 'true' makes the selection. Default is 'false'. `, - constraint: isCursorMoveArgs + constraint: isCursorMoveArgs, + schema: { + 'type': 'object', + 'required': ['to'], + 'properties': { + 'to': { + 'type': 'string', + 'enum': ['left', 'right', 'up', 'down', 'wrappedLineStart', 'wrappedLineEnd', 'wrappedLineColumnCenter', 'wrappedLineFirstNonWhitespaceCharacter', 'wrappedLineLastNonWhitespaceCharacter', 'viewPortTop', 'viewPortCenter', 'viewPortBottom', 'viewPortIfOutside'] + }, + 'by': { + 'type': 'string', + 'enum': ['line', 'wrappedLine', 'character', 'halfLine'] + }, + 'value': { + 'type': 'number', + 'default': 1 + }, + 'select': { + 'type': 'boolean', + 'default': false + } + } + } } ] }; diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index 655f207006..4424c42c2d 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -31,7 +31,8 @@ export class TypeOperations { commands[i] = new ShiftCommand(selections[i], { isUnshift: false, tabSize: config.tabSize, - oneIndent: config.oneIndent, + indentSize: config.indentSize, + insertSpaces: config.insertSpaces, useTabStops: config.useTabStops }); } @@ -44,7 +45,8 @@ export class TypeOperations { commands[i] = new ShiftCommand(selections[i], { isUnshift: true, tabSize: config.tabSize, - oneIndent: config.oneIndent, + indentSize: config.indentSize, + insertSpaces: config.insertSpaces, useTabStops: config.useTabStops }); } @@ -53,24 +55,12 @@ export class TypeOperations { public static shiftIndent(config: CursorConfiguration, indentation: string, count?: number): string { count = count || 1; - let desiredIndentCount = ShiftCommand.shiftIndentCount(indentation, indentation.length + count, config.tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; + return ShiftCommand.shiftIndent(indentation, indentation.length + count, config.tabSize, config.indentSize, config.insertSpaces); } public static unshiftIndent(config: CursorConfiguration, indentation: string, count?: number): string { count = count || 1; - let desiredIndentCount = ShiftCommand.unshiftIndentCount(indentation, indentation.length + count, config.tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; + return ShiftCommand.unshiftIndent(indentation, indentation.length + count, config.tabSize, config.indentSize, config.insertSpaces); } private static _distributedPaste(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[], text: string[]): EditOperationResult { @@ -209,8 +199,8 @@ export class TypeOperations { let position = selection.getStartPosition(); if (config.insertSpaces) { let visibleColumnFromColumn = CursorColumns.visibleColumnFromColumn2(config, model, position); - let tabSize = config.tabSize; - let spacesCnt = tabSize - (visibleColumnFromColumn % tabSize); + let indentSize = config.indentSize; + let spacesCnt = indentSize - (visibleColumnFromColumn % indentSize); for (let i = 0; i < spacesCnt; i++) { typeText += ' '; } @@ -254,7 +244,8 @@ export class TypeOperations { commands[i] = new ShiftCommand(selection, { isUnshift: false, tabSize: config.tabSize, - oneIndent: config.oneIndent, + indentSize: config.indentSize, + insertSpaces: config.insertSpaces, useTabStops: config.useTabStops }); } @@ -377,7 +368,7 @@ export class TypeOperations { let offset = 0; if (oldEndColumn <= firstNonWhitespace + 1) { if (!config.insertSpaces) { - oldEndViewColumn = Math.ceil(oldEndViewColumn / config.tabSize); + oldEndViewColumn = Math.ceil(oldEndViewColumn / config.indentSize); } offset = Math.min(oldEndViewColumn + 1 - config.normalizeIndentation(ir.afterEnter).length - 1, 0); } @@ -535,7 +526,7 @@ export class TypeOperations { const lineText = model.getLineContent(position.lineNumber); // Do not auto-close ' or " after a word character - if (chIsQuote && position.column > 1) { + if ((chIsQuote && position.column > 1) && autoCloseConfig !== 'always') { const wordSeparators = getMapForWordSeparators(config.wordSeparators); const characterBeforeCode = lineText.charCodeAt(position.column - 2); const characterBeforeType = wordSeparators.get(characterBeforeCode); diff --git a/src/vs/editor/common/core/characterClassifier.ts b/src/vs/editor/common/core/characterClassifier.ts index 30903097fd..b2d5cac068 100644 --- a/src/vs/editor/common/core/characterClassifier.ts +++ b/src/vs/editor/common/core/characterClassifier.ts @@ -63,7 +63,7 @@ const enum Boolean { export class CharacterSet { - private _actual: CharacterClassifier; + private readonly _actual: CharacterClassifier; constructor() { this._actual = new CharacterClassifier(Boolean.False); diff --git a/src/vs/editor/common/core/editOperation.ts b/src/vs/editor/common/core/editOperation.ts index 71a79b1be4..dbd6e31b27 100644 --- a/src/vs/editor/common/core/editOperation.ts +++ b/src/vs/editor/common/core/editOperation.ts @@ -24,14 +24,14 @@ export class EditOperation { }; } - public static replace(range: Range, text: string): IIdentifiedSingleEditOperation { + public static replace(range: Range, text: string | null): IIdentifiedSingleEditOperation { return { range: range, text: text }; } - public static replaceMove(range: Range, text: string): IIdentifiedSingleEditOperation { + public static replaceMove(range: Range, text: string | null): IIdentifiedSingleEditOperation { return { range: range, text: text, diff --git a/src/vs/editor/common/core/uint.ts b/src/vs/editor/common/core/uint.ts index f01378cb36..a91e40a647 100644 --- a/src/vs/editor/common/core/uint.ts +++ b/src/vs/editor/common/core/uint.ts @@ -5,7 +5,7 @@ export class Uint8Matrix { - private _data: Uint8Array; + private readonly _data: Uint8Array; public readonly rows: number; public readonly cols: number; diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 5d55dbe8ae..601b472fe9 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -469,7 +469,7 @@ export interface IDiffEditor extends IEditor { /** * Type the getModel() of IEditor. */ - getModel(): IDiffEditorModel; + getModel(): IDiffEditorModel | null; /** * Get the `original` editor. diff --git a/src/vs/editor/common/model.ts b/src/vs/editor/common/model.ts index f8f1e3a8c1..2c208f0b85 100644 --- a/src/vs/editor/common/model.ts +++ b/src/vs/editor/common/model.ts @@ -12,7 +12,7 @@ import { IRange, Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { IModelContentChange, IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent, ModelRawContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { SearchData } from 'vs/editor/common/model/textModelSearch'; -import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes'; +import { LanguageId, LanguageIdentifier, FormattingOptions } from 'vs/editor/common/modes'; import { ITextSnapshot } from 'vs/platform/files/common/files'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; @@ -289,7 +289,7 @@ export interface ISingleEditOperation { /** * The text to replace with. This can be null to emulate a simple delete. */ - text: string; + text: string | null; /** * This indicates that this operation has "insert" semantics. * i.e. forceMoveMarkers = true => if `range` is collapsed, all markers at the position will be moved. @@ -346,6 +346,7 @@ export class TextModelResolvedOptions { _textModelResolvedOptionsBrand: void; readonly tabSize: number; + readonly indentSize: number; readonly insertSpaces: boolean; readonly defaultEOL: DefaultEndOfLine; readonly trimAutoWhitespace: boolean; @@ -355,11 +356,13 @@ export class TextModelResolvedOptions { */ constructor(src: { tabSize: number; + indentSize: number; insertSpaces: boolean; defaultEOL: DefaultEndOfLine; trimAutoWhitespace: boolean; }) { this.tabSize = src.tabSize | 0; + this.indentSize = src.tabSize | 0; this.insertSpaces = Boolean(src.insertSpaces); this.defaultEOL = src.defaultEOL | 0; this.trimAutoWhitespace = Boolean(src.trimAutoWhitespace); @@ -371,6 +374,7 @@ export class TextModelResolvedOptions { public equals(other: TextModelResolvedOptions): boolean { return ( this.tabSize === other.tabSize + && this.indentSize === other.indentSize && this.insertSpaces === other.insertSpaces && this.defaultEOL === other.defaultEOL && this.trimAutoWhitespace === other.trimAutoWhitespace @@ -383,6 +387,7 @@ export class TextModelResolvedOptions { public createChangeEvent(newOpts: TextModelResolvedOptions): IModelOptionsChangedEvent { return { tabSize: this.tabSize !== newOpts.tabSize, + indentSize: this.indentSize !== newOpts.indentSize, insertSpaces: this.insertSpaces !== newOpts.insertSpaces, trimAutoWhitespace: this.trimAutoWhitespace !== newOpts.trimAutoWhitespace, }; @@ -394,6 +399,7 @@ export class TextModelResolvedOptions { */ export interface ITextModelCreationOptions { tabSize: number; + indentSize: number; insertSpaces: boolean; detectIndentation: boolean; trimAutoWhitespace: boolean; @@ -404,6 +410,7 @@ export interface ITextModelCreationOptions { export interface ITextModelUpdateOptions { tabSize?: number; + indentSize?: number; insertSpaces?: boolean; trimAutoWhitespace?: boolean; } @@ -493,6 +500,12 @@ export interface ITextModel { */ getOptions(): TextModelResolvedOptions; + /** + * Get the formatting options for this model. + * @internal + */ + getFormattingOptions(): FormattingOptions; + /** * Get the current version id of the model. * Anytime a change happens to the model (even undo/redo), @@ -951,11 +964,6 @@ export interface ITextModel { */ normalizeIndentation(str: string): string; - /** - * Get what is considered to be one indent (e.g. a tab character or 4 spaces, etc.). - */ - getOneIndent(): string; - /** * Change the options of this model. */ diff --git a/src/vs/editor/common/model/editStack.ts b/src/vs/editor/common/model/editStack.ts index 677ac84fe7..8bc4b12381 100644 --- a/src/vs/editor/common/model/editStack.ts +++ b/src/vs/editor/common/model/editStack.ts @@ -102,7 +102,7 @@ export interface IUndoRedoResult { export class EditStack { - private model: TextModel; + private readonly model: TextModel; private currentOpenStackElement: IStackElement | null; private past: IStackElement[]; private future: IStackElement[]; @@ -210,7 +210,7 @@ export class EditStack { } public canUndo(): boolean { - return (this.past.length > 0); + return (this.past.length > 0) || this.currentOpenStackElement !== null; } public redo(): IUndoRedoResult | null { diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts index d3c586e372..a40dcc5693 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeBase.ts @@ -157,10 +157,10 @@ export class StringBuffer { * 2. TreeNode/Buffers normalization should not happen during snapshot reading. */ class PieceTreeSnapshot implements ITextSnapshot { - private _pieces: Piece[]; + private readonly _pieces: Piece[]; private _index: number; - private _tree: PieceTreeBase; - private _BOM: string; + private readonly _tree: PieceTreeBase; + private readonly _BOM: string; constructor(tree: PieceTreeBase, BOM: string) { this._pieces = []; @@ -205,7 +205,7 @@ interface CacheEntry { } class PieceTreeSearchCache { - private _limit: number; + private readonly _limit: number; private _cache: CacheEntry[]; constructor(limit: number) { diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts index 3a94d1af47..b0e1a166a3 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer.ts @@ -27,8 +27,8 @@ export interface IReverseSingleEditOperation extends IIdentifiedSingleEditOperat } export class PieceTreeTextBuffer implements ITextBuffer { - private _pieceTree: PieceTreeBase; - private _BOM: string; + private readonly _pieceTree: PieceTreeBase; + private readonly _BOM: string; private _mightContainRTL: boolean; private _mightContainNonBasicASCII: boolean; diff --git a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts index 7ee8bda093..744b6f2f30 100644 --- a/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts +++ b/src/vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder.ts @@ -62,12 +62,12 @@ export class PieceTreeTextBufferFactory implements ITextBufferFactory { } export class PieceTreeTextBufferBuilder implements ITextBufferBuilder { - private chunks: StringBuffer[]; + private readonly chunks: StringBuffer[]; private BOM: string; private _hasPreviousChar: boolean; private _previousChar: number; - private _tmpLineStarts: number[]; + private readonly _tmpLineStarts: number[]; private cr: number; private lf: number; diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index b6c03a2e67..51b87cf9a3 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -25,7 +25,7 @@ import { IModelContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguag import { SearchData, SearchParams, TextModelSearch } from 'vs/editor/common/model/textModelSearch'; import { ModelLinesTokens, ModelTokensChangedEventBuilder } from 'vs/editor/common/model/textModelTokens'; import { getWordAtText } from 'vs/editor/common/model/wordHelper'; -import { IState, LanguageId, LanguageIdentifier, TokenizationRegistry } from 'vs/editor/common/modes'; +import { IState, LanguageId, LanguageIdentifier, TokenizationRegistry, FormattingOptions } from 'vs/editor/common/modes'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { NULL_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/nullMode'; import { ignoreBracketsInToken } from 'vs/editor/common/modes/supports'; @@ -163,6 +163,7 @@ export class TextModel extends Disposable implements model.ITextModel { public static DEFAULT_CREATION_OPTIONS: model.ITextModelCreationOptions = { isForSimpleWidget: false, tabSize: EDITOR_MODEL_DEFAULTS.tabSize, + indentSize: EDITOR_MODEL_DEFAULTS.indentSize, insertSpaces: EDITOR_MODEL_DEFAULTS.insertSpaces, detectIndentation: false, defaultEOL: model.DefaultEndOfLine.LF, @@ -179,6 +180,7 @@ export class TextModel extends Disposable implements model.ITextModel { const guessedIndentation = guessIndentation(textBuffer, options.tabSize, options.insertSpaces); return new model.TextModelResolvedOptions({ tabSize: guessedIndentation.tabSize, + indentSize: guessedIndentation.tabSize, // TODO@Alex: guess indentSize independent of tabSize insertSpaces: guessedIndentation.insertSpaces, trimAutoWhitespace: options.trimAutoWhitespace, defaultEOL: options.defaultEOL @@ -187,6 +189,7 @@ export class TextModel extends Disposable implements model.ITextModel { return new model.TextModelResolvedOptions({ tabSize: options.tabSize, + indentSize: options.indentSize, insertSpaces: options.insertSpaces, trimAutoWhitespace: options.trimAutoWhitespace, defaultEOL: options.defaultEOL @@ -262,8 +265,8 @@ export class TextModel extends Disposable implements model.ITextModel { //#region Tokenization private _languageIdentifier: LanguageIdentifier; - private _tokenizationListener: IDisposable; - private _languageRegistryListener: IDisposable; + private readonly _tokenizationListener: IDisposable; + private readonly _languageRegistryListener: IDisposable; private _revalidateTokensTimeout: any; /*private*/_tokens: ModelLinesTokens; //#endregion @@ -587,14 +590,23 @@ export class TextModel extends Disposable implements model.ITextModel { return this._options; } + public getFormattingOptions(): FormattingOptions { + return { + tabSize: this._options.indentSize, + insertSpaces: this._options.insertSpaces + }; + } + public updateOptions(_newOpts: model.ITextModelUpdateOptions): void { this._assertNotDisposed(); let tabSize = (typeof _newOpts.tabSize !== 'undefined') ? _newOpts.tabSize : this._options.tabSize; + let indentSize = (typeof _newOpts.indentSize !== 'undefined') ? _newOpts.indentSize : this._options.indentSize; let insertSpaces = (typeof _newOpts.insertSpaces !== 'undefined') ? _newOpts.insertSpaces : this._options.insertSpaces; let trimAutoWhitespace = (typeof _newOpts.trimAutoWhitespace !== 'undefined') ? _newOpts.trimAutoWhitespace : this._options.trimAutoWhitespace; let newOpts = new model.TextModelResolvedOptions({ tabSize: tabSize, + indentSize: indentSize, insertSpaces: insertSpaces, defaultEOL: this._options.defaultEOL, trimAutoWhitespace: trimAutoWhitespace @@ -615,15 +627,16 @@ export class TextModel extends Disposable implements model.ITextModel { let guessedIndentation = guessIndentation(this._buffer, defaultTabSize, defaultInsertSpaces); this.updateOptions({ insertSpaces: guessedIndentation.insertSpaces, - tabSize: guessedIndentation.tabSize + tabSize: guessedIndentation.tabSize, + indentSize: guessedIndentation.tabSize, // TODO@Alex: guess indentSize independent of tabSize }); } - private static _normalizeIndentationFromWhitespace(str: string, tabSize: number, insertSpaces: boolean): string { + private static _normalizeIndentationFromWhitespace(str: string, indentSize: number, insertSpaces: boolean): string { let spacesCnt = 0; for (let i = 0; i < str.length; i++) { if (str.charAt(i) === '\t') { - spacesCnt += tabSize; + spacesCnt += indentSize; } else { spacesCnt++; } @@ -631,8 +644,8 @@ export class TextModel extends Disposable implements model.ITextModel { let result = ''; if (!insertSpaces) { - let tabsCnt = Math.floor(spacesCnt / tabSize); - spacesCnt = spacesCnt % tabSize; + let tabsCnt = Math.floor(spacesCnt / indentSize); + spacesCnt = spacesCnt % indentSize; for (let i = 0; i < tabsCnt; i++) { result += '\t'; } @@ -645,33 +658,17 @@ export class TextModel extends Disposable implements model.ITextModel { return result; } - public static normalizeIndentation(str: string, tabSize: number, insertSpaces: boolean): string { + public static normalizeIndentation(str: string, indentSize: number, insertSpaces: boolean): string { let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(str); if (firstNonWhitespaceIndex === -1) { firstNonWhitespaceIndex = str.length; } - return TextModel._normalizeIndentationFromWhitespace(str.substring(0, firstNonWhitespaceIndex), tabSize, insertSpaces) + str.substring(firstNonWhitespaceIndex); + return TextModel._normalizeIndentationFromWhitespace(str.substring(0, firstNonWhitespaceIndex), indentSize, insertSpaces) + str.substring(firstNonWhitespaceIndex); } public normalizeIndentation(str: string): string { this._assertNotDisposed(); - return TextModel.normalizeIndentation(str, this._options.tabSize, this._options.insertSpaces); - } - - public getOneIndent(): string { - this._assertNotDisposed(); - let tabSize = this._options.tabSize; - let insertSpaces = this._options.insertSpaces; - - if (insertSpaces) { - let result = ''; - for (let i = 0; i < tabSize; i++) { - result += ' '; - } - return result; - } else { - return '\t'; - } + return TextModel.normalizeIndentation(str, this._options.indentSize, this._options.insertSpaces); } //#endregion @@ -2574,7 +2571,7 @@ export class TextModel extends Disposable implements model.ITextModel { // Use the line's indent up_belowContentLineIndex = upLineNumber - 1; up_belowContentLineIndent = currentIndent; - upLineIndentLevel = Math.ceil(currentIndent / this._options.tabSize); + upLineIndentLevel = Math.ceil(currentIndent / this._options.indentSize); } else { up_resolveIndents(upLineNumber); upLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, up_aboveContentLineIndent, up_belowContentLineIndent); @@ -2609,7 +2606,7 @@ export class TextModel extends Disposable implements model.ITextModel { // Use the line's indent down_aboveContentLineIndex = downLineNumber - 1; down_aboveContentLineIndent = currentIndent; - downLineIndentLevel = Math.ceil(currentIndent / this._options.tabSize); + downLineIndentLevel = Math.ceil(currentIndent / this._options.indentSize); } else { down_resolveIndents(downLineNumber); downLineIndentLevel = this._getIndentLevelForWhitespaceLine(offSide, down_aboveContentLineIndent, down_belowContentLineIndent); @@ -2657,7 +2654,7 @@ export class TextModel extends Disposable implements model.ITextModel { // Use the line's indent aboveContentLineIndex = lineNumber - 1; aboveContentLineIndent = currentIndent; - result[resultIndex] = Math.ceil(currentIndent / this._options.tabSize); + result[resultIndex] = Math.ceil(currentIndent / this._options.indentSize); continue; } @@ -2704,20 +2701,20 @@ export class TextModel extends Disposable implements model.ITextModel { } else if (aboveContentLineIndent < belowContentLineIndent) { // we are inside the region above - return (1 + Math.floor(aboveContentLineIndent / this._options.tabSize)); + return (1 + Math.floor(aboveContentLineIndent / this._options.indentSize)); } else if (aboveContentLineIndent === belowContentLineIndent) { // we are in between two regions - return Math.ceil(belowContentLineIndent / this._options.tabSize); + return Math.ceil(belowContentLineIndent / this._options.indentSize); } else { if (offSide) { // same level as region below - return Math.ceil(belowContentLineIndent / this._options.tabSize); + return Math.ceil(belowContentLineIndent / this._options.indentSize); } else { // we are inside the region that ends below - return (1 + Math.floor(belowContentLineIndent / this._options.tabSize)); + return (1 + Math.floor(belowContentLineIndent / this._options.indentSize)); } } @@ -2733,12 +2730,12 @@ class DecorationsTrees { /** * This tree holds decorations that do not show up in the overview ruler. */ - private _decorationsTree0: IntervalTree; + private readonly _decorationsTree0: IntervalTree; /** * This tree holds decorations that show up in the overview ruler. */ - private _decorationsTree1: IntervalTree; + private readonly _decorationsTree1: IntervalTree; constructor() { this._decorationsTree0 = new IntervalTree(); diff --git a/src/vs/editor/common/model/textModelEvents.ts b/src/vs/editor/common/model/textModelEvents.ts index 62829bc9d8..95a179c735 100644 --- a/src/vs/editor/common/model/textModelEvents.ts +++ b/src/vs/editor/common/model/textModelEvents.ts @@ -97,6 +97,7 @@ export interface IModelTokensChangedEvent { export interface IModelOptionsChangedEvent { readonly tabSize: boolean; + readonly indentSize: boolean; readonly insertSpaces: boolean; readonly trimAutoWhitespace: boolean; } diff --git a/src/vs/editor/common/model/textModelSearch.ts b/src/vs/editor/common/model/textModelSearch.ts index 3987a3c536..287418ec63 100644 --- a/src/vs/editor/common/model/textModelSearch.ts +++ b/src/vs/editor/common/model/textModelSearch.ts @@ -509,8 +509,8 @@ export function isValidMatch(wordSeparators: WordCharacterClassifier, text: stri } export class Searcher { - private _wordSeparators: WordCharacterClassifier | null; - private _searchRegex: RegExp; + private readonly _wordSeparators: WordCharacterClassifier | null; + private readonly _searchRegex: RegExp; private _prevMatchStartIndex: number; private _prevMatchLength: number; diff --git a/src/vs/editor/common/model/textModelTokens.ts b/src/vs/editor/common/model/textModelTokens.ts index d8f47985a6..e0c80f0701 100644 --- a/src/vs/editor/common/model/textModelTokens.ts +++ b/src/vs/editor/common/model/textModelTokens.ts @@ -472,7 +472,7 @@ export class ModelLinesTokens { export class ModelTokensChangedEventBuilder { - private _ranges: { fromLineNumber: number; toLineNumber: number; }[]; + private readonly _ranges: { fromLineNumber: number; toLineNumber: number; }[]; constructor() { this._ranges = []; diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 37fbca0f4b..92a647c0a4 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -9,7 +9,7 @@ import { Event } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { IDisposable } from 'vs/base/common/lifecycle'; import { isObject } from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; @@ -18,6 +18,7 @@ import * as model from 'vs/editor/common/model'; import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry'; import { TokenizationRegistryImpl } from 'vs/editor/common/modes/tokenizationRegistry'; import { IMarkerData } from 'vs/platform/markers/common/markers'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; /** * Open ended enum at runtime @@ -286,7 +287,7 @@ export const enum CompletionItemKind { /** * @internal */ -export let completionKindToCssClass = (function () { +export const completionKindToCssClass = (function () { let data = Object.create(null); data[CompletionItemKind.Method] = 'method'; data[CompletionItemKind.Function] = 'function'; @@ -323,11 +324,14 @@ export let completionKindToCssClass = (function () { /** * @internal */ -export let completionKindFromLegacyString = (function () { - let data = Object.create(null); +export let completionKindFromString: { + (value: string): CompletionItemKind; + (value: string, strict: true): CompletionItemKind | undefined; +} = (function () { + let data: Record = Object.create(null); data['method'] = CompletionItemKind.Method; data['function'] = CompletionItemKind.Function; - data['constructor'] = CompletionItemKind.Constructor; + data['constructor'] = CompletionItemKind.Constructor; data['field'] = CompletionItemKind.Field; data['variable'] = CompletionItemKind.Variable; data['class'] = CompletionItemKind.Class; @@ -342,6 +346,7 @@ export let completionKindFromLegacyString = (function () { data['constant'] = CompletionItemKind.Constant; data['enum'] = CompletionItemKind.Enum; data['enum-member'] = CompletionItemKind.EnumMember; + data['enumMember'] = CompletionItemKind.EnumMember; data['keyword'] = CompletionItemKind.Keyword; data['snippet'] = CompletionItemKind.Snippet; data['text'] = CompletionItemKind.Text; @@ -351,9 +356,14 @@ export let completionKindFromLegacyString = (function () { data['customcolor'] = CompletionItemKind.Customcolor; data['folder'] = CompletionItemKind.Folder; data['type-parameter'] = CompletionItemKind.TypeParameter; + data['typeParameter'] = CompletionItemKind.TypeParameter; - return function (value: string) { - return data[value] || 'property'; + return function (value: string, strict?: true) { + let res = data[value]; + if (typeof res === 'undefined' && !strict) { + res = CompletionItemKind.Property; + } + return res; }; })(); @@ -667,7 +677,7 @@ export interface DocumentHighlight { /** * The highlight kind, default is [text](#DocumentHighlightKind.Text). */ - kind: DocumentHighlightKind; + kind?: DocumentHighlightKind; } /** * The document highlight provider interface defines the contract between extensions and @@ -864,8 +874,8 @@ export const symbolKindToCssClass = (function () { _fromMapping[SymbolKind.Operator] = 'operator'; _fromMapping[SymbolKind.TypeParameter] = 'type-parameter'; - return function toCssClassName(kind: SymbolKind): string { - return `symbol-icon ${_fromMapping[kind] || 'property'}`; + return function toCssClassName(kind: SymbolKind, inline?: boolean): string { + return `symbol-icon ${inline ? 'inline' : 'block'} ${_fromMapping[kind] || 'property'}`; }; })(); @@ -914,7 +924,10 @@ export interface FormattingOptions { */ export interface DocumentFormattingEditProvider { - displayName?: string; + /** + * @internal + */ + readonly extensionId?: ExtensionIdentifier; /** * Provide formatting edits for a whole document. @@ -927,7 +940,11 @@ export interface DocumentFormattingEditProvider { */ export interface DocumentRangeFormattingEditProvider { - displayName?: string; + + /** + * @internal + */ + readonly extensionId?: ExtensionIdentifier; /** * Provide formatting edits for a range in a document. @@ -943,7 +960,15 @@ export interface DocumentRangeFormattingEditProvider { * the formatting-feature. */ export interface OnTypeFormattingEditProvider { + + + /** + * @internal + */ + readonly extensionId?: ExtensionIdentifier; + autoFormatTriggerCharacters: string[]; + /** * Provide formatting edits after a character has been typed. * @@ -967,7 +992,7 @@ export interface IInplaceReplaceSupportResult { */ export interface ILink { range: IRange; - url?: string; + url?: URI | string; } /** * A provider of links. @@ -1064,7 +1089,7 @@ export interface SelectionRangeProvider { /** * Provide ranges that should be selected from the given position. */ - provideSelectionRanges(model: model.ITextModel, position: Position, token: CancellationToken): ProviderResult; + provideSelectionRanges(model: model.ITextModel, positions: Position[], token: CancellationToken): ProviderResult; } export interface FoldingContext { @@ -1178,11 +1203,11 @@ export interface Command { * @internal */ export interface CommentInfo { - extensionId: string; + extensionId?: string; threads: CommentThread[]; - commentingRanges?: IRange[]; + commentingRanges?: (IRange[] | CommentingRanges); reply?: Command; - draftMode: DraftMode; + draftMode?: DraftMode; } /** @@ -1208,13 +1233,68 @@ export enum CommentThreadCollapsibleState { Expanded = 1 } + + +/** + * @internal + */ +export interface CommentWidget { + commentThread: CommentThread; + comment?: Comment; + input: string; + onDidChangeInput: Event; +} + +/** + * @internal + */ +export interface CommentInput { + value: string; + uri: URI; +} + +/** + * @internal + */ +export interface CommentThread2 { + commentThreadHandle: number; + extensionId?: string; + threadId: string | null; + resource: string | null; + range: IRange; + label: string; + comments: Comment[]; + onDidChangeComments: Event; + collapsibleState?: CommentThreadCollapsibleState; + input?: CommentInput; + onDidChangeInput: Event; + acceptInputCommand?: Command; + additionalCommands: Command[]; + onDidChangeAcceptInputCommand: Event; + onDidChangeAdditionalCommands: Event; + onDidChangeRange: Event; + onDidChangeLabel: Event; + onDidChangeCollasibleState: Event; +} + +/** + * @internal + */ + +export interface CommentingRanges { + readonly resource: URI; + ranges: IRange[]; + newCommentThreadCommand?: Command; + newCommentThreadCallback?: (uri: UriComponents, range: IRange) => void; +} + /** * @internal */ export interface CommentThread { - extensionId: string; - threadId: string; - resource: string; + extensionId?: string; + threadId: string | null; + resource: string | null; range: IRange; comments: Comment[]; collapsibleState?: CommentThreadCollapsibleState; @@ -1234,7 +1314,10 @@ export interface NewCommentAction { */ export interface CommentReaction { readonly label?: string; + readonly iconPath?: UriComponents; + readonly count?: number; readonly hasReacted?: boolean; + readonly canEdit?: boolean; } /** @@ -1244,12 +1327,15 @@ export interface Comment { readonly commentId: string; readonly body: IMarkdownString; readonly userName: string; - readonly userIconPath: string; + readonly userIconPath?: string; readonly canEdit?: boolean; readonly canDelete?: boolean; - readonly command?: Command; + readonly selectCommand?: Command; + readonly editCommand?: Command; + readonly deleteCommand?: Command; readonly isDraft?: boolean; readonly commentReactions?: CommentReaction[]; + readonly label?: string; } /** @@ -1259,31 +1345,31 @@ export interface CommentThreadChangedEvent { /** * Added comment threads. */ - readonly added: CommentThread[]; + readonly added: (CommentThread | CommentThread2)[]; /** * Removed comment threads. */ - readonly removed: CommentThread[]; + readonly removed: (CommentThread | CommentThread2)[]; /** * Changed comment threads. */ - readonly changed: CommentThread[]; + readonly changed: (CommentThread | CommentThread2)[]; /** * changed draft mode. */ - readonly draftMode: DraftMode; + readonly draftMode?: DraftMode; } /** * @internal */ export interface DocumentCommentProvider { - provideDocumentComments(resource: URI, token: CancellationToken): Promise; - createNewCommentThread(resource: URI, range: Range, text: string, token: CancellationToken): Promise; - replyToCommentThread(resource: URI, range: Range, thread: CommentThread, text: string, token: CancellationToken): Promise; + provideDocumentComments(resource: URI, token: CancellationToken): Promise; + createNewCommentThread(resource: URI, range: Range, text: string, token: CancellationToken): Promise; + replyToCommentThread(resource: URI, range: Range, thread: CommentThread, text: string, token: CancellationToken): Promise; editComment(resource: URI, comment: Comment, text: string, token: CancellationToken): Promise; deleteComment(resource: URI, comment: Comment, token: CancellationToken): Promise; startDraft?(resource: URI, token: CancellationToken): Promise; @@ -1298,7 +1384,7 @@ export interface DocumentCommentProvider { deleteReaction?(resource: URI, comment: Comment, reaction: CommentReaction, token: CancellationToken): Promise; reactionGroup?: CommentReaction[]; - onDidChangeCommentThreads(): Event; + onDidChangeCommentThreads?(): Event; } /** diff --git a/src/vs/editor/common/modes/abstractMode.ts b/src/vs/editor/common/modes/abstractMode.ts index 21272b3df6..cd01761b1d 100644 --- a/src/vs/editor/common/modes/abstractMode.ts +++ b/src/vs/editor/common/modes/abstractMode.ts @@ -7,7 +7,7 @@ import { IMode, LanguageIdentifier } from 'vs/editor/common/modes'; export class FrankensteinMode implements IMode { - private _languageIdentifier: LanguageIdentifier; + private readonly _languageIdentifier: LanguageIdentifier; constructor(languageIdentifier: LanguageIdentifier) { this._languageIdentifier = languageIdentifier; diff --git a/src/vs/editor/common/modes/languageConfiguration.ts b/src/vs/editor/common/modes/languageConfiguration.ts index 99e47b99ff..9f65e8d3de 100644 --- a/src/vs/editor/common/modes/languageConfiguration.ts +++ b/src/vs/editor/common/modes/languageConfiguration.ts @@ -170,7 +170,7 @@ export interface IDocComment { /** * The string that appears on the last line and closes the doc comment (e.g. ' * /'). */ - close: string; + close?: string; } /** diff --git a/src/vs/editor/common/modes/languageConfigurationRegistry.ts b/src/vs/editor/common/modes/languageConfigurationRegistry.ts index 1316e47033..42599182c6 100644 --- a/src/vs/editor/common/modes/languageConfigurationRegistry.ts +++ b/src/vs/editor/common/modes/languageConfigurationRegistry.ts @@ -175,7 +175,7 @@ export class LanguageConfigurationChangeEvent { export class LanguageConfigurationRegistryImpl { - private _entries: RichEditSupport[]; + private readonly _entries: RichEditSupport[]; private readonly _onDidChange = new Emitter(); public readonly onDidChange: Event = this._onDidChange.event; diff --git a/src/vs/editor/common/modes/languageFeatureRegistry.ts b/src/vs/editor/common/modes/languageFeatureRegistry.ts index d702f4ea5c..75791a68cd 100644 --- a/src/vs/editor/common/modes/languageFeatureRegistry.ts +++ b/src/vs/editor/common/modes/languageFeatureRegistry.ts @@ -29,7 +29,7 @@ function isExclusive(selector: LanguageSelector): boolean { export class LanguageFeatureRegistry { private _clock: number = 0; - private _entries: Entry[] = []; + private readonly _entries: Entry[] = []; private readonly _onDidChange = new Emitter(); constructor() { diff --git a/src/vs/editor/common/modes/languageSelector.ts b/src/vs/editor/common/modes/languageSelector.ts index 8113007702..27482e5a9b 100644 --- a/src/vs/editor/common/modes/languageSelector.ts +++ b/src/vs/editor/common/modes/languageSelector.ts @@ -19,7 +19,7 @@ export interface LanguageFilter { export type LanguageSelector = string | LanguageFilter | Array; -export function score(selector: LanguageSelector, candidateUri: URI, candidateLanguage: string, candidateIsSynchronized: boolean): number { +export function score(selector: LanguageSelector | undefined, candidateUri: URI, candidateLanguage: string, candidateIsSynchronized: boolean): number { if (Array.isArray(selector)) { // array -> take max individual value diff --git a/src/vs/editor/common/modes/linkComputer.ts b/src/vs/editor/common/modes/linkComputer.ts index e45f168f8e..0ac58a68f3 100644 --- a/src/vs/editor/common/modes/linkComputer.ts +++ b/src/vs/editor/common/modes/linkComputer.ts @@ -35,8 +35,8 @@ export type Edge = [State, number, State]; export class StateMachine { - private _states: Uint8Matrix; - private _maxCharCode: number; + private readonly _states: Uint8Matrix; + private readonly _maxCharCode: number; constructor(edges: Edge[]) { let maxCharCode = 0; diff --git a/src/vs/editor/common/modes/modesRegistry.ts b/src/vs/editor/common/modes/modesRegistry.ts index 90de9bf652..516b59532c 100644 --- a/src/vs/editor/common/modes/modesRegistry.ts +++ b/src/vs/editor/common/modes/modesRegistry.ts @@ -17,7 +17,7 @@ export const Extensions = { export class EditorModesRegistry { - private _languages: ILanguageExtensionPoint[]; + private readonly _languages: ILanguageExtensionPoint[]; private _dynamicLanguages: ILanguageExtensionPoint[]; private readonly _onDidChangeLanguages = new Emitter(); diff --git a/src/vs/editor/common/modes/supports/electricCharacter.ts b/src/vs/editor/common/modes/supports/electricCharacter.ts index 55e082a68c..5dfb1ff4e1 100644 --- a/src/vs/editor/common/modes/supports/electricCharacter.ts +++ b/src/vs/editor/common/modes/supports/electricCharacter.ts @@ -33,7 +33,7 @@ export class BracketElectricCharacterSupport { this._complexAutoClosePairs = autoClosePairs.filter(pair => pair.open.length > 1 && !!pair.close).map(el => new StandardAutoClosingPairConditional(el)); if (contribution.docComment) { // IDocComment is legacy, only partially supported - this._complexAutoClosePairs.push(new StandardAutoClosingPairConditional({ open: contribution.docComment.open, close: contribution.docComment.close })); + this._complexAutoClosePairs.push(new StandardAutoClosingPairConditional({ open: contribution.docComment.open, close: contribution.docComment.close || '' })); } } diff --git a/src/vs/editor/common/modes/supports/inplaceReplaceSupport.ts b/src/vs/editor/common/modes/supports/inplaceReplaceSupport.ts index 46319cbb02..9d5fbeb4b6 100644 --- a/src/vs/editor/common/modes/supports/inplaceReplaceSupport.ts +++ b/src/vs/editor/common/modes/supports/inplaceReplaceSupport.ts @@ -64,7 +64,7 @@ export class BasicInplaceReplace { return null; } - private _defaultValueSet: string[][] = [ + private readonly _defaultValueSet: string[][] = [ ['true', 'false'], ['True', 'False'], ['Private', 'Public', 'Friend', 'ReadOnly', 'Partial', 'Protected', 'WriteOnly'], diff --git a/src/vs/editor/common/modes/supports/tokenization.ts b/src/vs/editor/common/modes/supports/tokenization.ts index d1ebac4dc7..24e129bc88 100644 --- a/src/vs/editor/common/modes/supports/tokenization.ts +++ b/src/vs/editor/common/modes/supports/tokenization.ts @@ -151,8 +151,8 @@ const colorRegExp = /^#?([0-9A-Fa-f]{6})([0-9A-Fa-f]{2})?$/; export class ColorMap { private _lastColorId: number; - private _id2color: Color[]; - private _color2id: Map; + private readonly _id2color: Color[]; + private readonly _color2id: Map; constructor() { this._lastColorId = 0; @@ -240,7 +240,7 @@ export class TokenTheme { } } -const STANDARD_TOKEN_TYPE_REGEXP = /\b(comment|string|regex)\b/; +const STANDARD_TOKEN_TYPE_REGEXP = /\b(comment|string|regex|regexp)\b/; export function toStandardTokenType(tokenType: string): StandardTokenType { let m = tokenType.match(STANDARD_TOKEN_TYPE_REGEXP); if (!m) { @@ -253,6 +253,8 @@ export function toStandardTokenType(tokenType: string): StandardTokenType { return StandardTokenType.String; case 'regex': return StandardTokenType.RegEx; + case 'regexp': + return StandardTokenType.RegEx; } throw new Error('Unexpected match for standard token type!'); } diff --git a/src/vs/editor/common/modes/tokenizationRegistry.ts b/src/vs/editor/common/modes/tokenizationRegistry.ts index 561ad9135a..c3045a1a44 100644 --- a/src/vs/editor/common/modes/tokenizationRegistry.ts +++ b/src/vs/editor/common/modes/tokenizationRegistry.ts @@ -10,8 +10,8 @@ import { ColorId, ITokenizationRegistry, ITokenizationSupport, ITokenizationSupp export class TokenizationRegistryImpl implements ITokenizationRegistry { - private _map: { [language: string]: ITokenizationSupport }; - private _promises: { [language: string]: Thenable }; + private readonly _map: { [language: string]: ITokenizationSupport }; + private readonly _promises: { [language: string]: Thenable }; private readonly _onDidChange = new Emitter(); public readonly onDidChange: Event = this._onDidChange.event; diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 81177a9def..6e6c79c717 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -22,6 +22,7 @@ import { ILinkComputerTarget, computeLinks } from 'vs/editor/common/modes/linkCo import { BasicInplaceReplace } from 'vs/editor/common/modes/supports/inplaceReplaceSupport'; import { IDiffComputationResult } from 'vs/editor/common/services/editorWorkerService'; import { createMonacoBaseAPI } from 'vs/editor/common/standalone/standaloneBase'; +import { getAllPropertyNames } from 'vs/base/common/types'; export interface IMirrorModel { readonly uri: URI; @@ -322,7 +323,7 @@ declare var require: any; * @internal */ export abstract class BaseEditorSimpleWorker { - private _foreignModuleFactory: IForeignModuleFactory | null; + private readonly _foreignModuleFactory: IForeignModuleFactory | null; private _foreignModule: any; constructor(foreignModuleFactory: IForeignModuleFactory | null) { @@ -490,12 +491,15 @@ export abstract class BaseEditorSimpleWorker { return Promise.resolve(null); } + const seen: Record = Object.create(null); const suggestions: CompletionItem[] = []; const wordDefRegExp = new RegExp(wordDef, wordDefFlags); - const currentWord = model.getWordUntilPosition(position, wordDefRegExp); + const wordUntil = model.getWordUntilPosition(position, wordDefRegExp); - const seen: Record = Object.create(null); - seen[currentWord.word] = true; + const wordAt = model.getWordAtPosition(position, wordDefRegExp); + if (wordAt) { + seen[model.getValueInRange(wordAt)] = true; + } for ( let iter = model.createWordIterator(wordDefRegExp), e = iter.next(); @@ -515,10 +519,9 @@ export abstract class BaseEditorSimpleWorker { kind: CompletionItemKind.Text, label: word, insertText: word, - range: { startLineNumber: position.lineNumber, startColumn: currentWord.startColumn, endLineNumber: position.lineNumber, endColumn: currentWord.endColumn } + range: { startLineNumber: position.lineNumber, startColumn: wordUntil.startColumn, endLineNumber: position.lineNumber, endColumn: wordUntil.endColumn } }); } - return Promise.resolve({ suggestions }); } @@ -599,7 +602,7 @@ export abstract class BaseEditorSimpleWorker { this._foreignModule = this._foreignModuleFactory(ctx, createData); // static foreing module let methods: string[] = []; - for (let prop in this._foreignModule) { + for (const prop of getAllPropertyNames(this._foreignModule)) { if (typeof this._foreignModule[prop] === 'function') { methods.push(prop); } @@ -612,7 +615,7 @@ export abstract class BaseEditorSimpleWorker { this._foreignModule = foreignModule.create(ctx, createData); let methods: string[] = []; - for (let prop in this._foreignModule) { + for (const prop of getAllPropertyNames(this._foreignModule)) { if (typeof this._foreignModule[prop] === 'function') { methods.push(prop); } diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index 76ef3aa389..ec5bfbd3e1 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -146,7 +146,7 @@ class WordBasedCompletionItemProvider implements modes.CompletionItemProvider { class WorkerManager extends Disposable { - private _modelService: IModelService; + private readonly _modelService: IModelService; private _editorWorkerClient: EditorWorkerClient | null; private _lastWorkerUsedTime: number; @@ -211,8 +211,8 @@ class WorkerManager extends Disposable { class EditorModelManager extends Disposable { - private _proxy: EditorSimpleWorkerImpl; - private _modelService: IModelService; + private readonly _proxy: EditorSimpleWorkerImpl; + private readonly _modelService: IModelService; private _syncedModels: { [modelUrl: string]: IDisposable[]; } = Object.create(null); private _syncedModelsLastUsedTime: { [modelUrl: string]: number; } = Object.create(null); @@ -312,8 +312,8 @@ interface IWorkerClient { } class SynchronousWorkerClient implements IWorkerClient { - private _instance: T; - private _proxyObj: Promise; + private readonly _instance: T; + private readonly _proxyObj: Promise; constructor(instance: T) { this._instance = instance; @@ -331,9 +331,9 @@ class SynchronousWorkerClient implements IWorkerClient export class EditorWorkerClient extends Disposable { - private _modelService: IModelService; + private readonly _modelService: IModelService; private _worker: IWorkerClient | null; - private _workerFactory: DefaultWorkerFactory; + private readonly _workerFactory: DefaultWorkerFactory; private _modelManager: EditorModelManager | null; constructor(modelService: IModelService, label: string | undefined) { diff --git a/src/vs/editor/common/services/getIconClasses.ts b/src/vs/editor/common/services/getIconClasses.ts index 33b951678b..9854146d45 100644 --- a/src/vs/editor/common/services/getIconClasses.ts +++ b/src/vs/editor/common/services/getIconClasses.ts @@ -12,9 +12,11 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { FileKind } from 'vs/platform/files/common/files'; export function getIconClasses(modelService: IModelService, modeService: IModeService, resource: uri | undefined, fileKind?: FileKind): string[] { + // we always set these base classes even if we do not have a path const classes = fileKind === FileKind.ROOT_FOLDER ? ['rootfolder-icon'] : fileKind === FileKind.FOLDER ? ['folder-icon'] : ['file-icon']; if (resource) { + // Get the path and name of the resource. For data-URIs, we need to parse specially let name: string | undefined; let path: string | undefined; @@ -22,17 +24,19 @@ export function getIconClasses(modelService: IModelService, modeService: IModeSe const metadata = DataUri.parseMetaData(resource); name = metadata.get(DataUri.META_DATA_LABEL); path = name; - } - else { + } else { name = cssEscape(basenameOrAuthority(resource).toLowerCase()); path = resource.path.toLowerCase(); } + // Folders if (fileKind === FileKind.FOLDER) { classes.push(`${name}-name-folder-icon`); } + // Files else { + // Name & Extension(s) if (name) { classes.push(`${name}-name-file-icon`); @@ -42,8 +46,9 @@ export function getIconClasses(modelService: IModelService, modeService: IModeSe } classes.push(`ext-file-icon`); // extra segment to increase file-ext score } + // Configured Language - let configuredLangId: string | null = getConfiguredLangId(modelService, resource); + let configuredLangId: string | null = getConfiguredLangId(modelService, modeService, resource); configuredLangId = configuredLangId || (path ? modeService.getModeIdByFilepathOrFirstLine(path) : null); if (configuredLangId) { classes.push(`${cssEscape(configuredLangId)}-lang-file-icon`); @@ -53,16 +58,32 @@ export function getIconClasses(modelService: IModelService, modeService: IModeSe return classes; } -export function getConfiguredLangId(modelService: IModelService, resource: uri): string | null { +export function getConfiguredLangId(modelService: IModelService, modeService: IModeService, resource: uri): string | null { let configuredLangId: string | null = null; if (resource) { - const model = modelService.getModel(resource); - if (model) { - const modeId = model.getLanguageIdentifier().language; - if (modeId && modeId !== PLAINTEXT_MODE_ID) { - configuredLangId = modeId; // only take if the mode is specific (aka no just plain text) + let modeId: string | null = null; + + // Data URI: check for encoded metadata + if (resource.scheme === Schemas.data) { + const metadata = DataUri.parseMetaData(resource); + const mime = metadata.get(DataUri.META_DATA_MIME); + + if (mime) { + modeId = modeService.getModeId(mime); } } + + // Any other URI: check for model if existing + else { + const model = modelService.getModel(resource); + if (model) { + modeId = model.getLanguageIdentifier().language; + } + } + + if (modeId && modeId !== PLAINTEXT_MODE_ID) { + configuredLangId = modeId; // only take if the mode is specific (aka no just plain text) + } } return configuredLangId; diff --git a/src/vs/editor/common/services/languagesRegistry.ts b/src/vs/editor/common/services/languagesRegistry.ts index a528911a3e..1326a17b81 100644 --- a/src/vs/editor/common/services/languagesRegistry.ts +++ b/src/vs/editor/common/services/languagesRegistry.ts @@ -36,8 +36,8 @@ export class LanguagesRegistry extends Disposable { private readonly _warnOnOverwrite: boolean; private _nextLanguageId2: number; - private _languageIdToLanguage: string[]; - private _languageToLanguageId: { [id: string]: number; }; + private readonly _languageIdToLanguage: string[]; + private readonly _languageToLanguageId: { [id: string]: number; }; private _languages: { [id: string]: IResolvedLanguage; }; private _mimeTypesMap: { [mimeType: string]: LanguageIdentifier; }; @@ -273,7 +273,7 @@ export class LanguagesRegistry extends Disposable { return (language.mimetypes[0] || null); } - public extractModeIds(commaSeparatedMimetypesOrCommaSeparatedIds: string): string[] { + public extractModeIds(commaSeparatedMimetypesOrCommaSeparatedIds: string | undefined): string[] { if (!commaSeparatedMimetypesOrCommaSeparatedIds) { return []; } diff --git a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts index 0c70434746..e4e3108509 100644 --- a/src/vs/editor/common/services/markerDecorationsServiceImpl.ts +++ b/src/vs/editor/common/services/markerDecorationsServiceImpl.ts @@ -15,6 +15,7 @@ import { Range } from 'vs/editor/common/core/range'; import { keys } from 'vs/base/common/map'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; import { Schemas } from 'vs/base/common/network'; +import { Emitter, Event } from 'vs/base/common/event'; function MODEL_ID(resource: URI): string { return resource.toString(); @@ -44,12 +45,26 @@ class MarkerDecorations extends Disposable { getMarker(decoration: IModelDecoration): IMarker | undefined { return this._markersData.get(decoration.id); } + + getMarkers(): [Range, IMarker][] { + const res: [Range, IMarker][] = []; + this._markersData.forEach((marker, id) => { + let range = this.model.getDecorationRange(id); + if (range) { + res.push([range, marker]); + } + }); + return res; + } } export class MarkerDecorationsService extends Disposable implements IMarkerDecorationsService { _serviceBrand: any; + private readonly _onDidChangeMarker = new Emitter(); + readonly onDidChangeMarker: Event = this._onDidChangeMarker.event; + private readonly _markerDecorations: Map = new Map(); constructor( @@ -68,11 +83,16 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor return markerDecorations ? markerDecorations.getMarker(decoration) || null : null; } + getLiveMarkers(model: ITextModel): [Range, IMarker][] { + const markerDecorations = this._markerDecorations.get(MODEL_ID(model.uri)); + return markerDecorations ? markerDecorations.getMarkers() : []; + } + private _handleMarkerChange(changedResources: URI[]): void { changedResources.forEach((resource) => { const markerDecorations = this._markerDecorations.get(MODEL_ID(resource)); if (markerDecorations) { - this.updateDecorations(markerDecorations); + this._updateDecorations(markerDecorations); } }); } @@ -80,7 +100,7 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor private _onModelAdded(model: ITextModel): void { const markerDecorations = new MarkerDecorations(model); this._markerDecorations.set(MODEL_ID(model.uri), markerDecorations); - this.updateDecorations(markerDecorations); + this._updateDecorations(markerDecorations); } private _onModelRemoved(model: ITextModel): void { @@ -100,7 +120,7 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor } } - private updateDecorations(markerDecorations: MarkerDecorations): void { + private _updateDecorations(markerDecorations: MarkerDecorations): void { // Limit to the first 500 errors/warnings const markers = this._markerService.read({ resource: markerDecorations.model.uri, take: 500 }); let newModelDecorations: IModelDeltaDecoration[] = markers.map((marker) => { @@ -110,6 +130,7 @@ export class MarkerDecorationsService extends Disposable implements IMarkerDecor }; }); markerDecorations.update(markers, newModelDecorations); + this._onDidChangeMarker.fire(markerDecorations.model); } private _createDecorationRange(model: ITextModel, rawMarker: IMarker): Range { diff --git a/src/vs/editor/common/services/markersDecorationService.ts b/src/vs/editor/common/services/markersDecorationService.ts index 8bff3324c4..339f911e47 100644 --- a/src/vs/editor/common/services/markersDecorationService.ts +++ b/src/vs/editor/common/services/markersDecorationService.ts @@ -6,11 +6,17 @@ import { ITextModel, IModelDecoration } from 'vs/editor/common/model'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IMarker } from 'vs/platform/markers/common/markers'; +import { Event } from 'vs/base/common/event'; +import { Range } from 'vs/editor/common/core/range'; export const IMarkerDecorationsService = createDecorator('markerDecorationsService'); export interface IMarkerDecorationsService { _serviceBrand: any; + onDidChangeMarker: Event; + getMarker(model: ITextModel, decoration: IModelDecoration): IMarker | null; -} \ No newline at end of file + + getLiveMarkers(model: ITextModel): [Range, IMarker][]; +} diff --git a/src/vs/editor/common/services/modeService.ts b/src/vs/editor/common/services/modeService.ts index a6e025fcfd..f2a54a7189 100644 --- a/src/vs/editor/common/services/modeService.ts +++ b/src/vs/editor/common/services/modeService.ts @@ -47,7 +47,7 @@ export interface IModeService { getConfigurationFiles(modeId: string): URI[]; // --- instantiation - create(commaSeparatedMimetypesOrCommaSeparatedIds: string): ILanguageSelection; + create(commaSeparatedMimetypesOrCommaSeparatedIds: string | undefined): ILanguageSelection; createByLanguageName(languageName: string): ILanguageSelection; createByFilepathOrFirstLine(filepath: string | null, firstLine?: string): ILanguageSelection; diff --git a/src/vs/editor/common/services/modeServiceImpl.ts b/src/vs/editor/common/services/modeServiceImpl.ts index bd78ae050c..ef8aa2c45e 100644 --- a/src/vs/editor/common/services/modeServiceImpl.ts +++ b/src/vs/editor/common/services/modeServiceImpl.ts @@ -104,7 +104,7 @@ export class ModeServiceImpl implements IModeService { return null; } - public getModeId(commaSeparatedMimetypesOrCommaSeparatedIds: string): string | null { + public getModeId(commaSeparatedMimetypesOrCommaSeparatedIds: string | undefined): string | null { const modeIds = this._registry.extractModeIds(commaSeparatedMimetypesOrCommaSeparatedIds); if (modeIds.length > 0) { @@ -124,7 +124,7 @@ export class ModeServiceImpl implements IModeService { // --- instantiation - public create(commaSeparatedMimetypesOrCommaSeparatedIds: string): ILanguageSelection { + public create(commaSeparatedMimetypesOrCommaSeparatedIds: string | undefined): ILanguageSelection { return new LanguageSelection(this.onLanguagesMaybeChanged, () => { const modeId = this.getModeId(commaSeparatedMimetypesOrCommaSeparatedIds); return this._createModeAndGetLanguageIdentifier(modeId); diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 55ab58d318..42a1e59c01 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -73,6 +73,7 @@ class ModelData implements IDisposable { interface IRawEditorConfig { tabSize?: any; + indentSize?: any; insertSpaces?: any; detectIndentation?: any; trimAutoWhitespace?: any; @@ -90,9 +91,9 @@ const DEFAULT_EOL = (platform.isLinux || platform.isMacintosh) ? DefaultEndOfLin export class ModelServiceImpl extends Disposable implements IModelService { public _serviceBrand: any; - private _configurationService: IConfigurationService; - private _configurationServiceSubscription: IDisposable; - private _resourcePropertiesService: ITextResourcePropertiesService; + private readonly _configurationService: IConfigurationService; + private readonly _configurationServiceSubscription: IDisposable; + private readonly _resourcePropertiesService: ITextResourcePropertiesService; private readonly _onModelAdded: Emitter = this._register(new Emitter()); public readonly onModelAdded: Event = this._onModelAdded.event; @@ -110,7 +111,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { /** * All the models known in the system. */ - private _models: { [modelId: string]: ModelData; }; + private readonly _models: { [modelId: string]: ModelData; }; constructor( @IConfigurationService configurationService: IConfigurationService, @@ -138,6 +139,17 @@ export class ModelServiceImpl extends Disposable implements IModelService { } } + let indentSize = tabSize; + if (config.editor && typeof config.editor.indentSize !== 'undefined' && config.editor.indentSize !== 'tabSize') { + let parsedIndentSize = parseInt(config.editor.indentSize, 10); + if (!isNaN(parsedIndentSize)) { + indentSize = parsedIndentSize; + } + if (indentSize < 1) { + indentSize = 1; + } + } + let insertSpaces = EDITOR_MODEL_DEFAULTS.insertSpaces; if (config.editor && typeof config.editor.insertSpaces !== 'undefined') { insertSpaces = (config.editor.insertSpaces === 'false' ? false : Boolean(config.editor.insertSpaces)); @@ -169,6 +181,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { return { isForSimpleWidget: isForSimpleWidget, tabSize: tabSize, + indentSize: indentSize, insertSpaces: insertSpaces, detectIndentation: detectIndentation, defaultEOL: newDefaultEOL, @@ -210,6 +223,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { && (currentOptions.detectIndentation === newOptions.detectIndentation) && (currentOptions.insertSpaces === newOptions.insertSpaces) && (currentOptions.tabSize === newOptions.tabSize) + && (currentOptions.indentSize === newOptions.indentSize) && (currentOptions.trimAutoWhitespace === newOptions.trimAutoWhitespace) ) { // Same indent opts, no need to touch the model @@ -225,6 +239,7 @@ export class ModelServiceImpl extends Disposable implements IModelService { model.updateOptions({ insertSpaces: newOptions.insertSpaces, tabSize: newOptions.tabSize, + indentSize: newOptions.indentSize, trimAutoWhitespace: newOptions.trimAutoWhitespace }); } diff --git a/src/vs/editor/common/services/resolverService.ts b/src/vs/editor/common/services/resolverService.ts index 042151242d..7e7194fe4e 100644 --- a/src/vs/editor/common/services/resolverService.ts +++ b/src/vs/editor/common/services/resolverService.ts @@ -18,7 +18,7 @@ export interface ITextModelService { * Provided a resource URI, it will return a model reference * which should be disposed once not needed anymore. */ - createModelReference(resource: URI): Promise>; + createModelReference(resource: URI): Promise>; /** * Registers a specific `scheme` content provider. @@ -36,7 +36,7 @@ export interface ITextModelContentProvider { /** * Given a resource, return the content of the resource as `ITextModel`. */ - provideTextContent(resource: URI): Promise | null; + provideTextContent(resource: URI): Promise | null | undefined; } export interface ITextEditorModel extends IEditorModel { @@ -44,7 +44,15 @@ export interface ITextEditorModel extends IEditorModel { /** * Provides access to the underlying `ITextModel`. */ - readonly textEditorModel: ITextModel; + readonly textEditorModel: ITextModel | null; isReadonly(): boolean; } + +export interface IResolvedTextEditorModel extends ITextEditorModel { + + /** + * Same as ITextEditorModel#textEditorModel, but never null. + */ + readonly textEditorModel: ITextModel; +} diff --git a/src/vs/editor/common/services/resourceConfiguration.ts b/src/vs/editor/common/services/resourceConfiguration.ts index b363b22904..a46e84a761 100644 --- a/src/vs/editor/common/services/resourceConfiguration.ts +++ b/src/vs/editor/common/services/resourceConfiguration.ts @@ -24,13 +24,13 @@ export interface ITextResourceConfigurationService { * Fetches the value of the section for the given resource by applying language overrides. * Value can be of native type or an object keyed off the section name. * - * @param resource - Resource for which the configuration has to be fetched. Can be `null` or `undefined`. - * @param postion - Position in the resource for which configuration has to be fetched. Can be `null` or `undefined`. - * @param section - Section of the configuraion. Can be `null` or `undefined`. + * @param resource - Resource for which the configuration has to be fetched. + * @param postion - Position in the resource for which configuration has to be fetched. + * @param section - Section of the configuraion. * */ - getValue(resource: URI, section?: string): T; - getValue(resource: URI, position?: IPosition, section?: string): T; + getValue(resource: URI | undefined, section?: string): T; + getValue(resource: URI | undefined, position?: IPosition, section?: string): T; } diff --git a/src/vs/editor/common/services/webWorker.ts b/src/vs/editor/common/services/webWorker.ts index 7de762e683..cd274432cf 100644 --- a/src/vs/editor/common/services/webWorker.ts +++ b/src/vs/editor/common/services/webWorker.ts @@ -52,7 +52,7 @@ export interface IWebWorkerOptions { class MonacoWebWorkerImpl extends EditorWorkerClient implements MonacoWebWorker { - private _foreignModuleId: string; + private readonly _foreignModuleId: string; private _foreignModuleCreateData: any | null; private _foreignProxy: Promise | null; diff --git a/src/vs/editor/common/view/overviewZoneManager.ts b/src/vs/editor/common/view/overviewZoneManager.ts index b61e8a4b7b..bf956bc11a 100644 --- a/src/vs/editor/common/view/overviewZoneManager.ts +++ b/src/vs/editor/common/view/overviewZoneManager.ts @@ -75,7 +75,7 @@ export class OverviewRulerZone { export class OverviewZoneManager { - private _getVerticalOffsetForLine: (lineNumber: number) => number; + private readonly _getVerticalOffsetForLine: (lineNumber: number) => number; private _zones: OverviewRulerZone[]; private _colorZonesInvalid: boolean; private _lineHeight: number; @@ -85,8 +85,8 @@ export class OverviewZoneManager { private _pixelRatio: number; private _lastAssignedId: number; - private _color2Id: { [color: string]: number; }; - private _id2Color: string[]; + private readonly _color2Id: { [color: string]: number; }; + private readonly _id2Color: string[]; constructor(getVerticalOffsetForLine: (lineNumber: number) => number) { this._getVerticalOffsetForLine = getVerticalOffsetForLine; diff --git a/src/vs/editor/common/view/viewEventDispatcher.ts b/src/vs/editor/common/view/viewEventDispatcher.ts index 536420634b..a77287e675 100644 --- a/src/vs/editor/common/view/viewEventDispatcher.ts +++ b/src/vs/editor/common/view/viewEventDispatcher.ts @@ -8,8 +8,8 @@ import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; export class ViewEventDispatcher { - private _eventHandlerGateKeeper: (callback: () => void) => void; - private _eventHandlers: ViewEventHandler[]; + private readonly _eventHandlerGateKeeper: (callback: () => void) => void; + private readonly _eventHandlers: ViewEventHandler[]; private _eventQueue: ViewEvent[] | null; private _isConsumingQueue: boolean; diff --git a/src/vs/editor/common/viewLayout/lineDecorations.ts b/src/vs/editor/common/viewLayout/lineDecorations.ts index c0f2cb4faf..3dfa13a841 100644 --- a/src/vs/editor/common/viewLayout/lineDecorations.ts +++ b/src/vs/editor/common/viewLayout/lineDecorations.ts @@ -102,8 +102,8 @@ export class DecorationSegment { class Stack { public count: number; - private stopOffsets: number[]; - private classNames: string[]; + private readonly stopOffsets: number[]; + private readonly classNames: string[]; constructor() { this.stopOffsets = []; diff --git a/src/vs/editor/common/viewLayout/linesLayout.ts b/src/vs/editor/common/viewLayout/linesLayout.ts index 4261dfee3e..822be751ea 100644 --- a/src/vs/editor/common/viewLayout/linesLayout.ts +++ b/src/vs/editor/common/viewLayout/linesLayout.ts @@ -30,7 +30,7 @@ export class LinesLayout { /** * Contains whitespace information in pixels */ - private _whitespaces: WhitespaceComputer; + private readonly _whitespaces: WhitespaceComputer; constructor(lineCount: number, lineHeight: number) { this._lineCount = lineCount; diff --git a/src/vs/editor/common/viewLayout/whitespaceComputer.ts b/src/vs/editor/common/viewLayout/whitespaceComputer.ts index 47d1ed4912..c920ea5a7e 100644 --- a/src/vs/editor/common/viewLayout/whitespaceComputer.ts +++ b/src/vs/editor/common/viewLayout/whitespaceComputer.ts @@ -18,27 +18,27 @@ export class WhitespaceComputer { /** * heights[i] is the height in pixels for whitespace at index i */ - private _heights: number[]; + private readonly _heights: number[]; /** * minWidths[i] is the min width in pixels for whitespace at index i */ - private _minWidths: number[]; + private readonly _minWidths: number[]; /** * afterLineNumbers[i] is the line number whitespace at index i is after */ - private _afterLineNumbers: number[]; + private readonly _afterLineNumbers: number[]; /** * ordinals[i] is the orinal of the whitespace at index i */ - private _ordinals: number[]; + private readonly _ordinals: number[]; /** * prefixSum[i] = SUM(heights[j]), 1 <= j <= i */ - private _prefixSum: number[]; + private readonly _prefixSum: number[]; /** * prefixSum[i], 1 <= i <= prefixSumValidIndex can be trusted @@ -48,12 +48,12 @@ export class WhitespaceComputer { /** * ids[i] is the whitespace id of whitespace at index i */ - private _ids: number[]; + private readonly _ids: number[]; /** * index at which a whitespace is positioned (inside heights, afterLineNumbers, prefixSum members) */ - private _whitespaceId2Index: { + private readonly _whitespaceId2Index: { [id: string]: number; }; diff --git a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts index 0a0510c946..ae09f749cb 100644 --- a/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts +++ b/src/vs/editor/common/viewModel/characterHardWrappingLineMapper.ts @@ -56,7 +56,7 @@ class WrappingCharacterClassifier extends CharacterClassifier { export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactory { - private classifier: WrappingCharacterClassifier; + private readonly classifier: WrappingCharacterClassifier; constructor(breakBeforeChars: string, breakAfterChars: string, breakObtrusiveChars: string) { this.classifier = new WrappingCharacterClassifier(breakBeforeChars, breakAfterChars, breakObtrusiveChars); @@ -255,8 +255,8 @@ export class CharacterHardWrappingLineMapperFactory implements ILineMapperFactor export class CharacterHardWrappingLineMapping implements ILineMapping { - private _prefixSums: PrefixSumComputer; - private _wrappedLinesIndent: string; + private readonly _prefixSums: PrefixSumComputer; + private readonly _wrappedLinesIndent: string; constructor(prefixSums: PrefixSumComputer, wrappedLinesIndent: string) { this._prefixSums = prefixSums; diff --git a/src/vs/editor/common/viewModel/prefixSumComputer.ts b/src/vs/editor/common/viewModel/prefixSumComputer.ts index c46f0beb46..2f06c6fccf 100644 --- a/src/vs/editor/common/viewModel/prefixSumComputer.ts +++ b/src/vs/editor/common/viewModel/prefixSumComputer.ts @@ -32,7 +32,7 @@ export class PrefixSumComputer { /** * prefixSum[i], 0 <= i <= prefixSumValidIndex can be trusted */ - private prefixSumValidIndex: Int32Array; + private readonly prefixSumValidIndex: Int32Array; constructor(values: Uint32Array) { this.values = values; diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 79ea808767..faf1bd239b 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -149,7 +149,7 @@ const enum IndentGuideRepeatOption { export class SplitLinesCollection implements IViewModelLinesCollection { - private model: ITextModel; + private readonly model: ITextModel; private _validModelVersionId: number; private wrappingColumn: number; @@ -160,7 +160,7 @@ export class SplitLinesCollection implements IViewModelLinesCollection { private prefixSumComputer: PrefixSumComputerWithCache; - private linePositionMapperFactory: ILineMapperFactory; + private readonly linePositionMapperFactory: ILineMapperFactory; private hiddenAreasIds: string[]; @@ -1001,11 +1001,11 @@ class InvisibleIdentitySplitLine implements ISplitLine { export class SplitLine implements ISplitLine { - private positionMapper: ILineMapping; - private outputLineCount: number; + private readonly positionMapper: ILineMapping; + private readonly outputLineCount: number; - private wrappedIndent: string; - private wrappedIndentLength: number; + private readonly wrappedIndent: string; + private readonly wrappedIndentLength: number; private _isVisible: boolean; constructor(positionMapper: ILineMapping, isVisible: boolean) { diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 4ee4c44d77..2435a223a7 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -10,7 +10,7 @@ import { IViewLineTokens } from 'vs/editor/common/core/lineTokens'; import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { INewScrollPosition } from 'vs/editor/common/editorCommon'; -import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecorationOptions } from 'vs/editor/common/model'; +import { EndOfLinePreference, IActiveIndentGuideInfo, IModelDecorationOptions, TextModelResolvedOptions } from 'vs/editor/common/model'; import { IViewEventListener } from 'vs/editor/common/view/viewEvents'; import { IPartialViewLinesViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; @@ -129,7 +129,7 @@ export interface IViewModel { getCompletelyVisibleViewRange(): Range; getCompletelyVisibleViewRangeAtScrollTop(scrollTop: number): Range; - getTabSize(): number; + getOptions(): TextModelResolvedOptions; getLineCount(): number; getLineContent(lineNumber: number): string; getLineLength(lineNumber: number): number; diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index a609aae9a9..74ed9c3a84 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -10,7 +10,7 @@ import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOption import { IPosition, Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { EndOfLinePreference, IActiveIndentGuideInfo, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { EndOfLinePreference, IActiveIndentGuideInfo, ITextModel, TrackedRangeStickiness, TextModelResolvedOptions } from 'vs/editor/common/model'; import { ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModel'; import * as textModelEvents from 'vs/editor/common/model/textModelEvents'; import { ColorId, LanguageId, TokenizationRegistry } from 'vs/editor/common/modes'; @@ -448,10 +448,14 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel }; } - public getTabSize(): number { + private getTabSize(): number { return this.model.getOptions().tabSize; } + public getOptions(): TextModelResolvedOptions { + return this.model.getOptions(); + } + public getLineCount(): number { return this.lines.getViewLineCount(); } diff --git a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts index db0eaec6c1..bd719e9729 100644 --- a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts @@ -92,7 +92,7 @@ export class BracketMatchingController extends Disposable implements editorCommo private _lastBracketsData: BracketsData[]; private _lastVersionId: number; private _decorations: string[]; - private _updateBracketsSoon: RunOnceScheduler; + private readonly _updateBracketsSoon: RunOnceScheduler; private _matchBrackets: boolean; constructor( diff --git a/src/vs/editor/contrib/caretOperations/caretOperations.ts b/src/vs/editor/contrib/caretOperations/caretOperations.ts index 25054f90b9..ac2261f3ce 100644 --- a/src/vs/editor/contrib/caretOperations/caretOperations.ts +++ b/src/vs/editor/contrib/caretOperations/caretOperations.ts @@ -12,7 +12,7 @@ import { MoveCaretCommand } from 'vs/editor/contrib/caretOperations/moveCaretCom class MoveCaretAction extends EditorAction { - private left: boolean; + private readonly left: boolean; constructor(left: boolean, opts: IActionOptions) { super(opts); diff --git a/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts b/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts index 83f394554e..8bf28d190b 100644 --- a/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts +++ b/src/vs/editor/contrib/caretOperations/moveCaretCommand.ts @@ -10,8 +10,8 @@ import { ITextModel } from 'vs/editor/common/model'; export class MoveCaretCommand implements ICommand { - private _selection: Selection; - private _isMovingLeft: boolean; + private readonly _selection: Selection; + private readonly _isMovingLeft: boolean; private _cutStartIndex: number; private _cutEndIndex: number; diff --git a/src/vs/editor/contrib/clipboard/clipboard.ts b/src/vs/editor/contrib/clipboard/clipboard.ts index 3baa743c43..f8264c769f 100644 --- a/src/vs/editor/contrib/clipboard/clipboard.ts +++ b/src/vs/editor/contrib/clipboard/clipboard.ts @@ -32,7 +32,7 @@ type ExecCommand = 'cut' | 'copy' | 'paste'; abstract class ExecCommandAction extends EditorAction { - private browserCommand: ExecCommand; + private readonly browserCommand: ExecCommand; constructor(browserCommand: ExecCommand, opts: IActionOptions) { super(opts); diff --git a/src/vs/editor/contrib/codeAction/codeAction.ts b/src/vs/editor/contrib/codeAction/codeAction.ts index 16b9c48059..be7a590b7f 100644 --- a/src/vs/editor/contrib/codeAction/codeAction.ts +++ b/src/vs/editor/contrib/codeAction/codeAction.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { flatten, isNonEmptyArray, mergeSort } from 'vs/base/common/arrays'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { equals, flatten, isNonEmptyArray, mergeSort } from 'vs/base/common/arrays'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { illegalArgument, isPromiseCanceledError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; @@ -13,14 +13,41 @@ import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; import { CodeAction, CodeActionContext, CodeActionProviderRegistry, CodeActionTrigger as CodeActionTriggerKind } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind, CodeActionFilter } from './codeActionTrigger'; +import { CodeActionFilter, CodeActionKind, CodeActionTrigger, filtersAction, mayIncludeActionsOfKind } from './codeActionTrigger'; + +export class CodeActionSet { + + private static codeActionsComparator(a: CodeAction, b: CodeAction): number { + if (isNonEmptyArray(a.diagnostics)) { + if (isNonEmptyArray(b.diagnostics)) { + return a.diagnostics[0].message.localeCompare(b.diagnostics[0].message); + } else { + return -1; + } + } else if (isNonEmptyArray(b.diagnostics)) { + return 1; + } else { + return 0; // both have no diagnostics + } + } + + public readonly actions: ReadonlyArray; + + public constructor(actions: CodeAction[]) { + this.actions = mergeSort(actions, CodeActionSet.codeActionsComparator); + } + + public get hasAutoFix() { + return this.actions.some(fix => !!fix.kind && CodeActionKind.QuickFix.contains(new CodeActionKind(fix.kind)) && !!fix.isPreferred); + } +} export function getCodeActions( model: ITextModel, rangeOrSelection: Range | Selection, trigger: CodeActionTrigger, token: CancellationToken -): Promise { +): Promise { const filter = trigger.filter || {}; const codeActionContext: CodeActionContext = { @@ -28,12 +55,13 @@ export function getCodeActions( trigger: trigger.type === 'manual' ? CodeActionTriggerKind.Manual : CodeActionTriggerKind.Automatic }; - if (filter.kind && CodeActionKind.Source.contains(filter.kind) && rangeOrSelection.isEmpty()) { - rangeOrSelection = model.getFullModelRange(); - } + const chainedCancellation = new CancellationTokenSource(); + token.onCancellationRequested(() => chainedCancellation.cancel()); - const promises = getCodeActionProviders(model, filter).map(provider => { - return Promise.resolve(provider.provideCodeActions(model, rangeOrSelection, codeActionContext, token)).then(providedCodeActions => { + const providers = getCodeActionProviders(model, filter); + + const promises = providers.map(provider => { + return Promise.resolve(provider.provideCodeActions(model, rangeOrSelection, codeActionContext, chainedCancellation.token)).then(providedCodeActions => { if (!Array.isArray(providedCodeActions)) { return []; } @@ -48,9 +76,19 @@ export function getCodeActions( }); }); + const listener = CodeActionProviderRegistry.onDidChange(() => { + const newProviders = CodeActionProviderRegistry.all(model); + if (!equals(newProviders, providers)) { + chainedCancellation.cancel(); + } + }); + return Promise.all(promises) .then(flatten) - .then(allCodeActions => mergeSort(allCodeActions, codeActionsComparator)); + .then(actions => new CodeActionSet(actions)) + .finally(() => { + listener.dispose(); + }); } function getCodeActionProviders( @@ -68,22 +106,8 @@ function getCodeActionProviders( }); } -function codeActionsComparator(a: CodeAction, b: CodeAction): number { - if (isNonEmptyArray(a.diagnostics)) { - if (isNonEmptyArray(b.diagnostics)) { - return a.diagnostics[0].message.localeCompare(b.diagnostics[0].message); - } else { - return -1; - } - } else if (isNonEmptyArray(b.diagnostics)) { - return 1; - } else { - return 0; // both have no diagnostics - } -} - -registerLanguageCommand('_executeCodeActionProvider', function (accessor, args) { - const { resource, range } = args; +registerLanguageCommand('_executeCodeActionProvider', function (accessor, args): Promise> { + const { resource, range, kind } = args; if (!(resource instanceof URI) || !Range.isIRange(range)) { throw illegalArgument(); } @@ -96,6 +120,6 @@ registerLanguageCommand('_executeCodeActionProvider', function (accessor, args) return getCodeActions( model, model.validateRange(range), - { type: 'manual', filter: { includeSourceActions: true } }, - CancellationToken.None); + { type: 'manual', filter: { includeSourceActions: true, kind: kind && kind.value ? new CodeActionKind(kind.value) : undefined } }, + CancellationToken.None).then(actions => actions.actions); }); diff --git a/src/vs/editor/contrib/codeAction/codeActionCommands.ts b/src/vs/editor/contrib/codeAction/codeActionCommands.ts index e38fbda576..2c19e2d7e4 100644 --- a/src/vs/editor/contrib/codeAction/codeActionCommands.ts +++ b/src/vs/editor/contrib/codeAction/codeActionCommands.ts @@ -5,7 +5,7 @@ import { CancelablePromise } from 'vs/base/common/async'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable } from 'vs/base/common/lifecycle'; import { escapeRegExpCharacters } from 'vs/base/common/strings'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, EditorCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions'; @@ -27,6 +27,7 @@ import { CodeActionContextMenu } from './codeActionWidget'; import { LightBulbWidget } from './lightBulbWidget'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; function contextKeyForSupportedActions(kind: CodeActionKind) { return ContextKeyExpr.regex( @@ -34,7 +35,7 @@ function contextKeyForSupportedActions(kind: CodeActionKind) { new RegExp('(\\s|^)' + escapeRegExpCharacters(kind.value) + '\\b')); } -export class QuickFixController implements IEditorContribution { +export class QuickFixController extends Disposable implements IEditorContribution { private static readonly ID = 'editor.contrib.quickFixController'; @@ -42,15 +43,15 @@ export class QuickFixController implements IEditorContribution { return editor.getContribution(QuickFixController.ID); } - private _editor: ICodeEditor; - private _model: CodeActionModel; - private _codeActionContextMenu: CodeActionContextMenu; - private _lightBulbWidget: LightBulbWidget; - private _disposables: IDisposable[] = []; + private readonly _editor: ICodeEditor; + private readonly _model: CodeActionModel; + private readonly _codeActionContextMenu: CodeActionContextMenu; + private readonly _lightBulbWidget: LightBulbWidget; - private _activeRequest: CancelablePromise | undefined; + private _activeRequest: CancelablePromise | undefined; - constructor(editor: ICodeEditor, + constructor( + editor: ICodeEditor, @IMarkerService markerService: IMarkerService, @IContextKeyService contextKeyService: IContextKeyService, @IProgressService progressService: IProgressService, @@ -59,24 +60,24 @@ export class QuickFixController implements IEditorContribution { @IKeybindingService private readonly _keybindingService: IKeybindingService, @IBulkEditService private readonly _bulkEditService: IBulkEditService, ) { + super(); + this._editor = editor; this._model = new CodeActionModel(this._editor, markerService, contextKeyService, progressService); this._codeActionContextMenu = new CodeActionContextMenu(editor, contextMenuService, action => this._onApplyCodeAction(action)); - this._lightBulbWidget = new LightBulbWidget(editor); + this._lightBulbWidget = this._register(new LightBulbWidget(editor)); this._updateLightBulbTitle(); - this._disposables.push( - this._codeActionContextMenu.onDidExecuteCodeAction(_ => this._model.trigger({ type: 'auto', filter: {} })), - this._lightBulbWidget.onClick(this._handleLightBulbSelect, this), - this._model.onDidChangeState(e => this._onDidChangeCodeActionsState(e)), - this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitle, this) - ); + this._register(this._codeActionContextMenu.onDidExecuteCodeAction(_ => this._model.trigger({ type: 'auto', filter: {} }))); + this._register(this._lightBulbWidget.onClick(this._handleLightBulbSelect, this)); + this._register(this._model.onDidChangeState(e => this._onDidChangeCodeActionsState(e))); + this._register(this._keybindingService.onDidUpdateKeybindings(this._updateLightBulbTitle, this)); } public dispose(): void { + super.dispose(); this._model.dispose(); - dispose(this._disposables); } private _onDidChangeCodeActionsState(newState: CodeActionsState.State): void { @@ -91,10 +92,10 @@ export class QuickFixController implements IEditorContribution { if (newState.trigger.filter && newState.trigger.filter.kind) { // Triggered for specific scope newState.actions.then(fixes => { - if (fixes.length > 0) { + if (fixes.actions.length > 0) { // Apply if we only have one action or requested autoApply - if (newState.trigger.autoApply === CodeActionAutoApply.First || (newState.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.length === 1)) { - this._onApplyCodeAction(fixes[0]); + if (newState.trigger.autoApply === CodeActionAutoApply.First || (newState.trigger.autoApply === CodeActionAutoApply.IfSingle && fixes.actions.length === 1)) { + this._onApplyCodeAction(fixes.actions[0]); return; } } @@ -126,7 +127,7 @@ export class QuickFixController implements IEditorContribution { this._codeActionContextMenu.show(e.state.actions, e); } - public triggerFromEditorSelection(filter?: CodeActionFilter, autoApply?: CodeActionAutoApply): Promise { + public triggerFromEditorSelection(filter?: CodeActionFilter, autoApply?: CodeActionAutoApply): Promise { return this._model.trigger({ type: 'manual', filter, autoApply }); } @@ -177,7 +178,7 @@ function showCodeActionsForEditorSelection( const pos = editor.getPosition(); controller.triggerFromEditorSelection(filter, autoApply).then(codeActions => { - if (!codeActions || !codeActions.length) { + if (!codeActions || !codeActions.actions.length) { MessageController.get(editor).showMessage(notAvailableMessage, pos); } }); @@ -253,7 +254,27 @@ export class CodeActionCommand extends EditorCommand { constructor() { super({ id: CodeActionCommand.Id, - precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider) + precondition: ContextKeyExpr.and(EditorContextKeys.writable, EditorContextKeys.hasCodeActionsProvider), + description: { + description: `Trigger a code action`, + args: [{ + name: 'args', + schema: { + 'type': 'object', + 'required': ['kind'], + 'properties': { + 'kind': { + 'type': 'string' + }, + 'apply': { + 'type': 'string', + 'default': 'ifSingle', + 'enum': ['first', 'ifSingle', 'never'] + } + } + } + }] + } }); } @@ -297,6 +318,25 @@ export class RefactorAction extends EditorAction { when: ContextKeyExpr.and( EditorContextKeys.writable, contextKeyForSupportedActions(CodeActionKind.Refactor)), + }, + description: { + description: 'Refactor...', + args: [{ + name: 'args', + schema: { + 'type': 'object', + 'properties': { + 'kind': { + 'type': 'string' + }, + 'apply': { + 'type': 'string', + 'default': 'never', + 'enum': ['first', 'ifSingle', 'never'] + } + } + } + }] } }); } @@ -333,6 +373,25 @@ export class SourceAction extends EditorAction { when: ContextKeyExpr.and( EditorContextKeys.writable, contextKeyForSupportedActions(CodeActionKind.Source)), + }, + description: { + description: 'Source Action...', + args: [{ + name: 'args', + schema: { + 'type': 'object', + 'properties': { + 'kind': { + 'type': 'string' + }, + 'apply': { + 'type': 'string', + 'default': 'never', + 'enum': ['first', 'ifSingle', 'never'] + } + } + } + }] } }); } @@ -381,6 +440,29 @@ export class OrganizeImportsAction extends EditorAction { } } +export class FixAllAction extends EditorAction { + + static readonly Id = 'editor.action.fixAll'; + + constructor() { + super({ + id: FixAllAction.Id, + label: nls.localize('fixAll.label', "Fix All"), + alias: 'Fix All', + precondition: ContextKeyExpr.and( + EditorContextKeys.writable, + contextKeyForSupportedActions(CodeActionKind.SourceFixAll)) + }); + } + + public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { + return showCodeActionsForEditorSelection(editor, + nls.localize('fixAll.noneMessage', "No fix all action available"), + { kind: CodeActionKind.SourceFixAll, includeSourceActions: true }, + CodeActionAutoApply.IfSingle); + } +} + export class AutoFixAction extends EditorAction { static readonly Id = 'editor.action.autoFix'; diff --git a/src/vs/editor/contrib/codeAction/codeActionModel.ts b/src/vs/editor/contrib/codeAction/codeActionModel.ts index 062a789756..fff21ce6b8 100644 --- a/src/vs/editor/contrib/codeAction/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/codeActionModel.ts @@ -11,11 +11,11 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import { CodeAction, CodeActionProviderRegistry } from 'vs/editor/common/modes'; +import { CodeActionProviderRegistry } from 'vs/editor/common/modes'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IProgressService } from 'vs/platform/progress/common/progress'; -import { getCodeActions } from './codeAction'; +import { getCodeActions, CodeActionSet } from './codeAction'; import { CodeActionTrigger } from './codeActionTrigger'; export const SUPPORTED_CODE_ACTIONS = new RawContextKey('supportedCodeAction', ''); @@ -26,9 +26,9 @@ export class CodeActionOracle { private readonly _autoTriggerTimer = new TimeoutTimer(); constructor( - private _editor: ICodeEditor, + private readonly _editor: ICodeEditor, private readonly _markerService: IMarkerService, - private _signalChange: (newState: CodeActionsState.State) => void, + private readonly _signalChange: (newState: CodeActionsState.State) => void, private readonly _delay: number = 250, private readonly _progressService?: IProgressService, ) { @@ -112,7 +112,7 @@ export class CodeActionOracle { return selection ? selection : undefined; } - private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): Promise { + private _createEventAndSignalChange(trigger: CodeActionTrigger, selection: Selection | undefined): Promise { if (!selection) { // cancel this._signalChange(CodeActionsState.Empty); @@ -160,7 +160,7 @@ export namespace CodeActionsState { public readonly trigger: CodeActionTrigger, public readonly rangeOrSelection: Range | Selection, public readonly position: Position, - public readonly actions: CancelablePromise, + public readonly actions: CancelablePromise, ) { } } @@ -169,18 +169,18 @@ export namespace CodeActionsState { export class CodeActionModel { - private _editor: ICodeEditor; - private _markerService: IMarkerService; private _codeActionOracle?: CodeActionOracle; private _state: CodeActionsState.State = CodeActionsState.Empty; private _onDidChangeState = new Emitter(); private _disposables: IDisposable[] = []; private readonly _supportedCodeActions: IContextKey; - constructor(editor: ICodeEditor, markerService: IMarkerService, contextKeyService: IContextKeyService, private readonly _progressService: IProgressService) { - this._editor = editor; - this._markerService = markerService; - + constructor( + private readonly _editor: ICodeEditor, + private readonly _markerService: IMarkerService, + contextKeyService: IContextKeyService, + private readonly _progressService: IProgressService + ) { this._supportedCodeActions = SUPPORTED_CODE_ACTIONS.bindTo(contextKeyService); this._disposables.push(this._editor.onDidChangeModel(() => this._update())); @@ -206,7 +206,7 @@ export class CodeActionModel { } if (this._state.type === CodeActionsState.Type.Triggered) { - // this._state.actions.cancel(); + this._state.actions.cancel(); } this.setState(CodeActionsState.Empty); @@ -231,7 +231,7 @@ export class CodeActionModel { } } - public trigger(trigger: CodeActionTrigger): Promise { + public trigger(trigger: CodeActionTrigger): Promise { if (this._codeActionOracle) { return this._codeActionOracle.trigger(trigger); } diff --git a/src/vs/editor/contrib/codeAction/codeActionWidget.ts b/src/vs/editor/contrib/codeAction/codeActionWidget.ts index ceebd76fca..8a7c0ea9eb 100644 --- a/src/vs/editor/contrib/codeAction/codeActionWidget.ts +++ b/src/vs/editor/contrib/codeAction/codeActionWidget.ts @@ -12,13 +12,14 @@ import { Position } from 'vs/editor/common/core/position'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { CodeAction } from 'vs/editor/common/modes'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; export class CodeActionContextMenu { private _visible: boolean; - private _onDidExecuteCodeAction = new Emitter(); - readonly onDidExecuteCodeAction: Event = this._onDidExecuteCodeAction.event; + private readonly _onDidExecuteCodeAction = new Emitter(); + public readonly onDidExecuteCodeAction: Event = this._onDidExecuteCodeAction.event; constructor( private readonly _editor: ICodeEditor, @@ -26,13 +27,14 @@ export class CodeActionContextMenu { private readonly _onApplyCodeAction: (action: CodeAction) => Promise ) { } - async show(actionsToShow: Promise, at?: { x: number; y: number } | Position): Promise { + async show(actionsToShow: Promise, at?: { x: number; y: number } | Position): Promise { const codeActions = await actionsToShow; if (!this._editor.getDomNode()) { // cancel when editor went off-dom return Promise.reject(canceled()); } - const actions = codeActions.map(action => this.codeActionToAction(action)); + this._visible = true; + const actions = codeActions.actions.map(action => this.codeActionToAction(action)); this._contextMenuService.showContextMenu({ getAnchor: () => { if (Position.isIPosition(at)) { @@ -51,7 +53,7 @@ export class CodeActionContextMenu { private codeActionToAction(action: CodeAction): Action { const id = action.command ? action.command.id : action.title; - const title = action.isPreferred ? `${action.title} ★` : action.title; + const title = action.title; return new Action(id, title, undefined, true, () => this._onApplyCodeAction(action) .finally(() => this._onDidExecuteCodeAction.fire(undefined))); @@ -69,7 +71,7 @@ export class CodeActionContextMenu { this._editor.render(); // Translate to absolute editor position - const cursorCoords = this._editor.getScrolledVisiblePosition(this._editor.getPosition()); + const cursorCoords = this._editor.getScrolledVisiblePosition(position); const editorCoords = getDomNodePagePosition(this._editor.getDomNode()); const x = editorCoords.left + cursorCoords.left; const y = editorCoords.top + cursorCoords.top + cursorCoords.height; diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.css b/src/vs/editor/contrib/codeAction/lightBulbWidget.css index 3776a655e8..d5bde8dacc 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.css +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.css @@ -21,7 +21,16 @@ background: url('lightbulb.svg') center center no-repeat; } +.monaco-editor.vs .lightbulb-glyph.autofixable { + background: url('lightbulb-autofix.svg') center center no-repeat; +} + .monaco-editor.vs-dark .lightbulb-glyph, .monaco-editor.hc-black .lightbulb-glyph { background: url('lightbulb-dark.svg') center center no-repeat; } + +.monaco-editor.vs-dark .lightbulb-glyph.autofixable, +.monaco-editor.hc-black .lightbulb-glyph.autofixable { + background: url('lightbulb-autofix-dark.svg') center center no-repeat; +} diff --git a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts index e9ce7b27f4..2cbf5f9354 100644 --- a/src/vs/editor/contrib/codeAction/lightBulbWidget.ts +++ b/src/vs/editor/contrib/codeAction/lightBulbWidget.ts @@ -11,6 +11,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import 'vs/css!./lightBulbWidget'; import { ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser'; import { TextModel } from 'vs/editor/common/model/textModel'; +import { CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; import { CodeActionsState } from './codeActionModel'; export class LightBulbWidget extends Disposable implements IContentWidget { @@ -126,8 +127,8 @@ export class LightBulbWidget extends Disposable implements IContentWidget { const selection = this._state.rangeOrSelection; this._state.actions.then(fixes => { - if (!token.isCancellationRequested && fixes && fixes.length > 0 && selection) { - this._show(); + if (!token.isCancellationRequested && fixes.actions.length > 0 && selection) { + this._show(fixes); } else { this.hide(); } @@ -144,7 +145,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { return this._domNode.title; } - private _show(): void { + private _show(codeActions: CodeActionSet): void { const config = this._editor.getConfiguration(); if (!config.contribInfo.lightbulbEnabled) { return; @@ -184,6 +185,7 @@ export class LightBulbWidget extends Disposable implements IContentWidget { position: { lineNumber: effectiveLineNumber, column: 1 }, preference: LightBulbWidget._posPref }; + dom.toggleClass(this._domNode, 'autofixable', codeActions.hasAutoFix); this._editor.layoutContentWidget(this); } diff --git a/src/vs/editor/contrib/codeAction/lightbulb-autofix-dark.svg b/src/vs/editor/contrib/codeAction/lightbulb-autofix-dark.svg new file mode 100644 index 0000000000..40678e79d7 --- /dev/null +++ b/src/vs/editor/contrib/codeAction/lightbulb-autofix-dark.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/editor/contrib/codeAction/lightbulb-autofix.svg b/src/vs/editor/contrib/codeAction/lightbulb-autofix.svg new file mode 100644 index 0000000000..a4b4858e4d --- /dev/null +++ b/src/vs/editor/contrib/codeAction/lightbulb-autofix.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts index 535f6853a1..aaaca49740 100644 --- a/src/vs/editor/contrib/codeAction/test/codeAction.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeAction.test.ts @@ -12,7 +12,6 @@ import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ITextModel } from 'vs/editor/common/model'; suite('CodeAction', () => { @@ -117,7 +116,7 @@ suite('CodeAction', () => { testData.tsLint.abc ]; - const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'manual' }, CancellationToken.None); + const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'manual' }, CancellationToken.None); assert.equal(actions.length, 6); assert.deepEqual(actions, expected); }); @@ -136,20 +135,20 @@ suite('CodeAction', () => { disposables.push(CodeActionProviderRegistry.register('fooLang', provider)); { - const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None); + const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None); assert.equal(actions.length, 2); assert.strictEqual(actions[0].title, 'a'); assert.strictEqual(actions[1].title, 'a.b'); } { - const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b') } }, CancellationToken.None); + const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b') } }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a.b'); } { - const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b.c') } }, CancellationToken.None); + const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a.b.c') } }, CancellationToken.None); assert.equal(actions.length, 0); } }); @@ -165,7 +164,7 @@ suite('CodeAction', () => { disposables.push(CodeActionProviderRegistry.register('fooLang', provider)); - const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None); + const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: new CodeActionKind('a') } }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); }); @@ -183,13 +182,13 @@ suite('CodeAction', () => { disposables.push(CodeActionProviderRegistry.register('fooLang', provider)); { - const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' }, CancellationToken.None); + const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto' }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'b'); } { - const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None); + const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: CodeActionKind.Source, includeSourceActions: true } }, CancellationToken.None); assert.equal(actions.length, 1); assert.strictEqual(actions[0].title, 'a'); } @@ -208,7 +207,7 @@ suite('CodeAction', () => { disposables.push(CodeActionProviderRegistry.register('fooLang', provider)); - const actions = await getCodeActions(model, new Range(1, 1, 2, 1), { + const { actions } = await getCodeActions(model, new Range(1, 1, 2, 1), { type: 'auto', filter: { kind: CodeActionKind.QuickFix @@ -217,49 +216,5 @@ suite('CodeAction', () => { assert.strictEqual(actions.length, 0); assert.strictEqual(wasInvoked, false); }); - - test('getCodeActions requests for source actions should expand source actions range to entire document #53525', async function () { - const provider = new class implements CodeActionProvider { - provideCodeActions(model: ITextModel, range: Range): CodeAction[] { - return [{ - title: rangeToString(range), - kind: CodeActionKind.Source.value, - }]; - } - }; - - disposables.push(CodeActionProviderRegistry.register('fooLang', provider)); - - { - const actions = await getCodeActions(model, new Range(1, 1, 1, 1), { - type: 'manual', - filter: { - kind: CodeActionKind.Source, - includeSourceActions: true, - } - }, CancellationToken.None); - assert.strictEqual(actions.length, 1); - assert.strictEqual(actions[0].title, rangeToString(model.getFullModelRange())); - } - - { - const range = new Range(1, 1, 1, 2); - - // But we should not expand for non-empty selections - const actions = await getCodeActions(model, range, { - type: 'manual', - filter: { - kind: CodeActionKind.Source, - includeSourceActions: true, - } - }, CancellationToken.None); - assert.strictEqual(actions.length, 1); - assert.strictEqual(actions[0].title, rangeToString(range)); - } - }); }); -function rangeToString(range: Range): string { - return `${range.startLineNumber},${range.startColumn} ${range.endLineNumber},${range.endColumn} `; -} - diff --git a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts index 61c3ec5a1d..a5bef22904 100644 --- a/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts +++ b/src/vs/editor/contrib/codeAction/test/codeActionModel.test.ts @@ -51,9 +51,9 @@ suite('CodeAction', () => { assert.equal(e.trigger.type, 'auto'); assert.ok(e.actions); - e.actions!.then(fixes => { + e.actions.then(fixes => { oracle.dispose(); - assert.equal(fixes.length, 1); + assert.equal(fixes.actions.length, 1); done(); }, done); }); @@ -88,9 +88,9 @@ suite('CodeAction', () => { const oracle = new CodeActionOracle(editor, markerService, (e: CodeActionsState.Triggered) => { assert.equal(e.trigger.type, 'auto'); assert.ok(e.actions); - e.actions!.then(fixes => { + e.actions.then(fixes => { oracle.dispose(); - assert.equal(fixes.length, 1); + assert.equal(fixes.actions.length, 1); resolve(undefined); }, reject); }); diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index 30b669e92b..552c4e952b 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -33,7 +33,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { private _detectVisibleLenses: RunOnceScheduler; constructor( - private _editor: editorBrowser.ICodeEditor, + private readonly _editor: editorBrowser.ICodeEditor, @ICommandService private readonly _commandService: ICommandService, @INotificationService private readonly _notificationService: INotificationService ) { @@ -309,13 +309,14 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { const resolvedSymbols = new Array(request.length); const promises = request.map((request, i) => { - if (typeof request.provider.resolveCodeLens === 'function') { + if (!request.symbol.command && typeof request.provider.resolveCodeLens === 'function') { return Promise.resolve(request.provider.resolveCodeLens(model, request.symbol, token)).then(symbol => { resolvedSymbols[i] = symbol; }); + } else { + resolvedSymbols[i] = request.symbol; + return Promise.resolve(undefined); } - resolvedSymbols[i] = request.symbol; - return Promise.resolve(undefined); }); return Promise.all(promises).then(() => { diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index 5139bef8a9..584ae2c8e8 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -26,7 +26,7 @@ class CodeLensViewZone implements editorBrowser.IViewZone { afterLineNumber: number; private _lastHeight: number; - private _onHeight: Function; + private readonly _onHeight: Function; constructor(afterLineNumber: number, onHeight: Function) { this.afterLineNumber = afterLineNumber; @@ -99,7 +99,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget { this._commands = Object.create(null); const symbols = coalesce(inSymbols); if (isFalsyOrEmpty(symbols)) { - this._domNode.innerHTML = 'no commands'; + this._domNode.innerHTML = 'no commands'; return; } @@ -164,9 +164,9 @@ export interface IDecorationIdCallback { export class CodeLensHelper { - private _removeDecorations: string[]; - private _addDecorations: IModelDeltaDecoration[]; - private _addDecorationsCallbacks: IDecorationIdCallback[]; + private readonly _removeDecorations: string[]; + private readonly _addDecorations: IModelDeltaDecoration[]; + private readonly _addDecorationsCallbacks: IDecorationIdCallback[]; constructor() { this._removeDecorations = []; @@ -290,6 +290,13 @@ export class CodeLens { updateCommands(symbols: Array): void { this._contentWidget.withCommands(symbols); + for (let i = 0; i < this._data.length; i++) { + const resolved = symbols[i]; + if (resolved) { + const { symbol } = this._data[i]; + symbol.command = resolved.command || symbol.command; + } + } } updateHeight(): void { diff --git a/src/vs/editor/contrib/colorPicker/colorDetector.ts b/src/vs/editor/contrib/colorPicker/colorDetector.ts index dfffbf2610..b1ebfaa12f 100644 --- a/src/vs/editor/contrib/colorPicker/colorDetector.ts +++ b/src/vs/editor/contrib/colorPicker/colorDetector.ts @@ -37,11 +37,11 @@ export class ColorDetector implements IEditorContribution { private _colorDatas = new Map(); private _colorDecoratorIds: string[] = []; - private _decorationsTypes: { [key: string]: boolean } = {}; + private readonly _decorationsTypes: { [key: string]: boolean } = {}; private _isEnabled: boolean; - constructor(private _editor: ICodeEditor, + constructor(private readonly _editor: ICodeEditor, @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, @IConfigurationService private readonly _configurationService: IConfigurationService ) { diff --git a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts index bea27c1fe4..63a180e483 100644 --- a/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts +++ b/src/vs/editor/contrib/colorPicker/colorPickerWidget.ts @@ -19,11 +19,11 @@ const $ = dom.$; export class ColorPickerHeader extends Disposable { - private domNode: HTMLElement; - private pickedColorNode: HTMLElement; + private readonly domNode: HTMLElement; + private readonly pickedColorNode: HTMLElement; private backgroundColor: Color; - constructor(container: HTMLElement, private model: ColorPickerModel, themeService: IThemeService) { + constructor(container: HTMLElement, private readonly model: ColorPickerModel, themeService: IThemeService) { super(); this.domNode = $('.colorpicker-header'); @@ -63,12 +63,12 @@ export class ColorPickerHeader extends Disposable { export class ColorPickerBody extends Disposable { - private domNode: HTMLElement; - private saturationBox: SaturationBox; - private hueStrip: Strip; - private opacityStrip: Strip; + private readonly domNode: HTMLElement; + private readonly saturationBox: SaturationBox; + private readonly hueStrip: Strip; + private readonly opacityStrip: Strip; - constructor(container: HTMLElement, private model: ColorPickerModel, private pixelRatio: number) { + constructor(container: HTMLElement, private readonly model: ColorPickerModel, private pixelRatio: number) { super(); this.domNode = $('.colorpicker-body'); @@ -120,9 +120,9 @@ export class ColorPickerBody extends Disposable { class SaturationBox extends Disposable { - private domNode: HTMLElement; - private selection: HTMLElement; - private canvas: HTMLCanvasElement; + private readonly domNode: HTMLElement; + private readonly selection: HTMLElement; + private readonly canvas: HTMLCanvasElement; private width: number; private height: number; @@ -133,7 +133,7 @@ class SaturationBox extends Disposable { private _onColorFlushed = new Emitter(); readonly onColorFlushed: Event = this._onColorFlushed.event; - constructor(container: HTMLElement, private model: ColorPickerModel, private pixelRatio: number) { + constructor(container: HTMLElement, private readonly model: ColorPickerModel, private pixelRatio: number) { super(); this.domNode = $('.saturation-wrap'); @@ -335,7 +335,7 @@ export class ColorPickerWidget extends Widget { body: ColorPickerBody; - constructor(container: Node, private model: ColorPickerModel, private pixelRatio: number, themeService: IThemeService) { + constructor(container: Node, private readonly model: ColorPickerModel, private pixelRatio: number, themeService: IThemeService) { super(); this._register(onDidChangeZoomLevel(() => this.layout())); diff --git a/src/vs/editor/contrib/comment/blockCommentCommand.ts b/src/vs/editor/contrib/comment/blockCommentCommand.ts index 7fdead0cad..057d45be6d 100644 --- a/src/vs/editor/contrib/comment/blockCommentCommand.ts +++ b/src/vs/editor/contrib/comment/blockCommentCommand.ts @@ -14,7 +14,7 @@ import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageCo export class BlockCommentCommand implements editorCommon.ICommand { - private _selection: Selection; + private readonly _selection: Selection; private _usedEndToken: string | null; constructor(selection: Selection) { diff --git a/src/vs/editor/contrib/comment/comment.ts b/src/vs/editor/contrib/comment/comment.ts index 7bdda1374b..56eda9fe34 100644 --- a/src/vs/editor/contrib/comment/comment.ts +++ b/src/vs/editor/contrib/comment/comment.ts @@ -16,7 +16,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis abstract class CommentLineAction extends EditorAction { - private _type: Type; + private readonly _type: Type; constructor(type: Type, opts: IActionOptions) { super(opts); diff --git a/src/vs/editor/contrib/comment/lineCommentCommand.ts b/src/vs/editor/contrib/comment/lineCommentCommand.ts index d9c2aca1fa..4f744095f6 100644 --- a/src/vs/editor/contrib/comment/lineCommentCommand.ts +++ b/src/vs/editor/contrib/comment/lineCommentCommand.ts @@ -48,12 +48,12 @@ export const enum Type { export class LineCommentCommand implements editorCommon.ICommand { - private _selection: Selection; + private readonly _selection: Selection; private _selectionId: string; private _deltaColumn: number; private _moveEndPositionDown: boolean; - private _tabSize: number; - private _type: Type; + private readonly _tabSize: number; + private readonly _type: Type; constructor(selection: Selection, tabSize: number, type: Type) { this._selection = selection; diff --git a/src/vs/editor/contrib/contextmenu/contextmenu.ts b/src/vs/editor/contrib/contextmenu/contextmenu.ts index 3ff0392495..9ce70f55fa 100644 --- a/src/vs/editor/contrib/contextmenu/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/contextmenu.ts @@ -32,7 +32,7 @@ export class ContextMenuController implements IEditorContribution { private _toDispose: IDisposable[] = []; private _contextMenuIsBeingShownCount: number = 0; - private _editor: ICodeEditor; + private readonly _editor: ICodeEditor; constructor( editor: ICodeEditor, @@ -190,7 +190,7 @@ export class ContextMenuController implements IEditorContribution { }, getKeyBinding: (action): ResolvedKeybinding | undefined => { - return this._keybindingFor(action) || undefined; + return this._keybindingFor(action); }, onHide: (wasCancelled: boolean) => { diff --git a/src/vs/editor/contrib/dnd/dnd.ts b/src/vs/editor/contrib/dnd/dnd.ts index 5a3a0a4c6c..e2957bab58 100644 --- a/src/vs/editor/contrib/dnd/dnd.ts +++ b/src/vs/editor/contrib/dnd/dnd.ts @@ -32,12 +32,12 @@ export class DragAndDropController implements editorCommon.IEditorContribution { private static readonly ID = 'editor.contrib.dragAndDrop'; - private _editor: ICodeEditor; + private readonly _editor: ICodeEditor; private _toUnhook: IDisposable[]; private _dragSelection: Selection | null; private _dndDecorationIds: string[]; private _mouseDown: boolean; - private _modiferPressed: boolean; + private _modifierPressed: boolean; static TRIGGER_KEY_VALUE = isMacintosh ? KeyCode.Alt : KeyCode.Ctrl; static get(editor: ICodeEditor): DragAndDropController { @@ -56,7 +56,7 @@ export class DragAndDropController implements editorCommon.IEditorContribution { this._toUnhook.push(this._editor.onDidBlurEditorWidget(() => this.onEditorBlur())); this._dndDecorationIds = []; this._mouseDown = false; - this._modiferPressed = false; + this._modifierPressed = false; this._dragSelection = null; } @@ -64,7 +64,7 @@ export class DragAndDropController implements editorCommon.IEditorContribution { this._removeDecoration(); this._dragSelection = null; this._mouseDown = false; - this._modiferPressed = false; + this._modifierPressed = false; } private onEditorKeyDown(e: IKeyboardEvent): void { @@ -73,7 +73,7 @@ export class DragAndDropController implements editorCommon.IEditorContribution { } if (hasTriggerModifier(e)) { - this._modiferPressed = true; + this._modifierPressed = true; } if (this._mouseDown && hasTriggerModifier(e)) { @@ -89,7 +89,7 @@ export class DragAndDropController implements editorCommon.IEditorContribution { } if (hasTriggerModifier(e)) { - this._modiferPressed = false; + this._modifierPressed = false; } if (this._mouseDown && e.keyCode === DragAndDropController.TRIGGER_KEY_VALUE) { @@ -170,13 +170,13 @@ export class DragAndDropController implements editorCommon.IEditorContribution { ( ( hasTriggerModifier(mouseEvent.event) || - this._modiferPressed + this._modifierPressed ) && ( this._dragSelection.getEndPosition().equals(newCursorPosition) || this._dragSelection.getStartPosition().equals(newCursorPosition) ) // we allow users to paste content beside the selection )) { this._editor.pushUndoStop(); - this._editor.executeCommand(DragAndDropController.ID, new DragAndDropCommand(this._dragSelection, newCursorPosition, hasTriggerModifier(mouseEvent.event) || this._modiferPressed)); + this._editor.executeCommand(DragAndDropController.ID, new DragAndDropCommand(this._dragSelection, newCursorPosition, hasTriggerModifier(mouseEvent.event) || this._modifierPressed)); this._editor.pushUndoStop(); } } @@ -227,7 +227,7 @@ export class DragAndDropController implements editorCommon.IEditorContribution { this._removeDecoration(); this._dragSelection = null; this._mouseDown = false; - this._modiferPressed = false; + this._modifierPressed = false; this._toUnhook = dispose(this._toUnhook); } } diff --git a/src/vs/editor/contrib/dnd/dragAndDropCommand.ts b/src/vs/editor/contrib/dnd/dragAndDropCommand.ts index 4a86be4152..c78e4c4565 100644 --- a/src/vs/editor/contrib/dnd/dragAndDropCommand.ts +++ b/src/vs/editor/contrib/dnd/dragAndDropCommand.ts @@ -12,10 +12,10 @@ import { ITextModel } from 'vs/editor/common/model'; export class DragAndDropCommand implements editorCommon.ICommand { - private selection: Selection; - private targetPosition: Position; + private readonly selection: Selection; + private readonly targetPosition: Position; private targetSelection: Selection; - private copy: boolean; + private readonly copy: boolean; constructor(selection: Selection, targetPosition: Position, copy: boolean) { this.selection = selection; diff --git a/src/vs/editor/contrib/documentSymbols/media/outlineTree.css b/src/vs/editor/contrib/documentSymbols/media/outlineTree.css index 2ff62df529..eaee13302b 100644 --- a/src/vs/editor/contrib/documentSymbols/media/outlineTree.css +++ b/src/vs/editor/contrib/documentSymbols/media/outlineTree.css @@ -3,49 +3,28 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-tree.focused .selected .outline-element-label, .monaco-tree.focused .selected .outline-element-decoration { +.monaco-list .monaco-list-row.focused.selected .outline-element .monaco-highlighted-label, +.monaco-list .monaco-list-row.focused.selected .outline-element-decoration { /* make sure selection color wins when a label is being selected */ color: inherit !important; } -.monaco-tree .outline-element { +.monaco-list .outline-element { display: flex; flex: 1; flex-flow: row nowrap; align-items: center; } -.monaco-tree .outline-element .outline-element-icon { - padding-right: 3px; -} - -.monaco-tree .outline-element .outline-element-label { - text-overflow: ellipsis; - overflow: hidden; +.monaco-list .outline-element .monaco-highlighted-label { color: var(--outline-element-color); } -.monaco-tree .outline-element .outline-element-label .monaco-highlighted-label .highlight { - font-weight: bold; -} - -.monaco-tree .outline-element .outline-element-detail { - visibility: hidden; - flex: 1; - flex-basis: 10%; - opacity: 0.8; - overflow: hidden; - text-overflow: ellipsis; - font-size: 90%; - padding-left: 4px; - padding-top: 3px; -} - .monaco-tree .monaco-tree-row.focused .outline-element .outline-element-detail { visibility: inherit; } -.monaco-tree .outline-element .outline-element-decoration { +.monaco-list .outline-element .outline-element-decoration { opacity: 0.75; font-size: 90%; font-weight: 600; @@ -55,7 +34,7 @@ color: var(--outline-element-color); } -.monaco-tree .outline-element .outline-element-decoration.bubble { +.monaco-list .outline-element .outline-element-decoration.bubble { font-family: octicons; font-size: 14px; opacity: 0.4; diff --git a/src/vs/editor/contrib/documentSymbols/media/symbol-icons.css b/src/vs/editor/contrib/documentSymbols/media/symbol-icons.css index e346b02e07..9d582c102a 100644 --- a/src/vs/editor/contrib/documentSymbols/media/symbol-icons.css +++ b/src/vs/editor/contrib/documentSymbols/media/symbol-icons.css @@ -3,7 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-workbench .symbol-icon { +.monaco-workbench .symbol-icon.inline { + background-position: left center; + padding-left: 20px; + background-size: 16px 16px; +} + +.monaco-workbench .symbol-icon.block { display: inline-block; height: 14px; width: 16px; diff --git a/src/vs/workbench/parts/outline/electron-browser/outline.ts b/src/vs/editor/contrib/documentSymbols/outline.ts similarity index 100% rename from src/vs/workbench/parts/outline/electron-browser/outline.ts rename to src/vs/editor/contrib/documentSymbols/outline.ts diff --git a/src/vs/editor/contrib/documentSymbols/outlineModel.ts b/src/vs/editor/contrib/documentSymbols/outlineModel.ts index 9fe9da850f..89b41e4c71 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineModel.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineModel.ts @@ -3,18 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { binarySearch, coalesceInPlace } from 'vs/base/common/arrays'; +import { binarySearch, coalesceInPlace, equals } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { first, forEach, size } from 'vs/base/common/collections'; import { onUnexpectedExternalError } from 'vs/base/common/errors'; -import { fuzzyScore, FuzzyScore } from 'vs/base/common/filters'; import { LRUCache } from 'vs/base/common/map'; import { commonPrefixLength } from 'vs/base/common/strings'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; import { DocumentSymbol, DocumentSymbolProvider, DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; -import { IMarker, MarkerSeverity } from 'vs/platform/markers/common/markers'; +import { MarkerSeverity } from 'vs/platform/markers/common/markers'; export abstract class TreeElement { @@ -87,10 +86,17 @@ export abstract class TreeElement { } } +export interface IOutlineMarker { + startLineNumber: number; + startColumn: number; + endLineNumber: number; + endColumn: number; + severity: MarkerSeverity; +} + export class OutlineElement extends TreeElement { children: { [id: string]: OutlineElement; } = Object.create(null); - score: FuzzyScore | undefined = FuzzyScore.Default; marker: { count: number, topSev: MarkerSeverity } | undefined; constructor( @@ -127,33 +133,6 @@ export class OutlineGroup extends TreeElement { return res; } - updateMatches(pattern: string, topMatch: OutlineElement | undefined): OutlineElement | undefined { - for (const key in this.children) { - topMatch = this._updateMatches(pattern, this.children[key], topMatch); - } - return topMatch; - } - - private _updateMatches(pattern: string, item: OutlineElement, topMatch: OutlineElement | undefined): OutlineElement | undefined { - - item.score = pattern - ? fuzzyScore(pattern, pattern.toLowerCase(), 0, item.symbol.name, item.symbol.name.toLowerCase(), 0, true) - : FuzzyScore.Default; - - if (item.score && (!topMatch || !topMatch.score || item.score[0] > topMatch.score[0])) { - topMatch = item; - } - for (const key in item.children) { - let child = item.children[key]; - topMatch = this._updateMatches(pattern, child, topMatch); - if (!item.score && child.score) { - // don't filter parents with unfiltered children - item.score = FuzzyScore.Default; - } - } - return topMatch; - } - getItemEnclosingPosition(position: IPosition): OutlineElement | undefined { return position ? this._getItemEnclosingPosition(position, this.children) : undefined; } @@ -169,13 +148,13 @@ export class OutlineGroup extends TreeElement { return undefined; } - updateMarker(marker: IMarker[]): void { + updateMarker(marker: IOutlineMarker[]): void { for (const key in this.children) { this._updateMarker(marker, this.children[key]); } } - private _updateMarker(markers: IMarker[], item: OutlineElement): void { + private _updateMarker(markers: IOutlineMarker[], item: OutlineElement): void { item.marker = undefined; // find the proper start index to check for item/marker overlap. @@ -190,7 +169,7 @@ export class OutlineGroup extends TreeElement { start = idx; } - let myMarkers: IMarker[] = []; + let myMarkers: IOutlineMarker[] = []; let myTopSev: MarkerSeverity | undefined; for (; start < markers.length && Range.areIntersecting(item.symbol.range, markers[start]); start++) { @@ -198,7 +177,7 @@ export class OutlineGroup extends TreeElement { // and store them in a 'private' array. let marker = markers[start]; myMarkers.push(marker); - (markers as Array)[start] = undefined; + (markers as Array)[start] = undefined; if (!myTopSev || marker.severity > myTopSev) { myTopSev = marker.severity; } @@ -268,7 +247,7 @@ export class OutlineModel extends TreeElement { if (data!.model) { // resolved -> return data - return Promise.resolve(data.model); + return Promise.resolve(data.model!); } // increase usage counter @@ -295,13 +274,17 @@ export class OutlineModel extends TreeElement { static _create(textModel: ITextModel, token: CancellationToken): Promise { - let result = new OutlineModel(textModel); - let promises = DocumentSymbolProviderRegistry.ordered(textModel).map((provider, index) => { + const chainedCancellation = new CancellationTokenSource(); + token.onCancellationRequested(() => chainedCancellation.cancel()); + + const result = new OutlineModel(textModel); + const provider = DocumentSymbolProviderRegistry.ordered(textModel); + const promises = provider.map((provider, index) => { let id = TreeElement.findId(`provider_${index}`, result); let group = new OutlineGroup(id, result, provider, index); - return Promise.resolve(provider.provideDocumentSymbols(result.textModel, token)).then(result => { + return Promise.resolve(provider.provideDocumentSymbols(result.textModel, chainedCancellation.token)).then(result => { for (const info of result || []) { OutlineModel._makeOutlineElement(info, group); } @@ -318,7 +301,22 @@ export class OutlineModel extends TreeElement { }); }); - return Promise.all(promises).then(() => result._compact()); + const listener = DocumentSymbolProviderRegistry.onDidChange(() => { + const newProvider = DocumentSymbolProviderRegistry.ordered(textModel); + if (!equals(newProvider, provider)) { + chainedCancellation.cancel(); + } + }); + + return Promise.all(promises).then(() => { + if (chainedCancellation.token.isCancellationRequested && !token.isCancellationRequested) { + return OutlineModel._create(textModel, token); + } else { + return result._compact(); + } + }).finally(() => { + listener.dispose(); + }); } private static _makeOutlineElement(info: DocumentSymbol, container: OutlineGroup | OutlineElement): void { @@ -395,20 +393,6 @@ export class OutlineModel extends TreeElement { return true; } - private _matches: [string, OutlineElement | undefined]; - - updateMatches(pattern: string): OutlineElement | undefined { - if (this._matches && this._matches[0] === pattern) { - return this._matches[1]; - } - let topMatch: OutlineElement | undefined; - for (const key in this._groups) { - topMatch = this._groups[key].updateMatches(pattern, topMatch); - } - this._matches = [pattern, topMatch]; - return topMatch; - } - getItemEnclosingPosition(position: IPosition, context?: OutlineElement): OutlineElement | undefined { let preferredGroup: OutlineGroup | undefined; @@ -437,7 +421,7 @@ export class OutlineModel extends TreeElement { return TreeElement.getElementById(id, this); } - updateMarker(marker: IMarker[]): void { + updateMarker(marker: IOutlineMarker[]): void { // sort markers by start range so that we can use // outline element starts for quicker look up marker.sort(Range.compareRangesUsingStarts); diff --git a/src/vs/editor/contrib/documentSymbols/outlineTree.ts b/src/vs/editor/contrib/documentSymbols/outlineTree.ts index 89a5233fac..2c17fe9a5f 100644 --- a/src/vs/editor/contrib/documentSymbols/outlineTree.ts +++ b/src/vs/editor/contrib/documentSymbols/outlineTree.ts @@ -4,178 +4,141 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; +import { IIdentityProvider, IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; +import { IDataSource, ITreeNode, ITreeRenderer, ITreeSorter } from 'vs/base/browser/ui/tree/tree'; import { values } from 'vs/base/common/collections'; -import { createMatches } from 'vs/base/common/filters'; -import { IDataSource, IFilter, IRenderer, ISorter, ITree } from 'vs/base/parts/tree/browser/tree'; +import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import 'vs/css!./media/outlineTree'; import 'vs/css!./media/symbol-icons'; import { Range } from 'vs/editor/common/core/range'; import { SymbolKind, symbolKindToCssClass } from 'vs/editor/common/modes'; import { OutlineElement, OutlineGroup, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { localize } from 'vs/nls'; +import { IKeybindingService, IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; +import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { WorkbenchTreeController } from 'vs/platform/list/browser/listService'; +import { OutlineConfigKeys } from 'vs/editor/contrib/documentSymbols/outline'; import { MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { listErrorForeground, listWarningForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { listErrorForeground, listWarningForeground } from 'vs/platform/theme/common/colorRegistry'; -export const enum OutlineItemCompareType { - ByPosition, - ByName, - ByKind -} +export type OutlineItem = OutlineGroup | OutlineElement; -export class OutlineItemComparator implements ISorter { +export class OutlineNavigationLabelProvider implements IKeyboardNavigationLabelProvider { - constructor( - public type: OutlineItemCompareType = OutlineItemCompareType.ByPosition - ) { } + constructor(@IKeybindingService private readonly _keybindingService: IKeybindingService) { } - compare(tree: ITree, a: OutlineGroup | OutlineElement, b: OutlineGroup | OutlineElement): number { - - if (a instanceof OutlineGroup && b instanceof OutlineGroup) { - return a.providerIndex - b.providerIndex; + getKeyboardNavigationLabel(element: OutlineItem): { toString(): string; } { + if (element instanceof OutlineGroup) { + return element.provider.displayName || element.id; + } else { + return element.symbol.name; } + } - if (a instanceof OutlineElement && b instanceof OutlineElement) { - switch (this.type) { - case OutlineItemCompareType.ByKind: - return a.symbol.kind - b.symbol.kind; - case OutlineItemCompareType.ByName: - return a.symbol.name.localeCompare(b.symbol.name); - case OutlineItemCompareType.ByPosition: - default: - return Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range); - } - } - - return 0; + mightProducePrintableCharacter(event: IKeyboardEvent): boolean { + return this._keybindingService.mightProducePrintableCharacter(event); } } -export class OutlineItemFilter implements IFilter { - enabled: boolean = true; - - isVisible(tree: ITree, element: OutlineElement | any): boolean { - if (!this.enabled) { - return true; - } - return !(element instanceof OutlineElement) || Boolean(element.score); +export class OutlineIdentityProvider implements IIdentityProvider { + getId(element: TreeElement): { toString(): string; } { + return element.id; } } -export class OutlineDataSource implements IDataSource { +export class OutlineGroupTemplate { + static id = 'OutlineGroupTemplate'; - // this is a workaround for the tree showing twisties for items - // with only filtered children - filterOnScore: boolean = true; - - getId(tree: ITree, element: TreeElement): string { - return element ? element.id : 'empty'; - } - - hasChildren(tree: ITree, element: OutlineModel | OutlineGroup | OutlineElement): boolean { - if (!element) { - return false; - } - if (element instanceof OutlineModel) { - return true; - } - if (element instanceof OutlineElement && (this.filterOnScore && !element.score)) { - return false; - } - for (const id in element.children) { - if (!this.filterOnScore || element.children[id].score) { - return true; - } - } - return false; - } - - getChildren(tree: ITree, element: TreeElement): Promise { - let res = values(element.children); - // console.log(element.id + ' with children ' + res.length); - return Promise.resolve(res); - } - - getParent(tree: ITree, element: TreeElement | any): Promise { - return Promise.resolve(element && element.parent); - } - - shouldAutoexpand(tree: ITree, element: TreeElement): boolean { - return element && (element instanceof OutlineModel || element.parent instanceof OutlineModel || element instanceof OutlineGroup || element.parent instanceof OutlineGroup); - } -} - -export interface OutlineTemplate { labelContainer: HTMLElement; label: HighlightedLabel; - icon?: HTMLElement; - detail?: HTMLElement; - decoration?: HTMLElement; } -export class OutlineRenderer implements IRenderer { +export class OutlineElementTemplate { + static id = 'OutlineElementTemplate'; + container: HTMLElement; + iconLabel: IconLabel; + decoration: HTMLElement; +} - renderProblemColors = true; - renderProblemBadges = true; +export class OutlineVirtualDelegate implements IListVirtualDelegate { - constructor( - @IThemeService readonly _themeService: IThemeService, - @IConfigurationService readonly _configurationService: IConfigurationService - ) { - // - } - - getHeight(tree: ITree, element: any): number { + getHeight(_element: OutlineItem): number { return 22; } - getTemplateId(tree: ITree, element: OutlineGroup | OutlineElement): string { - return element instanceof OutlineGroup ? 'outline-group' : 'outline-element'; - } - - renderTemplate(tree: ITree, templateId: string, container: HTMLElement): OutlineTemplate { - if (templateId === 'outline-element') { - const icon = dom.$('.outline-element-icon symbol-icon'); - const labelContainer = dom.$('.outline-element-label'); - const detail = dom.$('.outline-element-detail'); - const decoration = dom.$('.outline-element-decoration'); - dom.addClass(container, 'outline-element'); - dom.append(container, icon, labelContainer, detail, decoration); - return { icon, labelContainer, label: new HighlightedLabel(labelContainer, true), detail, decoration }; - } - if (templateId === 'outline-group') { - const labelContainer = dom.$('.outline-element-label'); - dom.addClass(container, 'outline-element'); - dom.append(container, labelContainer); - return { labelContainer, label: new HighlightedLabel(labelContainer, true) }; - } - - throw new Error(templateId); - } - - renderElement(tree: ITree, element: OutlineGroup | OutlineElement, templateId: string, template: OutlineTemplate): void { - if (element instanceof OutlineElement) { - template.icon.className = `outline-element-icon ${symbolKindToCssClass(element.symbol.kind)}`; - template.label.set(element.symbol.name, element.score ? createMatches(element.score) : undefined, localize('title.template', "{0} ({1})", element.symbol.name, OutlineRenderer._symbolKindNames[element.symbol.kind])); - template.detail.innerText = element.symbol.detail || ''; - this._renderMarkerInfo(element, template); - - } + getTemplateId(element: OutlineItem): string { if (element instanceof OutlineGroup) { - template.label.set(element.provider.displayName || localize('provider', "Outline Provider")); + return OutlineGroupTemplate.id; + } else { + return OutlineElementTemplate.id; } } +} - private _renderMarkerInfo(element: OutlineElement, template: OutlineTemplate): void { +export class OutlineGroupRenderer implements ITreeRenderer { + + readonly templateId: string = OutlineGroupTemplate.id; + + renderTemplate(container: HTMLElement): OutlineGroupTemplate { + const labelContainer = dom.$('.outline-element-label'); + dom.addClass(container, 'outline-element'); + dom.append(container, labelContainer); + return { labelContainer, label: new HighlightedLabel(labelContainer, true) }; + } + + renderElement(node: ITreeNode, index: number, template: OutlineGroupTemplate): void { + template.label.set( + node.element.provider.displayName || localize('provider', "Outline Provider"), + createMatches(node.filterData) + ); + } + + disposeTemplate(_template: OutlineGroupTemplate): void { + // nothing + } +} + +export class OutlineElementRenderer implements ITreeRenderer { + + readonly templateId: string = OutlineElementTemplate.id; + + constructor( + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IThemeService private readonly _themeService: IThemeService, + ) { } + + renderTemplate(container: HTMLElement): OutlineElementTemplate { + dom.addClass(container, 'outline-element'); + const iconLabel = new IconLabel(container, { supportHighlights: true }); + const decoration = dom.$('.outline-element-decoration'); + container.appendChild(decoration); + return { container, iconLabel, decoration }; + } + + renderElement(node: ITreeNode, index: number, template: OutlineElementTemplate): void { + const { element } = node; + const options = { + matches: createMatches(node.filterData), + labelEscapeNewLines: true, + extraClasses: [], + title: localize('title.template', "{0} ({1})", element.symbol.name, OutlineElementRenderer._symbolKindNames[element.symbol.kind]) + }; + if (this._configurationService.getValue(OutlineConfigKeys.icons)) { + // add styles for the icons + options.extraClasses.push(`outline-element-icon ${symbolKindToCssClass(element.symbol.kind, true)}`); + } + template.iconLabel.setLabel(element.symbol.name, element.symbol.detail, options); + this._renderMarkerInfo(element, template); + } + + private _renderMarkerInfo(element: OutlineElement, template: OutlineElementTemplate): void { if (!element.marker) { dom.hide(template.decoration); - template.labelContainer.style.removeProperty('--outline-element-color'); + template.container.style.removeProperty('--outline-element-color'); return; } @@ -184,14 +147,14 @@ export class OutlineRenderer implements IRenderer { const cssColor = color ? color.toString() : 'inherit'; // color of the label - if (this.renderProblemColors) { - template.labelContainer.style.setProperty('--outline-element-color', cssColor); + if (this._configurationService.getValue(OutlineConfigKeys.problemsColors)) { + template.container.style.setProperty('--outline-element-color', cssColor); } else { - template.labelContainer.style.removeProperty('--outline-element-color'); + template.container.style.removeProperty('--outline-element-color'); } // badge with color/rollup - if (!this.renderProblemBadges) { + if (!this._configurationService.getValue(OutlineConfigKeys.problemsBadges)) { dom.hide(template.decoration); } else if (count > 0) { @@ -239,77 +202,46 @@ export class OutlineRenderer implements IRenderer { [SymbolKind.Variable]: localize('Variable', "variable"), }; - disposeTemplate(tree: ITree, templateId: string, template: OutlineTemplate): void { - // noop + disposeTemplate(_template: OutlineElementTemplate): void { + _template.iconLabel.dispose(); } } -export class OutlineTreeState { +export const enum OutlineSortOrder { + ByPosition, + ByName, + ByKind +} - readonly selected: string; - readonly focused: string; - readonly expanded: string[]; +export class OutlineItemComparator implements ITreeSorter { - static capture(tree: ITree): OutlineTreeState { - // selection - let selected: string; - let element = tree.getSelection()[0]; - if (element instanceof TreeElement) { - selected = element.id; - } + constructor( + public type: OutlineSortOrder = OutlineSortOrder.ByPosition + ) { } - // focus - let focused: string; - element = tree.getFocus(true); - if (element instanceof TreeElement) { - focused = element.id; - } + compare(a: OutlineItem, b: OutlineItem): number { + if (a instanceof OutlineGroup && b instanceof OutlineGroup) { + return a.providerIndex - b.providerIndex; - // expansion - let expanded = new Array(); - let nav = tree.getNavigator(); - while (nav.next()) { - let element = nav.current(); - if (element instanceof TreeElement) { - if (tree.isExpanded(element)) { - expanded.push(element.id); - } + } else if (a instanceof OutlineElement && b instanceof OutlineElement) { + if (this.type === OutlineSortOrder.ByKind) { + return a.symbol.kind - b.symbol.kind || a.symbol.name.localeCompare(b.symbol.name); + } else if (this.type === OutlineSortOrder.ByName) { + return a.symbol.name.localeCompare(b.symbol.name) || Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range); + } else if (this.type === OutlineSortOrder.ByPosition) { + return Range.compareRangesUsingStarts(a.symbol.range, b.symbol.range) || a.symbol.name.localeCompare(b.symbol.name); } } - return { selected, focused, expanded }; - } - - static async restore(tree: ITree, state: OutlineTreeState, eventPayload: any): Promise { - let model = tree.getInput(); - if (!state || !(model instanceof OutlineModel)) { - return Promise.resolve(undefined); - } - - // expansion - let items: TreeElement[] = []; - for (const id of state.expanded) { - let item = model.getItemById(id); - if (item) { - items.push(item); - } - } - await tree.collapseAll(undefined); - await tree.expandAll(items); - - // selection & focus - let selected = model.getItemById(state.selected); - let focused = model.getItemById(state.focused); - tree.setSelection([selected], eventPayload); - tree.setFocus(focused, eventPayload); + return 0; } } -export class OutlineController extends WorkbenchTreeController { - protected shouldToggleExpansion(element: any, event: IMouseEvent, origin: string): boolean { - if (element instanceof OutlineElement) { - return this.isClickOnTwistie(event); - } else { - return super.shouldToggleExpansion(element, event, origin); +export class OutlineDataSource implements IDataSource { + + getChildren(element: undefined | OutlineModel | OutlineGroup | OutlineElement): OutlineItem[] { + if (!element) { + return []; } + return values(element.children); } } diff --git a/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts b/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts index 99969c08c9..6dc986eb4d 100644 --- a/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts +++ b/src/vs/editor/contrib/documentSymbols/test/outlineModel.test.ts @@ -101,9 +101,9 @@ suite('OutlineModel', function () { group.updateMarker(data); assert.equal(data.length, 0); // all 'stolen' - assert.equal(e0.marker.count, 1); + assert.equal(e0.marker!.count, 1); assert.equal(e1.marker, undefined); - assert.equal(e2.marker.count, 2); + assert.equal(e2.marker!.count, 2); group.updateMarker([]); assert.equal(e0.marker, undefined); @@ -127,8 +127,8 @@ suite('OutlineModel', function () { ]; group.updateMarker(data); - assert.equal(p.marker.count, 0); - assert.equal(c1.marker.count, 1); + assert.equal(p.marker!.count, 0); + assert.equal(c1.marker!.count, 1); assert.equal(c2.marker, undefined); data = [ @@ -137,18 +137,18 @@ suite('OutlineModel', function () { fakeMarker(new Range(7, 6, 7, 8)), ]; group.updateMarker(data); - assert.equal(p.marker.count, 0); - assert.equal(c1.marker.count, 2); - assert.equal(c2.marker.count, 1); + assert.equal(p.marker!.count, 0); + assert.equal(c1.marker!.count, 2); + assert.equal(c2.marker!.count, 1); data = [ fakeMarker(new Range(1, 4, 1, 11)), fakeMarker(new Range(7, 6, 7, 8)), ]; group.updateMarker(data); - assert.equal(p.marker.count, 1); + assert.equal(p.marker!.count, 1); assert.equal(c1.marker, undefined); - assert.equal(c2.marker.count, 1); + assert.equal(c2.marker!.count, 1); }); test('OutlineElement - updateMarker/multiple groups', function () { @@ -178,9 +178,9 @@ suite('OutlineModel', function () { model.updateMarker(data); - assert.equal(model.children['g1'].children['c1'].marker.count, 2); - assert.equal(model.children['g2'].children['c2'].children['c2.1'].marker.count, 1); - assert.equal(model.children['g2'].children['c2'].children['c2.2'].marker.count, 1); + assert.equal(model.children['g1']!.children['c1'].marker!.count, 2); + assert.equal(model.children['g2']!.children['c2'].children['c2.1'].marker!.count, 1); + assert.equal(model.children['g2']!.children['c2'].children['c2.2'].marker!.count, 1); }); }); diff --git a/src/vs/editor/contrib/find/findController.ts b/src/vs/editor/contrib/find/findController.ts index e06f5a7f0e..5f7c2df44f 100644 --- a/src/vs/editor/contrib/find/findController.ts +++ b/src/vs/editor/contrib/find/findController.ts @@ -71,12 +71,12 @@ export class CommonFindController extends Disposable implements editorCommon.IEd private static readonly ID = 'editor.contrib.findController'; protected _editor: ICodeEditor; - private _findWidgetVisible: IContextKey; + private readonly _findWidgetVisible: IContextKey; protected _state: FindReplaceState; protected _updateHistoryDelayer: Delayer; private _model: FindModelBoundToEditorModel | null; - private _storageService: IStorageService; - private _clipboardService: IClipboardService; + private readonly _storageService: IStorageService; + private readonly _clipboardService: IClipboardService; protected readonly _contextKeyService: IContextKeyService; public static get(editor: ICodeEditor): CommonFindController { diff --git a/src/vs/editor/contrib/find/findDecorations.ts b/src/vs/editor/contrib/find/findDecorations.ts index 98b55e7c33..0a443a038b 100644 --- a/src/vs/editor/contrib/find/findDecorations.ts +++ b/src/vs/editor/contrib/find/findDecorations.ts @@ -14,7 +14,7 @@ import { themeColorFromId } from 'vs/platform/theme/common/themeService'; export class FindDecorations implements IDisposable { - private _editor: IActiveCodeEditor; + private readonly _editor: IActiveCodeEditor; private _decorations: string[]; private _overviewRulerApproximateDecorations: string[]; private _findScopeDecorationId: string | null; diff --git a/src/vs/editor/contrib/find/findModel.ts b/src/vs/editor/contrib/find/findModel.ts index bd3e8f5f2c..17505c4f34 100644 --- a/src/vs/editor/contrib/find/findModel.ts +++ b/src/vs/editor/contrib/find/findModel.ts @@ -69,14 +69,14 @@ const RESEARCH_DELAY = 240; export class FindModelBoundToEditorModel { - private _editor: IActiveCodeEditor; - private _state: FindReplaceState; + private readonly _editor: IActiveCodeEditor; + private readonly _state: FindReplaceState; private _toDispose: IDisposable[]; - private _decorations: FindDecorations; + private readonly _decorations: FindDecorations; private _ignoreModelContentChanged: boolean; - private _startSearchingTimer: TimeoutTimer; + private readonly _startSearchingTimer: TimeoutTimer; - private _updateDecorationsScheduler: RunOnceScheduler; + private readonly _updateDecorationsScheduler: RunOnceScheduler; private _isDisposed: boolean; constructor(editor: IActiveCodeEditor, state: FindReplaceState) { diff --git a/src/vs/editor/contrib/find/findOptionsWidget.ts b/src/vs/editor/contrib/find/findOptionsWidget.ts index 41c7d7e90d..68594813e4 100644 --- a/src/vs/editor/contrib/find/findOptionsWidget.ts +++ b/src/vs/editor/contrib/find/findOptionsWidget.ts @@ -18,14 +18,14 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { private static readonly ID = 'editor.contrib.findOptionsWidget'; - private _editor: ICodeEditor; - private _state: FindReplaceState; - private _keybindingService: IKeybindingService; + private readonly _editor: ICodeEditor; + private readonly _state: FindReplaceState; + private readonly _keybindingService: IKeybindingService; - private _domNode: HTMLElement; - private regex: RegexCheckbox; - private wholeWords: WholeWordsCheckbox; - private caseSensitive: CaseSensitiveCheckbox; + private readonly _domNode: HTMLElement; + private readonly regex: RegexCheckbox; + private readonly wholeWords: WholeWordsCheckbox; + private readonly caseSensitive: CaseSensitiveCheckbox; constructor( editor: ICodeEditor, @@ -46,7 +46,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { this._domNode.setAttribute('role', 'presentation'); this._domNode.setAttribute('aria-hidden', 'true'); - const inputActiveOptionBorderColor = themeService.getTheme().getColor(inputActiveOptionBorder) || undefined; + const inputActiveOptionBorderColor = themeService.getTheme().getColor(inputActiveOptionBorder); this.caseSensitive = this._register(new CaseSensitiveCheckbox({ appendTitle: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand), @@ -179,7 +179,7 @@ export class FindOptionsWidget extends Widget implements IOverlayWidget { } private _applyTheme(theme: ITheme) { - let inputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder) || undefined }; + let inputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder) }; this.caseSensitive.style(inputStyles); this.wholeWords.style(inputStyles); this.regex.style(inputStyles); diff --git a/src/vs/editor/contrib/find/findWidget.css b/src/vs/editor/contrib/find/findWidget.css index 53583ce45a..6d6b526424 100644 --- a/src/vs/editor/contrib/find/findWidget.css +++ b/src/vs/editor/contrib/find/findWidget.css @@ -79,10 +79,6 @@ height: 25px; } -.monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .input { - width: 100% !important; - padding-right: 66px; -} .monaco-editor .find-widget > .find-part .monaco-inputbox > .wrapper > .input, .monaco-editor .find-widget > .replace-part .monaco-inputbox > .wrapper > .input { padding-top: 2px; diff --git a/src/vs/editor/contrib/find/findWidget.ts b/src/vs/editor/contrib/find/findWidget.ts index 1ab1837a5b..84bbf11eb6 100644 --- a/src/vs/editor/contrib/find/findWidget.ts +++ b/src/vs/editor/contrib/find/findWidget.ts @@ -29,7 +29,8 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { contrastBorder, editorFindMatch, editorFindMatchBorder, editorFindMatchHighlight, editorFindMatchHighlightBorder, editorFindRangeHighlight, editorFindRangeHighlightBorder, editorWidgetBackground, editorWidgetBorder, editorWidgetResizeBorder, errorForeground, inputActiveOptionBorder, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { ContextScopedFindInput, ContextScopedHistoryInputBox } from 'vs/platform/widget/browser/contextScopedHistoryWidget'; +import { ContextScopedFindInput, ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; +import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; export interface IFindController { replace(): void; @@ -83,8 +84,8 @@ export class FindWidgetViewZone implements IViewZone { export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSashLayoutProvider { private static readonly ID = 'editor.contrib.findWidget'; private readonly _codeEditor: ICodeEditor; - private _state: FindReplaceState; - private _controller: IFindController; + private readonly _state: FindReplaceState; + private readonly _controller: IFindController; private readonly _contextViewProvider: IContextViewProvider; private readonly _keybindingService: IKeybindingService; private readonly _contextKeyService: IContextKeyService; @@ -106,16 +107,16 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private _isReplaceVisible: boolean; private _ignoreChangeEvent: boolean; - private _findFocusTracker: dom.IFocusTracker; - private _findInputFocused: IContextKey; - private _replaceFocusTracker: dom.IFocusTracker; - private _replaceInputFocused: IContextKey; - private _viewZone: FindWidgetViewZone; + private readonly _findFocusTracker: dom.IFocusTracker; + private readonly _findInputFocused: IContextKey; + private readonly _replaceFocusTracker: dom.IFocusTracker; + private readonly _replaceInputFocused: IContextKey; + private _viewZone?: FindWidgetViewZone; private _viewZoneId?: number; private _resizeSash: Sash; private _resized: boolean; - private _updateHistoryDelayer: Delayer; + private readonly _updateHistoryDelayer: Delayer; constructor( codeEditor: ICodeEditor, @@ -160,6 +161,17 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas if (e.accessibilitySupport) { this.updateAccessibilitySupport(); } + + if (e.contribInfo) { + const addExtraSpaceOnTop = this._codeEditor.getConfiguration().contribInfo.find.addExtraSpaceOnTop; + if (addExtraSpaceOnTop && !this._viewZone) { + this._viewZone = new FindWidgetViewZone(0); + this._showViewZone(); + } + if (!addExtraSpaceOnTop && this._viewZone) { + this._removeViewZone(); + } + } })); this.updateAccessibilitySupport(); this._register(this._codeEditor.onDidChangeCursorSelection(() => { @@ -197,7 +209,9 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas })); this._codeEditor.addOverlayWidget(this); - this._viewZone = new FindWidgetViewZone(0); // Put it before the first line then users can scroll beyond the first line. + if (this._codeEditor.getConfiguration().contribInfo.find.addExtraSpaceOnTop) { + this._viewZone = new FindWidgetViewZone(0); // Put it before the first line then users can scroll beyond the first line. + } this._applyTheme(themeService.getTheme()); this._register(themeService.onThemeChange(this._applyTheme.bind(this))); @@ -434,7 +448,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas const startLeft = editorCoords.left + (startCoords ? startCoords.left : 0); const startTop = startCoords ? startCoords.top : 0; - if (startTop < this._viewZone.heightInPx) { + if (this._viewZone && startTop < this._viewZone.heightInPx) { if (selection.endLineNumber > selection.startLineNumber) { adjustEditorScrollTop = false; } @@ -468,40 +482,42 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas this._codeEditor.focus(); } this._codeEditor.layoutOverlayWidget(this); - this._codeEditor.changeViewZones((accessor) => { - if (this._viewZoneId !== undefined) { - accessor.removeZone(this._viewZoneId); - this._viewZoneId = undefined; - this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() - this._viewZone.heightInPx); - } - }); + this._removeViewZone(); } } private _layoutViewZone() { - if (!this._isVisible) { + const addExtraSpaceOnTop = this._codeEditor.getConfiguration().contribInfo.find.addExtraSpaceOnTop; + + if (!addExtraSpaceOnTop) { + this._removeViewZone(); return; } - if (this._viewZoneId !== undefined) { + if (!this._isVisible) { + return; + } + const viewZone = this._viewZone; + if (this._viewZoneId !== undefined || !viewZone) { return; } this._codeEditor.changeViewZones((accessor) => { if (this._state.isReplaceRevealed) { - this._viewZone.heightInPx = FIND_REPLACE_AREA_HEIGHT; + viewZone.heightInPx = FIND_REPLACE_AREA_HEIGHT; } else { - this._viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT; + viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT; } - this._viewZoneId = accessor.addZone(this._viewZone); + this._viewZoneId = accessor.addZone(viewZone); // scroll top adjust to make sure the editor doesn't scroll when adding viewzone at the beginning. - this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + this._viewZone.heightInPx); + this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + viewZone.heightInPx); }); } private _showViewZone(adjustScroll: boolean = true) { - if (!this._isVisible) { + const viewZone = this._viewZone; + if (!this._isVisible || !viewZone) { return; } @@ -510,17 +526,17 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas if (this._viewZoneId !== undefined) { if (this._state.isReplaceRevealed) { - this._viewZone.heightInPx = FIND_REPLACE_AREA_HEIGHT; + viewZone.heightInPx = FIND_REPLACE_AREA_HEIGHT; scrollAdjustment = FIND_REPLACE_AREA_HEIGHT - FIND_INPUT_AREA_HEIGHT; } else { - this._viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT; + viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT; scrollAdjustment = FIND_INPUT_AREA_HEIGHT - FIND_REPLACE_AREA_HEIGHT; } accessor.removeZone(this._viewZoneId); } else { - this._viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT; + viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT; } - this._viewZoneId = accessor.addZone(this._viewZone); + this._viewZoneId = accessor.addZone(viewZone); if (adjustScroll) { this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + scrollAdjustment); @@ -528,6 +544,19 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas }); } + private _removeViewZone() { + this._codeEditor.changeViewZones((accessor) => { + if (this._viewZoneId !== undefined) { + accessor.removeZone(this._viewZoneId); + this._viewZoneId = undefined; + if (this._viewZone) { + this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() - this._viewZone.heightInPx); + this._viewZone = undefined; + } + } + }); + } + private _applyTheme(theme: ITheme) { let inputStyles: IFindInputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), @@ -542,7 +571,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas inputValidationWarningBorder: theme.getColor(inputValidationWarningBorder), inputValidationErrorBackground: theme.getColor(inputValidationErrorBackground), inputValidationErrorForeground: theme.getColor(inputValidationErrorForeground), - inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder) + inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder), }; this._findInput.style(inputStyles); this._replaceInputBox.style(inputStyles); @@ -825,7 +854,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas } } } else { - this._state.change({ searchScope: undefined }, true); + this._state.change({ searchScope: null }, true); } } })); @@ -835,7 +864,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand), className: 'close-fw', onTrigger: () => { - this._state.change({ isRevealed: false, searchScope: undefined }, false); + this._state.change({ isRevealed: false, searchScope: null }, false); }, onKeyDown: (e) => { if (e.equals(KeyCode.Tab)) { @@ -975,7 +1004,7 @@ export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSas private updateAccessibilitySupport(): void { const value = this._codeEditor.getConfiguration().accessibilitySupport; - this._findInput.setFocusInputOnOptionClick(value !== platform.AccessibilitySupport.Enabled); + this._findInput.setFocusInputOnOptionClick(value !== AccessibilitySupport.Enabled); } } @@ -1130,7 +1159,7 @@ export class SimpleButton extends Widget { // theming registerThemingParticipant((theme, collector) => { - const addBackgroundColorRule = (selector: string, color: Color | null): void => { + const addBackgroundColorRule = (selector: string, color: Color | undefined): void => { if (color) { collector.addRule(`.monaco-editor ${selector} { background-color: ${color}; }`); } diff --git a/src/vs/editor/contrib/find/replaceAllCommand.ts b/src/vs/editor/contrib/find/replaceAllCommand.ts index 2a9c32831d..85367510d0 100644 --- a/src/vs/editor/contrib/find/replaceAllCommand.ts +++ b/src/vs/editor/contrib/find/replaceAllCommand.ts @@ -15,10 +15,10 @@ interface IEditOperation { export class ReplaceAllCommand implements editorCommon.ICommand { - private _editorSelection: Selection; + private readonly _editorSelection: Selection; private _trackedEditorSelectionId: string; - private _ranges: Range[]; - private _replaceStrings: string[]; + private readonly _ranges: Range[]; + private readonly _replaceStrings: string[]; constructor(editorSelection: Selection, ranges: Range[], replaceStrings: string[]) { this._editorSelection = editorSelection; diff --git a/src/vs/editor/contrib/find/simpleFindWidget.css b/src/vs/editor/contrib/find/simpleFindWidget.css index 737cdfb07a..9ae6b32803 100644 --- a/src/vs/editor/contrib/find/simpleFindWidget.css +++ b/src/vs/editor/contrib/find/simpleFindWidget.css @@ -16,6 +16,7 @@ } .monaco-workbench .simple-find-part { + visibility: hidden; /* Use visibility to maintain flex layout while hidden otherwise interferes with transition */ z-index: 10; position: relative; top: -45px; @@ -27,6 +28,10 @@ } .monaco-workbench .simple-find-part.visible { + visibility: visible; +} + +.monaco-workbench .simple-find-part.visible-transition { top: 0; } @@ -34,10 +39,6 @@ flex: 1; } -.monaco-workbench .simple-find-part .monaco-findInput .monaco-inputbox .wrapper .input { - width: 100% !important; -} - .monaco-workbench .simple-find-part .button { min-width: 20px; width: 20px; diff --git a/src/vs/editor/contrib/find/simpleFindWidget.ts b/src/vs/editor/contrib/find/simpleFindWidget.ts index fb2bc20b48..5d26b6f437 100644 --- a/src/vs/editor/contrib/find/simpleFindWidget.ts +++ b/src/vs/editor/contrib/find/simpleFindWidget.ts @@ -6,7 +6,7 @@ import 'vs/css!./simpleFindWidget'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; -import { FindInput } from 'vs/base/browser/ui/findinput/findInput'; +import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput'; import { Widget } from 'vs/base/browser/ui/widget'; import { Delayer } from 'vs/base/common/async'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -16,7 +16,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { editorWidgetBackground, inputActiveOptionBorder, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { ContextScopedFindInput } from 'vs/platform/widget/browser/contextScopedHistoryWidget'; +import { ContextScopedFindInput } from 'vs/platform/browser/contextScopedHistoryWidget'; const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find"); const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find"); @@ -25,13 +25,13 @@ const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next mat const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close"); export abstract class SimpleFindWidget extends Widget { - private _findInput: FindInput; - private _domNode?: HTMLElement; - private _innerDomNode: HTMLElement; + private readonly _findInput: FindInput; + private readonly _domNode: HTMLElement; + private readonly _innerDomNode: HTMLElement; private _isVisible: boolean = false; - private _focusTracker: dom.IFocusTracker; - private _findInputFocusTracker: dom.IFocusTracker; - private _updateHistoryDelayer: Delayer; + private readonly _focusTracker: dom.IFocusTracker; + private readonly _findInputFocusTracker: dom.IFocusTracker; + private readonly _updateHistoryDelayer: Delayer; constructor( @IContextViewService private readonly _contextViewService: IContextViewService, @@ -159,7 +159,7 @@ export abstract class SimpleFindWidget extends Widget { } public updateTheme(theme: ITheme): void { - const inputStyles = { + const inputStyles: IFindInputStyles = { inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder), inputBackground: theme.getColor(inputBackground), inputForeground: theme.getColor(inputForeground), @@ -182,7 +182,6 @@ export abstract class SimpleFindWidget extends Widget { if (this._domNode && this._domNode.parentElement) { this._domNode.parentElement.removeChild(this._domNode); - this._domNode = undefined; } } @@ -204,10 +203,9 @@ export abstract class SimpleFindWidget extends Widget { setTimeout(() => { dom.addClass(this._innerDomNode, 'visible'); + dom.addClass(this._innerDomNode, 'visible-transition'); this._innerDomNode.setAttribute('aria-hidden', 'false'); - setTimeout(() => { - this._findInput.select(); - }, 200); + this._findInput.select(); }, 0); } @@ -220,16 +218,20 @@ export abstract class SimpleFindWidget extends Widget { setTimeout(() => { dom.addClass(this._innerDomNode, 'visible'); + dom.addClass(this._innerDomNode, 'visible-transition'); this._innerDomNode.setAttribute('aria-hidden', 'false'); }, 0); } public hide(): void { if (this._isVisible) { - this._isVisible = false; - - dom.removeClass(this._innerDomNode, 'visible'); + dom.removeClass(this._innerDomNode, 'visible-transition'); this._innerDomNode.setAttribute('aria-hidden', 'true'); + // Need to delay toggling visibility until after Transition, then visibility hidden - removes from tabIndex list + setTimeout(() => { + this._isVisible = false; + dom.removeClass(this._innerDomNode, 'visible'); + }, 200); } } diff --git a/src/vs/editor/contrib/find/test/findController.test.ts b/src/vs/editor/contrib/find/test/findController.test.ts index 4377955494..691062b24a 100644 --- a/src/vs/editor/contrib/find/test/findController.test.ts +++ b/src/vs/editor/contrib/find/test/findController.test.ts @@ -65,7 +65,7 @@ suite('FindController', () => { onWillSaveState: Event.None, get: (key: string) => queryState[key], getBoolean: (key: string) => !!queryState[key], - getInteger: (key: string) => undefined, + getNumber: (key: string) => undefined, store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); }, remove: (key) => undefined } as any); @@ -440,7 +440,7 @@ suite('FindController query options persistence', () => { onWillSaveState: Event.None, get: (key: string) => queryState[key], getBoolean: (key: string) => !!queryState[key], - getInteger: (key: string) => undefined, + getNumber: (key: string) => undefined, store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); }, remove: (key) => undefined } as any); diff --git a/src/vs/editor/contrib/find/test/findModel.test.ts b/src/vs/editor/contrib/find/test/findModel.test.ts index cc4c4161b7..5df00d6c85 100644 --- a/src/vs/editor/contrib/find/test/findModel.test.ts +++ b/src/vs/editor/contrib/find/test/findModel.test.ts @@ -1507,7 +1507,7 @@ suite('FindModel', () => { ] ); - editor.getModel().setValue('hello\nhi'); + editor!.getModel()!.setValue('hello\nhi'); assertFindState( editor, [1, 1, 1, 1], @@ -1538,7 +1538,7 @@ suite('FindModel', () => { findModel.selectAllMatches(); - assert.deepEqual(editor.getSelections().map(s => s.toString()), [ + assert.deepEqual(editor!.getSelections()!.map(s => s.toString()), [ new Selection(6, 14, 6, 19), new Selection(6, 27, 6, 32), new Selection(7, 14, 7, 19), @@ -1582,14 +1582,14 @@ suite('FindModel', () => { findModel.selectAllMatches(); - assert.deepEqual(editor.getSelections().map(s => s.toString()), [ + assert.deepEqual(editor!.getSelections()!.map(s => s.toString()), [ new Selection(7, 14, 7, 19), new Selection(6, 14, 6, 19), new Selection(6, 27, 6, 32), new Selection(8, 14, 8, 19) ].map(s => s.toString())); - assert.deepEqual(editor.getSelection().toString(), new Selection(7, 14, 7, 19).toString()); + assert.deepEqual(editor!.getSelection()!.toString(), new Selection(7, 14, 7, 19).toString()); assertFindState( editor, @@ -1984,7 +1984,7 @@ suite('FindModel', () => { for (let i = 0; i < 1100; i++) { initialText += 'line' + i + '\n'; } - editor.getModel().setValue(initialText); + editor!.getModel()!.setValue(initialText); let findState = new FindReplaceState(); findState.change({ searchString: '^', replaceString: 'a ', isRegex: true }, false); let findModel = new FindModelBoundToEditorModel(editor, findState); @@ -1996,7 +1996,7 @@ suite('FindModel', () => { expectedText += 'a line' + i + '\n'; } expectedText += 'a '; - assert.equal(editor.getModel().getValue(), expectedText); + assert.equal(editor!.getModel()!.getValue(), expectedText); findModel.dispose(); findState.dispose(); diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index 56ae2c94fb..ab52ff724e 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -55,12 +55,12 @@ export class FoldingController implements IEditorContribution { return editor.getContribution(ID); } - private editor: ICodeEditor; + private readonly editor: ICodeEditor; private _isEnabled: boolean; private _autoHideFoldingControls: boolean; private _useFoldingProviders: boolean; - private foldingDecorationProvider: FoldingDecorationProvider; + private readonly foldingDecorationProvider: FoldingDecorationProvider; private foldingModel: FoldingModel | null; private hiddenRangeModel: HiddenRangeModel | null; @@ -92,7 +92,6 @@ export class FoldingController implements IEditorContribution { this.foldingDecorationProvider.autoHideFoldingControls = this._autoHideFoldingControls; this.globalToDispose.push(this.editor.onDidChangeModel(() => this.onModelChanged())); - this.globalToDispose.push(FoldingRangeProviderRegistry.onDidChange(() => this.onFoldingStrategyChanged())); this.globalToDispose.push(this.editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => { if (e.contribInfo) { @@ -193,7 +192,8 @@ export class FoldingController implements IEditorContribution { this.cursorChangedScheduler = new RunOnceScheduler(() => this.revealCursor(), 200); this.localToDispose.push(this.cursorChangedScheduler); - this.localToDispose.push(this.editor.onDidChangeModelLanguageConfiguration(() => this.onModelContentChanged())); // covers model language changes as well + this.localToDispose.push(FoldingRangeProviderRegistry.onDidChange(() => this.onFoldingStrategyChanged())); + this.localToDispose.push(this.editor.onDidChangeModelLanguageConfiguration(() => this.onFoldingStrategyChanged())); // covers model language changes as well this.localToDispose.push(this.editor.onDidChangeModelContent(() => this.onModelContentChanged())); this.localToDispose.push(this.editor.onDidChangeCursorPosition(() => this.onCursorPositionChanged())); this.localToDispose.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); @@ -279,6 +279,9 @@ export class FoldingController implements IEditorContribution { } return foldingModel; }); + }).then(undefined, (err) => { + onUnexpectedError(err); + return null; }); } } @@ -519,7 +522,27 @@ class UnfoldAction extends FoldingAction { * 'direction': If 'up', unfold given number of levels up otherwise unfolds down. * 'selectionLines': The start lines (0-based) of the editor selections to apply the unfold action to. If not set, the active selection(s) will be used. `, - constraint: foldingArgumentsConstraint + constraint: foldingArgumentsConstraint, + schema: { + 'type': 'object', + 'properties': { + 'levels': { + 'type': 'number', + 'default': 1 + }, + 'direction': { + 'type': 'string', + 'enum': ['up', 'down'], + 'default': 'down' + }, + 'selectionLines': { + 'type': 'array', + 'items': { + 'type': 'number' + } + } + } + } } ] } @@ -584,7 +607,27 @@ class FoldAction extends FoldingAction { * 'direction': If 'up', folds given number of levels up otherwise folds down. * 'selectionLines': The start lines (0-based) of the editor selections to apply the fold action to. If not set, the active selection(s) will be used. `, - constraint: foldingArgumentsConstraint + constraint: foldingArgumentsConstraint, + schema: { + 'type': 'object', + 'properties': { + 'levels': { + 'type': 'number', + 'default': 1 + }, + 'direction': { + 'type': 'string', + 'enum': ['up', 'down'], + 'default': 'down' + }, + 'selectionLines': { + 'type': 'array', + 'items': { + 'type': 'number' + } + } + } + } } ] } diff --git a/src/vs/editor/contrib/folding/foldingDecorations.ts b/src/vs/editor/contrib/folding/foldingDecorations.ts index 448b641111..93a720c09b 100644 --- a/src/vs/editor/contrib/folding/foldingDecorations.ts +++ b/src/vs/editor/contrib/folding/foldingDecorations.ts @@ -28,7 +28,7 @@ export class FoldingDecorationProvider implements IDecorationProvider { public autoHideFoldingControls: boolean = true; - constructor(private editor: ICodeEditor) { + constructor(private readonly editor: ICodeEditor) { } getDecorationOption(isCollapsed: boolean): ModelDecorationOptions { diff --git a/src/vs/editor/contrib/folding/foldingModel.ts b/src/vs/editor/contrib/folding/foldingModel.ts index de960d99d2..d7c92ee77f 100644 --- a/src/vs/editor/contrib/folding/foldingModel.ts +++ b/src/vs/editor/contrib/folding/foldingModel.ts @@ -21,8 +21,8 @@ export interface FoldingModelChangeEvent { export type CollapseMemento = ILineRange[]; export class FoldingModel { - private _textModel: ITextModel; - private _decorationProvider: IDecorationProvider; + private readonly _textModel: ITextModel; + private readonly _decorationProvider: IDecorationProvider; private _regions: FoldingRegions; private _editorDecorationIds: string[]; diff --git a/src/vs/editor/contrib/folding/foldingRanges.ts b/src/vs/editor/contrib/folding/foldingRanges.ts index 6bc2b6da83..b1ab0220fd 100644 --- a/src/vs/editor/contrib/folding/foldingRanges.ts +++ b/src/vs/editor/contrib/folding/foldingRanges.ts @@ -14,11 +14,11 @@ export const MAX_LINE_NUMBER = 0xFFFFFF; const MASK_INDENT = 0xFF000000; export class FoldingRegions { - private _startIndexes: Uint32Array; - private _endIndexes: Uint32Array; - private _collapseStates: Uint32Array; + private readonly _startIndexes: Uint32Array; + private readonly _endIndexes: Uint32Array; + private readonly _collapseStates: Uint32Array; private _parentsComputed: boolean; - private _types: Array | undefined; + private readonly _types: Array | undefined; constructor(startIndexes: Uint32Array, endIndexes: Uint32Array, types?: Array) { if (startIndexes.length !== endIndexes.length || startIndexes.length > MAX_FOLDING_REGIONS) { @@ -154,7 +154,7 @@ export class FoldingRegions { export class FoldingRegion { - constructor(private ranges: FoldingRegions, private index: number) { + constructor(private readonly ranges: FoldingRegions, private index: number) { } public get startLineNumber() { diff --git a/src/vs/editor/contrib/folding/hiddenRangeModel.ts b/src/vs/editor/contrib/folding/hiddenRangeModel.ts index ae8aafde80..98b73811e9 100644 --- a/src/vs/editor/contrib/folding/hiddenRangeModel.ts +++ b/src/vs/editor/contrib/folding/hiddenRangeModel.ts @@ -11,7 +11,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { findFirstInSorted } from 'vs/base/common/arrays'; export class HiddenRangeModel { - private _foldingModel: FoldingModel; + private readonly _foldingModel: FoldingModel; private _hiddenRanges: IRange[]; private _foldingModelListener: IDisposable | null; private _updateEventEmitter = new Emitter(); diff --git a/src/vs/editor/contrib/folding/indentRangeProvider.ts b/src/vs/editor/contrib/folding/indentRangeProvider.ts index 3f6e3616cd..7b8b2c0102 100644 --- a/src/vs/editor/contrib/folding/indentRangeProvider.ts +++ b/src/vs/editor/contrib/folding/indentRangeProvider.ts @@ -20,7 +20,7 @@ export class IndentRangeProvider implements RangeProvider { readonly decorations; - constructor(private editorModel: ITextModel) { + constructor(private readonly editorModel: ITextModel) { } dispose() { @@ -36,11 +36,11 @@ export class IndentRangeProvider implements RangeProvider { // public only for testing export class RangesCollector { - private _startIndexes: number[]; - private _endIndexes: number[]; - private _indentOccurrences: number[]; + private readonly _startIndexes: number[]; + private readonly _endIndexes: number[]; + private readonly _indentOccurrences: number[]; private _length: number; - private _foldingRangesLimit: number; + private readonly _foldingRangesLimit: number; constructor(foldingRangesLimit: number) { this._startIndexes = []; diff --git a/src/vs/editor/contrib/folding/intializingRangeProvider.ts b/src/vs/editor/contrib/folding/intializingRangeProvider.ts index 4cc3c9b6de..f562e1d7cd 100644 --- a/src/vs/editor/contrib/folding/intializingRangeProvider.ts +++ b/src/vs/editor/contrib/folding/intializingRangeProvider.ts @@ -17,7 +17,7 @@ export class InitializingRangeProvider implements RangeProvider { private decorationIds: string[] | undefined; private timeout: any; - constructor(private editorModel: ITextModel, initialRanges: ILineRange[], onTimeout: () => void, timeoutTime: number) { + constructor(private readonly editorModel: ITextModel, initialRanges: ILineRange[], onTimeout: () => void, timeoutTime: number) { if (initialRanges.length) { let toDecorationRange = (range: ILineRange): IModelDeltaDecoration => { return { diff --git a/src/vs/editor/contrib/folding/syntaxRangeProvider.ts b/src/vs/editor/contrib/folding/syntaxRangeProvider.ts index ebe922a326..9dea274de8 100644 --- a/src/vs/editor/contrib/folding/syntaxRangeProvider.ts +++ b/src/vs/editor/contrib/folding/syntaxRangeProvider.ts @@ -25,7 +25,7 @@ export class SyntaxRangeProvider implements RangeProvider { readonly id = ID_SYNTAX_PROVIDER; - constructor(private editorModel: ITextModel, private providers: FoldingRangeProvider[], private limit = MAX_FOLDING_REGIONS) { + constructor(private readonly editorModel: ITextModel, private providers: FoldingRangeProvider[], private limit = MAX_FOLDING_REGIONS) { } compute(cancellationToken: CancellationToken): Promise { @@ -69,13 +69,13 @@ function collectSyntaxRanges(providers: FoldingRangeProvider[], model: ITextMode } export class RangesCollector { - private _startIndexes: number[]; - private _endIndexes: number[]; - private _nestingLevels: number[]; - private _nestingLevelCounts: number[]; - private _types: Array; + private readonly _startIndexes: number[]; + private readonly _endIndexes: number[]; + private readonly _nestingLevels: number[]; + private readonly _nestingLevelCounts: number[]; + private readonly _types: Array; private _length: number; - private _foldingRangesLimit: number; + private readonly _foldingRangesLimit: number; constructor(foldingRangesLimit: number) { this._startIndexes = []; diff --git a/src/vs/editor/contrib/format/format.ts b/src/vs/editor/contrib/format/format.ts index 274d8015cf..74bf8ab378 100644 --- a/src/vs/editor/contrib/format/format.ts +++ b/src/vs/editor/contrib/format/format.ts @@ -8,68 +8,168 @@ import { URI } from 'vs/base/common/uri'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; -import { registerDefaultLanguageCommand, registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; +import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { DocumentFormattingEditProviderRegistry, DocumentRangeFormattingEditProviderRegistry, OnTypeFormattingEditProviderRegistry, FormattingOptions, TextEdit } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; import { first } from 'vs/base/common/async'; import { Position } from 'vs/editor/common/core/position'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IDisposable } from 'vs/base/common/lifecycle'; -export class NoProviderError extends Error { +export const enum FormatMode { + Auto = 1, + Manual = 2, +} - static is(thing: any): thing is NoProviderError { - return thing instanceof Error && thing.name === NoProviderError._name; - } +export const enum FormatKind { + Document = 8, + Range = 16, + OnType = 32, +} - private static readonly _name = 'NOPRO'; +export interface IFormatterConflictCallback { + (extensionIds: (ExtensionIdentifier | undefined)[], model: ITextModel, mode: number): void; +} - constructor(message?: string) { - super(); - this.name = NoProviderError._name; - if (message) { - this.message = message; +let _conflictResolver: IFormatterConflictCallback | undefined; + +export function setFormatterConflictCallback(callback: IFormatterConflictCallback): IDisposable { + let oldCallback = _conflictResolver; + _conflictResolver = callback; + return { + dispose() { + if (oldCallback) { + _conflictResolver = oldCallback; + oldCallback = undefined; + } } + }; +} + +function invokeFormatterCallback(formatter: T[], model: ITextModel, mode: number): void { + if (_conflictResolver) { + const ids = formatter.map(formatter => formatter.extensionId); + _conflictResolver(ids, model, mode); } } -export function getDocumentRangeFormattingEdits(model: ITextModel, range: Range, options: FormattingOptions, token: CancellationToken): Promise { +export async function getDocumentRangeFormattingEdits( + telemetryService: ITelemetryService, + workerService: IEditorWorkerService, + model: ITextModel, + range: Range, + options: FormattingOptions, + mode: FormatMode, + token: CancellationToken +): Promise { const providers = DocumentRangeFormattingEditProviderRegistry.ordered(model); - if (providers.length === 0) { - return Promise.reject(new NoProviderError()); - } + /* __GDPR__ + "formatterInfo" : { + "type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "language" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "extensions" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + telemetryService.publicLog('formatterInfo', { + type: 'range', + language: model.getLanguageIdentifier().language, + count: providers.length, + extensions: providers.map(p => p.extensionId ? ExtensionIdentifier.toKey(p.extensionId) : 'unknown') + }); + + invokeFormatterCallback(providers, model, mode | FormatKind.Range); return first(providers.map(provider => () => { - return Promise.resolve(provider.provideDocumentRangeFormattingEdits(model, range, options, token)) - .then(undefined, onUnexpectedExternalError); - }), isNonEmptyArray); + return Promise.resolve(provider.provideDocumentRangeFormattingEdits(model, range, options, token)).catch(onUnexpectedExternalError); + }), isNonEmptyArray).then(edits => { + // break edits into smaller edits + return workerService.computeMoreMinimalEdits(model.uri, edits); + }); } -export function getDocumentFormattingEdits(model: ITextModel, options: FormattingOptions, token: CancellationToken): Promise { - const providers = DocumentFormattingEditProviderRegistry.ordered(model); +export function getDocumentFormattingEdits( + telemetryService: ITelemetryService, + workerService: IEditorWorkerService, + model: ITextModel, + options: FormattingOptions, + mode: FormatMode, + token: CancellationToken +): Promise { + + const docFormattingProviders = DocumentFormattingEditProviderRegistry.ordered(model); + + /* __GDPR__ + "formatterInfo" : { + "type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "language" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "extensions" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + telemetryService.publicLog('formatterInfo', { + type: 'document', + language: model.getLanguageIdentifier().language, + count: docFormattingProviders.length, + extensions: docFormattingProviders.map(p => p.extensionId ? ExtensionIdentifier.toKey(p.extensionId) : 'unknown') + }); + + if (docFormattingProviders.length > 0) { + return first(docFormattingProviders.map(provider => () => { + // first with result wins... + return Promise.resolve(provider.provideDocumentFormattingEdits(model, options, token)).catch(onUnexpectedExternalError); + }), isNonEmptyArray).then(edits => { + // break edits into smaller edits + return workerService.computeMoreMinimalEdits(model.uri, edits); + }); + } else { + // try range formatters when no document formatter is registered + return getDocumentRangeFormattingEdits(telemetryService, workerService, model, model.getFullModelRange(), options, mode | FormatKind.Document, token); + } +} + +export function getOnTypeFormattingEdits( + telemetryService: ITelemetryService, + workerService: IEditorWorkerService, + model: ITextModel, + position: Position, + ch: string, + options: FormattingOptions +): Promise { + + const providers = OnTypeFormattingEditProviderRegistry.ordered(model); + + /* __GDPR__ + "formatterInfo" : { + "type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "language" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "extensions" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + telemetryService.publicLog('formatterInfo', { + type: 'ontype', + language: model.getLanguageIdentifier().language, + count: providers.length, + extensions: providers.map(p => p.extensionId ? ExtensionIdentifier.toKey(p.extensionId) : 'unknown') + }); - // try range formatters when no document formatter is registered if (providers.length === 0) { - return getDocumentRangeFormattingEdits(model, model.getFullModelRange(), options, token); - } - - return first(providers.map(provider => () => { - return Promise.resolve(provider.provideDocumentFormattingEdits(model, options, token)) - .then(undefined, onUnexpectedExternalError); - }), isNonEmptyArray); -} - -export function getOnTypeFormattingEdits(model: ITextModel, position: Position, ch: string, options: FormattingOptions): Promise { - const [support] = OnTypeFormattingEditProviderRegistry.ordered(model); - if (!support) { - return Promise.resolve(undefined); - } - if (support.autoFormatTriggerCharacters.indexOf(ch) < 0) { return Promise.resolve(undefined); } - return Promise.resolve(support.provideOnTypeFormattingEdits(model, position, ch, options, CancellationToken.None)).then(r => r, onUnexpectedExternalError); + if (providers[0].autoFormatTriggerCharacters.indexOf(ch) < 0) { + return Promise.resolve(undefined); + } + + return Promise.resolve(providers[0].provideOnTypeFormattingEdits(model, position, ch, options, CancellationToken.None)).catch(onUnexpectedExternalError).then(edits => { + return workerService.computeMoreMinimalEdits(model.uri, edits); + }); } registerLanguageCommand('_executeFormatRangeProvider', function (accessor, args) { @@ -81,7 +181,7 @@ registerLanguageCommand('_executeFormatRangeProvider', function (accessor, args) if (!model) { throw illegalArgument('resource'); } - return getDocumentRangeFormattingEdits(model, Range.lift(range), options, CancellationToken.None); + return getDocumentRangeFormattingEdits(accessor.get(ITelemetryService), accessor.get(IEditorWorkerService), model, Range.lift(range), options, FormatMode.Auto, CancellationToken.None); }); registerLanguageCommand('_executeFormatDocumentProvider', function (accessor, args) { @@ -94,13 +194,18 @@ registerLanguageCommand('_executeFormatDocumentProvider', function (accessor, ar throw illegalArgument('resource'); } - return getDocumentFormattingEdits(model, options, CancellationToken.None); + return getDocumentFormattingEdits(accessor.get(ITelemetryService), accessor.get(IEditorWorkerService), model, options, FormatMode.Auto, CancellationToken.None); }); -registerDefaultLanguageCommand('_executeFormatOnTypeProvider', function (model, position, args) { - const { ch, options } = args; - if (typeof ch !== 'string') { - throw illegalArgument('ch'); +registerLanguageCommand('_executeFormatOnTypeProvider', function (accessor, args) { + const { resource, position, ch, options } = args; + if (!(resource instanceof URI) || !Position.isIPosition(position) || typeof ch !== 'string') { + throw illegalArgument(); } - return getOnTypeFormattingEdits(model, position, ch, options); + const model = accessor.get(IModelService).getModel(resource); + if (!model) { + throw illegalArgument('resource'); + } + + return getOnTypeFormattingEdits(accessor.get(ITelemetryService), accessor.get(IEditorWorkerService), model, Position.lift(position), ch, options); }); diff --git a/src/vs/editor/contrib/format/formatActions.ts b/src/vs/editor/contrib/format/formatActions.ts index e0a8ba6d71..51255e9f36 100644 --- a/src/vs/editor/contrib/format/formatActions.ts +++ b/src/vs/editor/contrib/format/formatActions.ts @@ -5,7 +5,6 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { sequence } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; @@ -18,17 +17,15 @@ import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ISingleEditOperation } from 'vs/editor/common/model'; -import { DocumentFormattingEditProviderRegistry, DocumentRangeFormattingEditProviderRegistry, FormattingOptions, OnTypeFormattingEditProviderRegistry } from 'vs/editor/common/modes'; +import { DocumentRangeFormattingEditProviderRegistry, FormattingOptions, OnTypeFormattingEditProviderRegistry } from 'vs/editor/common/modes'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; -import { getOnTypeFormattingEdits, NoProviderError } from 'vs/editor/contrib/format/format'; +import { getOnTypeFormattingEdits, getDocumentFormattingEdits, getDocumentRangeFormattingEdits, FormatMode } from 'vs/editor/contrib/format/format'; import { FormattingEdit } from 'vs/editor/contrib/format/formattingEdit'; import * as nls from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { INotificationService } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { MenuRegistry } from 'vs/platform/actions/common/actions'; function alertFormattingEdits(edits: ISingleEditOperation[]): void { @@ -57,131 +54,69 @@ function alertFormattingEdits(edits: ISingleEditOperation[]): void { } } -export const enum FormatRangeType { +const enum FormatRangeType { Full, Selection, } -export function formatDocumentRange(telemetryService: ITelemetryService, workerService: IEditorWorkerService, editor: IActiveCodeEditor, rangeOrRangeType: Range | FormatRangeType, options: FormattingOptions, token: CancellationToken): Promise { +function formatDocumentRange( + telemetryService: ITelemetryService, + workerService: IEditorWorkerService, + editor: IActiveCodeEditor, + rangeOrRangeType: Range | FormatRangeType, + options: FormattingOptions, + token: CancellationToken +): Promise { - const provider = DocumentRangeFormattingEditProviderRegistry.ordered(editor.getModel()); - if (provider.length === 0) { - return Promise.reject(new NoProviderError()); + + const state = new EditorState(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position); + const model = editor.getModel(); + + let range: Range; + if (rangeOrRangeType === FormatRangeType.Full) { + // full + range = model.getFullModelRange(); + + } else if (rangeOrRangeType === FormatRangeType.Selection) { + // selection or line (when empty) + range = editor.getSelection(); + if (range.isEmpty()) { + range = new Range(range.startLineNumber, 1, range.endLineNumber, model.getLineMaxColumn(range.endLineNumber)); + } + } else { + // as is + range = rangeOrRangeType; } - // Know how often multiple providers clash and (for now) - // continue picking the 'first' provider - if (provider.length !== 1) { - /* __GDPR__ - "manyformatters" : { - "type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "language" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - telemetryService.publicLog('manyformatters', { - type: 'range', - language: editor.getModel().getLanguageIdentifier().language, - count: provider.length, - }); - provider.length = 1; - } - - let allEdits: ISingleEditOperation[] = []; - - editor.pushUndoStop(); - return sequence(provider.map(provider => { - // create a formatting task per provider. they run sequentially, - // potentially undoing the working of a previous formatter - return () => { - const state = new EditorState(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position); - const model = editor.getModel(); - - let range: Range; - if (rangeOrRangeType === FormatRangeType.Full) { - // full - range = model.getFullModelRange(); - - } else if (rangeOrRangeType === FormatRangeType.Selection) { - // selection or line (when empty) - range = editor.getSelection(); - if (range.isEmpty()) { - range = new Range(range.startLineNumber, 1, range.endLineNumber, model.getLineMaxColumn(range.endLineNumber)); - } - } else { - // as is - range = rangeOrRangeType; - } - return Promise.resolve(provider.provideDocumentRangeFormattingEdits(model, range, options, token)).then(edits => { - // break edits into smaller edits - return workerService.computeMoreMinimalEdits(editor.getModel().uri, edits); - }).then(edits => { - // make edit only when the editor didn't change while - // computing and only when there are edits - if (state.validate(editor) && isNonEmptyArray(edits)) { - FormattingEdit.execute(editor, edits); - allEdits = allEdits.concat(edits); - } - }); - }; - })).then(() => { - alertFormattingEdits(allEdits); - editor.pushUndoStop(); - editor.focus(); - editor.revealPositionInCenterIfOutsideViewport(editor.getPosition(), editorCommon.ScrollType.Immediate); + return getDocumentRangeFormattingEdits(telemetryService, workerService, model, range, options, FormatMode.Manual, token).then(edits => { + // make edit only when the editor didn't change while + // computing and only when there are edits + if (state.validate(editor) && isNonEmptyArray(edits)) { + FormattingEdit.execute(editor, edits); + alertFormattingEdits(edits); + editor.focus(); + editor.revealPositionInCenterIfOutsideViewport(editor.getPosition(), editorCommon.ScrollType.Immediate); + } }); + } -export function formatDocument(telemetryService: ITelemetryService, workerService: IEditorWorkerService, editor: IActiveCodeEditor, options: FormattingOptions, token: CancellationToken): Promise { - const provider = DocumentFormattingEditProviderRegistry.ordered(editor.getModel()); - if (provider.length === 0) { - return formatDocumentRange(telemetryService, workerService, editor, FormatRangeType.Full, options, token); - } +function formatDocument(telemetryService: ITelemetryService, workerService: IEditorWorkerService, editor: IActiveCodeEditor, options: FormattingOptions, token: CancellationToken): Promise { - // Know how often multiple providers clash and (for now) - // continue picking the 'first' provider - if (provider.length !== 1) { - /* __GDPR__ - "manyformatters" : { - "type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "language" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - telemetryService.publicLog('manyformatters', { - type: 'document', - language: editor.getModel().getLanguageIdentifier().language, - count: provider.length, - }); - provider.length = 1; - } + const allEdits: ISingleEditOperation[] = []; + const state = new EditorState(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position); - let allEdits: ISingleEditOperation[] = []; + return getDocumentFormattingEdits(telemetryService, workerService, editor.getModel(), options, FormatMode.Manual, token).then(edits => { + // make edit only when the editor didn't change while + // computing and only when there are edits + if (state.validate(editor) && isNonEmptyArray(edits)) { + FormattingEdit.execute(editor, edits); - editor.pushUndoStop(); - return sequence(provider.map(provider => { - // create a formatting task per provider. they run sequentially, - // potentially undoing the working of a previous formatter - return () => { - const state = new EditorState(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position); - const model = editor.getModel(); - return Promise.resolve(provider.provideDocumentFormattingEdits(model, options, token)).then(edits => { - // break edits into smaller edits - return workerService.computeMoreMinimalEdits(editor.getModel().uri, edits); - }).then(edits => { - // make edit only when the editor didn't change while - // computing and only when there are edits - if (state.validate(editor) && isNonEmptyArray(edits)) { - FormattingEdit.execute(editor, edits); - allEdits = allEdits.concat(edits); - } - }); - }; - })).then(() => { - alertFormattingEdits(allEdits); - editor.pushUndoStop(); - editor.focus(); - editor.revealPositionInCenterIfOutsideViewport(editor.getPosition(), editorCommon.ScrollType.Immediate); + alertFormattingEdits(allEdits); + editor.pushUndoStop(); + editor.focus(); + editor.revealPositionInCenterIfOutsideViewport(editor.getPosition(), editorCommon.ScrollType.Immediate); + } }); } @@ -189,39 +124,38 @@ class FormatOnType implements editorCommon.IEditorContribution { private static readonly ID = 'editor.contrib.autoFormat'; - private editor: ICodeEditor; - private workerService: IEditorWorkerService; - private callOnDispose: IDisposable[]; - private callOnModel: IDisposable[]; + private readonly _editor: ICodeEditor; + private _callOnDispose: IDisposable[] = []; + private _callOnModel: IDisposable[] = []; - constructor(editor: ICodeEditor, @IEditorWorkerService workerService: IEditorWorkerService) { - this.editor = editor; - this.workerService = workerService; - this.callOnDispose = []; - this.callOnModel = []; - - this.callOnDispose.push(editor.onDidChangeConfiguration(() => this.update())); - this.callOnDispose.push(editor.onDidChangeModel(() => this.update())); - this.callOnDispose.push(editor.onDidChangeModelLanguage(() => this.update())); - this.callOnDispose.push(OnTypeFormattingEditProviderRegistry.onDidChange(this.update, this)); + constructor( + editor: ICodeEditor, + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IEditorWorkerService private readonly _workerService: IEditorWorkerService + ) { + this._editor = editor; + this._callOnDispose.push(editor.onDidChangeConfiguration(() => this.update())); + this._callOnDispose.push(editor.onDidChangeModel(() => this.update())); + this._callOnDispose.push(editor.onDidChangeModelLanguage(() => this.update())); + this._callOnDispose.push(OnTypeFormattingEditProviderRegistry.onDidChange(this.update, this)); } private update(): void { // clean up - this.callOnModel = dispose(this.callOnModel); + this._callOnModel = dispose(this._callOnModel); // we are disabled - if (!this.editor.getConfiguration().contribInfo.formatOnType) { + if (!this._editor.getConfiguration().contribInfo.formatOnType) { return; } // no model - if (!this.editor.hasModel()) { + if (!this._editor.hasModel()) { return; } - const model = this.editor.getModel(); + const model = this._editor.getModel(); // no support const [support] = OnTypeFormattingEditProviderRegistry.ordered(model); @@ -234,7 +168,7 @@ class FormatOnType implements editorCommon.IEditorContribution { for (let ch of support.autoFormatTriggerCharacters) { triggerChars.add(ch.charCodeAt(0)); } - this.callOnModel.push(this.editor.onDidType((text: string) => { + this._callOnModel.push(this._editor.onDidType((text: string) => { let lastCharCode = text.charCodeAt(text.length - 1); if (triggerChars.has(lastCharCode)) { this.trigger(String.fromCharCode(lastCharCode)); @@ -243,22 +177,22 @@ class FormatOnType implements editorCommon.IEditorContribution { } private trigger(ch: string): void { - if (!this.editor.hasModel()) { + if (!this._editor.hasModel()) { return; } - if (this.editor.getSelections().length > 1) { + if (this._editor.getSelections().length > 1) { return; } - const model = this.editor.getModel(); - const position = this.editor.getPosition(); + const model = this._editor.getModel(); + const position = this._editor.getPosition(); let canceled = false; // install a listener that checks if edits happens before the // position on which we format right now. If so, we won't // apply the format edits - const unbind = this.editor.onDidChangeModelContent((e) => { + const unbind = this._editor.onDidChangeModelContent((e) => { if (e.isFlush) { // a model.setValue() was called // cancel only once @@ -279,14 +213,14 @@ class FormatOnType implements editorCommon.IEditorContribution { }); - let modelOpts = model.getOptions(); - - getOnTypeFormattingEdits(model, position, ch, { - tabSize: modelOpts.tabSize, - insertSpaces: modelOpts.insertSpaces - }).then(edits => { - return this.workerService.computeMoreMinimalEdits(model.uri, edits); - }).then(edits => { + getOnTypeFormattingEdits( + this._telemetryService, + this._workerService, + model, + position, + ch, + model.getFormattingOptions() + ).then(edits => { unbind.dispose(); @@ -295,7 +229,7 @@ class FormatOnType implements editorCommon.IEditorContribution { } if (isNonEmptyArray(edits)) { - FormattingEdit.execute(this.editor, edits); + FormattingEdit.execute(this._editor, edits); alertFormattingEdits(edits); } @@ -310,8 +244,8 @@ class FormatOnType implements editorCommon.IEditorContribution { } public dispose(): void { - this.callOnDispose = dispose(this.callOnDispose); - this.callOnModel = dispose(this.callOnModel); + this._callOnDispose = dispose(this._callOnDispose); + this._callOnModel = dispose(this._callOnModel); } } @@ -373,8 +307,7 @@ class FormatOnPaste implements editorCommon.IEditorContribution { } const model = this.editor.getModel(); - const { tabSize, insertSpaces } = model.getOptions(); - formatDocumentRange(this.telemetryService, this.workerService, this.editor, range, { tabSize, insertSpaces }, CancellationToken.None); + formatDocumentRange(this.telemetryService, this.workerService, this.editor, range, model.getFormattingOptions(), CancellationToken.None); } public getId(): string { @@ -414,15 +347,9 @@ export class FormatDocumentAction extends EditorAction { if (!editor.hasModel()) { return; } - const notificationService = accessor.get(INotificationService); const workerService = accessor.get(IEditorWorkerService); const telemetryService = accessor.get(ITelemetryService); - const { tabSize, insertSpaces } = editor.getModel().getOptions(); - return formatDocument(telemetryService, workerService, editor, { tabSize, insertSpaces }, CancellationToken.None).catch(err => { - if (NoProviderError.is(err)) { - notificationService.info(nls.localize('no.documentprovider', "There is no document formatter for '{0}'-files installed.", editor.getModel().getLanguageIdentifier().language)); - } - }); + return formatDocument(telemetryService, workerService, editor, editor.getModel().getFormattingOptions(), CancellationToken.None); } } @@ -451,15 +378,9 @@ export class FormatSelectionAction extends EditorAction { if (!editor.hasModel()) { return; } - const notificationService = accessor.get(INotificationService); const workerService = accessor.get(IEditorWorkerService); const telemetryService = accessor.get(ITelemetryService); - const { tabSize, insertSpaces } = editor.getModel().getOptions(); - return formatDocumentRange(telemetryService, workerService, editor, FormatRangeType.Selection, { tabSize, insertSpaces }, CancellationToken.None).catch(err => { - if (NoProviderError.is(err)) { - notificationService.info(nls.localize('no.selectionprovider', "There is no selection formatter for '{0}'-files installed.", editor.getModel().getLanguageIdentifier().language)); - } - }); + return formatDocumentRange(telemetryService, workerService, editor, FormatRangeType.Selection, editor.getModel().getFormattingOptions(), CancellationToken.None); } } @@ -475,48 +396,12 @@ CommandsRegistry.registerCommand('editor.action.format', accessor => { if (!editor || !editor.hasModel()) { return undefined; } - const { tabSize, insertSpaces } = editor.getModel().getOptions(); const workerService = accessor.get(IEditorWorkerService); const telemetryService = accessor.get(ITelemetryService); if (editor.getSelection().isEmpty()) { - return formatDocument(telemetryService, workerService, editor, { tabSize, insertSpaces }, CancellationToken.None); + return formatDocument(telemetryService, workerService, editor, editor.getModel().getFormattingOptions(), CancellationToken.None); } else { - return formatDocumentRange(telemetryService, workerService, editor, FormatRangeType.Selection, { tabSize, insertSpaces }, CancellationToken.None); + return formatDocumentRange(telemetryService, workerService, editor, FormatRangeType.Selection, editor.getModel().getFormattingOptions(), CancellationToken.None); } }); - - -CommandsRegistry.registerCommand('editor.action.formatInspect', accessor => { - - const editor = accessor.get(ICodeEditorService).getActiveCodeEditor(); - if (!editor || !editor.hasModel()) { - return; - } - console.log(`Available Formatters for: ${editor.getModel().uri.toString(true)}`); - // range formatters - const documentRangeProvider = DocumentRangeFormattingEditProviderRegistry.ordered(editor.getModel()); - console.group('Range Formatters'); - if (documentRangeProvider.length === 0) { - console.log('none'); - } else { - documentRangeProvider.forEach(value => console.log(value.displayName)); - } - console.groupEnd(); - - // whole document formatters - const documentProvider = DocumentFormattingEditProviderRegistry.ordered(editor.getModel()); - console.group('Document Formatters'); - if (documentProvider.length === 0) { - console.log('none'); - } else { - documentProvider.forEach(value => console.log(value.displayName)); - } - console.groupEnd(); -}); - -MenuRegistry.addCommand({ - id: 'editor.action.formatInspect', - category: nls.localize('cat', "Developer"), - title: nls.localize('title', "Print Available Formatters..."), -}); diff --git a/src/vs/editor/contrib/format/formattingEdit.ts b/src/vs/editor/contrib/format/formattingEdit.ts index 593040af4d..431adda97c 100644 --- a/src/vs/editor/contrib/format/formattingEdit.ts +++ b/src/vs/editor/contrib/format/formattingEdit.ts @@ -45,7 +45,7 @@ export class FormattingEdit { static execute(editor: ICodeEditor, _edits: TextEdit[]) { editor.pushUndoStop(); - let edits = FormattingEdit._handleEolEdits(editor, _edits); + const edits = FormattingEdit._handleEolEdits(editor, _edits); if (edits.length === 1 && FormattingEdit._isFullModelReplaceEdit(editor, edits[0])) { // We use replace semantics and hope that markers stay put... editor.executeEdits('formatEditsCommand', edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts index 3515a26049..5832495666 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts @@ -51,6 +51,9 @@ export class DefinitionAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + if (!editor.hasModel()) { + return Promise.resolve(undefined); + } const notificationService = accessor.get(INotificationService); const editorService = accessor.get(ICodeEditorService); const progressService = accessor.get(IProgressService); @@ -112,14 +115,14 @@ export class DefinitionAction extends EditorAction { return getDefinitionsAtPosition(model, position, token); } - protected _getNoResultFoundMessage(info?: IWordAtPosition): string { + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { return info && info.word ? nls.localize('noResultWord', "No definition found for '{0}'", info.word) : nls.localize('generic.noResults', "No definition found"); } protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 && nls.localize('meta.title', " – {0} definitions", model.references.length); + return model.references.length > 1 ? nls.localize('meta.title', " – {0} definitions", model.references.length) : ''; } private async _onResult(editorService: ICodeEditorService, editor: ICodeEditor, model: ReferencesModel): Promise { @@ -127,23 +130,26 @@ export class DefinitionAction extends EditorAction { const msg = model.getAriaMessage(); alert(msg); - if (this._configuration.openInPeek) { + const { gotoLocation } = editor.getConfiguration().contribInfo; + if (this._configuration.openInPeek || (gotoLocation.many === 'peek' && model.references.length > 1)) { this._openInPeek(editorService, editor, model); - } else { + } else if (editor.hasModel()) { const next = model.nearestReference(editor.getModel().uri, editor.getPosition()); - const targetEditor = await this._openReference(editor, editorService, next, this._configuration.openToSide); - if (targetEditor && model.references.length > 1) { - this._openInPeek(editorService, targetEditor, model); - } else { - model.dispose(); + if (next) { + const targetEditor = await this._openReference(editor, editorService, next, this._configuration.openToSide); + if (targetEditor && model.references.length > 1 && gotoLocation.many === 'revealAndPeek') { + this._openInPeek(editorService, targetEditor, model); + } else { + model.dispose(); + } } } } - private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean): Promise { + private _openReference(editor: ICodeEditor, editorService: ICodeEditorService, reference: Location | LocationLink, sideBySide: boolean): Promise { // range is the target-selection-range when we have one // and the the fallback is the 'full' range - let range: IRange = undefined; + let range: IRange | undefined = undefined; if (isLocationLink(reference)) { range = reference.targetSelectionRange; } @@ -163,7 +169,7 @@ export class DefinitionAction extends EditorAction { private _openInPeek(editorService: ICodeEditorService, target: ICodeEditor, model: ReferencesModel) { let controller = ReferencesController.get(target); - if (controller) { + if (controller && target.hasModel()) { controller.toggleWidget(target.getSelection(), createCancelablePromise(_ => Promise.resolve(model)), { getMetaTitle: (model) => { return this._getMetaTitle(model); @@ -265,14 +271,14 @@ export class DeclarationAction extends DefinitionAction { return getDeclarationsAtPosition(model, position, token); } - protected _getNoResultFoundMessage(info?: IWordAtPosition): string { + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { return info && info.word ? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word) : nls.localize('decl.generic.noResults', "No declaration found"); } protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 && nls.localize('decl.meta.title', " – {0} declarations", model.references.length); + return model.references.length > 1 ? nls.localize('decl.meta.title', " – {0} declarations", model.references.length) : ''; } } @@ -295,14 +301,14 @@ export class GoToDeclarationAction extends DeclarationAction { }); } - protected _getNoResultFoundMessage(info?: IWordAtPosition): string { + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { return info && info.word ? nls.localize('decl.noResultWord', "No declaration found for '{0}'", info.word) : nls.localize('decl.generic.noResults', "No declaration found"); } protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 && nls.localize('decl.meta.title', " – {0} declarations", model.references.length); + return model.references.length > 1 ? nls.localize('decl.meta.title', " – {0} declarations", model.references.length) : ''; } } @@ -329,14 +335,14 @@ export class ImplementationAction extends DefinitionAction { return getImplementationsAtPosition(model, position, token); } - protected _getNoResultFoundMessage(info?: IWordAtPosition): string { + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { return info && info.word ? nls.localize('goToImplementation.noResultWord', "No implementation found for '{0}'", info.word) : nls.localize('goToImplementation.generic.noResults', "No implementation found"); } protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 && nls.localize('meta.implementations.title', " – {0} implementations", model.references.length); + return model.references.length > 1 ? nls.localize('meta.implementations.title', " – {0} implementations", model.references.length) : ''; } } @@ -387,14 +393,14 @@ export class TypeDefinitionAction extends DefinitionAction { return getTypeDefinitionsAtPosition(model, position, token); } - protected _getNoResultFoundMessage(info?: IWordAtPosition): string { + protected _getNoResultFoundMessage(info: IWordAtPosition | null): string { return info && info.word ? nls.localize('goToTypeDefinition.noResultWord', "No type definition found for '{0}'", info.word) : nls.localize('goToTypeDefinition.generic.noResults', "No type definition found"); } protected _getMetaTitle(model: ReferencesModel): string { - return model.references.length > 1 && nls.localize('meta.typeDefinitions.title', " – {0} type definitions", model.references.length); + return model.references.length > 1 ? nls.localize('meta.typeDefinitions.title', " – {0} type definitions", model.references.length) : ''; } } diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts index 8f0b934a8f..6d2b86c198 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts @@ -25,17 +25,18 @@ import { DefinitionAction, DefinitionActionConfig } from './goToDefinitionComman import { ClickLinkGesture, ClickLinkMouseEvent, ClickLinkKeyboardEvent } from 'vs/editor/contrib/goToDefinition/clickLinkGesture'; import { IWordAtPosition, IModelDeltaDecoration, ITextModel, IFoundBracket } from 'vs/editor/common/model'; import { Position } from 'vs/editor/common/core/position'; +import { withNullAsUndefined } from 'vs/base/common/types'; class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorContribution { private static readonly ID = 'editor.contrib.gotodefinitionwithmouse'; static MAX_SOURCE_PREVIEW_LINES = 8; - private editor: ICodeEditor; + private readonly editor: ICodeEditor; private toUnhook: IDisposable[]; private decorations: string[]; - private currentWordUnderMouse: IWordAtPosition; - private previousPromise: CancelablePromise; + private currentWordUnderMouse: IWordAtPosition | null; + private previousPromise: CancelablePromise | null; constructor( editor: ICodeEditor, @@ -51,7 +52,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC this.toUnhook.push(linkGesture); this.toUnhook.push(linkGesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, keyboardEvent]) => { - this.startFindDefinition(mouseEvent, keyboardEvent); + this.startFindDefinition(mouseEvent, withNullAsUndefined(keyboardEvent)); })); this.toUnhook.push(linkGesture.onExecute((mouseEvent: ClickLinkMouseEvent) => { @@ -79,20 +80,20 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC return; } - if (!this.isEnabled(mouseEvent, withKey)) { + if (!this.editor.hasModel() || !this.isEnabled(mouseEvent, withKey)) { this.currentWordUnderMouse = null; this.removeDecorations(); return; } // Find word at mouse position - let position = mouseEvent.target.position; - let word = position ? this.editor.getModel().getWordAtPosition(position) : null; + const word = mouseEvent.target.position ? this.editor.getModel().getWordAtPosition(mouseEvent.target.position) : null; if (!word) { this.currentWordUnderMouse = null; this.removeDecorations(); return; } + const position = mouseEvent.target.position!; // Return early if word at position is still the same if (this.currentWordUnderMouse && this.currentWordUnderMouse.startColumn === word.startColumn && this.currentWordUnderMouse.endColumn === word.endColumn && this.currentWordUnderMouse.word === word.word) { @@ -158,9 +159,10 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC wordRange = new Range(position.lineNumber, word.startColumn, position.lineNumber, word.endColumn); } + const modeId = this.modeService.getModeIdByFilepathOrFirstLine(textEditorModel.uri.fsPath); this.addDecoration( wordRange, - new MarkdownString().appendCodeblock(this.modeService.getModeIdByFilepathOrFirstLine(textEditorModel.uri.fsPath), previewValue) + new MarkdownString().appendCodeblock(modeId ? modeId : '', previewValue) ); ref.dispose(); }); @@ -274,10 +276,10 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC } private isEnabled(mouseEvent: ClickLinkMouseEvent, withKey?: ClickLinkKeyboardEvent): boolean { - return this.editor.getModel() && + return this.editor.hasModel() && mouseEvent.isNoneOrSingleMouseDown && (mouseEvent.target.type === MouseTargetType.CONTENT_TEXT) && - (mouseEvent.hasTriggerModifier || (withKey && withKey.keyCodeIsTriggerKey)) && + (mouseEvent.hasTriggerModifier || (withKey ? withKey.keyCodeIsTriggerKey : false)) && DefinitionProviderRegistry.has(this.editor.getModel()); } @@ -287,12 +289,12 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC return Promise.resolve(null); } - return getDefinitionsAtPosition(model, target.position, token); + return getDefinitionsAtPosition(model, target.position!, token); } private gotoDefinition(target: IMouseTarget, sideBySide: boolean): Promise { - this.editor.setPosition(target.position); - const action = new DefinitionAction(new DefinitionActionConfig(sideBySide, false, true, false), { alias: undefined, label: undefined, id: undefined, precondition: undefined }); + this.editor.setPosition(target.position!); + const action = new DefinitionAction(new DefinitionActionConfig(sideBySide, false, true, false), { alias: '', label: '', id: '', precondition: null }); return this.editor.invokeWithinContext(accessor => action.run(accessor, this.editor)); } diff --git a/src/vs/editor/contrib/gotoError/gotoError.ts b/src/vs/editor/contrib/gotoError/gotoError.ts index ee59fc5007..6eeae6aee1 100644 --- a/src/vs/editor/contrib/gotoError/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/gotoError.ts @@ -24,10 +24,12 @@ import { binarySearch } from 'vs/base/common/arrays'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { onUnexpectedError } from 'vs/base/common/errors'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { Action } from 'vs/base/common/actions'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; class MarkerModel { - private _editor: ICodeEditor; + private readonly _editor: ICodeEditor; private _markers: IMarker[]; private _nextIdx: number; private _toUnbind: IDisposable[]; @@ -120,6 +122,17 @@ class MarkerModel { return this.canNavigate() ? this._markers[this._nextIdx] : undefined; } + set currentMarker(marker: IMarker | undefined) { + const idx = this._nextIdx; + this._nextIdx = -1; + if (marker) { + this._nextIdx = this.indexOf(marker); + } + if (this._nextIdx !== idx) { + this._onCurrentMarkerChanged.fire(marker); + } + } + public move(fwd: boolean, inCircles: boolean): boolean { if (!this.canNavigate()) { this._onCurrentMarkerChanged.fire(undefined); @@ -181,7 +194,7 @@ class MarkerModel { } } -class MarkerController implements editorCommon.IEditorContribution { +export class MarkerController implements editorCommon.IEditorContribution { private static readonly ID = 'editor.contrib.markerController'; @@ -189,10 +202,10 @@ class MarkerController implements editorCommon.IEditorContribution { return editor.getContribution(MarkerController.ID); } - private _editor: ICodeEditor; + private readonly _editor: ICodeEditor; private _model: MarkerModel | null; private _widget: MarkerNavigationWidget | null; - private _widgetVisible: IContextKey; + private readonly _widgetVisible: IContextKey; private _disposeOnClose: IDisposable[] = []; constructor( @@ -200,7 +213,8 @@ class MarkerController implements editorCommon.IEditorContribution { @IMarkerService private readonly _markerService: IMarkerService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IThemeService private readonly _themeService: IThemeService, - @ICodeEditorService private readonly _editorService: ICodeEditorService + @ICodeEditorService private readonly _editorService: ICodeEditorService, + @IKeybindingService private readonly _keybindingService: IKeybindingService ) { this._editor = editor; this._widgetVisible = CONTEXT_MARKERS_NAVIGATION_VISIBLE.bindTo(this._contextKeyService); @@ -231,11 +245,19 @@ class MarkerController implements editorCommon.IEditorContribution { this._model = new MarkerModel(this._editor, markers); this._markerService.onMarkerChanged(this._onMarkerChanged, this, this._disposeOnClose); - this._widget = new MarkerNavigationWidget(this._editor, this._themeService); + const prevMarkerKeybinding = this._keybindingService.lookupKeybinding(PrevMarkerAction.ID); + const nextMarkerKeybinding = this._keybindingService.lookupKeybinding(NextMarkerAction.ID); + const actions = [ + new Action(PrevMarkerAction.ID, PrevMarkerAction.LABEL + (prevMarkerKeybinding ? ` (${prevMarkerKeybinding.getLabel()})` : ''), 'show-previous-problem chevron-up', this._model.canNavigate(), async () => { if (this._model) { this._model.move(false, true); } }), + new Action(NextMarkerAction.ID, NextMarkerAction.LABEL + (nextMarkerKeybinding ? ` (${nextMarkerKeybinding.getLabel()})` : ''), 'show-next-problem chevron-down', this._model.canNavigate(), async () => { if (this._model) { this._model.move(true, true); } }) + ]; + this._widget = new MarkerNavigationWidget(this._editor, actions, this._themeService); this._widgetVisible.set(true); + this._widget.onDidClose(() => this._cleanUp(), this, this._disposeOnClose); this._disposeOnClose.push(this._model); this._disposeOnClose.push(this._widget); + this._disposeOnClose.push(...actions); this._disposeOnClose.push(this._widget.onDidSelectRelatedInformation(related => { this._editorService.openCodeEditor({ resource: related.resource, @@ -280,6 +302,11 @@ class MarkerController implements editorCommon.IEditorContribution { } } + public show(marker: IMarker): void { + const model = this.getOrCreateModel(); + model.currentMarker = marker; + } + private _onMarkerChanged(changedResources: URI[]): void { let editorModel = this._editor.getModel(); if (!editorModel) { @@ -311,9 +338,9 @@ class MarkerController implements editorCommon.IEditorContribution { class MarkerNavigationAction extends EditorAction { - private _isNext: boolean; + private readonly _isNext: boolean; - private _multiFile: boolean; + private readonly _multiFile: boolean; constructor(next: boolean, multiFile: boolean, opts: IActionOptions) { super(opts); @@ -394,24 +421,30 @@ class MarkerNavigationAction extends EditorAction { } } -class NextMarkerAction extends MarkerNavigationAction { +export class NextMarkerAction extends MarkerNavigationAction { + static ID: string = 'editor.action.marker.next'; + static LABEL: string = nls.localize('markerAction.next.label', "Go to Next Problem (Error, Warning, Info)"); constructor() { super(true, false, { - id: 'editor.action.marker.next', - label: nls.localize('markerAction.next.label', "Go to Next Problem (Error, Warning, Info)"), + id: NextMarkerAction.ID, + label: NextMarkerAction.LABEL, alias: 'Go to Next Error or Warning', - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.Alt | KeyCode.F8, weight: KeybindingWeight.EditorContrib } }); } } class PrevMarkerAction extends MarkerNavigationAction { + static ID: string = 'editor.action.marker.prev'; + static LABEL: string = nls.localize('markerAction.previous.label', "Go to Previous Problem (Error, Warning, Info)"); constructor() { super(false, false, { - id: 'editor.action.marker.prev', - label: nls.localize('markerAction.previous.label', "Go to Previous Problem (Error, Warning, Info)"), + id: PrevMarkerAction.ID, + label: PrevMarkerAction.LABEL, alias: 'Go to Previous Error or Warning', - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + kbOpts: { kbExpr: EditorContextKeys.editorTextFocus, primary: KeyMod.Shift | KeyMod.Alt | KeyCode.F8, weight: KeybindingWeight.EditorContrib } }); } } diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts index df238568ca..9ed7491b50 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts +++ b/src/vs/editor/contrib/gotoError/gotoErrorWidget.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./gotoErrorWidget'; +import 'vs/css!./media/gotoErrorWidget'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -11,11 +11,9 @@ import { IMarker, MarkerSeverity, IRelatedInformation } from 'vs/platform/marker import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; -import { registerColor, oneOf } from 'vs/platform/theme/common/colorRegistry'; -import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; +import { registerColor, oneOf, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { IThemeService, ITheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; -import { AccessibilitySupport } from 'vs/base/common/platform'; import { editorErrorForeground, editorErrorBorder, editorWarningForeground, editorWarningBorder, editorInfoForeground, editorInfoBorder } from 'vs/editor/common/view/editorColorRegistry'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; @@ -23,6 +21,12 @@ import { ScrollType } from 'vs/editor/common/editorCommon'; import { getBaseLabel, getPathLabel } from 'vs/base/common/labels'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { Event, Emitter } from 'vs/base/common/event'; +import { PeekViewWidget } from 'vs/editor/contrib/referenceSearch/peekViewWidget'; +import { basename } from 'vs/base/common/resources'; +import { IAction } from 'vs/base/common/actions'; +import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; +import { peekViewTitleForeground, peekViewTitleInfoForeground } from 'vs/editor/contrib/referenceSearch/referencesWidget'; +import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; class MessageWidget { @@ -36,7 +40,7 @@ class MessageWidget { private readonly _relatedDiagnostics = new WeakMap(); private readonly _disposables: IDisposable[] = []; - constructor(parent: HTMLElement, editor: ICodeEditor, onRelatedInformation: (related: IRelatedInformation) => void, ) { + constructor(parent: HTMLElement, editor: ICodeEditor, onRelatedInformation: (related: IRelatedInformation) => void) { this._editor = editor; const domNode = document.createElement('div'); @@ -65,7 +69,6 @@ class MessageWidget { horizontalScrollbarSize: 3, verticalScrollbarSize: 3 }); - dom.addClass(this._scrollable.getDomNode(), 'block'); parent.appendChild(this._scrollable.getDomNode()); this._disposables.push(this._scrollable.onScroll(e => { domNode.style.left = `-${e.scrollLeft}px`; @@ -88,11 +91,14 @@ class MessageWidget { } dom.clearNode(this._messageBlock); + this._editor.applyFontInfo(this._messageBlock); let lastLineElement = this._messageBlock; for (const line of lines) { lastLineElement = document.createElement('div'); lastLineElement.innerText = line; - this._editor.applyFontInfo(lastLineElement); + if (line === '') { + lastLineElement.style.height = this._messageBlock.style.lineHeight; + } this._messageBlock.appendChild(lastLineElement); } if (source || code) { @@ -114,6 +120,7 @@ class MessageWidget { } dom.clearNode(this._relatedBlock); + this._editor.applyFontInfo(this._relatedBlock); if (isNonEmptyArray(relatedInformation)) { const relatedInformationNode = this._relatedBlock.appendChild(document.createElement('div')); relatedInformationNode.style.paddingTop = `${Math.floor(this._editor.getConfiguration().lineHeight * 0.66)}px`; @@ -123,7 +130,7 @@ class MessageWidget { let container = document.createElement('div'); - let relatedResource = document.createElement('span'); + let relatedResource = document.createElement('a'); dom.addClass(relatedResource, 'filename'); relatedResource.innerHTML = `${getBaseLabel(related.resource)}(${related.startLineNumber}, ${related.startColumn}): `; relatedResource.title = getPathLabel(related.resource, undefined); @@ -131,7 +138,6 @@ class MessageWidget { let relatedMessage = document.createElement('span'); relatedMessage.innerText = related.message; - this._editor.applyFontInfo(relatedMessage); container.appendChild(relatedResource); container.appendChild(relatedMessage); @@ -149,6 +155,7 @@ class MessageWidget { layout(height: number, width: number): void { this._scrollable.getDomNode().style.height = `${height}px`; + this._scrollable.getDomNode().style.width = `${width}px`; this._scrollable.setScrollDimensions({ width, height }); } @@ -157,22 +164,23 @@ class MessageWidget { } } -export class MarkerNavigationWidget extends ZoneWidget { +export class MarkerNavigationWidget extends PeekViewWidget { private _parentContainer: HTMLElement; private _container: HTMLElement; - private _title: HTMLElement; private _message: MessageWidget; private _callOnDispose: IDisposable[] = []; private _severity: MarkerSeverity; - private _backgroundColor: Color | null; + private _backgroundColor?: Color; private _onDidSelectRelatedInformation = new Emitter(); + private _heightInPixel: number; readonly onDidSelectRelatedInformation: Event = this._onDidSelectRelatedInformation.event; constructor( editor: ICodeEditor, - private _themeService: IThemeService + private readonly actions: IAction[], + private readonly _themeService: IThemeService ) { super(editor, { showArrow: true, showFrame: true, isAccessible: true }); this._severity = MarkerSeverity.Warning; @@ -195,7 +203,10 @@ export class MarkerNavigationWidget extends ZoneWidget { const frameColor = theme.getColor(colorId); this.style({ arrowColor: frameColor, - frameColor: frameColor + frameColor: frameColor, + headerBackgroundColor: this._backgroundColor, + primaryHeadingColor: theme.getColor(peekViewTitleForeground), + secondaryHeadingColor: theme.getColor(peekViewTitleInfoForeground) }); // style() will trigger _applyStyles } @@ -215,7 +226,18 @@ export class MarkerNavigationWidget extends ZoneWidget { this._parentContainer.focus(); } - protected _fillContainer(container: HTMLElement): void { + protected _fillHead(container: HTMLElement): void { + super._fillHead(container); + this._actionbarWidget.push(this.actions, { label: false, icon: true }); + } + + protected _getActionBarOptions(): IActionBarOptions { + return { + orientation: ActionsOrientation.HORIZONTAL_REVERSE + }; + } + + protected _fillBody(container: HTMLElement): void { this._parentContainer = container; dom.addClass(container, 'marker-widget'); this._parentContainer.tabIndex = 0; @@ -224,10 +246,6 @@ export class MarkerNavigationWidget extends ZoneWidget { this._container = document.createElement('div'); container.appendChild(this._container); - this._title = document.createElement('div'); - this._title.className = 'block title'; - this._container.appendChild(this._title); - this._message = new MessageWidget(this._container, this.editor, related => this._onDidSelectRelatedInformation.fire(related)); this._disposables.push(this._message); } @@ -241,7 +259,6 @@ export class MarkerNavigationWidget extends ZoneWidget { // * title // * message this._container.classList.remove('stale'); - this._title.innerHTML = nls.localize('title.wo_source', "({0}/{1})", markerIdx, markerCount); this._message.update(marker); // update frame color (only applied on 'show') @@ -254,6 +271,21 @@ export class MarkerNavigationWidget extends ZoneWidget { let position = editorPosition && range.containsPosition(editorPosition) ? editorPosition : range.getStartPosition(); super.show(position, this.computeRequiredHeight()); + const model = this.editor.getModel(); + if (model) { + const detail = markerCount > 1 + ? nls.localize('problems', "{0} of {1} problems", markerIdx, markerCount) + : nls.localize('change', "{0} of {1} problem", markerIdx, markerCount); + this.setTitle(basename(model.uri), detail); + } + let headingIconClassName = 'error'; + if (this._severity === MarkerSeverity.Warning) { + headingIconClassName = 'warning'; + } else if (this._severity === MarkerSeverity.Info) { + headingIconClassName = 'info'; + } + this.setTitleIcon(headingIconClassName); + this.editor.revealPositionInCenter(position, ScrollType.Smooth); if (this.editor.getConfiguration().accessibilitySupport !== AccessibilitySupport.Disabled) { @@ -271,17 +303,23 @@ export class MarkerNavigationWidget extends ZoneWidget { this._relayout(); } - protected _doLayout(heightInPixel: number, widthInPixel: number): void { + protected _doLayoutBody(heightInPixel: number, widthInPixel: number): void { + super._doLayoutBody(heightInPixel, widthInPixel); + this._heightInPixel = heightInPixel; this._message.layout(heightInPixel, widthInPixel); this._container.style.height = `${heightInPixel}px`; } + public _onWidth(widthInPixel: number): void { + this._message.layout(this._heightInPixel, widthInPixel); + } + protected _relayout(): void { super._relayout(this.computeRequiredHeight()); } private computeRequiredHeight() { - return 1 + this._message.getHeightInLines(); + return 3 + this._message.getHeightInLines(); } } @@ -295,3 +333,10 @@ export const editorMarkerNavigationError = registerColor('editorMarkerNavigation export const editorMarkerNavigationWarning = registerColor('editorMarkerNavigationWarning.background', { dark: warningDefault, light: warningDefault, hc: warningDefault }, nls.localize('editorMarkerNavigationWarning', 'Editor marker navigation widget warning color.')); export const editorMarkerNavigationInfo = registerColor('editorMarkerNavigationInfo.background', { dark: infoDefault, light: infoDefault, hc: infoDefault }, nls.localize('editorMarkerNavigationInfo', 'Editor marker navigation widget info color.')); export const editorMarkerNavigationBackground = registerColor('editorMarkerNavigation.background', { dark: '#2D2D30', light: Color.white, hc: '#0C141F' }, nls.localize('editorMarkerNavigationBackground', 'Editor marker navigation widget background.')); + +registerThemingParticipant((theme, collector) => { + const link = theme.getColor(textLinkForeground); + if (link) { + collector.addRule(`.monaco-editor .marker-widget a { color: ${link}; }`); + } +}); diff --git a/src/vs/editor/contrib/gotoError/gotoErrorWidget.css b/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css similarity index 56% rename from src/vs/editor/contrib/gotoError/gotoErrorWidget.css rename to src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css index 645117609f..d24b98149f 100644 --- a/src/vs/editor/contrib/gotoError/gotoErrorWidget.css +++ b/src/vs/editor/contrib/gotoError/media/gotoErrorWidget.css @@ -5,8 +5,31 @@ /* marker zone */ +.monaco-editor .peekview-widget .head .peekview-title .icon.warning { + background: url('status-warning.svg') center center no-repeat; +} + +.monaco-editor .peekview-widget .head .peekview-title .icon.error { + background: url('status-error.svg') center center no-repeat; +} + +.monaco-editor .peekview-widget .head .peekview-title .icon.info { + background: url('status-info.svg') center center no-repeat; +} + +.vs-dark .monaco-editor .peekview-widget .head .peekview-title .icon.warning { + background: url('status-warning-inverse.svg') center center no-repeat; +} + +.vs-dark .monaco-editor .peekview-widget .head .peekview-title .icon.error { + background: url('status-error-inverse.svg') center center no-repeat; +} + +.vs-dark .monaco-editor .peekview-widget .head .peekview-title .icon.info { + background: url('status-info-inverse.svg') center center no-repeat; +} + .monaco-editor .marker-widget { - padding: 3px 12px; text-overflow: ellipsis; white-space: nowrap; } @@ -16,21 +39,17 @@ font-style: italic; } -.monaco-editor .marker-widget div.block { - display: inline-block; - vertical-align: top; -} - .monaco-editor .marker-widget .title { display: inline-block; padding-right: 5px; } .monaco-editor .marker-widget .descriptioncontainer { - position: relative; + position: absolute; white-space: pre; -webkit-user-select: text; user-select: text; + padding: 8px 12px 0px 20px; } .monaco-editor .marker-widget .descriptioncontainer .message { @@ -43,8 +62,7 @@ } .monaco-editor .marker-widget .descriptioncontainer .message .source, -.monaco-editor .marker-widget .descriptioncontainer .message .code, -.monaco-editor .marker-widget .descriptioncontainer .filename { +.monaco-editor .marker-widget .descriptioncontainer .message .code { opacity: 0.6; } diff --git a/src/vs/workbench/parts/markers/electron-browser/media/status-error-inverse.svg b/src/vs/editor/contrib/gotoError/media/status-error-inverse.svg similarity index 100% rename from src/vs/workbench/parts/markers/electron-browser/media/status-error-inverse.svg rename to src/vs/editor/contrib/gotoError/media/status-error-inverse.svg diff --git a/src/vs/workbench/parts/markers/electron-browser/media/status-error.svg b/src/vs/editor/contrib/gotoError/media/status-error.svg similarity index 100% rename from src/vs/workbench/parts/markers/electron-browser/media/status-error.svg rename to src/vs/editor/contrib/gotoError/media/status-error.svg diff --git a/src/vs/workbench/parts/markers/electron-browser/media/status-info-inverse.svg b/src/vs/editor/contrib/gotoError/media/status-info-inverse.svg similarity index 100% rename from src/vs/workbench/parts/markers/electron-browser/media/status-info-inverse.svg rename to src/vs/editor/contrib/gotoError/media/status-info-inverse.svg diff --git a/src/vs/workbench/parts/markers/electron-browser/media/status-info.svg b/src/vs/editor/contrib/gotoError/media/status-info.svg similarity index 100% rename from src/vs/workbench/parts/markers/electron-browser/media/status-info.svg rename to src/vs/editor/contrib/gotoError/media/status-info.svg diff --git a/src/vs/workbench/parts/markers/electron-browser/media/status-warning-inverse.svg b/src/vs/editor/contrib/gotoError/media/status-warning-inverse.svg similarity index 100% rename from src/vs/workbench/parts/markers/electron-browser/media/status-warning-inverse.svg rename to src/vs/editor/contrib/gotoError/media/status-warning-inverse.svg diff --git a/src/vs/workbench/parts/markers/electron-browser/media/status-warning.svg b/src/vs/editor/contrib/gotoError/media/status-warning.svg similarity index 100% rename from src/vs/workbench/parts/markers/electron-browser/media/status-warning.svg rename to src/vs/editor/contrib/gotoError/media/status-warning.svg diff --git a/src/vs/editor/contrib/hover/hover.css b/src/vs/editor/contrib/hover/hover.css index a147b3356f..ceb23ae42c 100644 --- a/src/vs/editor/contrib/hover/hover.css +++ b/src/vs/editor/contrib/hover/hover.css @@ -23,12 +23,12 @@ display: none; } -.monaco-editor-hover .monaco-editor-hover-content { - max-width: 500px; +.monaco-editor-hover .hover-contents { + padding: 4px 8px; } -.monaco-editor-hover .hover-row { - padding: 4px 5px; +.monaco-editor-hover .markdown-hover > .hover-contents:not(.code-hover-contents) { + max-width: 500px; } .monaco-editor-hover p, @@ -75,3 +75,21 @@ white-space: pre-wrap; word-break: break-all; } + +.monaco-editor-hover .hover-row.status-bar { + font-size: 12px; + line-height: 22px; +} + +.monaco-editor-hover .hover-row.status-bar .actions { + display: flex; +} + +.monaco-editor-hover .hover-row.status-bar .actions .action-container { + margin: 0px 8px; + cursor: pointer; +} + +.monaco-editor-hover .hover-row.status-bar .actions .action-container .action .icon { + padding-right: 4px; +} \ No newline at end of file diff --git a/src/vs/editor/contrib/hover/hover.ts b/src/vs/editor/contrib/hover/hover.ts index 1a8df415ee..fef2d75cda 100644 --- a/src/vs/editor/contrib/hover/hover.ts +++ b/src/vs/editor/contrib/hover/hover.ts @@ -19,19 +19,23 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { HoverStartMode } from 'vs/editor/contrib/hover/hoverOperation'; import { ModesContentHoverWidget } from 'vs/editor/contrib/hover/modesContentHover'; import { ModesGlyphHoverWidget } from 'vs/editor/contrib/hover/modesGlyphHover'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorHoverBackground, editorHoverBorder, editorHoverHighlight, textCodeBlockBackground, textLinkForeground, editorHoverStatusBarBackground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; export class ModesHoverController implements IEditorContribution { private static readonly ID = 'editor.contrib.hover'; private _toUnhook: IDisposable[]; - private _didChangeConfigurationHandler: IDisposable; + private readonly _didChangeConfigurationHandler: IDisposable; private _contentWidget: ModesContentHoverWidget; private _glyphWidget: ModesGlyphHoverWidget; @@ -63,6 +67,10 @@ export class ModesHoverController implements IEditorContribution { @IOpenerService private readonly _openerService: IOpenerService, @IModeService private readonly _modeService: IModeService, @IMarkerDecorationsService private readonly _markerDecorationsService: IMarkerDecorationsService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IContextMenuService private readonly _contextMenuService: IContextMenuService, + @IBulkEditService private readonly _bulkEditService: IBulkEditService, + @ICommandService private readonly _commandService: ICommandService, @IThemeService private readonly _themeService: IThemeService ) { this._toUnhook = []; @@ -205,9 +213,8 @@ export class ModesHoverController implements IEditorContribution { } private _createHoverWidget() { - const renderer = new MarkdownRenderer(this._editor, this._modeService, this._openerService); - this._contentWidget = new ModesContentHoverWidget(this._editor, renderer, this._markerDecorationsService, this._themeService, this._openerService); - this._glyphWidget = new ModesGlyphHoverWidget(this._editor, renderer); + this._contentWidget = new ModesContentHoverWidget(this._editor, this._markerDecorationsService, this._themeService, this._keybindingService, this._contextMenuService, this._bulkEditService, this._commandService, this._modeService, this._openerService); + this._glyphWidget = new ModesGlyphHoverWidget(this._editor, this._modeService, this._openerService); } public showContentHover(range: Range, mode: HoverStartMode, focus: boolean): void { @@ -263,7 +270,8 @@ class ShowHoverAction extends EditorAction { } const position = editor.getPosition(); const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); - controller.showContentHover(range, HoverStartMode.Immediate, true); + const focus = editor.getConfiguration().accessibilitySupport === AccessibilitySupport.Enabled; + controller.showContentHover(range, HoverStartMode.Immediate, focus); } } @@ -291,6 +299,10 @@ registerThemingParticipant((theme, collector) => { if (link) { collector.addRule(`.monaco-editor .monaco-editor-hover a { color: ${link}; }`); } + const actionsBackground = theme.getColor(editorHoverStatusBarBackground); + if (actionsBackground) { + collector.addRule(`.monaco-editor .monaco-editor-hover .hover-row .actions { background-color: ${actionsBackground}; }`); + } const codeBackground = theme.getColor(textCodeBlockBackground); if (codeBackground) { collector.addRule(`.monaco-editor .monaco-editor-hover code { background-color: ${codeBackground}; }`); diff --git a/src/vs/editor/contrib/hover/hoverOperation.ts b/src/vs/editor/contrib/hover/hoverOperation.ts index 33a842b0ab..8b9860aa9e 100644 --- a/src/vs/editor/contrib/hover/hoverOperation.ts +++ b/src/vs/editor/contrib/hover/hoverOperation.ts @@ -47,19 +47,19 @@ export const enum HoverStartMode { export class HoverOperation { - private _computer: IHoverComputer; + private readonly _computer: IHoverComputer; private _state: ComputeHoverOperationState; private _hoverTime: number; - private _firstWaitScheduler: RunOnceScheduler; - private _secondWaitScheduler: RunOnceScheduler; - private _loadingMessageScheduler: RunOnceScheduler; + private readonly _firstWaitScheduler: RunOnceScheduler; + private readonly _secondWaitScheduler: RunOnceScheduler; + private readonly _loadingMessageScheduler: RunOnceScheduler; private _asyncComputationPromise: CancelablePromise | null; private _asyncComputationPromiseDone: boolean; - private _completeCallback: (r: Result) => void; - private _errorCallback: ((err: any) => void) | null | undefined; - private _progressCallback: (progress: any) => void; + private readonly _completeCallback: (r: Result) => void; + private readonly _errorCallback: ((err: any) => void) | null | undefined; + private readonly _progressCallback: (progress: any) => void; constructor(computer: IHoverComputer, success: (r: Result) => void, error: ((err: any) => void) | null | undefined, progress: (progress: any) => void, hoverTime: number) { this._computer = computer; diff --git a/src/vs/editor/contrib/hover/hoverWidgets.ts b/src/vs/editor/contrib/hover/hoverWidgets.ts index 5cd67b3c47..ece230a25c 100644 --- a/src/vs/editor/contrib/hover/hoverWidgets.ts +++ b/src/vs/editor/contrib/hover/hoverWidgets.ts @@ -16,15 +16,15 @@ import { Range } from 'vs/editor/common/core/range'; export class ContentHoverWidget extends Widget implements editorBrowser.IContentWidget { - private _id: string; + private readonly _id: string; protected _editor: editorBrowser.ICodeEditor; private _isVisible: boolean; - private _containerDomNode: HTMLElement; - private _domNode: HTMLElement; + private readonly _containerDomNode: HTMLElement; + private readonly _domNode: HTMLElement; protected _showAtPosition: Position | null; protected _showAtRange: Range | null; private _stoleFocus: boolean; - private scrollbar: DomScrollableElement; + private readonly scrollbar: DomScrollableElement; private disposables: IDisposable[] = []; // Editor.IContentWidget.allowEditorOverflow @@ -68,9 +68,9 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent } })); - this._editor.onDidLayoutChange(e => this.updateMaxHeight()); + this._editor.onDidLayoutChange(e => this.layout()); - this.updateMaxHeight(); + this.layout(); this._editor.addContentWidget(this); this._showAtPosition = null; this._showAtRange = null; @@ -151,22 +151,23 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent this.scrollbar.scanDomNode(); } - private updateMaxHeight(): void { + private layout(): void { const height = Math.max(this._editor.getLayoutInfo().height / 4, 250); const { fontSize, lineHeight } = this._editor.getConfiguration().fontInfo; this._domNode.style.fontSize = `${fontSize}px`; this._domNode.style.lineHeight = `${lineHeight}px`; this._domNode.style.maxHeight = `${height}px`; + this._domNode.style.maxWidth = `${Math.max(this._editor.getLayoutInfo().width * 0.66, 500)}px`; } } export class GlyphHoverWidget extends Widget implements editorBrowser.IOverlayWidget { - private _id: string; + private readonly _id: string; protected _editor: editorBrowser.ICodeEditor; private _isVisible: boolean; - private _domNode: HTMLElement; + private readonly _domNode: HTMLElement; protected _showAtLineNumber: number; constructor(id: string, editor: editorBrowser.ICodeEditor) { diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index c9a96c00b7..bc2e021a00 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -8,7 +8,7 @@ import * as dom from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; import { IMarkdownString, MarkdownString, isEmptyMarkdownString, markedStringsEquals } from 'vs/base/common/htmlContent'; -import { Disposable, IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; @@ -24,11 +24,23 @@ import { ContentHoverWidget } from 'vs/editor/contrib/hover/hoverWidgets'; import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays'; -import { IMarker, IMarkerData } from 'vs/platform/markers/common/markers'; -import { basename } from 'vs/base/common/paths'; +import { IMarker, IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; +import { basename } from 'vs/base/common/resources'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener'; +import { MarkerController, NextMarkerAction } from 'vs/editor/contrib/gotoError/gotoError'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; +import { applyCodeAction, QuickFixAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; +import { Action } from 'vs/base/common/actions'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { withNullAsUndefined } from 'vs/base/common/types'; const $ = dom.$; @@ -53,13 +65,13 @@ type HoverPart = MarkdownHover | ColorHover | MarkerHover; class ModesContentComputer implements IHoverComputer { - private _editor: ICodeEditor; + private readonly _editor: ICodeEditor; private _result: HoverPart[]; private _range: Range | null; constructor( editor: ICodeEditor, - private _markerDecorationsService: IMarkerDecorationsService + private readonly _markerDecorationsService: IMarkerDecorationsService ) { this._editor = editor; this._range = null; @@ -179,7 +191,7 @@ class ModesContentComputer implements IHoverComputer { private _getLoadingMessage(): HoverPart { return { - range: this._range || undefined, + range: withNullAsUndefined(this._range), contents: [new MarkdownString().appendText(nls.localize('modesContentHover.loading', "Loading..."))] }; } @@ -191,11 +203,10 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private _messages: HoverPart[]; private _lastRange: Range | null; - private _computer: ModesContentComputer; - private _hoverOperation: HoverOperation; + private readonly _computer: ModesContentComputer; + private readonly _hoverOperation: HoverOperation; private _highlightDecorations: string[]; private _isChangingDecorations: boolean; - private _markdownRenderer: MarkdownRenderer; private _shouldFocus: boolean; private _colorPicker: ColorPickerWidget | null; @@ -203,9 +214,13 @@ export class ModesContentHoverWidget extends ContentHoverWidget { constructor( editor: ICodeEditor, - markdownRenderer: MarkdownRenderer, markerDecorationsService: IMarkerDecorationsService, private readonly _themeService: IThemeService, + private readonly _keybindingService: IKeybindingService, + private readonly _contextMenuService: IContextMenuService, + private readonly _bulkEditService: IBulkEditService, + private readonly _commandService: ICommandService, + private readonly _modeService: IModeService, private readonly _openerService: IOpenerService | null = NullOpenerService, ) { super(ModesContentHoverWidget.ID, editor); @@ -216,9 +231,6 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._highlightDecorations = []; this._isChangingDecorations = false; - this._markdownRenderer = markdownRenderer; - this._register(markdownRenderer.onDidRenderCodeBlock(this.onContentsChange, this)); - this._hoverOperation = new HoverOperation( this._computer, result => this._withResult(result, true), @@ -343,7 +355,8 @@ export class ModesContentHoverWidget extends ContentHoverWidget { let isEmptyHoverContent = true; let containColorPicker = false; - let markdownDisposeable: IDisposable; + let markdownDisposeables: IDisposable[] = []; + const markerMessages: MarkerHover[] = []; messages.forEach((msg) => { if (!msg.range) { return; @@ -433,25 +446,39 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this.updateContents(fragment); this._colorPicker.layout(); - this.renderDisposable = combinedDisposable([colorListener, colorChangeListener, widget, markdownDisposeable]); + this.renderDisposable = combinedDisposable([colorListener, colorChangeListener, widget, ...markdownDisposeables]); }); } else { if (msg instanceof MarkerHover) { + markerMessages.push(msg); isEmptyHoverContent = false; - fragment.appendChild($('div.hover-row', undefined, this.renderMarkerHover(msg))); } else { msg.contents .filter(contents => !isEmptyMarkdownString(contents)) .forEach(contents => { - const renderedContents = this._markdownRenderer.render(contents); - markdownDisposeable = renderedContents; - fragment.appendChild($('div.hover-row', undefined, renderedContents.element)); + const markdownHoverElement = $('div.hover-row.markdown-hover'); + const hoverContentsElement = dom.append(markdownHoverElement, $('div.hover-contents')); + const renderer = new MarkdownRenderer(this._editor, this._modeService, this._openerService); + markdownDisposeables.push(renderer.onDidRenderCodeBlock(() => { + hoverContentsElement.className = 'hover-contents code-hover-contents'; + this.onContentsChange(); + })); + const renderedContents = renderer.render(contents); + hoverContentsElement.appendChild(renderedContents.element); + fragment.appendChild(markdownHoverElement); + markdownDisposeables.push(renderedContents); isEmptyHoverContent = false; }); } } }); + if (markerMessages.length) { + markerMessages.forEach(msg => fragment.appendChild(this.renderMarkerHover(msg))); + const markerHoverForStatusbar = markerMessages.length === 1 ? markerMessages[0] : markerMessages.sort((a, b) => MarkerSeverity.compare(a.marker.severity, b.marker.severity))[0]; + fragment.appendChild(this.renderMarkerStatusbar(markerHoverForStatusbar)); + } + // show if (!containColorPicker && !isEmptyHoverContent) { @@ -468,27 +495,28 @@ export class ModesContentHoverWidget extends ContentHoverWidget { } private renderMarkerHover(markerHover: MarkerHover): HTMLElement { - const hoverElement = $('div'); + const hoverElement = $('div.hover-row'); + const markerElement = dom.append(hoverElement, $('div.marker.hover-contents')); const { source, message, code, relatedInformation } = markerHover.marker; - const messageElement = dom.append(hoverElement, $('span')); + this._editor.applyFontInfo(markerElement); + const messageElement = dom.append(markerElement, $('span')); messageElement.style.whiteSpace = 'pre-wrap'; - messageElement.innerText = message.trim(); - this._editor.applyFontInfo(messageElement); + messageElement.innerText = message; if (source || code) { - const detailsElement = dom.append(hoverElement, $('span')); + const detailsElement = dom.append(markerElement, $('span')); detailsElement.style.opacity = '0.6'; detailsElement.style.paddingLeft = '6px'; - detailsElement.innerText = source && code ? `${source}(${code})` : `(${code})`; + detailsElement.innerText = source && code ? `${source}(${code})` : source ? source : `(${code})`; } if (isNonEmptyArray(relatedInformation)) { - const listElement = dom.append(hoverElement, $('ul')); for (const { message, resource, startLineNumber, startColumn } of relatedInformation) { - const item = dom.append(listElement, $('li')); - const a = dom.append(item, $('a')); - a.innerText = `${basename(resource.path)}(${startLineNumber}, ${startColumn})`; + const relatedInfoContainer = dom.append(markerElement, $('div')); + relatedInfoContainer.style.marginTop = '8px'; + const a = dom.append(relatedInfoContainer, $('a')); + a.innerText = `${basename(resource)}(${startLineNumber}, ${startColumn}): `; a.style.cursor = 'pointer'; a.onclick = e => { e.stopPropagation(); @@ -497,13 +525,84 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._openerService.open(resource.with({ fragment: `${startLineNumber},${startColumn}` })).catch(onUnexpectedError); } }; - const messageElement = dom.append(item, $('span')); - messageElement.innerText = `: ${message}`; + const messageElement = dom.append(relatedInfoContainer, $('span')); + messageElement.innerText = message; + this._editor.applyFontInfo(messageElement); } } + return hoverElement; } + private renderMarkerStatusbar(markerHover: MarkerHover): HTMLElement { + const hoverElement = $('div.hover-row.status-bar'); + const disposables: IDisposable[] = []; + const actionsElement = dom.append(hoverElement, $('div.actions')); + disposables.push(this.renderAction(actionsElement, { + label: nls.localize('quick fixes', "Quick Fix..."), + commandId: QuickFixAction.Id, + run: async (target) => { + const codeActionsPromise = this.getCodeActions(markerHover.marker); + disposables.push(toDisposable(() => codeActionsPromise.cancel())); + const actions = await codeActionsPromise; + const elementPosition = dom.getDomNodePagePosition(target); + this._contextMenuService.showContextMenu({ + getAnchor: () => ({ x: elementPosition.left + 6, y: elementPosition.top + elementPosition.height + 6 }), + getActions: () => actions + }); + } + })); + if (markerHover.marker.severity === MarkerSeverity.Error || markerHover.marker.severity === MarkerSeverity.Warning || markerHover.marker.severity === MarkerSeverity.Info) { + disposables.push(this.renderAction(actionsElement, { + label: nls.localize('peek problem', "Peek Problem"), + commandId: NextMarkerAction.ID, + run: () => { + this.hide(); + MarkerController.get(this._editor).show(markerHover.marker); + this._editor.focus(); + } + })); + } + this.renderDisposable = combinedDisposable(disposables); + return hoverElement; + } + + private getCodeActions(marker: IMarker): CancelablePromise { + return createCancelablePromise(async cancellationToken => { + const codeActions = await getCodeActions(this._editor.getModel()!, new Range(marker.startLineNumber, marker.startColumn, marker.endLineNumber, marker.endColumn), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken); + if (codeActions.actions.length) { + return codeActions.actions.map(codeAction => new Action( + codeAction.command ? codeAction.command.id : codeAction.title, + codeAction.title, + undefined, + true, + () => applyCodeAction(codeAction, this._bulkEditService, this._commandService))); + } + return [ + new Action('', nls.localize('editor.action.quickFix.noneMessage', "No code actions available")) + ]; + }); + } + + private renderAction(parent: HTMLElement, actionOptions: { label: string, iconClass?: string, run: (target: HTMLElement) => void, commandId: string }): IDisposable { + const actionContainer = dom.append(parent, $('div.action-container')); + const action = dom.append(actionContainer, $('a.action')); + if (actionOptions.iconClass) { + dom.append(action, $(`span.icon.${actionOptions.iconClass}`)); + } + const label = dom.append(action, $('span')); + label.textContent = actionOptions.label; + const keybinding = this._keybindingService.lookupKeybinding(actionOptions.commandId); + if (keybinding) { + label.title = `${actionOptions.label} (${keybinding.getLabel()})`; + } + return dom.addDisposableListener(actionContainer, dom.EventType.CLICK, e => { + e.stopPropagation(); + e.preventDefault(); + actionOptions.run(actionContainer); + }); + } + private static readonly _DECORATION_OPTIONS = ModelDecorationOptions.register({ className: 'hoverHighlight' }); diff --git a/src/vs/editor/contrib/hover/modesGlyphHover.ts b/src/vs/editor/contrib/hover/modesGlyphHover.ts index 54dab02898..f64b16c293 100644 --- a/src/vs/editor/contrib/hover/modesGlyphHover.ts +++ b/src/vs/editor/contrib/hover/modesGlyphHover.ts @@ -10,6 +10,8 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { HoverOperation, HoverStartMode, IHoverComputer } from 'vs/editor/contrib/hover/hoverOperation'; import { GlyphHoverWidget } from 'vs/editor/contrib/hover/hoverWidgets'; import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IOpenerService, NullOpenerService } from 'vs/platform/opener/common/opener'; export interface IHoverMessage { value: IMarkdownString; @@ -17,7 +19,7 @@ export interface IHoverMessage { class MarginComputer implements IHoverComputer { - private _editor: ICodeEditor; + private readonly _editor: ICodeEditor; private _lineNumber: number; private _result: IHoverMessage[]; @@ -89,17 +91,21 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { private _messages: IHoverMessage[]; private _lastLineNumber: number; - private _markdownRenderer: MarkdownRenderer; - private _computer: MarginComputer; - private _hoverOperation: HoverOperation; + private readonly _markdownRenderer: MarkdownRenderer; + private readonly _computer: MarginComputer; + private readonly _hoverOperation: HoverOperation; private _renderDisposeables: IDisposable[]; - constructor(editor: ICodeEditor, markdownRenderer: MarkdownRenderer) { + constructor( + editor: ICodeEditor, + modeService: IModeService, + openerService: IOpenerService | null = NullOpenerService, + ) { super(ModesGlyphHoverWidget.ID, editor); this._lastLineNumber = -1; - this._markdownRenderer = markdownRenderer; + this._markdownRenderer = new MarkdownRenderer(this._editor, modeService, openerService); this._computer = new MarginComputer(this._editor); this._hoverOperation = new HoverOperation( diff --git a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplaceCommand.ts b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplaceCommand.ts index 72da12c71f..ab43d59d20 100644 --- a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplaceCommand.ts +++ b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplaceCommand.ts @@ -10,9 +10,9 @@ import { ITextModel } from 'vs/editor/common/model'; export class InPlaceReplaceCommand implements editorCommon.ICommand { - private _editRange: Range; - private _originalSelection: Selection; - private _text: string; + private readonly _editRange: Range; + private readonly _originalSelection: Selection; + private readonly _text: string; constructor(editRange: Range, originalSelection: Selection, text: string) { this._editRange = editRange; diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index 43d53af31d..d6d228993f 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -23,28 +23,6 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import * as indentUtils from 'vs/editor/contrib/indentation/indentUtils'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -export function shiftIndent(tabSize: number, indentation: string, count?: number): string { - count = count || 1; - let desiredIndentCount = ShiftCommand.shiftIndentCount(indentation, indentation.length + count, tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; -} - -export function unshiftIndent(tabSize: number, indentation: string, count?: number): string { - count = count || 1; - let desiredIndentCount = ShiftCommand.unshiftIndentCount(indentation, indentation.length + count, tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; -} - export function getReindentEditOperations(model: ITextModel, startLineNumber: number, endLineNumber: number, inheritedIndent?: string): IIdentifiedSingleEditOperation[] { if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) { // Model is empty @@ -76,7 +54,15 @@ export function getReindentEditOperations(model: ITextModel, startLineNumber: nu return []; } - let { tabSize, insertSpaces } = model.getOptions(); + const { tabSize, indentSize, insertSpaces } = model.getOptions(); + const shiftIndent = (indentation: string, count?: number) => { + count = count || 1; + return ShiftCommand.shiftIndent(indentation, indentation.length + count, tabSize, indentSize, insertSpaces); + }; + const unshiftIndent = (indentation: string, count?: number) => { + count = count || 1; + return ShiftCommand.unshiftIndent(indentation, indentation.length + count, tabSize, indentSize, insertSpaces); + }; let indentEdits: IIdentifiedSingleEditOperation[] = []; // indentation being passed to lines below @@ -92,12 +78,12 @@ export function getReindentEditOperations(model: ITextModel, startLineNumber: nu adjustedLineContent = globalIndent + currentLineText.substring(oldIndentation.length); if (indentationRules.decreaseIndentPattern && indentationRules.decreaseIndentPattern.test(adjustedLineContent)) { - globalIndent = unshiftIndent(tabSize, globalIndent); + globalIndent = unshiftIndent(globalIndent); adjustedLineContent = globalIndent + currentLineText.substring(oldIndentation.length); } if (currentLineText !== adjustedLineContent) { - indentEdits.push(EditOperation.replace(new Selection(startLineNumber, 1, startLineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(globalIndent, tabSize, insertSpaces))); + indentEdits.push(EditOperation.replace(new Selection(startLineNumber, 1, startLineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(globalIndent, indentSize, insertSpaces))); } } else { globalIndent = strings.getLeadingWhitespace(currentLineText); @@ -107,11 +93,11 @@ export function getReindentEditOperations(model: ITextModel, startLineNumber: nu let idealIndentForNextLine: string = globalIndent; if (indentationRules.increaseIndentPattern && indentationRules.increaseIndentPattern.test(adjustedLineContent)) { - idealIndentForNextLine = shiftIndent(tabSize, idealIndentForNextLine); - globalIndent = shiftIndent(tabSize, globalIndent); + idealIndentForNextLine = shiftIndent(idealIndentForNextLine); + globalIndent = shiftIndent(globalIndent); } else if (indentationRules.indentNextLinePattern && indentationRules.indentNextLinePattern.test(adjustedLineContent)) { - idealIndentForNextLine = shiftIndent(tabSize, idealIndentForNextLine); + idealIndentForNextLine = shiftIndent(idealIndentForNextLine); } startLineNumber++; @@ -123,12 +109,12 @@ export function getReindentEditOperations(model: ITextModel, startLineNumber: nu let adjustedLineContent = idealIndentForNextLine + text.substring(oldIndentation.length); if (indentationRules.decreaseIndentPattern && indentationRules.decreaseIndentPattern.test(adjustedLineContent)) { - idealIndentForNextLine = unshiftIndent(tabSize, idealIndentForNextLine); - globalIndent = unshiftIndent(tabSize, globalIndent); + idealIndentForNextLine = unshiftIndent(idealIndentForNextLine); + globalIndent = unshiftIndent(globalIndent); } if (oldIndentation !== idealIndentForNextLine) { - indentEdits.push(EditOperation.replace(new Selection(lineNumber, 1, lineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(idealIndentForNextLine, tabSize, insertSpaces))); + indentEdits.push(EditOperation.replace(new Selection(lineNumber, 1, lineNumber, oldIndentation.length + 1), TextModel.normalizeIndentation(idealIndentForNextLine, indentSize, insertSpaces))); } // calculate idealIndentForNextLine @@ -137,10 +123,10 @@ export function getReindentEditOperations(model: ITextModel, startLineNumber: nu // but don't change globalIndent and idealIndentForNextLine. continue; } else if (indentationRules.increaseIndentPattern && indentationRules.increaseIndentPattern.test(adjustedLineContent)) { - globalIndent = shiftIndent(tabSize, globalIndent); + globalIndent = shiftIndent(globalIndent); idealIndentForNextLine = globalIndent; } else if (indentationRules.indentNextLinePattern && indentationRules.indentNextLinePattern.test(adjustedLineContent)) { - idealIndentForNextLine = shiftIndent(tabSize, idealIndentForNextLine); + idealIndentForNextLine = shiftIndent(idealIndentForNextLine); } else { idealIndentForNextLine = globalIndent; } @@ -219,7 +205,7 @@ export class IndentationToTabsAction extends EditorAction { export class ChangeIndentationSizeAction extends EditorAction { - constructor(private insertSpaces: boolean, opts: IActionOptions) { + constructor(private readonly insertSpaces: boolean, opts: IActionOptions) { super(opts); } @@ -389,9 +375,9 @@ export class ReindentSelectedLinesAction extends EditorAction { export class AutoIndentOnPasteCommand implements ICommand { - private _edits: { range: IRange; text: string; eol?: EndOfLineSequence; }[]; + private readonly _edits: { range: IRange; text: string; eol?: EndOfLineSequence; }[]; - private _initialSelection: Selection; + private readonly _initialSelection: Selection; private _selectionId: string; constructor(edits: TextEdit[], initialSelection: Selection) { @@ -436,7 +422,7 @@ export class AutoIndentOnPasteCommand implements ICommand { export class AutoIndentOnPaste implements IEditorContribution { private static readonly ID = 'editor.contrib.autoIndentOnPaste'; - private editor: ICodeEditor; + private readonly editor: ICodeEditor; private callOnDispose: IDisposable[]; private callOnModel: IDisposable[]; @@ -484,28 +470,16 @@ export class AutoIndentOnPaste implements IEditorContribution { if (!model.isCheapToTokenize(range.getStartPosition().lineNumber)) { return; } - const { tabSize, insertSpaces } = model.getOptions(); + const { tabSize, indentSize, insertSpaces } = model.getOptions(); this.editor.pushUndoStop(); let textEdits: TextEdit[] = []; let indentConverter = { shiftIndent: (indentation: string) => { - let desiredIndentCount = ShiftCommand.shiftIndentCount(indentation, indentation.length + 1, tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; + return ShiftCommand.shiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces); }, unshiftIndent: (indentation: string) => { - let desiredIndentCount = ShiftCommand.unshiftIndentCount(indentation, indentation.length + 1, tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; + return ShiftCommand.unshiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces); } }; @@ -679,7 +653,7 @@ export class IndentationToSpacesCommand implements ICommand { private selectionId: string; - constructor(private selection: Selection, private tabSize: number) { } + constructor(private readonly selection: Selection, private tabSize: number) { } public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { this.selectionId = builder.trackSelection(this.selection); @@ -695,7 +669,7 @@ export class IndentationToTabsCommand implements ICommand { private selectionId: string; - constructor(private selection: Selection, private tabSize: number) { } + constructor(private readonly selection: Selection, private tabSize: number) { } public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { this.selectionId = builder.trackSelection(this.selection); diff --git a/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts b/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts index 735e9db826..cbce40dd7c 100644 --- a/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/copyLinesCommand.ts @@ -10,8 +10,8 @@ import { ITextModel } from 'vs/editor/common/model'; export class CopyLinesCommand implements editorCommon.ICommand { - private _selection: Selection; - private _isCopyingDown: boolean; + private readonly _selection: Selection; + private readonly _isCopyingDown: boolean; private _selectionDirection: SelectionDirection; private _selectionId: string; diff --git a/src/vs/editor/contrib/linesOperations/deleteLinesCommand.ts b/src/vs/editor/contrib/linesOperations/deleteLinesCommand.ts deleted file mode 100644 index 9aac059311..0000000000 --- a/src/vs/editor/contrib/linesOperations/deleteLinesCommand.ts +++ /dev/null @@ -1,55 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Range } from 'vs/editor/common/core/range'; -import { Selection } from 'vs/editor/common/core/selection'; -import { ICommand, ICursorStateComputerData, IEditOperationBuilder } from 'vs/editor/common/editorCommon'; -import { ITextModel } from 'vs/editor/common/model'; - -export class DeleteLinesCommand implements ICommand { - - private startLineNumber: number; - private endLineNumber: number; - private restoreCursorToColumn: number; - - constructor(startLineNumber: number, endLineNumber: number, restoreCursorToColumn: number) { - this.startLineNumber = startLineNumber; - this.endLineNumber = endLineNumber; - this.restoreCursorToColumn = restoreCursorToColumn; - } - - public getEditOperations(model: ITextModel, builder: IEditOperationBuilder): void { - if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) { - // Model is empty - return; - } - - let startLineNumber = this.startLineNumber; - let endLineNumber = this.endLineNumber; - - let startColumn = 1; - let endColumn = model.getLineMaxColumn(endLineNumber); - if (endLineNumber < model.getLineCount()) { - endLineNumber += 1; - endColumn = 1; - } else if (startLineNumber > 1) { - startLineNumber -= 1; - startColumn = model.getLineMaxColumn(startLineNumber); - } - - builder.addTrackedEditOperation(new Range(startLineNumber, startColumn, endLineNumber, endColumn), null); - } - - public computeCursorState(model: ITextModel, helper: ICursorStateComputerData): Selection { - let inverseEditOperations = helper.getInverseEditOperations(); - let srcRange = inverseEditOperations[0].range; - return new Selection( - srcRange.endLineNumber, - this.restoreCursorToColumn, - srcRange.endLineNumber, - this.restoreCursorToColumn - ); - } -} diff --git a/src/vs/editor/contrib/linesOperations/linesOperations.ts b/src/vs/editor/contrib/linesOperations/linesOperations.ts index 01589655a4..27394356b9 100644 --- a/src/vs/editor/contrib/linesOperations/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/linesOperations.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, IActionOptions, ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { ReplaceCommand, ReplaceCommandThatPreservesSelection } from 'vs/editor/common/commands/replaceCommand'; import { TrimTrailingWhitespaceCommand } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; @@ -17,9 +17,8 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ICommand } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; -import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; +import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { CopyLinesCommand } from 'vs/editor/contrib/linesOperations/copyLinesCommand'; -import { DeleteLinesCommand } from 'vs/editor/contrib/linesOperations/deleteLinesCommand'; import { MoveLinesCommand } from 'vs/editor/contrib/linesOperations/moveLinesCommand'; import { SortLinesCommand } from 'vs/editor/contrib/linesOperations/sortLinesCommand'; import { MenuId } from 'vs/platform/actions/common/actions'; @@ -29,7 +28,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis abstract class AbstractCopyLinesAction extends EditorAction { - private down: boolean; + private readonly down: boolean; constructor(down: boolean, opts: IActionOptions) { super(opts); @@ -101,7 +100,7 @@ class CopyLinesDownAction extends AbstractCopyLinesAction { abstract class AbstractMoveLinesAction extends EditorAction { - private down: boolean; + private readonly down: boolean; constructor(down: boolean, opts: IActionOptions) { super(opts); @@ -171,7 +170,7 @@ class MoveLinesDownAction extends AbstractMoveLinesAction { } export abstract class AbstractSortLinesAction extends EditorAction { - private descending: boolean; + private readonly descending: boolean; constructor(descending: boolean, opts: IActionOptions) { super(opts); @@ -265,6 +264,7 @@ export class TrimTrailingWhitespaceAction extends EditorAction { interface IDeleteLinesOperation { startLineNumber: number; + selectionStartColumn: number; endLineNumber: number; positionColumn: number; } @@ -286,26 +286,50 @@ export class DeleteLinesAction extends EditorAction { } public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { + if (!editor.hasModel()) { + return; + } let ops = this._getLinesToRemove(editor); - // Finally, construct the delete lines commands - let commands: ICommand[] = ops.map((op) => { - return new DeleteLinesCommand(op.startLineNumber, op.endLineNumber, op.positionColumn); - }); + let model: ITextModel = editor.getModel(); + if (model.getLineCount() === 1 && model.getLineMaxColumn(1) === 1) { + // Model is empty + return; + } + + let linesDeleted = 0; + let edits: IIdentifiedSingleEditOperation[] = []; + let cursorState: Selection[] = []; + for (let i = 0, len = ops.length; i < len; i++) { + const op = ops[i]; + + let startLineNumber = op.startLineNumber; + let endLineNumber = op.endLineNumber; + + let startColumn = 1; + let endColumn = model.getLineMaxColumn(endLineNumber); + if (endLineNumber < model.getLineCount()) { + endLineNumber += 1; + endColumn = 1; + } else if (startLineNumber > 1) { + startLineNumber -= 1; + startColumn = model.getLineMaxColumn(startLineNumber); + } + + edits.push(EditOperation.replace(new Selection(startLineNumber, startColumn, endLineNumber, endColumn), '')); + cursorState.push(new Selection(startLineNumber - linesDeleted, op.positionColumn, startLineNumber - linesDeleted, op.positionColumn)); + linesDeleted += (op.endLineNumber - op.startLineNumber + 1); + } editor.pushUndoStop(); - editor.executeCommands(this.id, commands); + editor.executeEdits(this.id, edits, cursorState); editor.pushUndoStop(); } - private _getLinesToRemove(editor: ICodeEditor): IDeleteLinesOperation[] { + private _getLinesToRemove(editor: IActiveCodeEditor): IDeleteLinesOperation[] { // Construct delete operations - let selections = editor.getSelections(); - if (selections === null) { - return []; - } - let operations: IDeleteLinesOperation[] = selections.map((s) => { + let operations: IDeleteLinesOperation[] = editor.getSelections().map((s) => { let endLineNumber = s.endLineNumber; if (s.startLineNumber < s.endLineNumber && s.endColumn === 1) { @@ -314,6 +338,7 @@ export class DeleteLinesAction extends EditorAction { return { startLineNumber: s.startLineNumber, + selectionStartColumn: s.selectionStartColumn, endLineNumber: endLineNumber, positionColumn: s.positionColumn }; @@ -445,10 +470,10 @@ export class InsertLineAfterAction extends EditorAction { export abstract class AbstractDeleteAllToBoundaryAction extends EditorAction { public run(_accessor: ServicesAccessor, editor: ICodeEditor): void { - const primaryCursor = editor.getSelection(); - if (primaryCursor === null) { + if (!editor.hasModel()) { return; } + const primaryCursor = editor.getSelection(); let rangesToDelete = this._getRangesToDelete(editor); // merge overlapping selections @@ -483,7 +508,7 @@ export abstract class AbstractDeleteAllToBoundaryAction extends EditorAction { */ protected abstract _getEndCursorState(primaryCursor: Range, rangesToDelete: Range[]): Selection[]; - protected abstract _getRangesToDelete(editor: ICodeEditor): Range[]; + protected abstract _getRangesToDelete(editor: IActiveCodeEditor): Range[]; } export class DeleteAllLeftAction extends AbstractDeleteAllToBoundaryAction { @@ -532,7 +557,7 @@ export class DeleteAllLeftAction extends AbstractDeleteAllToBoundaryAction { return endCursorState; } - _getRangesToDelete(editor: ICodeEditor): Range[] { + _getRangesToDelete(editor: IActiveCodeEditor): Range[] { let selections = editor.getSelections(); if (selections === null) { return []; @@ -550,7 +575,7 @@ export class DeleteAllLeftAction extends AbstractDeleteAllToBoundaryAction { if (selection.isEmpty()) { if (selection.startColumn === 1) { let deleteFromLine = Math.max(1, selection.startLineNumber - 1); - let deleteFromColumn = selection.startLineNumber === 1 ? 1 : model!.getLineContent(deleteFromLine).length + 1; + let deleteFromColumn = selection.startLineNumber === 1 ? 1 : model.getLineContent(deleteFromLine).length + 1; return new Range(deleteFromLine, deleteFromColumn, selection.startLineNumber, 1); } else { return new Range(selection.startLineNumber, 1, selection.startLineNumber, selection.startColumn); @@ -601,7 +626,7 @@ export class DeleteAllRightAction extends AbstractDeleteAllToBoundaryAction { return endCursorState; } - _getRangesToDelete(editor: ICodeEditor): Range[] { + _getRangesToDelete(editor: IActiveCodeEditor): Range[] { let model = editor.getModel(); if (model === null) { return []; @@ -615,7 +640,7 @@ export class DeleteAllRightAction extends AbstractDeleteAllToBoundaryAction { let rangesToDelete: Range[] = selections.map((sel) => { if (sel.isEmpty()) { - const maxColumn = model!.getLineMaxColumn(sel.startLineNumber); + const maxColumn = model.getLineMaxColumn(sel.startLineNumber); if (sel.startColumn === maxColumn) { return new Range(sel.startLineNumber, sel.startColumn, sel.startLineNumber + 1, 1); diff --git a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts index e9fb9edc70..98dd43a2f2 100644 --- a/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/moveLinesCommand.ts @@ -16,9 +16,9 @@ import * as indentUtils from 'vs/editor/contrib/indentation/indentUtils'; export class MoveLinesCommand implements ICommand { - private _selection: Selection; - private _isMovingDown: boolean; - private _autoIndent: boolean; + private readonly _selection: Selection; + private readonly _isMovingDown: boolean; + private readonly _autoIndent: boolean; private _selectionId: string; private _moveEndPositionDown: boolean; @@ -50,9 +50,8 @@ export class MoveLinesCommand implements ICommand { s = s.setEndPosition(s.endLineNumber - 1, model.getLineMaxColumn(s.endLineNumber - 1)); } - let tabSize = model.getOptions().tabSize; - let insertSpaces = model.getOptions().insertSpaces; - let indentConverter = this.buildIndentConverter(tabSize); + const { tabSize, indentSize, insertSpaces } = model.getOptions(); + let indentConverter = this.buildIndentConverter(tabSize, indentSize, insertSpaces); let virtualModel = { getLineTokens: (lineNumber: number) => { return model.getLineTokens(lineNumber); @@ -215,25 +214,13 @@ export class MoveLinesCommand implements ICommand { this._selectionId = builder.trackSelection(s); } - private buildIndentConverter(tabSize: number): IIndentConverter { + private buildIndentConverter(tabSize: number, indentSize: number, insertSpaces: boolean): IIndentConverter { return { shiftIndent: (indentation) => { - let desiredIndentCount = ShiftCommand.shiftIndentCount(indentation, indentation.length + 1, tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; + return ShiftCommand.shiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces); }, unshiftIndent: (indentation) => { - let desiredIndentCount = ShiftCommand.unshiftIndentCount(indentation, indentation.length + 1, tabSize); - let newIndentation = ''; - for (let i = 0; i < desiredIndentCount; i++) { - newIndentation += '\t'; - } - - return newIndentation; + return ShiftCommand.unshiftIndent(indentation, indentation.length + 1, tabSize, indentSize, insertSpaces); } }; } diff --git a/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts b/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts index 17ea387e3b..1ef4383cc5 100644 --- a/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts +++ b/src/vs/editor/contrib/linesOperations/sortLinesCommand.ts @@ -11,9 +11,9 @@ import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/mod export class SortLinesCommand implements editorCommon.ICommand { - private selection: Selection; + private readonly selection: Selection; private selectionId: string; - private descending: boolean; + private readonly descending: boolean; constructor(selection: Selection, descending: boolean) { this.selection = selection; diff --git a/src/vs/editor/contrib/linesOperations/test/deleteLinesCommand.test.ts b/src/vs/editor/contrib/linesOperations/test/deleteLinesCommand.test.ts deleted file mode 100644 index fd59c58331..0000000000 --- a/src/vs/editor/contrib/linesOperations/test/deleteLinesCommand.test.ts +++ /dev/null @@ -1,199 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { Selection } from 'vs/editor/common/core/selection'; -import { DeleteLinesCommand } from 'vs/editor/contrib/linesOperations/deleteLinesCommand'; -import { testCommand } from 'vs/editor/test/browser/testCommand'; - -function createFromSelection(selection: Selection): DeleteLinesCommand { - let endLineNumber = selection.endLineNumber; - if (selection.startLineNumber < selection.endLineNumber && selection.endColumn === 1) { - endLineNumber -= 1; - } - return new DeleteLinesCommand(selection.startLineNumber, endLineNumber, selection.positionColumn); -} - -function testDeleteLinesCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection): void { - testCommand(lines, null, selection, (sel) => createFromSelection(sel), expectedLines, expectedSelection); -} - -suite('Editor Contrib - Delete Lines Command', () => { - - test('empty selection in middle of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(2, 3, 2, 3), - [ - 'first', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(2, 3, 2, 3) - ); - }); - - test('empty selection at top of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(1, 5, 1, 5), - [ - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(1, 5, 1, 5) - ); - }); - - test('empty selection at end of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(5, 2, 5, 2), - [ - 'first', - 'second line', - 'third line', - 'fourth line' - ], - new Selection(4, 2, 4, 2) - ); - }); - - test('with selection in middle of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(3, 3, 2, 2), - [ - 'first', - 'fourth line', - 'fifth' - ], - new Selection(2, 2, 2, 2) - ); - }); - - test('with selection at top of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(1, 4, 1, 5), - [ - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(1, 5, 1, 5) - ); - }); - - test('with selection at end of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(5, 1, 5, 2), - [ - 'first', - 'second line', - 'third line', - 'fourth line' - ], - new Selection(4, 2, 4, 2) - ); - }); - - test('with full line selection in middle of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(4, 1, 2, 1), - [ - 'first', - 'fourth line', - 'fifth' - ], - new Selection(2, 1, 2, 1) - ); - }); - - test('with full line selection at top of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(2, 1, 1, 5), - [ - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(1, 5, 1, 5) - ); - }); - - test('with full line selection at end of lines', function () { - testDeleteLinesCommand( - [ - 'first', - 'second line', - 'third line', - 'fourth line', - 'fifth' - ], - new Selection(4, 1, 5, 2), - [ - 'first', - 'second line', - 'third line' - ], - new Selection(3, 2, 3, 2) - ); - }); -}); diff --git a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts index 37eb1ee76b..b04822e3e3 100644 --- a/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts +++ b/src/vs/editor/contrib/linesOperations/test/linesOperations.test.ts @@ -899,4 +899,250 @@ suite('Editor Contrib - Line Operations', () => { assert.equal(editor.getValue(), 'a\nc'); }); }); + + function testDeleteLinesCommand(initialText: string[], _initialSelections: Selection | Selection[], resultingText: string[], _resultingSelections: Selection | Selection[]): void { + const initialSelections = Array.isArray(_initialSelections) ? _initialSelections : [_initialSelections]; + const resultingSelections = Array.isArray(_resultingSelections) ? _resultingSelections : [_resultingSelections]; + withTestCodeEditor(initialText, {}, (editor) => { + editor.setSelections(initialSelections); + const deleteLinesAction = new DeleteLinesAction(); + deleteLinesAction.run(null!, editor); + + assert.equal(editor.getValue(), resultingText.join('\n')); + assert.deepEqual(editor.getSelections(), resultingSelections); + }); + } + + test('empty selection in middle of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(2, 3, 2, 3), + [ + 'first', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(2, 3, 2, 3) + ); + }); + + test('empty selection at top of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(1, 5, 1, 5), + [ + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(1, 5, 1, 5) + ); + }); + + test('empty selection at end of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(5, 2, 5, 2), + [ + 'first', + 'second line', + 'third line', + 'fourth line' + ], + new Selection(4, 2, 4, 2) + ); + }); + + test('with selection in middle of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(3, 3, 2, 2), + [ + 'first', + 'fourth line', + 'fifth' + ], + new Selection(2, 2, 2, 2) + ); + }); + + test('with selection at top of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(1, 4, 1, 5), + [ + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(1, 5, 1, 5) + ); + }); + + test('with selection at end of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(5, 1, 5, 2), + [ + 'first', + 'second line', + 'third line', + 'fourth line' + ], + new Selection(4, 2, 4, 2) + ); + }); + + test('with full line selection in middle of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(4, 1, 2, 1), + [ + 'first', + 'fourth line', + 'fifth' + ], + new Selection(2, 1, 2, 1) + ); + }); + + test('with full line selection at top of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(2, 1, 1, 5), + [ + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(1, 5, 1, 5) + ); + }); + + test('with full line selection at end of lines', function () { + testDeleteLinesCommand( + [ + 'first', + 'second line', + 'third line', + 'fourth line', + 'fifth' + ], + new Selection(4, 1, 5, 2), + [ + 'first', + 'second line', + 'third line' + ], + new Selection(3, 2, 3, 2) + ); + }); + + test('multicursor 1', function () { + testDeleteLinesCommand( + [ + 'class P {', + '', + ' getA() {', + ' if (true) {', + ' return "a";', + ' }', + ' }', + '', + ' getB() {', + ' if (true) {', + ' return "b";', + ' }', + ' }', + '', + ' getC() {', + ' if (true) {', + ' return "c";', + ' }', + ' }', + '}', + ], + [ + new Selection(4, 1, 5, 1), + new Selection(10, 1, 11, 1), + new Selection(16, 1, 17, 1), + ], + [ + 'class P {', + '', + ' getA() {', + ' return "a";', + ' }', + ' }', + '', + ' getB() {', + ' return "b";', + ' }', + ' }', + '', + ' getC() {', + ' return "c";', + ' }', + ' }', + '}', + ], + [ + new Selection(4, 1, 4, 1), + new Selection(9, 1, 9, 1), + new Selection(14, 1, 14, 1), + ] + ); + }); }); diff --git a/src/vs/editor/contrib/links/getLinks.ts b/src/vs/editor/contrib/links/getLinks.ts index 1757980555..f07bbe94f7 100644 --- a/src/vs/editor/contrib/links/getLinks.ts +++ b/src/vs/editor/contrib/links/getLinks.ts @@ -15,7 +15,7 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; export class Link implements ILink { private _link: ILink; - private _provider: LinkProvider; + private readonly _provider: LinkProvider; constructor(link: ILink, provider: LinkProvider) { this._link = link; @@ -33,14 +33,18 @@ export class Link implements ILink { return this._link.range; } - get url(): string | undefined { + get url(): URI | string | undefined { return this._link.url; } resolve(token: CancellationToken): Promise { if (this._link.url) { try { - return Promise.resolve(URI.parse(this._link.url)); + if (typeof this._link.url === 'string') { + return Promise.resolve(URI.parse(this._link.url)); + } else { + return Promise.resolve(this._link.url); + } } catch (e) { return Promise.reject(new Error('invalid')); } diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index 030a8566b5..e6b5b3db7d 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -111,7 +111,7 @@ class LinkOccurrence { } private static _getOptions(link: Link, useMetaKey: boolean, isActive: boolean): ModelDecorationOptions { - if (link.url && /^command:/i.test(link.url)) { + if (link.url && /^command:/i.test(link.url.toString())) { if (useMetaKey) { return (isActive ? decoration.metaCommandActive : decoration.metaCommand); } else { @@ -153,14 +153,14 @@ class LinkDetector implements editorCommon.IEditorContribution { static RECOMPUTE_TIME = 1000; // ms - private editor: ICodeEditor; + private readonly editor: ICodeEditor; private enabled: boolean; private listenersToRemove: IDisposable[]; - private timeout: async.TimeoutTimer; + private readonly timeout: async.TimeoutTimer; private computePromise: async.CancelablePromise | null; private activeLinkDecorationId: string | null; - private openerService: IOpenerService; - private notificationService: INotificationService; + private readonly openerService: IOpenerService; + private readonly notificationService: INotificationService; private currentOccurrences: { [decorationId: string]: LinkOccurrence; }; constructor( @@ -341,7 +341,7 @@ class LinkDetector implements editorCommon.IEditorContribution { }, err => { // different error cases if (err === 'invalid') { - this.notificationService.warn(nls.localize('invalid.url', 'Failed to open this link because it is not well-formed: {0}', link.url)); + this.notificationService.warn(nls.localize('invalid.url', 'Failed to open this link because it is not well-formed: {0}', link.url!.toString())); } else if (err === 'missing') { this.notificationService.warn(nls.localize('missing.url', 'Failed to open this link because its target is missing.')); } else { diff --git a/src/vs/editor/contrib/message/messageController.ts b/src/vs/editor/contrib/message/messageController.ts index 80a3efe85c..e3df2168f1 100644 --- a/src/vs/editor/contrib/message/messageController.ts +++ b/src/vs/editor/contrib/message/messageController.ts @@ -33,8 +33,8 @@ export class MessageController extends Disposable implements editorCommon.IEdito return MessageController._id; } - private _editor: ICodeEditor; - private _visible: IContextKey; + private readonly _editor: ICodeEditor; + private readonly _visible: IContextKey; private _messageWidget: MessageWidget; private _messageListeners: IDisposable[] = []; @@ -125,9 +125,9 @@ class MessageWidget implements IContentWidget { readonly allowEditorOverflow = true; readonly suppressMouseDown = false; - private _editor: ICodeEditor; - private _position: IPosition; - private _domNode: HTMLDivElement; + private readonly _editor: ICodeEditor; + private readonly _position: IPosition; + private readonly _domNode: HTMLDivElement; static fadeOut(messageWidget: MessageWidget): IDisposable { let handle: any; diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 1c01c2586c..4b1b71014b 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -802,10 +802,10 @@ class SelectionHighlighterState { export class SelectionHighlighter extends Disposable implements IEditorContribution { private static readonly ID = 'editor.contrib.selectionHighlighter'; - private editor: ICodeEditor; + private readonly editor: ICodeEditor; private _isEnabled: boolean; private decorations: string[]; - private updateSoon: RunOnceScheduler; + private readonly updateSoon: RunOnceScheduler; private state: SelectionHighlighterState | null; constructor(editor: ICodeEditor) { diff --git a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts index cf8a28ec22..25c8d96c4f 100644 --- a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts +++ b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts @@ -65,7 +65,7 @@ suite('Multicursor selection', () => { onWillSaveState: Event.None, get: (key: string) => queryState[key], getBoolean: (key: string) => !!queryState[key], - getInteger: (key: string) => undefined!, + getNumber: (key: string) => undefined!, store: (key: string, value: any) => { queryState[key] = value; return Promise.resolve(); }, remove: (key) => undefined } as IStorageService); diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts b/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts index 98e8835632..7086b5ec82 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsModel.ts @@ -26,7 +26,13 @@ namespace ParameterHintState { } export const Default = new class { readonly type = Type.Default; }; - export const Pending = new class { readonly type = Type.Pending; }; + + export class Pending { + readonly type = Type.Pending; + constructor( + readonly request: CancelablePromise + ) { } + } export class Active { readonly type = Type.Active; @@ -35,7 +41,7 @@ namespace ParameterHintState { ) { } } - export type State = typeof Default | typeof Pending | Active; + export type State = typeof Default | Pending | Active; } export class ParameterHintsModel extends Disposable { @@ -45,14 +51,13 @@ export class ParameterHintsModel extends Disposable { private readonly _onChangedHints = this._register(new Emitter()); public readonly onChangedHints = this._onChangedHints.event; - private editor: ICodeEditor; + private readonly editor: ICodeEditor; private enabled: boolean; - private state: ParameterHintState.State = ParameterHintState.Default; + private _state: ParameterHintState.State = ParameterHintState.Default; private triggerChars = new CharacterSet(); private retriggerChars = new CharacterSet(); - private throttledDelayer: Delayer; - private provideSignatureHelpRequest?: CancelablePromise; + private readonly throttledDelayer: Delayer; private triggerId = 0; constructor( @@ -78,7 +83,16 @@ export class ParameterHintsModel extends Disposable { this.onModelChanged(); } + private get state() { return this._state; } + private set state(value: ParameterHintState.State) { + if (this._state.type === ParameterHintState.Type.Pending) { + this._state.request.cancel(); + } + this._state = value; + } + cancel(silent: boolean = false): void { + this.state = ParameterHintState.Default; this.throttledDelayer.cancel(); @@ -86,16 +100,11 @@ export class ParameterHintsModel extends Disposable { if (!silent) { this._onChangedHints.fire(undefined); } - - if (this.provideSignatureHelpRequest) { - this.provideSignatureHelpRequest.cancel(); - this.provideSignatureHelpRequest = undefined; - } } trigger(context: TriggerContext, delay?: number): void { const model = this.editor.getModel(); - if (model === null || !modes.SignatureHelpProviderRegistry.has(model)) { + if (!model || !modes.SignatureHelpProviderRegistry.has(model)) { return; } @@ -166,12 +175,10 @@ export class ParameterHintsModel extends Disposable { const model = this.editor.getModel(); const position = this.editor.getPosition(); - this.state = ParameterHintState.Pending; + this.state = new ParameterHintState.Pending(createCancelablePromise(token => + provideSignatureHelp(model, position, triggerContext, token))); - this.provideSignatureHelpRequest = createCancelablePromise(token => - provideSignatureHelp(model, position, triggerContext, token)); - - return this.provideSignatureHelpRequest.then(result => { + return this.state.request.then(result => { // Check that we are still resolving the correct signature help if (triggerId !== this.triggerId) { return false; @@ -186,7 +193,9 @@ export class ParameterHintsModel extends Disposable { return true; } }).catch(error => { - this.state = ParameterHintState.Default; + if (triggerId === this.triggerId) { + this.state = ParameterHintState.Default; + } onUnexpectedError(error); return false; }); diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index 355d11250b..b105b43019 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -47,7 +47,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable { allowEditorOverflow = true; constructor( - private editor: ICodeEditor, + private readonly editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, @IOpenerService openerService: IOpenerService, @IModeService modeService: IModeService, @@ -72,6 +72,7 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable { private createParamaterHintDOMNodes() { this.element = $('.editor-widget.parameter-hints-widget'); const wrapper = dom.append(this.element, $('.wrapper')); + wrapper.tabIndex = -1; const buttons = dom.append(wrapper, $('.buttons')); const previous = dom.append(buttons, $('.button.previous')); diff --git a/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts b/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts index d0ee63b86c..3748063f98 100644 --- a/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts +++ b/src/vs/editor/contrib/parameterHints/test/parameterHintsModel.test.ts @@ -137,7 +137,7 @@ suite('ParameterHintsModel', () => { assert.strictEqual(invokeCount, 2); assert.strictEqual(context.triggerKind, modes.SignatureHelpTriggerKind.TriggerCharacter); assert.strictEqual(context.triggerCharacter, triggerChar); - assert.strictEqual(context.isRetrigger, false); + assert.strictEqual(context.isRetrigger, true); assert.strictEqual(context.activeSignatureHelp, undefined); done(); } diff --git a/src/vs/editor/contrib/referenceSearch/media/chevron-down-hc.svg b/src/vs/editor/contrib/referenceSearch/media/chevron-down-hc.svg new file mode 100644 index 0000000000..eca0994a74 --- /dev/null +++ b/src/vs/editor/contrib/referenceSearch/media/chevron-down-hc.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/editor/contrib/referenceSearch/media/chevron-down-inverse.svg b/src/vs/editor/contrib/referenceSearch/media/chevron-down-inverse.svg new file mode 100644 index 0000000000..0671cba2ff --- /dev/null +++ b/src/vs/editor/contrib/referenceSearch/media/chevron-down-inverse.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/editor/contrib/referenceSearch/media/chevron-down.svg b/src/vs/editor/contrib/referenceSearch/media/chevron-down.svg new file mode 100644 index 0000000000..9514d8f317 --- /dev/null +++ b/src/vs/editor/contrib/referenceSearch/media/chevron-down.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/editor/contrib/referenceSearch/media/chevron-up-hc.svg b/src/vs/editor/contrib/referenceSearch/media/chevron-up-hc.svg new file mode 100644 index 0000000000..1c668f52b4 --- /dev/null +++ b/src/vs/editor/contrib/referenceSearch/media/chevron-up-hc.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/editor/contrib/referenceSearch/media/chevron-up-inverse-hc.svg b/src/vs/editor/contrib/referenceSearch/media/chevron-up-inverse-hc.svg new file mode 100644 index 0000000000..1c668f52b4 --- /dev/null +++ b/src/vs/editor/contrib/referenceSearch/media/chevron-up-inverse-hc.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/editor/contrib/referenceSearch/media/chevron-up-inverse.svg b/src/vs/editor/contrib/referenceSearch/media/chevron-up-inverse.svg new file mode 100644 index 0000000000..31bdf3deeb --- /dev/null +++ b/src/vs/editor/contrib/referenceSearch/media/chevron-up-inverse.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/editor/contrib/referenceSearch/media/chevron-up.svg b/src/vs/editor/contrib/referenceSearch/media/chevron-up.svg new file mode 100644 index 0000000000..7e38887f57 --- /dev/null +++ b/src/vs/editor/contrib/referenceSearch/media/chevron-up.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css b/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css index 6bc5f0a76f..6d46559fe4 100644 --- a/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css +++ b/src/vs/editor/contrib/referenceSearch/media/peekViewWidget.css @@ -19,6 +19,14 @@ cursor: pointer; } +.monaco-editor .peekview-widget .head .peekview-title .icon { + display: inline-block; + height: 16px; + width: 16px; + vertical-align: text-bottom; + margin-right: 4px; +} + .monaco-editor .peekview-widget .head .peekview-title .dirname:not(:empty) { font-size: 0.9em; margin-left: 0.5em; @@ -73,3 +81,27 @@ background: url('close-inverse.svg') center center no-repeat; } +.monaco-editor .peekview-widget .peekview-actions .icon.chevron-up { + background: url('chevron-up-inverse.svg') center center no-repeat; +} + +.vs-dark .monaco-editor .peekview-widget .peekview-actions .icon.chevron-up { + background: url('chevron-up.svg') center center no-repeat; +} + +.hc-black .monaco-editor .peekview-widget .peekview-actions .icon.chevron-up { + background: url('chevron-up-inverse-hc.svg') center center no-repeat; +} + +.monaco-editor .peekview-widget .peekview-actions .icon.chevron-down { + background: url('chevron-down-inverse.svg') center center no-repeat; +} + +.vs-dark .monaco-editor .peekview-widget .peekview-actions .icon.chevron-down { + background: url('chevron-down.svg') center center no-repeat; +} + +.hc-black .monaco-editor .peekview-widget .peekview-actions .icon.chevron-down { + background: url('chevron-down-hc.svg') center center no-repeat; +} + diff --git a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts b/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts index 3fca714131..77cf881912 100644 --- a/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/peekViewWidget.ts @@ -34,9 +34,9 @@ export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor | null { } export interface IPeekViewStyles extends IStyles { - headerBackgroundColor?: Color | null; - primaryHeadingColor?: Color | null; - secondaryHeadingColor?: Color | null; + headerBackgroundColor?: Color; + primaryHeadingColor?: Color; + secondaryHeadingColor?: Color; } export type IPeekViewOptions = IOptions & IPeekViewStyles; @@ -54,6 +54,7 @@ export abstract class PeekViewWidget extends ZoneWidget { private _onDidClose = new Emitter(); protected _headElement: HTMLDivElement; + protected _headingIcon: HTMLElement; protected _primaryHeading: HTMLElement; protected _secondaryHeading: HTMLElement; protected _metaHeading: HTMLElement; @@ -123,10 +124,11 @@ export abstract class PeekViewWidget extends ZoneWidget { dom.append(this._headElement, titleElement); dom.addStandardDisposableListener(titleElement, 'click', event => this._onTitleClick(event)); + this._headingIcon = dom.$('span'); this._primaryHeading = dom.$('span.filename'); this._secondaryHeading = dom.$('span.dirname'); this._metaHeading = dom.$('span.meta'); - dom.append(titleElement, this._primaryHeading, this._secondaryHeading, this._metaHeading); + dom.append(titleElement, this._headingIcon, this._primaryHeading, this._secondaryHeading, this._metaHeading); const actionsContainer = dom.$('.peekview-actions'); dom.append(this._headElement, actionsContainer); @@ -149,6 +151,10 @@ export abstract class PeekViewWidget extends ZoneWidget { // implement me } + public setTitleIcon(iconClassName: string): void { + this._headingIcon.className = iconClassName ? `icon ${iconClassName}` : ''; + } + public setTitle(primaryHeading: string, secondaryHeading?: string): void { this._primaryHeading.innerHTML = strings.escape(primaryHeading); this._primaryHeading.setAttribute('aria-label', primaryHeading); diff --git a/src/vs/editor/contrib/referenceSearch/referencesController.ts b/src/vs/editor/contrib/referenceSearch/referencesController.ts index edb7cc6d41..b30ee91abc 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesController.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesController.ts @@ -32,21 +32,21 @@ export abstract class ReferencesController implements editorCommon.IEditorContri private static readonly ID = 'editor.contrib.referencesController'; - private _editor: ICodeEditor; - private _widget: ReferenceWidget; - private _model: ReferencesModel; + private readonly _editor: ICodeEditor; + private _widget: ReferenceWidget | null; + private _model: ReferencesModel | null; private _requestIdPool = 0; private _disposables: IDisposable[] = []; private _ignoreModelChangeEvent = false; - private _referenceSearchVisible: IContextKey; + private readonly _referenceSearchVisible: IContextKey; public static get(editor: ICodeEditor): ReferencesController { return editor.getContribution(ReferencesController.ID); } public constructor( - private _defaultTreeKeyboardSupport: boolean, + private readonly _defaultTreeKeyboardSupport: boolean, editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService, @ICodeEditorService private readonly _editorService: ICodeEditorService, @@ -66,23 +66,26 @@ export abstract class ReferencesController implements editorCommon.IEditorContri public dispose(): void { this._referenceSearchVisible.reset(); dispose(this._disposables); - dispose(this._widget); - dispose(this._model); - this._widget = null; - this._model = null; - this._editor = null; + if (this._widget) { + dispose(this._widget); + this._widget = null; + } + if (this._model) { + dispose(this._model); + this._model = null; + } } public toggleWidget(range: Range, modelPromise: CancelablePromise, options: RequestOptions): void { // close current widget and return early is position didn't change - let widgetPosition: Position; + let widgetPosition: Position | undefined; if (this._widget) { widgetPosition = this._widget.position; } this.closeWidget(); if (!!widgetPosition && range.containsPosition(widgetPosition)) { - return null; + return; } this._referenceSearchVisible.set(true); @@ -101,9 +104,10 @@ export abstract class ReferencesController implements editorCommon.IEditorContri this._widget.show(range); this._disposables.push(this._widget.onDidClose(() => { modelPromise.cancel(); - - this._storageService.store(storageKey, JSON.stringify(this._widget.layoutData), StorageScope.GLOBAL); - this._widget = null; + if (this._widget) { + this._storageService.store(storageKey, JSON.stringify(this._widget.layoutData), StorageScope.GLOBAL); + this._widget = null; + } this.closeWidget(); })); @@ -119,13 +123,17 @@ export abstract class ReferencesController implements editorCommon.IEditorContri break; } case 'side': - this.openReference(element, kind === 'side'); + if (element) { + this.openReference(element, kind === 'side'); + } break; case 'goto': - if (options.onGoto) { - options.onGoto(element); - } else { - this._gotoReference(element); + if (element) { + if (options.onGoto) { + options.onGoto(element); + } else { + this._gotoReference(element); + } } break; } @@ -148,7 +156,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri // show widget return this._widget.setModel(this._model).then(() => { - if (this._widget) { // might have been closed + if (this._widget && this._model && this._editor.hasModel()) { // might have been closed // set title this._widget.setMetaTitle(options.getMetaTitle(this._model)); @@ -169,31 +177,46 @@ export abstract class ReferencesController implements editorCommon.IEditorContri } public async goToNextOrPreviousReference(fwd: boolean) { - if (this._model) { // can be called while still resolving... - let source = this._model.nearestReference(this._editor.getModel().uri, this._widget.position); - let target = this._model.nextOrPreviousReference(source, fwd); - let editorFocus = this._editor.hasTextFocus(); - await this._widget.setSelection(target); - await this._gotoReference(target); - if (editorFocus) { - this._editor.focus(); - } + if (!this._editor.hasModel() || !this._model || !this._widget) { + // can be called while still resolving... + return; + } + const currentPosition = this._widget.position; + if (!currentPosition) { + return; + } + const source = this._model.nearestReference(this._editor.getModel().uri, currentPosition); + if (!source) { + return; + } + const target = this._model.nextOrPreviousReference(source, fwd); + const editorFocus = this._editor.hasTextFocus(); + await this._widget.setSelection(target); + await this._gotoReference(target); + if (editorFocus) { + this._editor.focus(); } } public closeWidget(): void { - dispose(this._widget); - this._widget = null; + if (this._widget) { + dispose(this._widget); + this._widget = null; + } this._referenceSearchVisible.reset(); this._disposables = dispose(this._disposables); - dispose(this._model); - this._model = null; + if (this._model) { + dispose(this._model); + this._model = null; + } this._editor.focus(); this._requestIdPool += 1; // Cancel pending requests } private _gotoReference(ref: Location): Promise { - this._widget.hide(); + if (this._widget) { + this._widget.hide(); + } this._ignoreModelChangeEvent = true; const range = Range.lift(ref.range).collapseToStart(); @@ -216,8 +239,10 @@ export abstract class ReferencesController implements editorCommon.IEditorContri return; } - this._widget.show(range); - this._widget.focus(); + if (this._widget) { + this._widget.show(range); + this._widget.focus(); + } }, (err) => { this._ignoreModelChangeEvent = false; diff --git a/src/vs/editor/contrib/referenceSearch/referencesModel.ts b/src/vs/editor/contrib/referenceSearch/referencesModel.ts index a628a4fa4d..162b1ef51c 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesModel.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesModel.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; -import { basename } from 'vs/base/common/paths'; +import { basename } from 'vs/base/common/resources'; import { IDisposable, dispose, IReference } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; @@ -44,7 +44,7 @@ export class OneReference { getAriaMessage(): string { return localize( 'aria.oneReference', "symbol in {0} on line {1} at column {2}", - basename(this.uri.fsPath), this.range.startLineNumber, this.range.startColumn + basename(this.uri), this.range.startLineNumber, this.range.startColumn ); } } @@ -89,7 +89,7 @@ export class FileReferences implements IDisposable { private _resolved: boolean; private _loadFailure: any; - constructor(private readonly _parent: ReferencesModel, private _uri: URI) { + constructor(private readonly _parent: ReferencesModel, private readonly _uri: URI) { this._children = []; } @@ -120,9 +120,9 @@ export class FileReferences implements IDisposable { getAriaMessage(): string { const len = this.children.length; if (len === 1) { - return localize('aria.fileReferences.1', "1 symbol in {0}, full path {1}", basename(this.uri.fsPath), this.uri.fsPath); + return localize('aria.fileReferences.1', "1 symbol in {0}, full path {1}", basename(this.uri), this.uri.fsPath); } else { - return localize('aria.fileReferences.N', "{0} symbols in {1}, full path {2}", len, basename(this.uri.fsPath), this.uri.fsPath); + return localize('aria.fileReferences.N', "{0} symbols in {1}, full path {2}", len, basename(this.uri), this.uri.fsPath); } } diff --git a/src/vs/editor/contrib/referenceSearch/referencesTree.ts b/src/vs/editor/contrib/referenceSearch/referencesTree.ts index 3858ed4dff..40778caf2d 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesTree.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesTree.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { ReferencesModel, FileReferences, OneReference } from './referencesModel'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITreeRenderer, ITreeNode, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; @@ -15,7 +14,7 @@ import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import * as dom from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { getBaseLabel } from 'vs/base/common/labels'; -import { dirname } from 'vs/base/common/resources'; +import { dirname, basename } from 'vs/base/common/resources'; import { escape } from 'vs/base/common/strings'; import { Disposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -23,7 +22,6 @@ import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate, IKeyboardNavigationLabelProvider, IIdentityProvider } from 'vs/base/browser/ui/list/list'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { basename } from 'vs/base/common/paths'; import { FuzzyScore, createMatches, IMatch } from 'vs/base/common/filters'; //#region data source @@ -86,7 +84,7 @@ export class StringRepresentationProvider implements IKeyboardNavigationLabelPro getKeyboardNavigationLabel(element: TreeElement): { toString(): string; } { // todo@joao `OneReference` elements are lazy and their "real" label // isn't known yet - return basename(element.uri.path); + return basename(element.uri); } mightProducePrintableCharacter(event: IKeyboardEvent): boolean { @@ -126,7 +124,7 @@ class FileReferencesTemplate extends Disposable { set(element: FileReferences, matches: IMatch[]) { let parent = dirname(element.uri); - this.file.setLabel(getBaseLabel(element.uri), parent ? this._uriLabel.getUriLabel(parent, { relative: true }) : undefined, { title: this._uriLabel.getUriLabel(element.uri), matches }); + this.file.setLabel(getBaseLabel(element.uri), this._uriLabel.getUriLabel(parent, { relative: true }), { title: this._uriLabel.getUriLabel(element.uri), matches }); const len = element.children.length; this.badge.setCount(len); if (element.failure) { diff --git a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts index 4665df630f..782a401e6e 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts @@ -400,7 +400,6 @@ export class ReferenceWidget extends PeekViewWidget { if (e.browserEvent instanceof KeyboardEvent) { // todo@joh make this a command goto = true; - } else if (e.browserEvent instanceof MouseEvent) { aside = e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey; goto = e.browserEvent.detail === 2; @@ -413,6 +412,18 @@ export class ReferenceWidget extends PeekViewWidget { onEvent(e.elements[0], 'show'); } }); + this._tree.onDidOpen(e => { + const aside = (e.browserEvent instanceof MouseEvent) && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey); + const goto = !e.browserEvent || ((e.browserEvent instanceof MouseEvent) && e.browserEvent.detail === 2); + + if (aside) { + onEvent(e.elements[0], 'side'); + } else if (goto) { + onEvent(e.elements[0], 'goto'); + } else { + onEvent(e.elements[0], 'show'); + } + }); dom.hide(this._treeContainer); } @@ -461,14 +472,14 @@ export class ReferenceWidget extends PeekViewWidget { }); } - public setModel(newModel: ReferencesModel | undefined): Promise | undefined { + public setModel(newModel: ReferencesModel | undefined): Promise { // clean up this._disposeOnNewModel = dispose(this._disposeOnNewModel); this._model = newModel; if (this._model) { return this._onNewModel(); } - return undefined; + return Promise.resolve(); } private _onNewModel(): Promise { @@ -488,7 +499,7 @@ export class ReferenceWidget extends PeekViewWidget { this._disposeOnNewModel.push(this._decorationsManager); // listen on model changes - this._disposeOnNewModel.push(this._model.onDidChangeReferenceRange(reference => this._tree.refresh(reference))); + this._disposeOnNewModel.push(this._model.onDidChangeReferenceRange(reference => this._tree.rerender(reference))); // listen on editor this._disposeOnNewModel.push(this._preview.onMouseDown(e => { @@ -543,7 +554,7 @@ export class ReferenceWidget extends PeekViewWidget { // Update widget header if (reference.uri.scheme !== Schemas.inMemory) { - this.setTitle(basenameOrAuthority(reference.uri), this._uriLabel.getUriLabel(dirname(reference.uri)!)); + this.setTitle(basenameOrAuthority(reference.uri), this._uriLabel.getUriLabel(dirname(reference.uri))); } else { this.setTitle(nls.localize('peekView.alternateTitle', "References")); } diff --git a/src/vs/editor/contrib/rename/rename.ts b/src/vs/editor/contrib/rename/rename.ts index c4f17bdacd..0941aa1512 100644 --- a/src/vs/editor/contrib/rename/rename.ts +++ b/src/vs/editor/contrib/rename/rename.ts @@ -283,7 +283,7 @@ export class RenameAction extends EditorAction { runCommand(accessor: ServicesAccessor, args: [URI, IPosition]): void | Promise { const editorService = accessor.get(ICodeEditorService); - const [uri, pos] = args || [undefined, undefined]; + const [uri, pos] = Array.isArray(args) && args || [undefined, undefined]; if (URI.isUri(uri) && Position.isIPosition(pos)) { return editorService.openCodeEditor({ resource: uri }, editorService.getActiveCodeEditor()).then(editor => { diff --git a/src/vs/editor/contrib/smartSelect/bracketSelections.ts b/src/vs/editor/contrib/smartSelect/bracketSelections.ts index f6ff0d1b12..6fd74cf046 100644 --- a/src/vs/editor/contrib/smartSelect/bracketSelections.ts +++ b/src/vs/editor/contrib/smartSelect/bracketSelections.ts @@ -11,12 +11,19 @@ import { LinkedList } from 'vs/base/common/linkedList'; export class BracketSelectionRangeProvider implements SelectionRangeProvider { - provideSelectionRanges(model: ITextModel, position: Position): Promise { - const bucket: SelectionRange[] = []; - const ranges = new Map>(); - return new Promise(resolve => BracketSelectionRangeProvider._bracketsRightYield(resolve, 0, model, position, ranges)) - .then(() => new Promise(resolve => BracketSelectionRangeProvider._bracketsLeftYield(resolve, 0, model, position, ranges, bucket))) - .then(() => bucket); + async provideSelectionRanges(model: ITextModel, positions: Position[]): Promise { + const result: SelectionRange[][] = []; + + for (const position of positions) { + const bucket: SelectionRange[] = []; + result.push(bucket); + + const ranges = new Map>(); + await new Promise(resolve => BracketSelectionRangeProvider._bracketsRightYield(resolve, 0, model, position, ranges)); + await new Promise(resolve => BracketSelectionRangeProvider._bracketsLeftYield(resolve, 0, model, position, ranges, bucket)); + } + + return result; } private static readonly _maxDuration = 30; diff --git a/src/vs/editor/contrib/smartSelect/smartSelect.ts b/src/vs/editor/contrib/smartSelect/smartSelect.ts index 9b25e2eb6f..8624cd30f6 100644 --- a/src/vs/editor/contrib/smartSelect/smartSelect.ts +++ b/src/vs/editor/contrib/smartSelect/smartSelect.ts @@ -10,6 +10,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EditorAction, IActionOptions, registerEditorAction, registerEditorContribution, ServicesAccessor, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ITextModel } from 'vs/editor/common/model'; @@ -21,6 +22,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSelections'; import { BracketSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/bracketSelections'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { onUnexpectedExternalError } from 'vs/base/common/errors'; class SelectionRanges { @@ -53,7 +55,7 @@ class SmartSelectController implements IEditorContribution { private readonly _editor: ICodeEditor; - private _state?: SelectionRanges; + private _state?: SelectionRanges[]; private _selectionListener?: IDisposable; private _ignoreSelection: boolean = false; @@ -74,7 +76,7 @@ class SmartSelectController implements IEditorContribution { return; } - const selection = this._editor.getSelection(); + const selections = this._editor.getSelections(); const model = this._editor.getModel(); if (!modes.SelectionRangeRegistry.has(model)) { @@ -85,25 +87,27 @@ class SmartSelectController implements IEditorContribution { let promise: Promise = Promise.resolve(undefined); if (!this._state) { - promise = provideSelectionRanges(model, selection.getPosition(), CancellationToken.None).then(ranges => { - if (!arrays.isNonEmptyArray(ranges)) { + promise = provideSelectionRanges(model, selections.map(s => s.getPosition()), CancellationToken.None).then(ranges => { + if (!arrays.isNonEmptyArray(ranges) || ranges.length !== selections.length) { // invalid result return; } - if (!this._editor.hasModel() || !this._editor.getSelection().equalsSelection(selection)) { + if (!this._editor.hasModel() || !arrays.equals(this._editor.getSelections(), selections, (a, b) => a.equalsSelection(b))) { // invalid editor state return; } - ranges = ranges.filter(range => { - // filter ranges inside the selection - return range.containsPosition(selection.getStartPosition()) && range.containsPosition(selection.getEndPosition()); - }); + for (let i = 0; i < ranges.length; i++) { + ranges[i] = ranges[i].filter(range => { + // filter ranges inside the selection + return range.containsPosition(selections[i].getStartPosition()) && range.containsPosition(selections[i].getEndPosition()); + }); + // prepend current selection + ranges[i].unshift(selections[i]); + } - // prepend current selection - ranges.unshift(selection); - this._state = new SelectionRanges(0, ranges); + this._state = ranges.map(ranges => new SelectionRanges(0, ranges)); // listen to caret move and forget about state dispose(this._selectionListener); @@ -121,11 +125,11 @@ class SmartSelectController implements IEditorContribution { // no state return; } - this._state = this._state.mov(forward); - const selection = this._state.ranges[this._state.index]; + this._state = this._state.map(state => state.mov(forward)); + const selections = this._state.map(state => Selection.fromPositions(state.ranges[state.index].getStartPosition(), state.ranges[state.index].getEndPosition())); this._ignoreSelection = true; try { - this._editor.setSelection(selection); + this._editor.setSelections(selections); } finally { this._ignoreSelection = false; } @@ -207,91 +211,95 @@ registerEditorAction(ShrinkSelectionAction); // word selection modes.SelectionRangeRegistry.register('*', new WordSelectionRangeProvider()); -export function provideSelectionRanges(model: ITextModel, position: Position, token: CancellationToken): Promise { +export function provideSelectionRanges(model: ITextModel, positions: Position[], token: CancellationToken): Promise { - const provider = modes.SelectionRangeRegistry.orderedGroups(model); + const providers = modes.SelectionRangeRegistry.all(model); - if (provider.length === 1) { + if (providers.length === 1) { // add word selection and bracket selection when no provider exists - provider.unshift([new BracketSelectionRangeProvider()]); - } - - interface RankedRange { - rank: number; - range: Range; + providers.unshift(new BracketSelectionRangeProvider()); } let work: Promise[] = []; - let ranges: RankedRange[] = []; - let rank = 0; + let allRawRanges: Range[][] = []; - for (const group of provider) { - rank += 1; - for (const prov of group) { - work.push(Promise.resolve(prov.provideSelectionRanges(model, position, token)).then(selectionRanges => { - if (arrays.isNonEmptyArray(selectionRanges)) { - for (const sel of selectionRanges) { - if (Range.isIRange(sel.range) && Range.containsPosition(sel.range, position)) { - ranges.push({ range: Range.lift(sel.range), rank }); + for (const provider of providers) { + + work.push(Promise.resolve(provider.provideSelectionRanges(model, positions, token)).then(allProviderRanges => { + if (arrays.isNonEmptyArray(allProviderRanges) && allProviderRanges.length === positions.length) { + for (let i = 0; i < positions.length; i++) { + if (!allRawRanges[i]) { + allRawRanges[i] = []; + } + for (const oneProviderRanges of allProviderRanges[i]) { + if (Range.isIRange(oneProviderRanges.range) && Range.containsPosition(oneProviderRanges.range, positions[i])) { + allRawRanges[i].push(Range.lift(oneProviderRanges.range)); } } } - })); - } + } + }, onUnexpectedExternalError)); } return Promise.all(work).then(() => { - if (ranges.length === 0) { - return []; - } + return allRawRanges.map(oneRawRanges => { - ranges.sort((a, b) => { - if (Position.isBefore(a.range.getStartPosition(), b.range.getStartPosition())) { - return 1; - } else if (Position.isBefore(b.range.getStartPosition(), a.range.getStartPosition())) { - return -1; - } else if (Position.isBefore(a.range.getEndPosition(), b.range.getEndPosition())) { - return -1; - } else if (Position.isBefore(b.range.getEndPosition(), a.range.getEndPosition())) { - return 1; - } else { - return b.rank - a.rank; + if (oneRawRanges.length === 0) { + return []; } + + // sort all by start/end position + oneRawRanges.sort((a, b) => { + if (Position.isBefore(a.getStartPosition(), b.getStartPosition())) { + return 1; + } else if (Position.isBefore(b.getStartPosition(), a.getStartPosition())) { + return -1; + } else if (Position.isBefore(a.getEndPosition(), b.getEndPosition())) { + return -1; + } else if (Position.isBefore(b.getEndPosition(), a.getEndPosition())) { + return 1; + } else { + return 0; + } + }); + + // remove ranges that don't contain the former range or that are equal to the + // former range + let oneRanges: Range[] = []; + let last: Range | undefined; + for (const range of oneRawRanges) { + if (!last || (Range.containsRange(range, last) && !Range.equalsRange(range, last))) { + oneRanges.push(range); + last = range; + } + } + + // add ranges that expand trivia at line starts and ends whenever a range + // wraps onto the a new line + let oneRangesWithTrivia: Range[] = [oneRanges[0]]; + for (let i = 1; i < oneRanges.length; i++) { + const prev = oneRanges[i - 1]; + const cur = oneRanges[i]; + if (cur.startLineNumber !== prev.startLineNumber || cur.endLineNumber !== prev.endLineNumber) { + // add line/block range without leading/failing whitespace + const rangeNoWhitespace = new Range(prev.startLineNumber, model.getLineFirstNonWhitespaceColumn(prev.startLineNumber), prev.endLineNumber, model.getLineLastNonWhitespaceColumn(prev.endLineNumber)); + if (rangeNoWhitespace.containsRange(prev) && !rangeNoWhitespace.equalsRange(prev)) { + oneRangesWithTrivia.push(rangeNoWhitespace); + } + // add line/block range + const rangeFull = new Range(prev.startLineNumber, 1, prev.endLineNumber, model.getLineMaxColumn(prev.endLineNumber)); + if (rangeFull.containsRange(prev) && !rangeFull.equalsRange(rangeNoWhitespace)) { + oneRangesWithTrivia.push(rangeFull); + } + } + oneRangesWithTrivia.push(cur); + } + return oneRangesWithTrivia; }); - - let result: Range[] = []; - let last: Range | undefined; - for (const { range } of ranges) { - if (!last || (Range.containsRange(range, last) && !Range.equalsRange(range, last))) { - result.push(range); - last = range; - } - } - - let result2: Range[] = [result[0]]; - for (let i = 1; i < result.length; i++) { - const prev = result[i - 1]; - const cur = result[i]; - if (cur.startLineNumber !== prev.startLineNumber || cur.endLineNumber !== prev.endLineNumber) { - // add line/block range without leading/failing whitespace - const rangeNoWhitespace = new Range(prev.startLineNumber, model.getLineFirstNonWhitespaceColumn(prev.startLineNumber), prev.endLineNumber, model.getLineLastNonWhitespaceColumn(prev.endLineNumber)); - if (rangeNoWhitespace.containsRange(prev) && !rangeNoWhitespace.equalsRange(prev)) { - result2.push(rangeNoWhitespace); - } - // add line/block range - const rangeFull = new Range(prev.startLineNumber, 1, prev.endLineNumber, model.getLineMaxColumn(prev.endLineNumber)); - if (rangeFull.containsRange(prev) && !rangeFull.equalsRange(rangeNoWhitespace)) { - result2.push(rangeFull); - } - } - result2.push(cur); - } - - return result2; }); } -registerDefaultLanguageCommand('_executeSelectionRangeProvider', function (model, position) { - return provideSelectionRanges(model, position, CancellationToken.None); +registerDefaultLanguageCommand('_executeSelectionRangeProvider', function (model, _position, args) { + return provideSelectionRanges(model, args.positions, CancellationToken.None); }); diff --git a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts index ae165c9d46..240209c453 100644 --- a/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts +++ b/src/vs/editor/contrib/smartSelect/test/smartSelect.test.ts @@ -79,7 +79,7 @@ suite('SmartSelect', () => { async function assertGetRangesToPosition(text: string[], lineNumber: number, column: number, ranges: Range[]): Promise { let uri = URI.file('test.js'); let model = modelService.createModel(text.join('\n'), new StaticLanguageSelector(mode.getLanguageIdentifier()), uri); - let actual = await provideSelectionRanges(model, new Position(lineNumber, column), CancellationToken.None); + let [actual] = await provideSelectionRanges(model, [new Position(lineNumber, column)], CancellationToken.None); let actualStr = actual!.map(r => new Range(r.startLineNumber, r.startColumn, r.endLineNumber, r.endColumn).toString()); let desiredStr = ranges.reverse().map(r => String(r)); @@ -203,7 +203,9 @@ suite('SmartSelect', () => { let model = modelService.createModel(value, new StaticLanguageSelector(mode.getLanguageIdentifier()), URI.parse('fake:lang')); let pos = model.getPositionAt(value.indexOf('|')); - let ranges = await provider.provideSelectionRanges(model, pos, CancellationToken.None); + let all = await provider.provideSelectionRanges(model, [pos], CancellationToken.None); + let ranges = all![0]; + modelService.destroyModel(model.uri); assert.equal(expected.length, ranges!.length); diff --git a/src/vs/editor/contrib/smartSelect/wordSelections.ts b/src/vs/editor/contrib/smartSelect/wordSelections.ts index ebc0112698..12ff057ef4 100644 --- a/src/vs/editor/contrib/smartSelect/wordSelections.ts +++ b/src/vs/editor/contrib/smartSelect/wordSelections.ts @@ -12,12 +12,16 @@ import { isUpperAsciiLetter, isLowerAsciiLetter } from 'vs/base/common/strings'; export class WordSelectionRangeProvider implements SelectionRangeProvider { - provideSelectionRanges(model: ITextModel, position: Position): SelectionRange[] { - let result: SelectionRange[] = []; - this._addInWordRanges(result, model, position); - this._addWordRanges(result, model, position); - this._addWhitespaceLine(result, model, position); - result.push({ range: model.getFullModelRange(), kind: 'statement.all' }); + provideSelectionRanges(model: ITextModel, positions: Position[]): SelectionRange[][] { + const result: SelectionRange[][] = []; + for (const position of positions) { + const bucket: SelectionRange[] = []; + result.push(bucket); + this._addInWordRanges(bucket, model, position); + this._addWordRanges(bucket, model, position); + this._addWhitespaceLine(bucket, model, position); + bucket.push({ range: model.getFullModelRange(), kind: 'statement.all' }); + } return result; } diff --git a/src/vs/editor/contrib/snippet/snippetController2.ts b/src/vs/editor/contrib/snippet/snippetController2.ts index 1d445944be..bed0d6ad17 100644 --- a/src/vs/editor/contrib/snippet/snippetController2.ts +++ b/src/vs/editor/contrib/snippet/snippetController2.ts @@ -19,6 +19,7 @@ import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from ' import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ILogService } from 'vs/platform/log/common/log'; import { SnippetSession } from './snippetSession'; +import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; export class SnippetController2 implements IEditorContribution { @@ -113,10 +114,26 @@ export class SnippetController2 implements IEditorContribution { this._updateState(); + // we listen on model and selection changes. usually + // both events come in together and this is to prevent + // that we don't call _updateState twice. + let state: EditorState; + let dedupedUpdateState = () => { + if (!state || !state.validate(this._editor)) { + this._updateState(); + state = new EditorState(this._editor, CodeEditorStateFlag.Selection | CodeEditorStateFlag.Value); + } + }; this._snippetListener = [ - this._editor.onDidChangeModelContent(e => e.isFlush && this.cancel()), + this._editor.onDidChangeModelContent(e => { + if (e.isFlush) { + this.cancel(); + } else { + setTimeout(dedupedUpdateState, 0); + } + }), + this._editor.onDidChangeCursorSelection(dedupedUpdateState), this._editor.onDidChangeModel(() => this.cancel()), - this._editor.onDidChangeCursorSelection(() => this._updateState()) ]; } @@ -193,7 +210,7 @@ export class SnippetController2 implements IEditorContribution { } } - cancel(): void { + cancel(resetSelection: boolean = false): void { this._inSnippet.reset(); this._hasPrevTabstop.reset(); this._hasNextTabstop.reset(); @@ -201,6 +218,12 @@ export class SnippetController2 implements IEditorContribution { dispose(this._session); this._session = undefined; this._modelVersionId = -1; + if (resetSelection) { + // reset selection to the primary cursor when being asked + // for. this happens when explicitly cancelling snippet mode, + // e.g. when pressing ESC + this._editor.setSelections([this._editor.getSelection()!]); + } } prev(): void { @@ -257,7 +280,7 @@ registerEditorCommand(new CommandCtor({ registerEditorCommand(new CommandCtor({ id: 'leaveSnippet', precondition: SnippetController2.InSnippetMode, - handler: ctrl => ctrl.cancel(), + handler: ctrl => ctrl.cancel(true), kbOpts: { weight: KeybindingWeight.EditorContrib + 30, kbExpr: EditorContextKeys.editorTextFocus, diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index e073d5ddba..d9797ef0a2 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -15,9 +15,10 @@ import { Selection } from 'vs/editor/common/core/selection'; import { IIdentifiedSingleEditOperation, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { optional } from 'vs/platform/instantiation/common/instantiation'; import { Choice, Placeholder, SnippetParser, Text, TextmateSnippet } from './snippetParser'; -import { ClipboardBasedVariableResolver, CompositeSnippetVariableResolver, ModelBasedVariableResolver, SelectionBasedVariableResolver, TimeBasedVariableResolver, CommentBasedVariableResolver } from './snippetVariables'; +import { ClipboardBasedVariableResolver, CompositeSnippetVariableResolver, ModelBasedVariableResolver, SelectionBasedVariableResolver, TimeBasedVariableResolver, CommentBasedVariableResolver, WorkspaceBasedVariableResolver } from './snippetVariables'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import * as colors from 'vs/platform/theme/common/colorRegistry'; @@ -196,10 +197,6 @@ export class OneSnippet { let ranges: Range[] | undefined; for (const placeholder of placeholdersWithEqualIndex) { - if (placeholder.isFinalTabstop) { - // ignore those - break; - } if (!ranges) { ranges = []; @@ -354,6 +351,7 @@ export class SnippetSession { const modelBasedVariableResolver = new ModelBasedVariableResolver(model); const clipboardService = editor.invokeWithinContext(accessor => accessor.get(IClipboardService, optional)); + const workspaceService = editor.invokeWithinContext(accessor => accessor.get(IWorkspaceContextService, optional)); let delta = 0; @@ -409,7 +407,8 @@ export class SnippetSession { new ClipboardBasedVariableResolver(clipboardService, idx, indexedSelections.length), new SelectionBasedVariableResolver(model, selection), new CommentBasedVariableResolver(model), - new TimeBasedVariableResolver + new TimeBasedVariableResolver, + new WorkspaceBasedVariableResolver(workspaceService), ])); const offset = model.getOffsetAt(start) + delta; @@ -571,6 +570,12 @@ export class SnippetSession { return false; } + if (allPossibleSelections.has(0)) { + // selection overlaps with a final tab stop which means + // we done + return false; + } + // add selections from 'this' snippet so that we know all // selections for this placeholder allPossibleSelections.forEach((array, index) => { diff --git a/src/vs/editor/contrib/snippet/snippetVariables.ts b/src/vs/editor/contrib/snippet/snippetVariables.ts index af18b3031d..bb253e2260 100644 --- a/src/vs/editor/contrib/snippet/snippetVariables.ts +++ b/src/vs/editor/contrib/snippet/snippetVariables.ts @@ -4,13 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { basename, dirname } from 'vs/base/common/paths'; +import { basename, dirname } from 'vs/base/common/path'; import { ITextModel } from 'vs/editor/common/model'; import { Selection } from 'vs/editor/common/core/selection'; import { VariableResolver, Variable, Text } from 'vs/editor/contrib/snippet/snippetParser'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { getLeadingWhitespace, commonPrefixLength, isFalsyOrWhitespace, pad } from 'vs/base/common/strings'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; export const KnownSnippetVariableNames = Object.freeze({ 'CURRENT_YEAR': true, @@ -38,6 +40,7 @@ export const KnownSnippetVariableNames = Object.freeze({ 'BLOCK_COMMENT_START': true, 'BLOCK_COMMENT_END': true, 'LINE_COMMENT': true, + 'WORKSPACE_NAME': true, }); export class CompositeSnippetVariableResolver implements VariableResolver { @@ -244,3 +247,29 @@ export class TimeBasedVariableResolver implements VariableResolver { return undefined; } } + +export class WorkspaceBasedVariableResolver implements VariableResolver { + constructor( + private readonly _workspaceService: IWorkspaceContextService, + ) { + // + } + + resolve(variable: Variable): string | undefined { + if (variable.name !== 'WORKSPACE_NAME' || !this._workspaceService) { + return undefined; + } + + const workspaceIdentifier = toWorkspaceIdentifier(this._workspaceService.getWorkspace()); + if (!workspaceIdentifier) { + return undefined; + } + + if (isSingleFolderWorkspaceIdentifier(workspaceIdentifier)) { + return basename(workspaceIdentifier.path); + } + + const filename = basename(workspaceIdentifier.configPath.path); + return filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1); + } +} \ No newline at end of file diff --git a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts index 45b2e56035..58be229805 100644 --- a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts @@ -11,6 +11,7 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { NullLogService } from 'vs/platform/log/common/log'; import { Handler } from 'vs/editor/common/editorCommon'; +import { timeout } from 'vs/base/common/async'; suite('SnippetController2', function () { @@ -133,11 +134,11 @@ suite('SnippetController2', function () { assertSelections(editor, new Selection(1, 1, 1, 7), new Selection(2, 5, 2, 11)); editor.trigger('test', 'cut', {}); - assertContextKeys(contextKeys, true, false, true); + assertContextKeys(contextKeys, false, false, false); assertSelections(editor, new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5)); editor.trigger('test', 'type', { text: 'abc' }); - assertContextKeys(contextKeys, true, false, true); + assertContextKeys(contextKeys, false, false, false); ctrl.next(); assertContextKeys(contextKeys, false, false, false); @@ -159,9 +160,9 @@ suite('SnippetController2', function () { assertSelections(editor, new Selection(1, 4, 1, 4), new Selection(2, 8, 2, 8)); assertContextKeys(contextKeys, true, false, true); - ctrl.next(); - assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11)); - assertContextKeys(contextKeys, true, true, true); + // ctrl.next(); + // assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11)); + // assertContextKeys(contextKeys, true, true, true); ctrl.next(); assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11)); @@ -176,10 +177,10 @@ suite('SnippetController2', function () { ctrl.insert('farboo'); assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11)); - assertContextKeys(contextKeys, true, false, true); + // assertContextKeys(contextKeys, true, false, true); - ctrl.next(); - assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11)); + // ctrl.next(); + // assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11)); assertContextKeys(contextKeys, false, false, false); }); @@ -361,4 +362,63 @@ suite('SnippetController2', function () { assertSelections(editor, new Selection(1, 7, 1, 7)); assertContextKeys(contextKeys, false, false, false); }); + + test('Cancelling snippet mode should discard added cursors #68512 (soft cancel)', function () { + const ctrl = new SnippetController2(editor, logService, contextKeys); + model.setValue(''); + editor.setSelection(new Selection(1, 1, 1, 1)); + + ctrl.insert('.REGION ${2:FUNCTION_NAME}\nCREATE.FUNCTION ${1:VOID} ${2:FUNCTION_NAME}(${3:})\n\t${4:}\nEND\n.ENDREGION$0'); + assertSelections(editor, new Selection(2, 17, 2, 21)); + + ctrl.next(); + assertSelections(editor, new Selection(1, 9, 1, 22), new Selection(2, 22, 2, 35)); + assertContextKeys(contextKeys, true, true, true); + + editor.setSelections([new Selection(1, 22, 1, 22), new Selection(2, 35, 2, 35)]); + assertContextKeys(contextKeys, true, true, true); + + editor.setSelections([new Selection(2, 1, 2, 1), new Selection(2, 36, 2, 36)]); + assertContextKeys(contextKeys, false, false, false); + assertSelections(editor, new Selection(2, 1, 2, 1), new Selection(2, 36, 2, 36)); + }); + + test('Cancelling snippet mode should discard added cursors #68512 (hard cancel)', function () { + const ctrl = new SnippetController2(editor, logService, contextKeys); + model.setValue(''); + editor.setSelection(new Selection(1, 1, 1, 1)); + + ctrl.insert('.REGION ${2:FUNCTION_NAME}\nCREATE.FUNCTION ${1:VOID} ${2:FUNCTION_NAME}(${3:})\n\t${4:}\nEND\n.ENDREGION$0'); + assertSelections(editor, new Selection(2, 17, 2, 21)); + + ctrl.next(); + assertSelections(editor, new Selection(1, 9, 1, 22), new Selection(2, 22, 2, 35)); + assertContextKeys(contextKeys, true, true, true); + + editor.setSelections([new Selection(1, 22, 1, 22), new Selection(2, 35, 2, 35)]); + assertContextKeys(contextKeys, true, true, true); + + ctrl.cancel(true); + assertContextKeys(contextKeys, false, false, false); + assertSelections(editor, new Selection(1, 22, 1, 22)); + }); + + test('A little confusing visual effect of highlighting for snippet tabstop #43270', async function () { + const ctrl = new SnippetController2(editor, logService, contextKeys); + model.setValue(''); + editor.setSelection(new Selection(1, 1, 1, 1)); + + ctrl.insert('background-color: ${1:fff};$0'); + assertSelections(editor, new Selection(1, 19, 1, 22)); + + editor.setSelection(new Selection(1, 22, 1, 22)); + assertContextKeys(contextKeys, true, false, true); + editor.trigger('', 'deleteRight', null); + + assert.equal(model.getValue(), 'background-color: fff'); + + await timeout(0); // this depends on re-scheduling of events... + + assertContextKeys(contextKeys, false, false, false); + }); }); diff --git a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts index 0de028eb30..4da4cfcbcd 100644 --- a/src/vs/editor/contrib/snippet/test/snippetSession.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetSession.test.ts @@ -331,7 +331,7 @@ suite('SnippetSession', function () { // reset selection to placeholder session.next(); - assert.equal(session.isSelectionWithinPlaceholders(), true); + assert.equal(session.isSelectionWithinPlaceholders(), false); assert.equal(session.isAtLastPlaceholder, true); assertSelections(editor, new Selection(1, 13, 1, 13), new Selection(2, 17, 2, 17)); }); diff --git a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts index 1c21c95b4c..db70b50a2d 100644 --- a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts @@ -6,10 +6,11 @@ import * as assert from 'assert'; import { isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { Selection } from 'vs/editor/common/core/selection'; -import { SelectionBasedVariableResolver, CompositeSnippetVariableResolver, ModelBasedVariableResolver, ClipboardBasedVariableResolver, TimeBasedVariableResolver } from 'vs/editor/contrib/snippet/snippetVariables'; +import { SelectionBasedVariableResolver, CompositeSnippetVariableResolver, ModelBasedVariableResolver, ClipboardBasedVariableResolver, TimeBasedVariableResolver, WorkspaceBasedVariableResolver } from 'vs/editor/contrib/snippet/snippetVariables'; import { SnippetParser, Variable, VariableResolver } from 'vs/editor/contrib/snippet/snippetParser'; import { TextModel } from 'vs/editor/common/model/textModel'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { Workspace, toWorkspaceFolders, IWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; suite('Snippet Variables Resolver', function () { @@ -301,4 +302,37 @@ suite('Snippet Variables Resolver', function () { snippet.resolveVariables({ resolve() { return '11'; } }); assert.equal(snippet.toString(), 'It is not line 10'); }); -}); + + test('Add workspace name variable for snippets #68261', function () { + + let workspace: IWorkspace; + let resolver: VariableResolver; + const workspaceService = new class implements IWorkspaceContextService { + _serviceBrand: any; + _throw = () => { throw new Error(); }; + onDidChangeWorkbenchState = this._throw; + onDidChangeWorkspaceName = this._throw; + onDidChangeWorkspaceFolders = this._throw; + getCompleteWorkspace = this._throw; + getWorkspace(): IWorkspace { return workspace; } + getWorkbenchState = this._throw; + getWorkspaceFolder = this._throw; + isCurrentWorkspace = this._throw; + isInsideWorkspace = this._throw; + }; + + resolver = new WorkspaceBasedVariableResolver(workspaceService); + + // empty workspace + workspace = new Workspace(''); + assertVariableResolve(resolver, 'WORKSPACE_NAME', undefined); + + // single folder workspace without config + workspace = new Workspace('', toWorkspaceFolders([{ path: '/folderName' }])); + assertVariableResolve(resolver, 'WORKSPACE_NAME', 'folderName'); + + // workspace with config + workspace = new Workspace('', toWorkspaceFolders([{ path: 'folderName' }]), URI.file('testWorkspace.code-workspace')); + assertVariableResolve(resolver, 'WORKSPACE_NAME', 'testWorkspace'); + }); +}); \ No newline at end of file diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index f22fa81306..463f8d3df4 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { first } from 'vs/base/common/async'; -import { isNonEmptyArray } from 'vs/base/common/arrays'; import { assign } from 'vs/base/common/objects'; import { onUnexpectedExternalError, canceled, isPromiseCanceledError } from 'vs/base/common/errors'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; @@ -87,7 +86,20 @@ export class CompletionItem { } } -export type SnippetConfig = 'top' | 'bottom' | 'inline' | 'none'; +export const enum SnippetSortOrder { + Top, Inline, Bottom +} + +export class CompletionOptions { + + static readonly default = new CompletionOptions(); + + constructor( + readonly snippetSortOrder = SnippetSortOrder.Bottom, + readonly kindFilter = new Set(), + readonly providerFilter = new Set(), + ) { } +} let _snippetSuggestSupport: modes.CompletionItemProvider; @@ -104,15 +116,12 @@ export function setSnippetSuggestSupport(support: modes.CompletionItemProvider): export function provideSuggestionItems( model: ITextModel, position: Position, - snippetConfig: SnippetConfig = 'bottom', - onlyFrom?: modes.CompletionItemProvider[], - context?: modes.CompletionContext, + options: CompletionOptions = CompletionOptions.default, + context: modes.CompletionContext = { triggerKind: modes.CompletionTriggerKind.Invoke }, token: CancellationToken = CancellationToken.None ): Promise { const allSuggestions: CompletionItem[] = []; - const acceptSuggestion = createSuggesionFilter(snippetConfig); - const wordUntil = model.getWordUntilPosition(position); const defaultRange = new Range(position.lineNumber, wordUntil.startColumn, position.lineNumber, wordUntil.endColumn); @@ -122,12 +131,10 @@ export function provideSuggestionItems( const supports = modes.CompletionProviderRegistry.orderedGroups(model); // add snippets provider unless turned off - if (snippetConfig !== 'none' && _snippetSuggestSupport) { + if (!options.kindFilter.has(modes.CompletionItemKind.Snippet) && _snippetSuggestSupport) { supports.unshift([_snippetSuggestSupport]); } - const suggestConext = context || { triggerKind: modes.CompletionTriggerKind.Invoke }; - // add suggestions from contributed providers - providers are ordered in groups of // equal score and once a group produces a result the process stops let hasResult = false; @@ -135,17 +142,17 @@ export function provideSuggestionItems( // for each support in the group ask for suggestions return Promise.all(supports.map(provider => { - if (isNonEmptyArray(onlyFrom) && onlyFrom.indexOf(provider) < 0) { + if (options.providerFilter.size > 0 && !options.providerFilter.has(provider)) { return undefined; } - return Promise.resolve(provider.provideCompletionItems(model, position, suggestConext, token)).then(container => { + return Promise.resolve(provider.provideCompletionItems(model, position, context, token)).then(container => { const len = allSuggestions.length; if (container) { for (let suggestion of container.suggestions || []) { - if (acceptSuggestion(suggestion)) { + if (!options.kindFilter.has(suggestion.kind)) { // fill in default range when missing if (!suggestion.range) { @@ -170,9 +177,9 @@ export function provideSuggestionItems( return hasResult || token.isCancellationRequested; }).then(() => { if (token.isCancellationRequested) { - return Promise.reject(canceled()); + return Promise.reject(canceled()); } - return allSuggestions.sort(getSuggestionComparator(snippetConfig)); + return allSuggestions.sort(getSuggestionComparator(options.snippetSortOrder)); }); // result.then(items => { @@ -185,13 +192,7 @@ export function provideSuggestionItems( return result; } -function createSuggesionFilter(snippetConfig: SnippetConfig): (candidate: modes.CompletionItem) => boolean { - if (snippetConfig === 'none') { - return suggestion => suggestion.kind !== modes.CompletionItemKind.Snippet; - } else { - return () => true; - } -} + function defaultComparator(a: CompletionItem, b: CompletionItem): number { // check with 'sortText' if (a.sortTextLow && b.sortTextLow) { @@ -233,14 +234,14 @@ function snippetDownComparator(a: CompletionItem, b: CompletionItem): number { return defaultComparator(a, b); } -export function getSuggestionComparator(snippetConfig: SnippetConfig): (a: CompletionItem, b: CompletionItem) => number { - if (snippetConfig === 'top') { - return snippetUpComparator; - } else if (snippetConfig === 'bottom') { - return snippetDownComparator; - } else { - return defaultComparator; - } +interface Comparator { (a: T, b: T): number; } +const _snippetComparators = new Map>(); +_snippetComparators.set(SnippetSortOrder.Top, snippetUpComparator); +_snippetComparators.set(SnippetSortOrder.Bottom, snippetDownComparator); +_snippetComparators.set(SnippetSortOrder.Inline, defaultComparator); + +export function getSuggestionComparator(snippetConfig: SnippetSortOrder): (a: CompletionItem, b: CompletionItem) => number { + return _snippetComparators.get(snippetConfig)!; } registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, position, args) => { @@ -269,11 +270,10 @@ registerDefaultLanguageCommand('_executeCompletionItemProvider', (model, positio }); interface SuggestController extends IEditorContribution { - triggerSuggest(onlyFrom?: modes.CompletionItemProvider[]): void; + triggerSuggest(onlyFrom?: Set): void; } - -let _provider = new class implements modes.CompletionItemProvider { +const _provider = new class implements modes.CompletionItemProvider { onlyOnceSuggestions: modes.CompletionItem[] = []; @@ -290,6 +290,6 @@ modes.CompletionProviderRegistry.register('*', _provider); export function showSimpleSuggestions(editor: ICodeEditor, suggestions: modes.CompletionItem[]) { setTimeout(() => { _provider.onlyOnceSuggestions.push(...suggestions); - editor.getContribution('editor.contrib.suggestController').triggerSuggest([_provider]); + editor.getContribution('editor.contrib.suggestController').triggerSuggest(new Set().add(_provider)); }, 0); } diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index 0680005034..991eb11ae0 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -181,7 +181,7 @@ export class SuggestController implements IEditorContribution { this._widget.getValue().hideWidget(); } })); - this._toDispose.push(this._editor.onDidBlurEditorText(() => { + this._toDispose.push(this._editor.onDidBlurEditorWidget(() => { if (!this._sticky) { this._model.cancel(); } @@ -292,11 +292,13 @@ export class SuggestController implements IEditorContribution { } private _alertCompletionItem({ completion: suggestion }: CompletionItem): void { - let msg = nls.localize('arai.alert.snippet', "Accepting '{0}' did insert the following text: {1}", suggestion.label, suggestion.insertText); - alert(msg); + if (isNonEmptyArray(suggestion.additionalTextEdits)) { + let msg = nls.localize('arai.alert.snippet', "Accepting '{0}' made {1} additional edits", suggestion.label, suggestion.additionalTextEdits.length); + alert(msg); + } } - triggerSuggest(onlyFrom?: CompletionItemProvider[]): void { + triggerSuggest(onlyFrom?: Set): void { if (this._editor.hasModel()) { this._model.trigger({ auto: false, shy: false }, false, onlyFrom); this._editor.revealLine(this._editor.getPosition().lineNumber, ScrollType.Smooth); diff --git a/src/vs/editor/contrib/suggest/suggestMemory.ts b/src/vs/editor/contrib/suggest/suggestMemory.ts index ccb126c43b..52660ee70f 100644 --- a/src/vs/editor/contrib/suggest/suggestMemory.ts +++ b/src/vs/editor/contrib/suggest/suggestMemory.ts @@ -8,7 +8,7 @@ import { LRUCache, TernarySearchTree } from 'vs/base/common/map'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITextModel } from 'vs/editor/common/model'; import { IPosition } from 'vs/editor/common/core/position'; -import { CompletionItemKind, completionKindFromLegacyString } from 'vs/editor/common/modes'; +import { CompletionItemKind, completionKindFromString } from 'vs/editor/common/modes'; import { Disposable } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -124,7 +124,7 @@ export class LRUMemory extends Memory { let seq = 0; for (const [key, value] of data) { value.touch = seq; - value.type = typeof value.type === 'number' ? value.type : completionKindFromLegacyString(value.type); + value.type = typeof value.type === 'number' ? value.type : completionKindFromString(value.type); this._cache.set(key, value); } this._seq = this._cache.size; @@ -188,7 +188,7 @@ export class PrefixMemory extends Memory { if (data.length > 0) { this._seq = data[0][1].touch + 1; for (const [key, value] of data) { - value.type = typeof value.type === 'number' ? value.type : completionKindFromLegacyString(value.type); + value.type = typeof value.type === 'number' ? value.type : completionKindFromString(value.type); this._trie.set(key, value); } } diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index 6f89b50d23..aa13657217 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -8,15 +8,14 @@ import { TimeoutTimer } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { values } from 'vs/base/common/map'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { CursorChangeReason, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { Position } from 'vs/editor/common/core/position'; import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; -import { CompletionItemProvider, StandardTokenType, CompletionContext, CompletionProviderRegistry, CompletionTriggerKind } from 'vs/editor/common/modes'; +import { CompletionItemProvider, StandardTokenType, CompletionContext, CompletionProviderRegistry, CompletionTriggerKind, CompletionItemKind, completionKindFromString } from 'vs/editor/common/modes'; import { CompletionModel } from './completionModel'; -import { CompletionItem, getSuggestionComparator, provideSuggestionItems, getSnippetSuggestSupport } from './suggest'; +import { CompletionItem, getSuggestionComparator, provideSuggestionItems, getSnippetSuggestSupport, SnippetSortOrder, CompletionOptions } from './suggest'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; @@ -209,7 +208,7 @@ export class SuggestModel implements IDisposable { // keep existing items that where not computed by the // supports/providers that want to trigger now const items: CompletionItem[] | undefined = this._completionModel ? this._completionModel.adopt(supports) : undefined; - this.trigger({ auto: true, shy: false, triggerCharacter: lastChar }, Boolean(this._completionModel), values(supports), items); + this.trigger({ auto: true, shy: false, triggerCharacter: lastChar }, Boolean(this._completionModel), supports, items); } }); } @@ -347,7 +346,7 @@ export class SuggestModel implements IDisposable { }, 25); } - trigger(context: SuggestTriggerContext, retrigger: boolean = false, onlyFrom?: CompletionItemProvider[], existingItems?: CompletionItem[]): void { + trigger(context: SuggestTriggerContext, retrigger: boolean = false, onlyFrom?: Set, existingItems?: CompletionItem[]): void { if (!this._editor.hasModel()) { return; } @@ -371,7 +370,7 @@ export class SuggestModel implements IDisposable { triggerKind: CompletionTriggerKind.TriggerCharacter, triggerCharacter: context.triggerCharacter }; - } else if (onlyFrom && onlyFrom.length) { + } else if (onlyFrom && onlyFrom.size > 0) { suggestCtx = { triggerKind: CompletionTriggerKind.TriggerForIncompleteCompletions }; } else { suggestCtx = { triggerKind: CompletionTriggerKind.Invoke }; @@ -379,13 +378,40 @@ export class SuggestModel implements IDisposable { this._requestToken = new CancellationTokenSource(); + // kind filter and snippet sort rules + const { contribInfo } = this._editor.getConfiguration(); + let itemKindFilter = new Set(); + let snippetSortOrder = SnippetSortOrder.Inline; + switch (contribInfo.suggest.snippets) { + case 'top': + snippetSortOrder = SnippetSortOrder.Top; + break; + // ↓ that's the default anyways... + // case 'inline': + // snippetSortOrder = SnippetSortOrder.Inline; + // break; + case 'bottom': + snippetSortOrder = SnippetSortOrder.Bottom; + break; + case 'none': + itemKindFilter.add(CompletionItemKind.Snippet); + break; + } + + // kind filter + for (const key in contribInfo.suggest.filteredTypes) { + const kind = completionKindFromString(key, true); + if (typeof kind !== 'undefined' && contribInfo.suggest.filteredTypes[key] === false) { + itemKindFilter.add(kind); + } + } + let wordDistance = WordDistance.create(this._editorWorker, this._editor); let items = provideSuggestionItems( model, this._editor.getPosition(), - this._editor.getConfiguration().contribInfo.suggest.snippets, - onlyFrom, + new CompletionOptions(snippetSortOrder, itemKindFilter, onlyFrom), suggestCtx, this._requestToken.token ); @@ -405,7 +431,7 @@ export class SuggestModel implements IDisposable { const model = this._editor.getModel(); if (isNonEmptyArray(existingItems)) { - const cmpFn = getSuggestionComparator(this._editor.getConfiguration().contribInfo.suggest.snippets); + const cmpFn = getSuggestionComparator(snippetSortOrder); items = items.concat(existingItems).sort(cmpFn); } @@ -461,7 +487,7 @@ export class SuggestModel implements IDisposable { // typed -> moved cursor RIGHT & incomple model & still on a word -> retrigger const { incomplete } = this._completionModel; const adopted = this._completionModel.adopt(incomplete); - this.trigger({ auto: this._state === State.Auto, shy: false }, true, values(incomplete), adopted); + this.trigger({ auto: this._state === State.Auto, shy: false }, true, incomplete, adopted); } else { // typed -> moved cursor RIGHT -> update UI diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index f4f07e8943..b448cca998 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -40,7 +40,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { FileKind } from 'vs/platform/files/common/files'; const expandSuggestionDocsByDefault = false; -const maxSuggestionsToShow = 12; interface ISuggestionTemplateData { root: HTMLElement; @@ -63,8 +62,16 @@ export const editorSuggestWidgetHighlightForeground = registerColor('editorSugge const colorRegExp = /^(#([\da-f]{3}){1,2}|(rgb|hsl)a\(\s*(\d{1,3}%?\s*,\s*){3}(1|0?\.\d+)\)|(rgb|hsl)\(\s*\d{1,3}%?(\s*,\s*\d{1,3}%?){2}\s*\))$/i; -function matchesColor(text: string): string | null { - return text && text.match(colorRegExp) ? text : null; +function extractColor(item: CompletionItem, out: string[]): boolean { + if (item.completion.label.match(colorRegExp)) { + out[0] = item.completion.label; + return true; + } + if (typeof item.completion.documentation === 'string' && item.completion.documentation.match(colorRegExp)) { + out[0] = item.completion.documentation; + return true; + } + return false; } function canExpandCompletionItem(item: CompletionItem | null) { @@ -156,11 +163,11 @@ class Renderer implements IListRenderer matches: createMatches(element.score) }; - let color: string | null = null; - if (suggestion.kind === CompletionItemKind.Color && ((color = matchesColor(suggestion.label) || typeof suggestion.documentation === 'string' ? matchesColor(suggestion.documentation as any) : null))) { + let color: string[] = []; + if (suggestion.kind === CompletionItemKind.Color && extractColor(element, color)) { // special logic for 'color' completion items data.icon.className = 'icon customcolor'; - data.colorspan.style.backgroundColor = color; + data.colorspan.style.backgroundColor = color[0]; } else if (suggestion.kind === CompletionItemKind.File && this._themeService.getIconTheme().hasFileIcons) { // special logic for 'file' completion items @@ -243,10 +250,10 @@ class SuggestionDetails { constructor( container: HTMLElement, - private widget: SuggestWidget, - private editor: ICodeEditor, - private markdownRenderer: MarkdownRenderer, - private triggerKeybindingLabel: string + private readonly widget: SuggestWidget, + private readonly editor: ICodeEditor, + private readonly markdownRenderer: MarkdownRenderer, + private readonly triggerKeybindingLabel: string ) { this.disposables = []; @@ -416,8 +423,8 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate; private listHeight: number; - private suggestWidgetVisible: IContextKey; - private suggestWidgetMultipleSuggestions: IContextKey; + private readonly suggestWidgetVisible: IContextKey; + private readonly suggestWidgetMultipleSuggestions: IContextKey; private readonly editorBlurTimeout = new TimeoutTimer(); private readonly showTimeout = new TimeoutTimer(); @@ -436,7 +443,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate toggleClass(this.element, 'no-icons', !this.editor.getConfiguration().contribInfo.suggest.showIcons); + applyIconStyle(); + let renderer = instantiationService.createInstance(Renderer, this, this.editor, triggerKeybindingLabel); this.list = new List(this.listElement, this, [renderer], { @@ -491,7 +498,8 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate this.onListMouseDown(e)), this.list.onSelectionChange(e => this.onListSelection(e)), this.list.onFocusChange(e => this.onListFocus(e)), - this.editor.onDidChangeCursorSelection(() => this.onCursorSelectionChanged()) + this.editor.onDidChangeCursorSelection(() => this.onCursorSelectionChanged()), + this.editor.onDidChangeConfiguration(e => e.contribInfo && applyIconStyle()) ]; this.suggestWidgetVisible = SuggestContext.Visible.bindTo(contextKeyService); @@ -522,6 +530,10 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate { this.onDidSelectEmitter.fire({ item, index, model: completionModel }); - alert(nls.localize('suggestionAriaAccepted', "{0}, accepted", item.completion.label)); this.editor.focus(); }); } private _getSuggestionAriaAlertLabel(item: CompletionItem): string { - const isSnippet = item.completion.kind === CompletionItemKind.Snippet; - - if (!canExpandCompletionItem(item)) { - return isSnippet ? nls.localize('ariaCurrentSnippetSuggestion', "{0}, snippet suggestion", item.completion.label) - : nls.localize('ariaCurrentSuggestion', "{0}, suggestion", item.completion.label); - } else if (this.expandDocsSettingFromStorage()) { - return isSnippet ? nls.localize('ariaCurrentSnippeSuggestionReadDetails', "{0}, snippet suggestion. Reading details. {1}", item.completion.label, this.details.getAriaLabel()) - : nls.localize('ariaCurrenttSuggestionReadDetails', "{0}, suggestion. Reading details. {1}", item.completion.label, this.details.getAriaLabel()); + if (this.expandDocsSettingFromStorage()) { + return nls.localize('ariaCurrenttSuggestionReadDetails', "Item {0}, docs: {1}", item.completion.label, this.details.getAriaLabel()); } else { - return isSnippet ? nls.localize('ariaCurrentSnippetSuggestionWithDetails', "{0}, snippet suggestion, has details", item.completion.label) - : nls.localize('ariaCurrentSuggestionWithDetails', "{0}, suggestion, has details", item.completion.label); + return item.completion.label; } } @@ -569,7 +573,7 @@ export class SuggestWidget implements IContentWidget, IListVirtualDelegate().add(CompletionItemKind.Snippet))); assert.equal(items.length, 1); assert.equal(items[0].completion.label, 'fff'); }); @@ -98,7 +98,7 @@ suite('Suggest', function () { }; const registration = CompletionProviderRegistry.register({ pattern: 'bar/path', scheme: 'foo' }, foo); - provideSuggestionItems(model, new Position(1, 1), undefined, [foo]).then(items => { + provideSuggestionItems(model, new Position(1, 1), new CompletionOptions(undefined, undefined, new Set().add(foo))).then(items => { registration.dispose(); assert.equal(items.length, 1); diff --git a/src/vs/editor/contrib/suggest/wordDistance.ts b/src/vs/editor/contrib/suggest/wordDistance.ts index ab6d0d6c96..ff7569babc 100644 --- a/src/vs/editor/contrib/suggest/wordDistance.ts +++ b/src/vs/editor/contrib/suggest/wordDistance.ts @@ -34,11 +34,11 @@ export abstract class WordDistance { return Promise.resolve(WordDistance.None); } - return new BracketSelectionRangeProvider().provideSelectionRanges(model, position).then(ranges => { - if (!ranges || ranges.length === 0) { + return new BracketSelectionRangeProvider().provideSelectionRanges(model, [position]).then(ranges => { + if (!ranges || ranges.length === 0 || ranges[0].length === 0) { return WordDistance.None; } - return service.computeWordRanges(model.uri, ranges[0].range).then(wordRanges => { + return service.computeWordRanges(model.uri, ranges[0][0].range).then(wordRanges => { return new class extends WordDistance { distance(anchor: IPosition, suggestion: CompletionItem) { if (!wordRanges || !position.equals(editor.getPosition())) { @@ -55,7 +55,7 @@ export abstract class WordDistance { let idx = binarySearch(wordLines, Range.fromPositions(anchor), Range.compareRangesUsingStarts); let bestWordRange = idx >= 0 ? wordLines[idx] : wordLines[Math.max(0, ~idx - 1)]; let blockDistance = ranges.length; - for (const range of ranges) { + for (const range of ranges[0]) { if (!Range.containsRange(range.range, bestWordRange)) { break; } diff --git a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts index f6e1b644dd..74e2e02be3 100644 --- a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts @@ -112,7 +112,7 @@ class SemanticOccurenceAtPositionRequest extends OccurenceAtPositionRequest { class TextualOccurenceAtPositionRequest extends OccurenceAtPositionRequest { - private _selectionIsEmpty: boolean; + private readonly _selectionIsEmpty: boolean; constructor(model: ITextModel, selection: Selection, wordSeparators: string) { super(model, selection, wordSeparators); @@ -160,9 +160,9 @@ registerDefaultLanguageCommand('_executeDocumentHighlights', (model, position) = class WordHighlighter { - private editor: IActiveCodeEditor; + private readonly editor: IActiveCodeEditor; private occurrencesHighlight: boolean; - private model: ITextModel; + private readonly model: ITextModel; private _decorationIds: string[]; private toUnhook: IDisposable[]; @@ -174,7 +174,7 @@ class WordHighlighter { private lastCursorPositionChangeTime: number = 0; private renderDecorationsTimer: any = -1; - private _hasWordHighlights: IContextKey; + private readonly _hasWordHighlights: IContextKey; private _ignorePositionChangeEvent: boolean; constructor(editor: IActiveCodeEditor, contextKeyService: IContextKeyService) { @@ -415,7 +415,7 @@ class WordHighlighter { this._hasWordHighlights.set(this.hasDecorations()); } - private static _getDecorationOptions(kind: DocumentHighlightKind): ModelDecorationOptions { + private static _getDecorationOptions(kind: DocumentHighlightKind | undefined): ModelDecorationOptions { if (kind === DocumentHighlightKind.Write) { return this._WRITE_OPTIONS; } else if (kind === DocumentHighlightKind.Text) { @@ -526,7 +526,7 @@ class WordHighlighterContribution extends Disposable implements editorCommon.IEd class WordHighlightNavigationAction extends EditorAction { - private _isNext: boolean; + private readonly _isNext: boolean; constructor(next: boolean, opts: IActionOptions) { super(opts); diff --git a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts index 0d7404c478..6a36320415 100644 --- a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts @@ -56,8 +56,8 @@ export class ViewZoneDelegate implements IViewZone { public afterColumn: number; public heightInLines: number; - private _onDomNodeTop: (top: number) => void; - private _onComputedHeight: (height: number) => void; + private readonly _onDomNodeTop: (top: number) => void; + private readonly _onComputedHeight: (height: number) => void; constructor(domNode: HTMLElement, afterLineNumber: number, afterColumn: number, heightInLines: number, onDomNodeTop: (top: number) => void, @@ -82,8 +82,8 @@ export class ViewZoneDelegate implements IViewZone { export class OverlayWidgetDelegate implements IOverlayWidget { - private _id: string; - private _domNode: HTMLElement; + private readonly _id: string; + private readonly _domNode: HTMLElement; constructor(id: string, domNode: HTMLElement) { this._id = id; diff --git a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts index 5089ccddf2..d62da6226c 100644 --- a/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts +++ b/src/vs/editor/standalone/browser/accessibilityHelp/accessibilityHelp.ts @@ -30,6 +30,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { IOpenerService } from 'vs/platform/opener/common/opener'; import { contrastBorder, editorWidgetBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; const CONTEXT_ACCESSIBILITY_WIDGET_VISIBLE = new RawContextKey('accessibilityHelpWidgetVisible', false); @@ -43,8 +44,8 @@ class AccessibilityHelpController extends Disposable ); } - private _editor: ICodeEditor; - private _widget: AccessibilityHelpWidget; + private readonly _editor: ICodeEditor; + private readonly _widget: AccessibilityHelpWidget; constructor( editor: ICodeEditor, @@ -106,11 +107,11 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { private static readonly WIDTH = 500; private static readonly HEIGHT = 300; - private _editor: ICodeEditor; - private _domNode: FastDomNode; - private _contentDomNode: FastDomNode; + private readonly _editor: ICodeEditor; + private readonly _domNode: FastDomNode; + private readonly _contentDomNode: FastDomNode; private _isVisible: boolean; - private _isVisibleKey: IContextKey; + private readonly _isVisibleKey: IContextKey; constructor( editor: ICodeEditor, @@ -263,13 +264,13 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { : nls.localize("changeConfigToOnWinLinux", "To configure the editor to be optimized for usage with a Screen Reader press Control+E now.") ); switch (opts.accessibilitySupport) { - case platform.AccessibilitySupport.Unknown: + case AccessibilitySupport.Unknown: text += '\n\n - ' + turnOnMessage; break; - case platform.AccessibilitySupport.Enabled: + case AccessibilitySupport.Enabled: text += '\n\n - ' + nls.localize("auto_on", "The editor is configured to be optimized for usage with a Screen Reader."); break; - case platform.AccessibilitySupport.Disabled: + case AccessibilitySupport.Disabled: text += '\n\n - ' + nls.localize("auto_off", "The editor is configured to never be optimized for usage with a Screen Reader, which is not the case at this time."); text += ' ' + turnOnMessage; break; diff --git a/src/vs/editor/standalone/browser/colorizer.ts b/src/vs/editor/standalone/browser/colorizer.ts index 883f0eb1e8..500d535798 100644 --- a/src/vs/editor/standalone/browser/colorizer.ts +++ b/src/vs/editor/standalone/browser/colorizer.ts @@ -13,6 +13,7 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { RenderLineInput, renderViewLine2 as renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { ViewLineRenderingData } from 'vs/editor/common/viewModel/viewModel'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; +import { MonarchTokenizer } from 'vs/editor/standalone/common/monarch/monarchLexer'; export interface IColorizerOptions { tabSize?: number; @@ -64,7 +65,17 @@ export class Colorizer { let tokenizationSupport = TokenizationRegistry.get(language); if (tokenizationSupport) { - return Promise.resolve(_colorize(lines, tabSize, tokenizationSupport)); + return _colorize(lines, tabSize, tokenizationSupport); + } + + let tokenizationSupportPromise = TokenizationRegistry.getPromise(language); + if (tokenizationSupportPromise) { + // A tokenizer will be registered soon + return new Promise((resolve, reject) => { + tokenizationSupportPromise!.then(tokenizationSupport => { + _colorize(lines, tabSize, tokenizationSupport).then(resolve, reject); + }, reject); + }); } return new Promise((resolve, reject) => { @@ -82,9 +93,10 @@ export class Colorizer { } const tokenizationSupport = TokenizationRegistry.get(language!); if (tokenizationSupport) { - return resolve(_colorize(lines, tabSize, tokenizationSupport)); + _colorize(lines, tabSize, tokenizationSupport).then(resolve, reject); + return; } - return resolve(_fakeColorize(lines, tabSize)); + resolve(_fakeColorize(lines, tabSize)); }; // wait 500ms for mode to load, then give up @@ -130,8 +142,21 @@ export class Colorizer { } } -function _colorize(lines: string[], tabSize: number, tokenizationSupport: ITokenizationSupport): string { - return _actualColorize(lines, tabSize, tokenizationSupport); +function _colorize(lines: string[], tabSize: number, tokenizationSupport: ITokenizationSupport): Promise { + return new Promise((c, e) => { + const execute = () => { + const result = _actualColorize(lines, tabSize, tokenizationSupport); + if (tokenizationSupport instanceof MonarchTokenizer) { + const status = tokenizationSupport.getLoadStatus(); + if (status.loaded === false) { + status.promise.then(execute, e); + return; + } + } + c(result); + }; + execute(); + }); } function _fakeColorize(lines: string[], tabSize: number): string { diff --git a/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts b/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts index b1d9c365da..ad74dc5e01 100644 --- a/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts +++ b/src/vs/editor/standalone/browser/iPadShowKeyboard/iPadShowKeyboard.ts @@ -15,7 +15,7 @@ export class IPadShowKeyboard implements IEditorContribution { private static readonly ID = 'editor.contrib.iPadShowKeyboard'; - private editor: ICodeEditor; + private readonly editor: ICodeEditor; private widget: ShowKeyboardWidget | null; private toDispose: IDisposable[]; @@ -60,9 +60,9 @@ class ShowKeyboardWidget implements IOverlayWidget { private static readonly ID = 'editor.contrib.ShowKeyboardWidget'; - private editor: ICodeEditor; + private readonly editor: ICodeEditor; - private _domNode: HTMLElement; + private readonly _domNode: HTMLElement; private _toDispose: IDisposable[]; constructor(editor: ICodeEditor) { diff --git a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts index 3422d072b3..c6dcd2fd69 100644 --- a/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts +++ b/src/vs/editor/standalone/browser/inspectTokens/inspectTokens.ts @@ -31,8 +31,8 @@ class InspectTokensController extends Disposable implements IEditorContribution return editor.getContribution(InspectTokensController.ID); } - private _editor: ICodeEditor; - private _modeService: IModeService; + private readonly _editor: ICodeEditor; + private readonly _modeService: IModeService; private _widget: InspectTokensWidget | null; constructor( @@ -162,11 +162,11 @@ class InspectTokensWidget extends Disposable implements IContentWidget { // Editor.IContentWidget.allowEditorOverflow public allowEditorOverflow = true; - private _editor: IActiveCodeEditor; - private _modeService: IModeService; - private _tokenizationSupport: ITokenizationSupport; - private _model: ITextModel; - private _domNode: HTMLElement; + private readonly _editor: IActiveCodeEditor; + private readonly _modeService: IModeService; + private readonly _tokenizationSupport: ITokenizationSupport; + private readonly _model: ITextModel; + private readonly _domNode: HTMLElement; constructor( editor: IActiveCodeEditor, diff --git a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts index d5362cf005..e66ae5505c 100644 --- a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts +++ b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts @@ -30,10 +30,10 @@ export class QuickOpenController implements editorCommon.IEditorContribution, ID return editor.getContribution(QuickOpenController.ID); } - private editor: ICodeEditor; - private widget: QuickOpenEditorWidget; - private rangeHighlightDecorationId: string; - private lastKnownEditorSelection: Selection; + private readonly editor: ICodeEditor; + private widget: QuickOpenEditorWidget | null; + private rangeHighlightDecorationId: string | null; + private lastKnownEditorSelection: Selection | null; constructor(editor: ICodeEditor, @IThemeService private readonly themeService: IThemeService) { this.editor = editor; @@ -83,7 +83,7 @@ export class QuickOpenController implements editorCommon.IEditorContribution, ID () => onClose(false), () => onClose(true), (value: string) => { - this.widget.setInput(opts.getModel(value), opts.getAutoFocus(value)); + this.widget!.setInput(opts.getModel(value), opts.getAutoFocus(value)); }, { inputAriaLabel: opts.inputAriaLabel @@ -148,7 +148,7 @@ export interface IQuickOpenOpts { */ export abstract class BaseEditorQuickOpenAction extends EditorAction { - private _inputAriaLabel: string; + private readonly _inputAriaLabel: string; constructor(inputAriaLabel: string, opts: IActionOptions) { super(opts); diff --git a/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts b/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts index 0c24a3b307..46ed0fc48c 100644 --- a/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts +++ b/src/vs/editor/standalone/browser/quickOpen/gotoLine.ts @@ -6,8 +6,8 @@ import 'vs/css!./gotoLine'; import * as nls from 'vs/nls'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IContext, QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus, Mode } from 'vs/base/parts/quickopen/common/quickOpen'; +import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; +import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; import { ICodeEditor, IDiffEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { Position } from 'vs/editor/common/core/position'; @@ -25,9 +25,9 @@ interface ParseResult { } export class GotoLineEntry extends QuickOpenEntry { - private parseResult: ParseResult; - private decorator: IDecorator; - private editor: editorCommon.IEditor; + private readonly parseResult: ParseResult; + private readonly decorator: IDecorator; + private readonly editor: editorCommon.IEditor; constructor(line: string, editor: editorCommon.IEditor, decorator: IDecorator) { super(); @@ -49,14 +49,15 @@ export class GotoLineEntry extends QuickOpenEntry { position = new Position(numbers[0], numbers[1]); } - let model: ITextModel; + let model: ITextModel | null; if (isCodeEditor(this.editor)) { model = this.editor.getModel(); } else { - model = (this.editor).getModel().modified; + const diffModel = (this.editor).getModel(); + model = diffModel ? diffModel.modified : null; } - const isValid = model.validatePosition(position).equals(position); + const isValid = model ? model.validatePosition(position).equals(position) : false; let label: string; if (isValid) { @@ -65,10 +66,10 @@ export class GotoLineEntry extends QuickOpenEntry { } else { label = nls.localize('gotoLineLabelValidLine', "Go to line {0}", position.lineNumber, position.column); } - } else if (position.lineNumber < 1 || position.lineNumber > model.getLineCount()) { - label = nls.localize('gotoLineLabelEmptyWithLineLimit', "Type a line number between 1 and {0} to navigate to", model.getLineCount()); + } else if (position.lineNumber < 1 || position.lineNumber > (model ? model.getLineCount() : 0)) { + label = nls.localize('gotoLineLabelEmptyWithLineLimit', "Type a line number between 1 and {0} to navigate to", model ? model.getLineCount() : 0); } else { - label = nls.localize('gotoLineLabelEmptyWithLineAndColumnLimit', "Type a character between 1 and {0} to navigate to", model.getLineMaxColumn(position.lineNumber)); + label = nls.localize('gotoLineLabelEmptyWithLineAndColumnLimit', "Type a character between 1 and {0} to navigate to", model ? model.getLineMaxColumn(position.lineNumber) : 0); } return { @@ -83,12 +84,12 @@ export class GotoLineEntry extends QuickOpenEntry { } getAriaLabel(): string { - const currentLine = this.editor.getPosition().lineNumber; - + const position = this.editor.getPosition(); + const currentLine = position ? position.lineNumber : 0; return nls.localize('gotoLineAriaLabel', "Current Line: {0}. Go to line {0}.", currentLine, this.parseResult.label); } - run(mode: Mode, context: IContext): boolean { + run(mode: Mode, _context: IEntryRunContext): boolean { if (mode === Mode.OPEN) { return this.runOpen(); } diff --git a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts b/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts index eaeded2de9..15f3ea345e 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts +++ b/src/vs/editor/standalone/browser/quickOpen/quickCommand.ts @@ -8,8 +8,8 @@ import * as browser from 'vs/base/browser/browser'; import { onUnexpectedError } from 'vs/base/common/errors'; import { matchesFuzzy } from 'vs/base/common/filters'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { IContext, IHighlight, QuickOpenEntryGroup, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus, Mode } from 'vs/base/parts/quickopen/common/quickOpen'; +import { IHighlight, QuickOpenEntryGroup, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; +import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { IEditor, IEditorAction } from 'vs/editor/common/editorCommon'; @@ -19,10 +19,10 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; export class EditorActionCommandEntry extends QuickOpenEntryGroup { - private key: string; - private action: IEditorAction; - private editor: IEditor; - private keyAriaLabel: string; + private readonly key: string; + private readonly action: IEditorAction; + private readonly editor: IEditor; + private readonly keyAriaLabel: string; constructor(key: string, keyAriaLabel: string, highlights: IHighlight[], action: IEditorAction, editor: IEditor) { super(); @@ -50,7 +50,7 @@ export class EditorActionCommandEntry extends QuickOpenEntryGroup { return this.key; } - public run(mode: Mode, context: IContext): boolean { + public run(mode: Mode, context: IEntryRunContext): boolean { if (mode === Mode.OPEN) { // Use a timeout to give the quick open widget a chance to close itself first @@ -112,8 +112,8 @@ export class QuickCommandAction extends BaseEditorQuickOpenAction { } private _sort(elementA: QuickOpenEntryGroup, elementB: QuickOpenEntryGroup): number { - let elementAName = elementA.getLabel().toLowerCase(); - let elementBName = elementB.getLabel().toLowerCase(); + let elementAName = (elementA.getLabel() || '').toLowerCase(); + let elementBName = (elementB.getLabel() || '').toLowerCase(); return elementAName.localeCompare(elementBName); } @@ -129,7 +129,7 @@ export class QuickCommandAction extends BaseEditorQuickOpenAction { if (action.label) { let highlights = matchesFuzzy(searchValue, action.label); if (highlights) { - entries.push(new EditorActionCommandEntry(keybinding ? keybinding.getLabel() : '', keybinding ? keybinding.getAriaLabel() : '', highlights, action, editor)); + entries.push(new EditorActionCommandEntry(keybinding ? keybinding.getLabel() || '' : '', keybinding ? keybinding.getAriaLabel() || '' : '', highlights, action, editor)); } } } diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts b/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts index dfcfbea051..e04ee805f1 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts +++ b/src/vs/editor/standalone/browser/quickOpen/quickOpenEditorWidget.ts @@ -21,8 +21,8 @@ export class QuickOpenEditorWidget implements IOverlayWidget { private static readonly ID = 'editor.contrib.quickOpenEditorWidget'; - private codeEditor: ICodeEditor; - private themeService: IThemeService; + private readonly codeEditor: ICodeEditor; + private readonly themeService: IThemeService; private visible: boolean; private quickOpenWidget: QuickOpenWidget; private domNode: HTMLElement; @@ -45,7 +45,7 @@ export class QuickOpenEditorWidget implements IOverlayWidget { onCancel: onCancel, onType: onType }, { - inputPlaceHolder: null, + inputPlaceHolder: undefined, inputAriaLabel: configuration.inputAriaLabel, keyboardSupport: true } @@ -98,7 +98,7 @@ export class QuickOpenEditorWidget implements IOverlayWidget { this.codeEditor.layoutOverlayWidget(this); } - public getPosition(): IOverlayWidgetPosition { + public getPosition(): IOverlayWidgetPosition | null { if (this.visible) { return { preference: OverlayWidgetPositionPreference.TOP_CENTER diff --git a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts index c2a5d4dd71..8cda0eb843 100644 --- a/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts +++ b/src/vs/editor/standalone/browser/quickOpen/quickOutline.ts @@ -9,8 +9,8 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { matchesFuzzy } from 'vs/base/common/filters'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as strings from 'vs/base/common/strings'; -import { IContext, IHighlight, QuickOpenEntryGroup, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IAutoFocus, Mode } from 'vs/base/parts/quickopen/common/quickOpen'; +import { IHighlight, QuickOpenEntryGroup, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; +import { IAutoFocus, Mode, IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ServicesAccessor, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { IRange, Range } from 'vs/editor/common/core/range'; @@ -24,14 +24,14 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis let SCOPE_PREFIX = ':'; export class SymbolEntry extends QuickOpenEntryGroup { - private name: string; - private type: string; - private description: string; - private range: Range; - private editor: ICodeEditor; - private decorator: IDecorator; + private readonly name: string; + private readonly type: string; + private readonly description: string | null; + private readonly range: Range; + private readonly editor: ICodeEditor; + private readonly decorator: IDecorator; - constructor(name: string, type: string, description: string, range: Range, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator) { + constructor(name: string, type: string, description: string | null, range: Range, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator) { super(); this.name = name; @@ -55,7 +55,7 @@ export class SymbolEntry extends QuickOpenEntryGroup { return this.type; } - public getDescription(): string { + public getDescription(): string | null { return this.description; } @@ -67,7 +67,7 @@ export class SymbolEntry extends QuickOpenEntryGroup { return this.range; } - public run(mode: Mode, context: IContext): boolean { + public run(mode: Mode, context: IEntryRunContext): boolean { if (mode === Mode.OPEN) { return this.runOpen(context); } @@ -75,7 +75,7 @@ export class SymbolEntry extends QuickOpenEntryGroup { return this.runPreview(); } - private runOpen(context: IContext): boolean { + private runOpen(_context: IEntryRunContext): boolean { // Apply selection and focus let range = this.toSelection(); @@ -128,12 +128,15 @@ export class QuickOutlineAction extends BaseEditorQuickOpenAction { }); } - public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { + public run(accessor: ServicesAccessor, editor: ICodeEditor) { + if (!editor.hasModel()) { + return undefined; + } - let model = editor.getModel(); + const model = editor.getModel(); if (!DocumentSymbolProviderRegistry.has(model)) { - return null; + return undefined; } // Resolve outline @@ -166,7 +169,7 @@ export class QuickOutlineAction extends BaseEditorQuickOpenAction { }); } - private symbolEntry(name: string, type: string, description: string, range: IRange, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator): SymbolEntry { + private symbolEntry(name: string, type: string, description: string | null, range: IRange, highlights: IHighlight[], editor: ICodeEditor, decorator: IDecorator): SymbolEntry { return new SymbolEntry(name, type, description, Range.lift(range), highlights, editor, decorator); } @@ -222,7 +225,7 @@ export class QuickOutlineAction extends BaseEditorQuickOpenAction { // Update previous result with count if (currentResult) { - currentResult.setGroupLabel(this.typeToLabel(currentType, typeCounter)); + currentResult.setGroupLabel(this.typeToLabel(currentType || '', typeCounter)); } currentType = result.getType(); @@ -240,7 +243,7 @@ export class QuickOutlineAction extends BaseEditorQuickOpenAction { // Update previous result with count if (currentResult) { - currentResult.setGroupLabel(this.typeToLabel(currentType, typeCounter)); + currentResult.setGroupLabel(this.typeToLabel(currentType || '', typeCounter)); } } diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index e36877382e..aba4a22666 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -22,7 +22,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { ITextModel } from 'vs/editor/common/model'; import { TextEdit, WorkspaceEdit, isResourceTextEdit } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { ITextEditorModel, ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IResolvedTextEditorModel, ITextModelContentProvider, ITextModelService } from 'vs/editor/common/services/resolverService'; import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { CommandsRegistry, ICommand, ICommandEvent, ICommandHandler, ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationChangeEvent, IConfigurationData, IConfigurationOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -42,10 +42,11 @@ import { IProgressRunner, IProgressService } from 'vs/platform/progress/common/p import { ITelemetryInfo, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder, IWorkspaceFoldersChangeEvent, WorkbenchState, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { ILayoutService, IDimension } from 'vs/platform/layout/browser/layoutService'; -export class SimpleModel implements ITextEditorModel { +export class SimpleModel implements IResolvedTextEditorModel { - private model: ITextModel; + private readonly model: ITextModel; private readonly _onDispose: Emitter; constructor(model: ITextModel) { @@ -97,7 +98,7 @@ export class SimpleEditorModelResolverService implements ITextModelService { this.editor = editor; } - public createModelReference(resource: URI): Promise> { + public createModelReference(resource: URI): Promise> { let model: ITextModel | null = withTypedEditor(this.editor, (editor) => this.findModel(editor, resource), (diffEditor) => this.findModel(diffEditor.getOriginalEditor(), resource) || this.findModel(diffEditor.getModifiedEditor(), resource) @@ -220,7 +221,7 @@ export class StandaloneCommandService implements ICommandService { _serviceBrand: any; private readonly _instantiationService: IInstantiationService; - private _dynamicCommands: { [id: string]: ICommand; }; + private readonly _dynamicCommands: { [id: string]: ICommand; }; private readonly _onWillExecuteCommand = new Emitter(); public readonly onWillExecuteCommand: Event = this._onWillExecuteCommand.event; @@ -256,7 +257,7 @@ export class StandaloneCommandService implements ICommandService { export class StandaloneKeybindingService extends AbstractKeybindingService { private _cachedResolver: KeybindingResolver | null; - private _dynamicKeybindings: IKeybindingItem[]; + private readonly _dynamicKeybindings: IKeybindingItem[]; constructor( contextKeyService: IContextKeyService, @@ -369,13 +370,17 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { keyboardEvent.altKey, keyboardEvent.metaKey, keyboardEvent.keyCode - ); + ).toChord(); return new USLayoutResolvedKeybinding(keybinding, OS); } public resolveUserBinding(userBinding: string): ResolvedKeybinding[] { return []; } + + public _dumpDebugInfo(): string { + return ''; + } } function isConfigurationOverrides(thing: any): thing is IConfigurationOverrides { @@ -392,7 +397,7 @@ export class SimpleConfigurationService implements IConfigurationService { private _onDidChangeConfiguration = new Emitter(); public readonly onDidChangeConfiguration: Event = this._onDidChangeConfiguration.event; - private _configuration: Configuration; + private readonly _configuration: Configuration; constructor() { this._configuration = new Configuration(new DefaultConfigurationModel(), new ConfigurationModel()); @@ -409,7 +414,7 @@ export class SimpleConfigurationService implements IConfigurationService { getValue(arg1?: any, arg2?: any): any { const section = typeof arg1 === 'string' ? arg1 : undefined; const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : {}; - return this.configuration().getValue(section, overrides, null); + return this.configuration().getValue(section, overrides, undefined); } public updateValue(key: string, value: any, arg3?: any, arg4?: any): Promise { @@ -424,11 +429,11 @@ export class SimpleConfigurationService implements IConfigurationService { workspaceFolder?: C value: C, } { - return this.configuration().inspect(key, options, null); + return this.configuration().inspect(key, options, undefined); } public keys() { - return this.configuration().keys(null); + return this.configuration().keys(undefined); } public reloadConfiguration(): Promise { @@ -447,7 +452,7 @@ export class SimpleResourceConfigurationService implements ITextResourceConfigur public readonly onDidChangeConfiguration: Event; private readonly _onDidChangeConfigurationEmitter = new Emitter(); - constructor(private configurationService: SimpleConfigurationService) { + constructor(private readonly configurationService: SimpleConfigurationService) { this.configurationService.onDidChangeConfiguration((e) => { this._onDidChangeConfigurationEmitter.fire(e); }); @@ -576,7 +581,7 @@ export class SimpleBulkEditService implements IBulkEditService { // } - apply(workspaceEdit: WorkspaceEdit, options: IBulkEditOptions): Promise { + apply(workspaceEdit: WorkspaceEdit, options?: IBulkEditOptions): Promise { let edits = new Map(); @@ -629,6 +634,10 @@ export class SimpleUriLabelService implements ILabelService { return ''; } + public getSeparator(scheme: string, authority?: string): '/' | '\\' { + return '/'; + } + public registerFormatter(formatter: ResourceLabelFormatter): IDisposable { throw new Error('Not implemented'); } @@ -637,3 +646,28 @@ export class SimpleUriLabelService implements ILabelService { return ''; } } + +export class SimpleLayoutService implements ILayoutService { + _serviceBrand: any; + + public onLayout = Event.None; + + private _dimension: IDimension; + get dimension(): IDimension { + if (!this._dimension) { + this._dimension = dom.getClientArea(window.document.body); + } + + return this._dimension; + } + + get container(): HTMLElement { + return this._container; + } + + get hasWorkbench(): boolean { + return false; + } + + constructor(private _container: HTMLElement) { } +} diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index deaae8cbfe..f8c458eb0e 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -28,6 +28,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; /** * Description of an action contribution @@ -152,7 +153,7 @@ function createAriaDomNode() { */ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandaloneCodeEditor { - private _standaloneKeybindingService: StandaloneKeybindingService; + private readonly _standaloneKeybindingService: StandaloneKeybindingService; constructor( domElement: HTMLElement, @@ -163,7 +164,8 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon @IContextKeyService contextKeyService: IContextKeyService, @IKeybindingService keybindingService: IKeybindingService, @IThemeService themeService: IThemeService, - @INotificationService notificationService: INotificationService + @INotificationService notificationService: INotificationService, + @IAccessibilityService accessibilityService: IAccessibilityService ) { options = options || {}; options.ariaLabel = options.ariaLabel || nls.localize('editorViewAccessibleLabel', "Editor content"); @@ -172,7 +174,7 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon ? nls.localize('accessibilityHelpMessageIE', "Press Ctrl+F1 for Accessibility Options.") : nls.localize('accessibilityHelpMessage', "Press Alt+F1 for Accessibility Options.") ); - super(domElement, options, {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService); + super(domElement, options, {}, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService); if (keybindingService instanceof StandaloneKeybindingService) { this._standaloneKeybindingService = keybindingService; @@ -278,7 +280,7 @@ export class StandaloneCodeEditor extends CodeEditorWidget implements IStandalon export class StandaloneEditor extends StandaloneCodeEditor implements IStandaloneCodeEditor { - private _contextViewService: ContextViewService; + private readonly _contextViewService: ContextViewService; private readonly _configurationService: IConfigurationService; private _ownsModel: boolean; @@ -294,7 +296,8 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon @IContextViewService contextViewService: IContextViewService, @IStandaloneThemeService themeService: IStandaloneThemeService, @INotificationService notificationService: INotificationService, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @IAccessibilityService accessibilityService: IAccessibilityService ) { applyConfigurationValues(configurationService, options, false); options = options || {}; @@ -303,7 +306,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon } let _model: ITextModel | null | undefined = options.model; delete options.model; - super(domElement, options, instantiationService, codeEditorService, commandService, contextKeyService, keybindingService, themeService, notificationService); + super(domElement, options, instantiationService, codeEditorService, commandService, contextKeyService, keybindingService, themeService, notificationService, accessibilityService); this._contextViewService = contextViewService; this._configurationService = configurationService; @@ -355,7 +358,7 @@ export class StandaloneEditor extends StandaloneCodeEditor implements IStandalon export class StandaloneDiffEditor extends DiffEditorWidget implements IStandaloneDiffEditor { - private _contextViewService: ContextViewService; + private readonly _contextViewService: ContextViewService; private readonly _configurationService: IConfigurationService; constructor( diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 4cab24d728..939d6de4e3 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -36,6 +36,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IMarker, IMarkerData } from 'vs/platform/markers/common/markers'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { clearAllFontInfos } from 'vs/editor/browser/config/configuration'; type Omit = Pick>; @@ -81,6 +83,7 @@ export function create(domElement: HTMLElement, options?: IEditorConstructionOpt services.get(IStandaloneThemeService), services.get(INotificationService), services.get(IConfigurationService), + services.get(IAccessibilityService) ); }); } @@ -309,6 +312,13 @@ export function setTheme(themeName: string): void { StaticServices.standaloneThemeService.get().setTheme(themeName); } +/** + * Clears all cached font measurements and triggers re-measurement. + */ +export function remeasureFonts(): void { + clearAllFontInfos(); +} + /** * @internal */ @@ -338,6 +348,7 @@ export function createMonacoEditorAPI(): typeof monaco.editor { tokenize: tokenize, defineTheme: defineTheme, setTheme: setTheme, + remeasureFonts: remeasureFonts, // enums ScrollbarVisibility: standaloneEnums.ScrollbarVisibility, diff --git a/src/vs/editor/standalone/browser/standaloneLanguages.ts b/src/vs/editor/standalone/browser/standaloneLanguages.ts index 454a201968..ee41d4a723 100644 --- a/src/vs/editor/standalone/browser/standaloneLanguages.ts +++ b/src/vs/editor/standalone/browser/standaloneLanguages.ts @@ -292,31 +292,47 @@ export interface EncodedTokensProvider { function isEncodedTokensProvider(provider: TokensProvider | EncodedTokensProvider): provider is EncodedTokensProvider { return provider['tokenizeEncoded']; } + +function isThenable(obj: any): obj is Thenable { + if (typeof obj.then === 'function') { + return true; + } + return false; +} + /** * Set the tokens provider for a language (manual implementation). */ -export function setTokensProvider(languageId: string, provider: TokensProvider | EncodedTokensProvider): IDisposable { +export function setTokensProvider(languageId: string, provider: TokensProvider | EncodedTokensProvider | Thenable): IDisposable { let languageIdentifier = StaticServices.modeService.get().getLanguageIdentifier(languageId); if (!languageIdentifier) { throw new Error(`Cannot set tokens provider for unknown language ${languageId}`); } - let adapter: modes.ITokenizationSupport; - if (isEncodedTokensProvider(provider)) { - adapter = new EncodedTokenizationSupport2Adapter(provider); - } else { - adapter = new TokenizationSupport2Adapter(StaticServices.standaloneThemeService.get(), languageIdentifier, provider); + const create = (provider: TokensProvider | EncodedTokensProvider) => { + if (isEncodedTokensProvider(provider)) { + return new EncodedTokenizationSupport2Adapter(provider); + } else { + return new TokenizationSupport2Adapter(StaticServices.standaloneThemeService.get(), languageIdentifier!, provider); + } + }; + if (isThenable(provider)) { + return modes.TokenizationRegistry.registerPromise(languageId, provider.then(provider => create(provider))); } - return modes.TokenizationRegistry.register(languageId, adapter); + return modes.TokenizationRegistry.register(languageId, create(provider)); } /** * Set the tokens provider for a language (monarch implementation). */ -export function setMonarchTokensProvider(languageId: string, languageDef: IMonarchLanguage): IDisposable { - let lexer = compile(languageId, languageDef); - let adapter = createTokenizationSupport(StaticServices.modeService.get(), StaticServices.standaloneThemeService.get(), languageId, lexer); - return modes.TokenizationRegistry.register(languageId, adapter); +export function setMonarchTokensProvider(languageId: string, languageDef: IMonarchLanguage | Thenable): IDisposable { + const create = (languageDef: IMonarchLanguage) => { + return createTokenizationSupport(StaticServices.modeService.get(), StaticServices.standaloneThemeService.get(), languageId, compile(languageId, languageDef)); + }; + if (isThenable(languageDef)) { + return modes.TokenizationRegistry.registerPromise(languageId, languageDef.then(languageDef => create(languageDef))); + } + return modes.TokenizationRegistry.register(languageId, create(languageDef)); } /** diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index de8b1bec95..90b2baf3ac 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -13,7 +13,7 @@ import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; -import { SimpleBulkEditService, SimpleConfigurationService, SimpleDialogService, SimpleNotificationService, SimpleProgressService, SimpleResourceConfigurationService, SimpleResourcePropertiesService, SimpleUriLabelService, SimpleWorkspaceContextService, StandaloneCommandService, StandaloneKeybindingService, StandaloneTelemetryService } from 'vs/editor/standalone/browser/simpleServices'; +import { SimpleBulkEditService, SimpleConfigurationService, SimpleDialogService, SimpleNotificationService, SimpleProgressService, SimpleResourceConfigurationService, SimpleResourcePropertiesService, SimpleUriLabelService, SimpleWorkspaceContextService, StandaloneCommandService, StandaloneKeybindingService, StandaloneTelemetryService, SimpleLayoutService } from 'vs/editor/standalone/browser/simpleServices'; import { StandaloneCodeEditorServiceImpl } from 'vs/editor/standalone/browser/standaloneCodeServiceImpl'; import { StandaloneThemeServiceImpl } from 'vs/editor/standalone/browser/standaloneThemeServiceImpl'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; @@ -45,6 +45,9 @@ import { MenuService } from 'vs/platform/actions/common/menuService'; import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; import { ISuggestMemoryService, SuggestMemoryService } from 'vs/editor/contrib/suggest/suggestMemory'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { BrowserAccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export interface IEditorOverrideServices { [index: string]: any; @@ -55,8 +58,8 @@ export module StaticServices { const _serviceCollection = new ServiceCollection(); export class LazyStaticService { - private _serviceId: ServiceIdentifier; - private _factory: (overrides?: IEditorOverrideServices) => T; + private readonly _serviceId: ServiceIdentifier; + private readonly _factory: (overrides?: IEditorOverrideServices) => T; private _value: T | null; public get id() { return this._serviceId; } @@ -132,6 +135,8 @@ export module StaticServices { export const notificationService = define(INotificationService, () => new SimpleNotificationService()); + export const accessibilityService = define(IAccessibilityService, () => new BrowserAccessibilityService()); + export const markerService = define(IMarkerService, () => new MarkerService()); export const modeService = define(IModeService, (o) => new ModeServiceImpl()); @@ -158,8 +163,8 @@ export module StaticServices { export class DynamicStandaloneServices extends Disposable { - private _serviceCollection: ServiceCollection; - private _instantiationService: IInstantiationService; + private readonly _serviceCollection: ServiceCollection; + private readonly _instantiationService: IInstantiationService; constructor(domElement: HTMLElement, overrides: IEditorOverrideServices) { super(); @@ -193,9 +198,11 @@ export class DynamicStandaloneServices extends Disposable { let keybindingService = ensure(IKeybindingService, () => this._register(new StandaloneKeybindingService(contextKeyService, commandService, telemetryService, notificationService, domElement))); - let contextViewService = ensure(IContextViewService, () => this._register(new ContextViewService(domElement, telemetryService, new NullLogService()))); + let layoutService = ensure(ILayoutService, () => new SimpleLayoutService(domElement)); - ensure(IContextMenuService, () => this._register(new ContextMenuService(domElement, telemetryService, notificationService, contextViewService, keybindingService, themeService))); + let contextViewService = ensure(IContextViewService, () => this._register(new ContextViewService(layoutService))); + + ensure(IContextMenuService, () => this._register(new ContextMenuService(layoutService, telemetryService, notificationService, contextViewService, keybindingService, themeService))); ensure(IMenuService, () => new MenuService(commandService)); diff --git a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts index 2e21781bcd..8078e99670 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeServiceImpl.ts @@ -26,9 +26,9 @@ class StandaloneTheme implements IStandaloneTheme { public readonly id: string; public readonly themeName: string; - private themeData: IStandaloneThemeData; + private readonly themeData: IStandaloneThemeData; private colors: { [colorId: string]: Color } | null; - private defaultColors: { [colorId: string]: Color | null; }; + private readonly defaultColors: { [colorId: string]: Color | undefined; }; private _tokenTheme: TokenTheme | null; constructor(name: string, standaloneThemeData: IStandaloneThemeData) { @@ -77,7 +77,7 @@ class StandaloneTheme implements IStandaloneTheme { return this.colors; } - public getColor(colorId: ColorIdentifier, useDefault?: boolean): Color | null { + public getColor(colorId: ColorIdentifier, useDefault?: boolean): Color | undefined { const color = this.getColors()[colorId]; if (color) { return color; @@ -85,10 +85,10 @@ class StandaloneTheme implements IStandaloneTheme { if (useDefault !== false) { return this.getDefault(colorId); } - return null; + return undefined; } - private getDefault(colorId: ColorIdentifier): Color | null { + private getDefault(colorId: ColorIdentifier): Color | undefined { let color = this.defaultColors[colorId]; if (color) { return color; @@ -159,12 +159,12 @@ export class StandaloneThemeServiceImpl implements IStandaloneThemeService { _serviceBrand: any; - private _knownThemes: Map; - private _styleElement: HTMLStyleElement; + private readonly _knownThemes: Map; + private readonly _styleElement: HTMLStyleElement; private _theme: IStandaloneTheme; private readonly _onThemeChange: Emitter; private readonly _onIconThemeChange: Emitter; - private environment: IEnvironmentService = Object.create(null); + private readonly environment: IEnvironmentService = Object.create(null); constructor() { this._onThemeChange = new Emitter(); diff --git a/src/vs/editor/standalone/common/monarch/monarchLexer.ts b/src/vs/editor/standalone/common/monarch/monarchLexer.ts index dcf3308d03..8c12bebfd6 100644 --- a/src/vs/editor/standalone/common/monarch/monarchLexer.ts +++ b/src/vs/editor/standalone/common/monarch/monarchLexer.ts @@ -289,8 +289,8 @@ class MonarchClassicTokensCollector implements IMonarchTokensCollector { class MonarchModernTokensCollector implements IMonarchTokensCollector { - private _modeService: IModeService; - private _theme: TokenTheme; + private readonly _modeService: IModeService; + private readonly _theme: TokenTheme; private _prependTokens: Uint32Array | null; private _tokens: number[]; private _currentLanguageId: modes.LanguageId; @@ -374,14 +374,17 @@ class MonarchModernTokensCollector implements IMonarchTokensCollector { } } -class MonarchTokenizer implements modes.ITokenizationSupport { +export type ILoadStatus = { loaded: true; } | { loaded: false; promise: Promise; }; + +export class MonarchTokenizer implements modes.ITokenizationSupport { private readonly _modeService: IModeService; private readonly _standaloneThemeService: IStandaloneThemeService; private readonly _modeId: string; private readonly _lexer: monarchCommon.ILexer; - private _embeddedModes: { [modeId: string]: boolean; }; - private _tokenizationRegistryListener: IDisposable; + private readonly _embeddedModes: { [modeId: string]: boolean; }; + public embeddedLoaded: Promise; + private readonly _tokenizationRegistryListener: IDisposable; constructor(modeService: IModeService, standaloneThemeService: IStandaloneThemeService, modeId: string, lexer: monarchCommon.ILexer) { this._modeService = modeService; @@ -389,6 +392,7 @@ class MonarchTokenizer implements modes.ITokenizationSupport { this._modeId = modeId; this._lexer = lexer; this._embeddedModes = Object.create(null); + this.embeddedLoaded = Promise.resolve(undefined); // Set up listening for embedded modes let emitting = false; @@ -416,6 +420,39 @@ class MonarchTokenizer implements modes.ITokenizationSupport { this._tokenizationRegistryListener.dispose(); } + public getLoadStatus(): ILoadStatus { + let promises: Thenable[] = []; + for (let nestedModeId in this._embeddedModes) { + const tokenizationSupport = modes.TokenizationRegistry.get(nestedModeId); + if (tokenizationSupport) { + // The nested mode is already loaded + if (tokenizationSupport instanceof MonarchTokenizer) { + const nestedModeStatus = tokenizationSupport.getLoadStatus(); + if (nestedModeStatus.loaded === false) { + promises.push(nestedModeStatus.promise); + } + } + continue; + } + + const tokenizationSupportPromise = modes.TokenizationRegistry.getPromise(nestedModeId); + if (tokenizationSupportPromise) { + // The nested mode is in the process of being loaded + promises.push(tokenizationSupportPromise); + } + } + + if (promises.length === 0) { + return { + loaded: true + }; + } + return { + loaded: false, + promise: Promise.all(promises).then(_ => undefined) + }; + } + public getInitialState(): modes.IState { let rootState = MonarchStackElementFactory.create(null, this._lexer.start!); return MonarchLineStateFactory.create(rootState, null); diff --git a/src/vs/editor/standalone/test/browser/simpleServices.test.ts b/src/vs/editor/standalone/test/browser/simpleServices.test.ts index 3aa57fe4be..102a2e01f5 100644 --- a/src/vs/editor/standalone/test/browser/simpleServices.test.ts +++ b/src/vs/editor/standalone/test/browser/simpleServices.test.ts @@ -42,6 +42,7 @@ suite('StandaloneKeybindingService', () => { }, null); keybindingService.testDispatch({ + _standardKeyboardEventBrand: true, ctrlKey: false, shiftKey: false, altKey: false, diff --git a/src/vs/editor/test/browser/commands/shiftCommand.test.ts b/src/vs/editor/test/browser/commands/shiftCommand.test.ts index f7529b86b4..909db815f2 100644 --- a/src/vs/editor/test/browser/commands/shiftCommand.test.ts +++ b/src/vs/editor/test/browser/commands/shiftCommand.test.ts @@ -47,7 +47,8 @@ function testShiftCommand(lines: string[], languageIdentifier: LanguageIdentifie testCommand(lines, languageIdentifier, selection, (sel) => new ShiftCommand(sel, { isUnshift: false, tabSize: 4, - oneIndent: '\t', + indentSize: 4, + insertSpaces: false, useTabStops: useTabStops, }), expectedLines, expectedSelection); } @@ -56,7 +57,8 @@ function testUnshiftCommand(lines: string[], languageIdentifier: LanguageIdentif testCommand(lines, languageIdentifier, selection, (sel) => new ShiftCommand(sel, { isUnshift: true, tabSize: 4, - oneIndent: '\t', + indentSize: 4, + insertSpaces: false, useTabStops: useTabStops, }), expectedLines, expectedSelection); } @@ -668,7 +670,8 @@ suite('Editor Commands - ShiftCommand', () => { (sel) => new ShiftCommand(sel, { isUnshift: false, tabSize: 4, - oneIndent: ' ', + indentSize: 4, + insertSpaces: true, useTabStops: false }), [ @@ -712,7 +715,8 @@ suite('Editor Commands - ShiftCommand', () => { (sel) => new ShiftCommand(sel, { isUnshift: true, tabSize: 4, - oneIndent: ' ', + indentSize: 4, + insertSpaces: true, useTabStops: false }), [ @@ -756,7 +760,8 @@ suite('Editor Commands - ShiftCommand', () => { (sel) => new ShiftCommand(sel, { isUnshift: true, tabSize: 4, - oneIndent: '\t', + indentSize: 4, + insertSpaces: false, useTabStops: false }), [ @@ -800,7 +805,8 @@ suite('Editor Commands - ShiftCommand', () => { (sel) => new ShiftCommand(sel, { isUnshift: true, tabSize: 4, - oneIndent: ' ', + indentSize: 4, + insertSpaces: true, useTabStops: false }), [ @@ -833,7 +839,8 @@ suite('Editor Commands - ShiftCommand', () => { (sel) => new ShiftCommand(sel, { isUnshift: false, tabSize: 4, - oneIndent: '\t', + indentSize: 4, + insertSpaces: false, useTabStops: true }), [ @@ -854,98 +861,96 @@ suite('Editor Commands - ShiftCommand', () => { return r; }; - let testOutdent = (tabSize: number, oneIndent: string, lineText: string, expectedIndents: number) => { + let testOutdent = (tabSize: number, indentSize: number, insertSpaces: boolean, lineText: string, expectedIndents: number) => { + const oneIndent = insertSpaces ? repeatStr(' ', indentSize) : '\t'; let expectedIndent = repeatStr(oneIndent, expectedIndents); if (lineText.length > 0) { - _assertUnshiftCommand(tabSize, oneIndent, [lineText + 'aaa'], [createSingleEditOp(expectedIndent, 1, 1, 1, lineText.length + 1)]); + _assertUnshiftCommand(tabSize, indentSize, insertSpaces, [lineText + 'aaa'], [createSingleEditOp(expectedIndent, 1, 1, 1, lineText.length + 1)]); } else { - _assertUnshiftCommand(tabSize, oneIndent, [lineText + 'aaa'], []); + _assertUnshiftCommand(tabSize, indentSize, insertSpaces, [lineText + 'aaa'], []); } }; - let testIndent = (tabSize: number, oneIndent: string, lineText: string, expectedIndents: number) => { + let testIndent = (tabSize: number, indentSize: number, insertSpaces: boolean, lineText: string, expectedIndents: number) => { + const oneIndent = insertSpaces ? repeatStr(' ', indentSize) : '\t'; let expectedIndent = repeatStr(oneIndent, expectedIndents); - _assertShiftCommand(tabSize, oneIndent, [lineText + 'aaa'], [createSingleEditOp(expectedIndent, 1, 1, 1, lineText.length + 1)]); + _assertShiftCommand(tabSize, indentSize, insertSpaces, [lineText + 'aaa'], [createSingleEditOp(expectedIndent, 1, 1, 1, lineText.length + 1)]); }; - let testIndentation = (tabSize: number, lineText: string, expectedOnOutdent: number, expectedOnIndent: number) => { - let spaceIndent = ''; - for (let i = 0; i < tabSize; i++) { - spaceIndent += ' '; - } + let testIndentation = (tabSize: number, indentSize: number, lineText: string, expectedOnOutdent: number, expectedOnIndent: number) => { + testOutdent(tabSize, indentSize, true, lineText, expectedOnOutdent); + testOutdent(tabSize, indentSize, false, lineText, expectedOnOutdent); - testOutdent(tabSize, spaceIndent, lineText, expectedOnOutdent); - testOutdent(tabSize, '\t', lineText, expectedOnOutdent); - - testIndent(tabSize, spaceIndent, lineText, expectedOnIndent); - testIndent(tabSize, '\t', lineText, expectedOnIndent); + testIndent(tabSize, indentSize, true, lineText, expectedOnIndent); + testIndent(tabSize, indentSize, false, lineText, expectedOnIndent); }; // insertSpaces: true // 0 => 0 - testIndentation(4, '', 0, 1); + testIndentation(4, 4, '', 0, 1); // 1 => 0 - testIndentation(4, '\t', 0, 2); - testIndentation(4, ' ', 0, 1); - testIndentation(4, ' \t', 0, 2); - testIndentation(4, ' ', 0, 1); - testIndentation(4, ' \t', 0, 2); - testIndentation(4, ' ', 0, 1); - testIndentation(4, ' \t', 0, 2); - testIndentation(4, ' ', 0, 2); + testIndentation(4, 4, '\t', 0, 2); + testIndentation(4, 4, ' ', 0, 1); + testIndentation(4, 4, ' \t', 0, 2); + testIndentation(4, 4, ' ', 0, 1); + testIndentation(4, 4, ' \t', 0, 2); + testIndentation(4, 4, ' ', 0, 1); + testIndentation(4, 4, ' \t', 0, 2); + testIndentation(4, 4, ' ', 0, 2); // 2 => 1 - testIndentation(4, '\t\t', 1, 3); - testIndentation(4, '\t ', 1, 2); - testIndentation(4, '\t \t', 1, 3); - testIndentation(4, '\t ', 1, 2); - testIndentation(4, '\t \t', 1, 3); - testIndentation(4, '\t ', 1, 2); - testIndentation(4, '\t \t', 1, 3); - testIndentation(4, '\t ', 1, 3); - testIndentation(4, ' \t\t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 3); - testIndentation(4, ' \t\t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 3); - testIndentation(4, ' \t\t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 2); - testIndentation(4, ' \t \t', 1, 3); - testIndentation(4, ' \t ', 1, 3); - testIndentation(4, ' \t', 1, 3); - testIndentation(4, ' ', 1, 2); - testIndentation(4, ' \t', 1, 3); - testIndentation(4, ' ', 1, 2); - testIndentation(4, ' \t', 1, 3); - testIndentation(4, ' ', 1, 2); - testIndentation(4, ' \t', 1, 3); - testIndentation(4, ' ', 1, 3); + testIndentation(4, 4, '\t\t', 1, 3); + testIndentation(4, 4, '\t ', 1, 2); + testIndentation(4, 4, '\t \t', 1, 3); + testIndentation(4, 4, '\t ', 1, 2); + testIndentation(4, 4, '\t \t', 1, 3); + testIndentation(4, 4, '\t ', 1, 2); + testIndentation(4, 4, '\t \t', 1, 3); + testIndentation(4, 4, '\t ', 1, 3); + testIndentation(4, 4, ' \t\t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 3); + testIndentation(4, 4, ' \t\t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 3); + testIndentation(4, 4, ' \t\t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 2); + testIndentation(4, 4, ' \t \t', 1, 3); + testIndentation(4, 4, ' \t ', 1, 3); + testIndentation(4, 4, ' \t', 1, 3); + testIndentation(4, 4, ' ', 1, 2); + testIndentation(4, 4, ' \t', 1, 3); + testIndentation(4, 4, ' ', 1, 2); + testIndentation(4, 4, ' \t', 1, 3); + testIndentation(4, 4, ' ', 1, 2); + testIndentation(4, 4, ' \t', 1, 3); + testIndentation(4, 4, ' ', 1, 3); // 3 => 2 - testIndentation(4, ' ', 2, 3); + testIndentation(4, 4, ' ', 2, 3); - function _assertUnshiftCommand(tabSize: number, oneIndent: string, text: string[], expected: IIdentifiedSingleEditOperation[]): void { + function _assertUnshiftCommand(tabSize: number, indentSize: number, insertSpaces: boolean, text: string[], expected: IIdentifiedSingleEditOperation[]): void { return withEditorModel(text, (model) => { let op = new ShiftCommand(new Selection(1, 1, text.length + 1, 1), { isUnshift: true, tabSize: tabSize, - oneIndent: oneIndent, + indentSize: indentSize, + insertSpaces: insertSpaces, useTabStops: true }); let actual = getEditOperation(model, op); @@ -953,12 +958,13 @@ suite('Editor Commands - ShiftCommand', () => { }); } - function _assertShiftCommand(tabSize: number, oneIndent: string, text: string[], expected: IIdentifiedSingleEditOperation[]): void { + function _assertShiftCommand(tabSize: number, indentSize: number, insertSpaces: boolean, text: string[], expected: IIdentifiedSingleEditOperation[]): void { return withEditorModel(text, (model) => { let op = new ShiftCommand(new Selection(1, 1, text.length + 1, 1), { isUnshift: false, tabSize: tabSize, - oneIndent: oneIndent, + indentSize: indentSize, + insertSpaces: insertSpaces, useTabStops: true }); let actual = getEditOperation(model, op); diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index dab0871208..c53ae2d138 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -2212,6 +2212,7 @@ suite('Editor Controller - Cursor Configuration', () => { ].join('\n'), { tabSize: 13, + indentSize: 13, } ); @@ -3122,7 +3123,10 @@ suite('Editor Controller - Indentation Rules', () => { '}a}' ], languageIdentifier: mode.getLanguageIdentifier(), - modelOpts: { tabSize: 2 } + modelOpts: { + tabSize: 2, + indentSize: 2 + } }, (model, cursor) => { moveTo(cursor, 3, 3, false); assertCursor(cursor, new Selection(3, 3, 3, 3)); @@ -3594,7 +3598,10 @@ suite('Editor Controller - Indentation Rules', () => { '', ')', ].join('\n'), - { tabSize: 2 }, + { + tabSize: 2, + indentSize: 2 + }, mode.getLanguageIdentifier() ); diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index b5b74b1d25..cbf8b2f460 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -44,7 +44,7 @@ class SingleLineTestModel implements ISimpleModel { class TestView { - private _model: SingleLineTestModel; + private readonly _model: SingleLineTestModel; constructor(model: SingleLineTestModel) { this._model = model; diff --git a/src/vs/editor/test/common/config/commonEditorConfig.test.ts b/src/vs/editor/test/common/config/commonEditorConfig.test.ts index 13445bba56..4fe98286da 100644 --- a/src/vs/editor/test/common/config/commonEditorConfig.test.ts +++ b/src/vs/editor/test/common/config/commonEditorConfig.test.ts @@ -3,11 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { AccessibilitySupport } from 'vs/base/common/platform'; import { IEnvConfiguration } from 'vs/editor/common/config/commonEditorConfig'; import { IEditorHoverOptions } from 'vs/editor/common/config/editorOptions'; import { EditorZoom } from 'vs/editor/common/config/editorZoom'; import { TestConfiguration } from 'vs/editor/test/common/mocks/testConfiguration'; +import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; suite('Common Editor Config', () => { test('Zoom Level', () => { diff --git a/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts index fbccc83fe5..4e948e22fe 100644 --- a/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts +++ b/src/vs/editor/test/common/controller/cursorMoveHelper.test.ts @@ -7,36 +7,36 @@ import { CursorColumns } from 'vs/editor/common/controller/cursorCommon'; suite('CursorMove', () => { - test('nextTabStop', () => { - assert.equal(CursorColumns.nextTabStop(0, 4), 4); - assert.equal(CursorColumns.nextTabStop(1, 4), 4); - assert.equal(CursorColumns.nextTabStop(2, 4), 4); - assert.equal(CursorColumns.nextTabStop(3, 4), 4); - assert.equal(CursorColumns.nextTabStop(4, 4), 8); - assert.equal(CursorColumns.nextTabStop(5, 4), 8); - assert.equal(CursorColumns.nextTabStop(6, 4), 8); - assert.equal(CursorColumns.nextTabStop(7, 4), 8); - assert.equal(CursorColumns.nextTabStop(8, 4), 12); + test('nextRenderTabStop', () => { + assert.equal(CursorColumns.nextRenderTabStop(0, 4), 4); + assert.equal(CursorColumns.nextRenderTabStop(1, 4), 4); + assert.equal(CursorColumns.nextRenderTabStop(2, 4), 4); + assert.equal(CursorColumns.nextRenderTabStop(3, 4), 4); + assert.equal(CursorColumns.nextRenderTabStop(4, 4), 8); + assert.equal(CursorColumns.nextRenderTabStop(5, 4), 8); + assert.equal(CursorColumns.nextRenderTabStop(6, 4), 8); + assert.equal(CursorColumns.nextRenderTabStop(7, 4), 8); + assert.equal(CursorColumns.nextRenderTabStop(8, 4), 12); - assert.equal(CursorColumns.nextTabStop(0, 2), 2); - assert.equal(CursorColumns.nextTabStop(1, 2), 2); - assert.equal(CursorColumns.nextTabStop(2, 2), 4); - assert.equal(CursorColumns.nextTabStop(3, 2), 4); - assert.equal(CursorColumns.nextTabStop(4, 2), 6); - assert.equal(CursorColumns.nextTabStop(5, 2), 6); - assert.equal(CursorColumns.nextTabStop(6, 2), 8); - assert.equal(CursorColumns.nextTabStop(7, 2), 8); - assert.equal(CursorColumns.nextTabStop(8, 2), 10); + assert.equal(CursorColumns.nextRenderTabStop(0, 2), 2); + assert.equal(CursorColumns.nextRenderTabStop(1, 2), 2); + assert.equal(CursorColumns.nextRenderTabStop(2, 2), 4); + assert.equal(CursorColumns.nextRenderTabStop(3, 2), 4); + assert.equal(CursorColumns.nextRenderTabStop(4, 2), 6); + assert.equal(CursorColumns.nextRenderTabStop(5, 2), 6); + assert.equal(CursorColumns.nextRenderTabStop(6, 2), 8); + assert.equal(CursorColumns.nextRenderTabStop(7, 2), 8); + assert.equal(CursorColumns.nextRenderTabStop(8, 2), 10); - assert.equal(CursorColumns.nextTabStop(0, 1), 1); - assert.equal(CursorColumns.nextTabStop(1, 1), 2); - assert.equal(CursorColumns.nextTabStop(2, 1), 3); - assert.equal(CursorColumns.nextTabStop(3, 1), 4); - assert.equal(CursorColumns.nextTabStop(4, 1), 5); - assert.equal(CursorColumns.nextTabStop(5, 1), 6); - assert.equal(CursorColumns.nextTabStop(6, 1), 7); - assert.equal(CursorColumns.nextTabStop(7, 1), 8); - assert.equal(CursorColumns.nextTabStop(8, 1), 9); + assert.equal(CursorColumns.nextRenderTabStop(0, 1), 1); + assert.equal(CursorColumns.nextRenderTabStop(1, 1), 2); + assert.equal(CursorColumns.nextRenderTabStop(2, 1), 3); + assert.equal(CursorColumns.nextRenderTabStop(3, 1), 4); + assert.equal(CursorColumns.nextRenderTabStop(4, 1), 5); + assert.equal(CursorColumns.nextRenderTabStop(5, 1), 6); + assert.equal(CursorColumns.nextRenderTabStop(6, 1), 7); + assert.equal(CursorColumns.nextRenderTabStop(7, 1), 8); + assert.equal(CursorColumns.nextRenderTabStop(8, 1), 9); }); test('visibleColumnFromColumn', () => { diff --git a/src/vs/editor/test/common/editorTestUtils.ts b/src/vs/editor/test/common/editorTestUtils.ts index 972aec2cc1..ee03ebb368 100644 --- a/src/vs/editor/test/common/editorTestUtils.ts +++ b/src/vs/editor/test/common/editorTestUtils.ts @@ -16,6 +16,7 @@ export function withEditorModel(text: string[], callback: (model: TextModel) => export interface IRelaxedTextModelCreationOptions { tabSize?: number; + indentSize?: number; insertSpaces?: boolean; detectIndentation?: boolean; trimAutoWhitespace?: boolean; @@ -27,6 +28,7 @@ export interface IRelaxedTextModelCreationOptions { export function createTextModel(text: string, _options: IRelaxedTextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier: LanguageIdentifier | null = null, uri: URI | null = null): TextModel { const options: ITextModelCreationOptions = { tabSize: (typeof _options.tabSize === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.tabSize : _options.tabSize), + indentSize: (typeof _options.indentSize === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.indentSize : _options.indentSize), insertSpaces: (typeof _options.insertSpaces === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.insertSpaces : _options.insertSpaces), detectIndentation: (typeof _options.detectIndentation === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.detectIndentation : _options.detectIndentation), trimAutoWhitespace: (typeof _options.trimAutoWhitespace === 'undefined' ? TextModel.DEFAULT_CREATION_OPTIONS.trimAutoWhitespace : _options.trimAutoWhitespace), diff --git a/src/vs/editor/test/common/mocks/mockMode.ts b/src/vs/editor/test/common/mocks/mockMode.ts index 52435aea34..7afa9664bb 100644 --- a/src/vs/editor/test/common/mocks/mockMode.ts +++ b/src/vs/editor/test/common/mocks/mockMode.ts @@ -9,7 +9,7 @@ import { IMode, LanguageIdentifier } from 'vs/editor/common/modes'; import { ILanguageSelection } from 'vs/editor/common/services/modeService'; export class MockMode extends Disposable implements IMode { - private _languageIdentifier: LanguageIdentifier; + private readonly _languageIdentifier: LanguageIdentifier; constructor(languageIdentifier: LanguageIdentifier) { super(); diff --git a/src/vs/editor/test/common/mocks/testConfiguration.ts b/src/vs/editor/test/common/mocks/testConfiguration.ts index a52c829bf5..19d93ad211 100644 --- a/src/vs/editor/test/common/mocks/testConfiguration.ts +++ b/src/vs/editor/test/common/mocks/testConfiguration.ts @@ -3,10 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { AccessibilitySupport } from 'vs/base/common/platform'; import { CommonEditorConfiguration, IEnvConfiguration } from 'vs/editor/common/config/commonEditorConfig'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo, FontInfo } from 'vs/editor/common/config/fontInfo'; +import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; export class TestConfiguration extends CommonEditorConfiguration { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 27a676bcbe..d3d8225bf5 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -380,6 +380,7 @@ declare namespace monaco { } export interface IKeyboardEvent { + readonly _standardKeyboardEventBrand: true; readonly browserEvent: KeyboardEvent; readonly target: HTMLElement; readonly ctrlKey: boolean; @@ -922,6 +923,11 @@ declare namespace monaco.editor { */ export function setTheme(themeName: string): void; + /** + * Clears all cached font measurements and triggers re-measurement. + */ + export function remeasureFonts(): void; + export type BuiltinTheme = 'vs' | 'vs-dark' | 'hc-black'; export interface IStandaloneThemeData { @@ -1381,7 +1387,7 @@ declare namespace monaco.editor { /** * The text to replace with. This can be null to emulate a simple delete. */ - text: string; + text: string | null; /** * This indicates that this operation has "insert" semantics. * i.e. forceMoveMarkers = true => if `range` is collapsed, all markers at the position will be moved. @@ -1421,6 +1427,7 @@ declare namespace monaco.editor { export class TextModelResolvedOptions { _textModelResolvedOptionsBrand: void; readonly tabSize: number; + readonly indentSize: number; readonly insertSpaces: boolean; readonly defaultEOL: DefaultEndOfLine; readonly trimAutoWhitespace: boolean; @@ -1428,6 +1435,7 @@ declare namespace monaco.editor { export interface ITextModelUpdateOptions { tabSize?: number; + indentSize?: number; insertSpaces?: boolean; trimAutoWhitespace?: boolean; } @@ -1714,10 +1722,6 @@ declare namespace monaco.editor { * Normalize a string containing whitespace according to indentation rules (converts to spaces or to tabs). */ normalizeIndentation(str: string): string; - /** - * Get what is considered to be one indent (e.g. a tab character or 4 spaces, etc.). - */ - getOneIndent(): string; /** * Change the options of this model. */ @@ -2278,6 +2282,7 @@ declare namespace monaco.editor { export interface IModelOptionsChangedEvent { readonly tabSize: boolean; + readonly indentSize: boolean; readonly insertSpaces: boolean; readonly trimAutoWhitespace: boolean; } @@ -2433,6 +2438,7 @@ declare namespace monaco.editor { * Controls if Find in Selection flag is turned on when multiple lines of text are selected in the editor. */ autoFindInSelection: boolean; + addExtraSpaceOnTop?: boolean; } /** @@ -2541,6 +2547,25 @@ declare namespace monaco.editor { * Enable using global storage for remembering suggestions. */ shareSuggestSelections?: boolean; + /** + * Enable or disable icons in suggestions. Defaults to true. + */ + showIcons?: boolean; + /** + * Max suggestions to show in suggestions. Defaults to 12. + */ + maxVisibleSuggestions?: boolean; + /** + * Names of suggestion types to filter. + */ + filteredTypes?: Record; + } + + export interface IGotoLocationOptions { + /** + * Control how goto-command work when having multiple results. + */ + many?: 'peek' | 'revealAndPeek' | 'reveal'; } /** @@ -2581,6 +2606,11 @@ declare namespace monaco.editor { * Defaults to true. */ lineNumbers?: 'on' | 'off' | 'relative' | 'interval' | ((lineNumber: number) => string); + /** + * Render last line number when the file ends with a newline. + * Defaults to true on Windows/Mac and to false on Linux. + */ + renderFinalNewline?: boolean; /** * Should the corresponding line be selected when clicking on the line number? * Defaults to true. @@ -2812,6 +2842,10 @@ declare namespace monaco.editor { * Suggest options. */ suggest?: ISuggestOptions; + /** + * + */ + gotoLocation?: IGotoLocationOptions; /** * Enable quick suggestions (shadow suggestions) * Defaults to true. @@ -2830,11 +2864,6 @@ declare namespace monaco.editor { * Parameter hint options. */ parameterHints?: IEditorParameterHintOptions; - /** - * Render icons in suggestions box. - * Defaults to true. - */ - iconsInSuggestions?: boolean; /** * Options for auto closing brackets. * Defaults to language defined behavior. @@ -3169,6 +3198,7 @@ declare namespace monaco.editor { export interface InternalEditorFindOptions { readonly seedSearchStringFromSelection: boolean; readonly autoFindInSelection: boolean; + readonly addExtraSpaceOnTop: boolean; } export interface InternalEditorHoverOptions { @@ -3177,12 +3207,19 @@ declare namespace monaco.editor { readonly sticky: boolean; } + export interface InternalGoToLocationOptions { + readonly many: 'peek' | 'revealAndPeek' | 'reveal'; + } + export interface InternalSuggestOptions { readonly filterGraceful: boolean; readonly snippets: 'top' | 'bottom' | 'inline' | 'none'; readonly snippetsPreventQuickSuggestions: boolean; readonly localityBonus: boolean; readonly shareSuggestSelections: boolean; + readonly showIcons: boolean; + readonly maxVisibleSuggestions: number; + readonly filteredTypes: Record; } export interface InternalParameterHintOptions { @@ -3217,6 +3254,7 @@ declare namespace monaco.editor { readonly ariaLabel: string; readonly renderLineNumbers: RenderLineNumbersType; readonly renderCustomLineNumbers: ((lineNumber: number) => string) | null; + readonly renderFinalNewline: boolean; readonly selectOnLineNumbers: boolean; readonly glyphMargin: boolean; readonly revealHorizontalRightPadding: number; @@ -3256,7 +3294,6 @@ declare namespace monaco.editor { }; readonly quickSuggestionsDelay: number; readonly parameterHints: InternalParameterHintOptions; - readonly iconsInSuggestions: boolean; readonly formatOnType: boolean; readonly formatOnPaste: boolean; readonly suggestOnTriggerCharacters: boolean; @@ -3268,6 +3305,7 @@ declare namespace monaco.editor { readonly suggestLineHeight: number; readonly tabCompletion: 'on' | 'off' | 'onlySnippets'; readonly suggest: InternalSuggestOptions; + readonly gotoLocation: InternalGoToLocationOptions; readonly selectionHighlight: boolean; readonly occurrencesHighlight: boolean; readonly codeLens: boolean; @@ -4271,12 +4309,12 @@ declare namespace monaco.languages { /** * Set the tokens provider for a language (manual implementation). */ - export function setTokensProvider(languageId: string, provider: TokensProvider | EncodedTokensProvider): IDisposable; + export function setTokensProvider(languageId: string, provider: TokensProvider | EncodedTokensProvider | Thenable): IDisposable; /** * Set the tokens provider for a language (monarch implementation). */ - export function setMonarchTokensProvider(languageId: string, languageDef: IMonarchLanguage): IDisposable; + export function setMonarchTokensProvider(languageId: string, languageDef: IMonarchLanguage | Thenable): IDisposable; /** * Register a reference provider (used by e.g. reference search). @@ -4554,7 +4592,7 @@ declare namespace monaco.languages { /** * The string that appears on the last line and closes the doc comment (e.g. ' * /'). */ - close: string; + close?: string; } /** @@ -4968,7 +5006,7 @@ declare namespace monaco.languages { /** * The highlight kind, default is [text](#DocumentHighlightKind.Text). */ - kind: DocumentHighlightKind; + kind?: DocumentHighlightKind; } /** @@ -5167,7 +5205,6 @@ declare namespace monaco.languages { * the formatting-feature. */ export interface DocumentFormattingEditProvider { - displayName?: string; /** * Provide formatting edits for a whole document. */ @@ -5179,7 +5216,6 @@ declare namespace monaco.languages { * the formatting-feature. */ export interface DocumentRangeFormattingEditProvider { - displayName?: string; /** * Provide formatting edits for a range in a document. * @@ -5211,7 +5247,7 @@ declare namespace monaco.languages { */ export interface ILink { range: IRange; - url?: string; + url?: Uri | string; } /** @@ -5303,7 +5339,7 @@ declare namespace monaco.languages { /** * Provide ranges that should be selected from the given position. */ - provideSelectionRanges(model: editor.ITextModel, position: Position, token: CancellationToken): ProviderResult; + provideSelectionRanges(model: editor.ITextModel, positions: Position[], token: CancellationToken): ProviderResult; } export interface FoldingContext { diff --git a/src/vs/platform/accessibility/common/accessibility.ts b/src/vs/platform/accessibility/common/accessibility.ts new file mode 100644 index 0000000000..b267735214 --- /dev/null +++ b/src/vs/platform/accessibility/common/accessibility.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Event } from 'vs/base/common/event'; + +export const IAccessibilityService = createDecorator('accessibilityService'); + +export interface IAccessibilityService { + _serviceBrand: any; + + readonly onDidChangeAccessibilitySupport: Event; + + alwaysUnderlineAccessKeys(): Promise; + getAccessibilitySupport(): AccessibilitySupport; + setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void; +} + +export const enum AccessibilitySupport { + /** + * This should be the browser case where it is not known if a screen reader is attached or no. + */ + Unknown = 0, + + Disabled = 1, + + Enabled = 2 +} diff --git a/src/vs/platform/accessibility/common/accessibilityService.ts b/src/vs/platform/accessibility/common/accessibilityService.ts new file mode 100644 index 0000000000..f40805e770 --- /dev/null +++ b/src/vs/platform/accessibility/common/accessibilityService.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter, Event } from 'vs/base/common/event'; +import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { Disposable } from 'vs/base/common/lifecycle'; + +export class BrowserAccessibilityService extends Disposable implements IAccessibilityService { + + _serviceBrand: any; + + private _accessibilitySupport = AccessibilitySupport.Unknown; + private readonly _onDidChangeAccessibilitySupport = new Emitter(); + readonly onDidChangeAccessibilitySupport: Event = this._onDidChangeAccessibilitySupport.event; + + alwaysUnderlineAccessKeys(): Promise { + return Promise.resolve(false); + } + + setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void { + if (this._accessibilitySupport === accessibilitySupport) { + return; + } + + this._accessibilitySupport = accessibilitySupport; + this._onDidChangeAccessibilitySupport.fire(); + } + + getAccessibilitySupport(): AccessibilitySupport { + return this._accessibilitySupport; + } +} diff --git a/src/vs/platform/accessibility/node/accessibilityService.ts b/src/vs/platform/accessibility/node/accessibilityService.ts new file mode 100644 index 0000000000..78120934b6 --- /dev/null +++ b/src/vs/platform/accessibility/node/accessibilityService.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { isWindows } from 'vs/base/common/platform'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IWindowService } from 'vs/platform/windows/common/windows'; + +export class AccessibilityService implements IAccessibilityService { + _serviceBrand: any; + + private _accessibilitySupport = AccessibilitySupport.Unknown; + private readonly _onDidChangeAccessibilitySupport = new Emitter(); + readonly onDidChangeAccessibilitySupport: Event = this._onDidChangeAccessibilitySupport.event; + + constructor( + @IWindowService private readonly windowService: IWindowService + ) { } + + alwaysUnderlineAccessKeys(): Promise { + if (!isWindows) { + return Promise.resolve(false); + } + + return new Promise(async (resolve) => { + const Registry = await import('vscode-windows-registry'); + + let value; + try { + value = Registry.GetStringRegKey('HKEY_CURRENT_USER', 'Control Panel\\Accessibility\\Keyboard Preference', 'On'); + } catch { + resolve(false); + } + + resolve(value === '1'); + }); + } + + setAccessibilitySupport(accessibilitySupport: AccessibilitySupport): void { + if (this._accessibilitySupport === accessibilitySupport) { + return; + } + + this._accessibilitySupport = accessibilitySupport; + this._onDidChangeAccessibilitySupport.fire(); + } + + getAccessibilitySupport(): AccessibilitySupport { + if (this._accessibilitySupport === AccessibilitySupport.Unknown) { + const config = this.windowService.getConfiguration(); + this._accessibilitySupport = (config && config.accessibilitySupport) ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled; + + } + + return this._accessibilitySupport; + } +} diff --git a/src/vs/platform/actions/browser/menuItemActionItem.ts b/src/vs/platform/actions/browser/menuItemActionItem.ts index ff839c0442..9e47d188b9 100644 --- a/src/vs/platform/actions/browser/menuItemActionItem.ts +++ b/src/vs/platform/actions/browser/menuItemActionItem.ts @@ -76,7 +76,7 @@ class AlternativeKeyEmitter extends Emitter { } } -export function fillInContextMenuActions(menu: IMenu, options: IMenuActionOptions, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, contextMenuService: IContextMenuService, isPrimaryGroup?: (group: string) => boolean): void { +export function fillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, contextMenuService: IContextMenuService, isPrimaryGroup?: (group: string) => boolean): void { const groups = menu.getActions(options); const getAlternativeActions = AlternativeKeyEmitter.getInstance(contextMenuService).isPressed; diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 51f4a1ba88..ca9bb2031b 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -6,9 +6,9 @@ import { Action } from 'vs/base/common/actions'; import { SyncDescriptor0, createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IConstructorSignature2, createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService, ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -65,6 +65,7 @@ export const enum MenuId { DebugConsoleContext, DebugVariablesContext, DebugWatchContext, + DebugToolbar, EditorContext, EditorTitle, EditorTitleContext, @@ -284,13 +285,13 @@ export class SyncActionDescriptor { private _descriptor: SyncDescriptor0; private _id: string; - private _label: string; + private _label?: string; private _keybindings: IKeybindings | undefined; private _keybindingContext: ContextKeyExpr | undefined; private _keybindingWeight: number | undefined; constructor(ctor: IConstructorSignature2, - id: string, label: string, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpr, keybindingWeight?: number + id: string, label: string | undefined, keybindings?: IKeybindings, keybindingContext?: ContextKeyExpr, keybindingWeight?: number ) { this._id = id; this._label = label; @@ -308,7 +309,7 @@ export class SyncActionDescriptor { return this._id; } - public get label(): string { + public get label(): string | undefined { return this._label; } @@ -324,3 +325,63 @@ export class SyncActionDescriptor { return this._keybindingWeight; } } + + +export interface IActionDescriptor { + id: string; + handler: ICommandHandler; + + // ICommandUI + title?: ILocalizedString; + category?: string; + f1?: boolean; + + // + menu?: { + menuId: MenuId, + when?: ContextKeyExpr; + group?: string; + }; + + // + keybinding?: { + when?: ContextKeyExpr; + weight?: number; + keys: IKeybindings; + }; +} + + +export function registerAction(desc: IActionDescriptor) { + + const { id, handler, title, category, menu, keybinding } = desc; + + // 1) register as command + CommandsRegistry.registerCommand(id, handler); + + // 2) menus + if (menu && title) { + let command = { id, title, category }; + let { menuId, when, group } = menu; + MenuRegistry.appendMenuItem(menuId, { + command, + when, + group + }); + } + + // 3) keybindings + if (keybinding) { + let { when, weight, keys } = keybinding; + KeybindingsRegistry.registerKeybindingRule({ + id, + when, + weight: weight || 0, + primary: keys.primary, + secondary: keys.secondary, + linux: keys.linux, + mac: keys.mac, + win: keys.win + }); + } +} diff --git a/src/vs/platform/backup/common/backup.ts b/src/vs/platform/backup/common/backup.ts index 1131ed880f..ee4856727f 100644 --- a/src/vs/platform/backup/common/backup.ts +++ b/src/vs/platform/backup/common/backup.ts @@ -7,14 +7,17 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; +export interface ISerializedWorkspace { id: string; configURIPath: string; remoteAuthority?: string; } + export interface IBackupWorkspacesFormat { - rootWorkspaces: IWorkspaceIdentifier[]; + rootURIWorkspaces: ISerializedWorkspace[]; folderURIWorkspaces: string[]; emptyWorkspaceInfos: IEmptyWindowBackupInfo[]; // deprecated folderWorkspaces?: string[]; // use folderURIWorkspaces instead emptyWorkspaces?: string[]; + rootWorkspaces?: { id: string, configPath: string }[]; // use rootURIWorkspaces instead } export const IBackupMainService = createDecorator('backupMainService'); @@ -24,18 +27,23 @@ export interface IEmptyWindowBackupInfo { remoteAuthority?: string; } +export interface IWorkspaceBackupInfo { + workspace: IWorkspaceIdentifier; + remoteAuthority?: string; +} + export interface IBackupMainService { _serviceBrand: any; isHotExitEnabled(): boolean; - getWorkspaceBackups(): IWorkspaceIdentifier[]; + getWorkspaceBackups(): IWorkspaceBackupInfo[]; getFolderBackupPaths(): URI[]; getEmptyWindowBackupPaths(): IEmptyWindowBackupInfo[]; - registerWorkspaceBackupSync(workspace: IWorkspaceIdentifier, migrateFrom?: string): string; + registerWorkspaceBackupSync(workspace: IWorkspaceBackupInfo, migrateFrom?: string): string; registerFolderBackupSync(folderUri: URI): string; - registerEmptyWindowBackupSync(backupInfo: IEmptyWindowBackupInfo): string; + registerEmptyWindowBackupSync(backupFolder?: string, remoteAuthority?: string): string; unregisterWorkspaceBackupSync(workspace: IWorkspaceIdentifier): void; unregisterFolderBackupSync(folderUri: URI): void; diff --git a/src/vs/platform/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts index 1b22f3642a..01ea0eb0f5 100644 --- a/src/vs/platform/backup/electron-main/backupMainService.ts +++ b/src/vs/platform/backup/electron-main/backupMainService.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; -import * as path from 'path'; import * as crypto from 'crypto'; +import * as path from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import { writeFileAndFlushSync } from 'vs/base/node/extfs'; import * as arrays from 'vs/base/common/arrays'; -import { IBackupMainService, IBackupWorkspacesFormat, IEmptyWindowBackupInfo } from 'vs/platform/backup/common/backup'; +import { IBackupMainService, IBackupWorkspacesFormat, IEmptyWindowBackupInfo, IWorkspaceBackupInfo } from 'vs/platform/backup/common/backup'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFilesConfiguration, HotExitConfiguration } from 'vs/platform/files/common/files'; @@ -17,7 +17,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; import { isEqual as areResourcesEquals, getComparisonKey, hasToIgnoreCase } from 'vs/base/common/resources'; -import { isEqual } from 'vs/base/common/paths'; +import { isEqual } from 'vs/base/common/extpath'; import { Schemas } from 'vs/base/common/network'; import { writeFile, readFile, readdir, exists, del, rename } from 'vs/base/node/pfs'; @@ -28,7 +28,7 @@ export class BackupMainService implements IBackupMainService { protected backupHome: string; protected workspacesJsonPath: string; - private rootWorkspaces: IWorkspaceIdentifier[]; + private rootWorkspaces: IWorkspaceBackupInfo[]; private folderWorkspaces: URI[]; private emptyWorkspaces: IEmptyWindowBackupInfo[]; @@ -60,7 +60,17 @@ export class BackupMainService implements IBackupMainService { } // read workspace backups - this.rootWorkspaces = await this.validateWorkspaces(backups.rootWorkspaces); + let rootWorkspaces: IWorkspaceBackupInfo[] = []; + try { + if (Array.isArray(backups.rootURIWorkspaces)) { + rootWorkspaces = backups.rootURIWorkspaces.map(f => ({ workspace: { id: f.id, configPath: URI.parse(f.configURIPath) }, remoteAuthority: f.remoteAuthority })); + } else if (Array.isArray(backups.rootWorkspaces)) { + rootWorkspaces = backups.rootWorkspaces.map(f => ({ workspace: { id: f.id, configPath: URI.file(f.configPath) } })); + } + } catch (e) { + // ignore URI parsing exceptions + } + this.rootWorkspaces = await this.validateWorkspaces(rootWorkspaces); // read folder backups let workspaceFolders: URI[] = []; @@ -90,7 +100,7 @@ export class BackupMainService implements IBackupMainService { await this.save(); } - getWorkspaceBackups(): IWorkspaceIdentifier[] { + getWorkspaceBackups(): IWorkspaceBackupInfo[] { if (this.isHotExitOnExitAndWindowClose()) { // Only non-folder windows are restored on main process launch when // hot exit is configured as onExitAndWindowClose. @@ -128,13 +138,13 @@ export class BackupMainService implements IBackupMainService { return this.emptyWorkspaces.slice(0); // return a copy } - registerWorkspaceBackupSync(workspace: IWorkspaceIdentifier, migrateFrom?: string): string { - if (!this.rootWorkspaces.some(w => w.id === workspace.id)) { - this.rootWorkspaces.push(workspace); + registerWorkspaceBackupSync(workspaceInfo: IWorkspaceBackupInfo, migrateFrom?: string): string { + if (!this.rootWorkspaces.some(w => workspaceInfo.workspace.id === w.workspace.id)) { + this.rootWorkspaces.push(workspaceInfo); this.saveSync(); } - const backupPath = this.getBackupPath(workspace.id); + const backupPath = this.getBackupPath(workspaceInfo.workspace.id); if (migrateFrom) { this.moveBackupFolderSync(backupPath, migrateFrom); @@ -178,7 +188,8 @@ export class BackupMainService implements IBackupMainService { } unregisterWorkspaceBackupSync(workspace: IWorkspaceIdentifier): void { - let index = arrays.firstIndex(this.rootWorkspaces, w => w.id === workspace.id); + const id = workspace.id; + let index = arrays.firstIndex(this.rootWorkspaces, w => w.workspace.id === id); if (index !== -1) { this.rootWorkspaces.splice(index, 1); this.saveSync(); @@ -202,16 +213,14 @@ export class BackupMainService implements IBackupMainService { } } - registerEmptyWindowBackupSync(backupInfo: IEmptyWindowBackupInfo): string { - let backupFolder = backupInfo.backupFolder; - let remoteAuthority = backupInfo.remoteAuthority; + registerEmptyWindowBackupSync(backupFolder?: string, remoteAuthority?: string): string { // Generate a new folder if this is a new empty workspace if (!backupFolder) { backupFolder = this.getRandomEmptyWindowId(); } - if (!this.emptyWorkspaces.some(w => isEqual(w.backupFolder, backupFolder, !platform.isLinux))) { + if (!this.emptyWorkspaces.some(w => !!w.backupFolder && isEqual(w.backupFolder, backupFolder!, !platform.isLinux))) { this.emptyWorkspaces.push({ backupFolder, remoteAuthority }); this.saveSync(); } @@ -220,7 +229,7 @@ export class BackupMainService implements IBackupMainService { } unregisterEmptyWindowBackupSync(backupFolder: string): void { - let index = arrays.firstIndex(this.emptyWorkspaces, w => isEqual(w.backupFolder, backupFolder, !platform.isLinux)); + let index = arrays.firstIndex(this.emptyWorkspaces, w => !!w.backupFolder && isEqual(w.backupFolder, backupFolder, !platform.isLinux)); if (index !== -1) { this.emptyWorkspaces.splice(index, 1); this.saveSync(); @@ -231,16 +240,17 @@ export class BackupMainService implements IBackupMainService { return path.join(this.backupHome, oldFolderHash); } - private async validateWorkspaces(rootWorkspaces: IWorkspaceIdentifier[]): Promise { + private async validateWorkspaces(rootWorkspaces: IWorkspaceBackupInfo[]): Promise { if (!Array.isArray(rootWorkspaces)) { return []; } const seenIds: { [id: string]: boolean } = Object.create(null); - const result: IWorkspaceIdentifier[] = []; + const result: IWorkspaceBackupInfo[] = []; // Validate Workspaces - for (let workspace of rootWorkspaces) { + for (let workspaceInfo of rootWorkspaces) { + const workspace = workspaceInfo.workspace; if (!isWorkspaceIdentifier(workspace)) { return []; // wrong format, skip all entries } @@ -253,8 +263,8 @@ export class BackupMainService implements IBackupMainService { // If the workspace has no backups, ignore it if (hasBackups) { - if (await exists(workspace.configPath)) { - result.push(workspace); + if (workspace.configPath.scheme !== Schemas.file || await exists(workspace.configPath.fsPath)) { + result.push(workspaceInfo); } else { // If the workspace has backups, but the target workspace is missing, convert backups to empty ones await this.convertToEmptyWindowBackup(backupPath); @@ -344,7 +354,7 @@ export class BackupMainService implements IBackupMainService { // New empty window backup let newBackupFolder = this.getRandomEmptyWindowId(); - while (this.emptyWorkspaces.some(w => isEqual(w.backupFolder, newBackupFolder, platform.isLinux))) { + while (this.emptyWorkspaces.some(w => !!w.backupFolder && isEqual(w.backupFolder, newBackupFolder, platform.isLinux))) { newBackupFolder = this.getRandomEmptyWindowId(); } @@ -365,7 +375,7 @@ export class BackupMainService implements IBackupMainService { // New empty window backup let newBackupFolder = this.getRandomEmptyWindowId(); - while (this.emptyWorkspaces.some(w => isEqual(w.backupFolder, newBackupFolder, platform.isLinux))) { + while (this.emptyWorkspaces.some(w => !!w.backupFolder && isEqual(w.backupFolder, newBackupFolder, platform.isLinux))) { newBackupFolder = this.getRandomEmptyWindowId(); } @@ -421,7 +431,7 @@ export class BackupMainService implements IBackupMainService { private serializeBackups(): IBackupWorkspacesFormat { return { - rootWorkspaces: this.rootWorkspaces, + rootURIWorkspaces: this.rootWorkspaces.map(f => ({ id: f.workspace.id, configURIPath: f.workspace.configPath.toString(), remoteAuthority: f.remoteAuthority })), folderURIWorkspaces: this.folderWorkspaces.map(f => f.toString()), emptyWorkspaceInfos: this.emptyWorkspaces, emptyWorkspaces: this.emptyWorkspaces.map(info => info.backupFolder) diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index c77548a28c..63b75b1f8b 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -7,19 +7,19 @@ import * as assert from 'assert'; import * as platform from 'vs/base/common/platform'; import * as fs from 'fs'; import * as os from 'os'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; -import { URI as Uri } from 'vs/base/common/uri'; +import { URI as Uri, URI } from 'vs/base/common/uri'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { parseArgs } from 'vs/platform/environment/node/argv'; import { BackupMainService } from 'vs/platform/backup/electron-main/backupMainService'; -import { IBackupWorkspacesFormat } from 'vs/platform/backup/common/backup'; +import { IBackupWorkspacesFormat, ISerializedWorkspace, IWorkspaceBackupInfo } from 'vs/platform/backup/common/backup'; import { HotExitConfiguration } from 'vs/platform/files/common/files'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { ConsoleLogMainService } from 'vs/platform/log/common/log'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { createHash } from 'crypto'; -import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices'; +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { Schemas } from 'vs/base/common/network'; suite('BackupMainService', () => { @@ -60,7 +60,24 @@ suite('BackupMainService', () => { function toWorkspace(path: string): IWorkspaceIdentifier { return { id: createHash('md5').update(sanitizePath(path)).digest('hex'), - configPath: path + configPath: URI.file(path) + }; + } + + function toWorkspaceBackupInfo(path: string, remoteAuthority?: string): IWorkspaceBackupInfo { + return { + workspace: { + id: createHash('md5').update(sanitizePath(path)).digest('hex'), + configPath: URI.file(path) + }, + remoteAuthority + }; + } + + function toSerializedWorkspace(ws: IWorkspaceIdentifier): ISerializedWorkspace { + return { + id: ws.id, + configURIPath: ws.configPath.toString() }; } @@ -73,8 +90,8 @@ suite('BackupMainService', () => { } async function ensureWorkspaceExists(workspace: IWorkspaceIdentifier): Promise { - if (!fs.existsSync(workspace.configPath)) { - await pfs.writeFile(workspace.configPath, 'Hello'); + if (!fs.existsSync(workspace.configPath.fsPath)) { + await pfs.writeFile(workspace.configPath.fsPath, 'Hello'); } const backupFolder = service.toBackupPath(workspace.id); await createBackupFolder(backupFolder); @@ -168,16 +185,16 @@ suite('BackupMainService', () => { this.timeout(1000 * 10); // increase timeout for this test // 1) backup workspace path does not exist - service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath)); - service.registerWorkspaceBackupSync(toWorkspace(barFile.fsPath)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); await service.initialize(); assert.deepEqual(service.getWorkspaceBackups(), []); // 2) backup workspace path exists with empty contents within fs.mkdirSync(service.toBackupPath(fooFile)); fs.mkdirSync(service.toBackupPath(barFile)); - service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath)); - service.registerWorkspaceBackupSync(toWorkspace(barFile.fsPath)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); await service.initialize(); assert.deepEqual(service.getWorkspaceBackups(), []); assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); @@ -188,8 +205,8 @@ suite('BackupMainService', () => { fs.mkdirSync(service.toBackupPath(barFile)); fs.mkdirSync(path.join(service.toBackupPath(fooFile), Schemas.file)); fs.mkdirSync(path.join(service.toBackupPath(barFile), Schemas.untitled)); - service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath)); - service.registerWorkspaceBackupSync(toWorkspace(barFile.fsPath)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath)); await service.initialize(); assert.deepEqual(service.getWorkspaceBackups(), []); assert.ok(!fs.existsSync(service.toBackupPath(fooFile))); @@ -201,7 +218,7 @@ suite('BackupMainService', () => { fs.mkdirSync(service.toBackupPath(fooFile)); fs.mkdirSync(service.toBackupPath(barFile)); fs.mkdirSync(fileBackups); - service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); assert.equal(service.getWorkspaceBackups().length, 1); assert.equal(service.getEmptyWindowBackupPaths().length, 0); fs.writeFileSync(path.join(fileBackups, 'backup.txt'), ''); @@ -216,7 +233,7 @@ suite('BackupMainService', () => { fs.writeFileSync(path.join(backupPathToMigrate, 'backup.txt'), 'Some Data'); service.registerFolderBackupSync(Uri.file(backupPathToMigrate)); - const workspaceBackupPath = service.registerWorkspaceBackupSync(toWorkspace(barFile.fsPath), backupPathToMigrate); + const workspaceBackupPath = service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath), backupPathToMigrate); assert.ok(fs.existsSync(workspaceBackupPath)); assert.ok(fs.existsSync(path.join(workspaceBackupPath, 'backup.txt'))); @@ -237,7 +254,7 @@ suite('BackupMainService', () => { fs.writeFileSync(path.join(backupPathToPreserve, 'backup.txt'), 'Some Data'); service.registerFolderBackupSync(Uri.file(backupPathToPreserve)); - const workspaceBackupPath = service.registerWorkspaceBackupSync(toWorkspace(barFile.fsPath), backupPathToMigrate); + const workspaceBackupPath = service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(barFile.fsPath), backupPathToMigrate); assert.ok(fs.existsSync(workspaceBackupPath)); assert.ok(fs.existsSync(path.join(workspaceBackupPath, 'backup.txt'))); @@ -245,18 +262,14 @@ suite('BackupMainService', () => { const emptyBackups = service.getEmptyWindowBackupPaths(); assert.equal(1, emptyBackups.length); - assert.equal(1, fs.readdirSync(path.join(backupHome, emptyBackups[0].backupFolder)).length); + assert.equal(1, fs.readdirSync(path.join(backupHome, emptyBackups[0].backupFolder!)).length); }); - suite('migrate folderPath to folderURI', () => { + suite('migrate path to URI', () => { - test('migration makes sure to preserve existing backups', async () => { - if (platform.isLinux) { - return; // TODO:Martin #54483 fix tests - } - - let path1 = path.join(parentDir, 'folder1').toLowerCase(); - let path2 = path.join(parentDir, 'folder2').toUpperCase(); + test('migration folder path to URI makes sure to preserve existing backups', async () => { + let path1 = path.join(parentDir, 'folder1'); + let path2 = path.join(parentDir, 'FOLDER2'); let uri1 = Uri.file(path1); let uri2 = Uri.file(path2); @@ -290,8 +303,30 @@ suite('BackupMainService', () => { const newBackupFolder2 = service.toBackupPath(uri2); assert.ok(fs.existsSync(path.join(newBackupFolder2, Schemas.file, 'unsaved2.txt'))); }); + + test('migrate storage file', async () => { + let folderPath = path.join(parentDir, 'f1'); + ensureFolderExists(URI.file(folderPath)); + const backupFolderPath = service.toLegacyBackupPath(folderPath); + await createBackupFolder(backupFolderPath); + + let workspacePath = path.join(parentDir, 'f2.code-workspace'); + const workspace = toWorkspace(workspacePath); + await ensureWorkspaceExists(workspace); + + const workspacesJson = { rootWorkspaces: [{ id: workspace.id, configPath: workspacePath }], folderWorkspaces: [folderPath], emptyWorkspaces: [] }; + await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); + await service.initialize(); + const content = await pfs.readFile(backupWorkspacesPath, 'utf-8'); + const json = (JSON.parse(content)); + assert.deepEqual(json.folderURIWorkspaces, [URI.file(folderPath).toString()]); + assert.deepEqual(json.rootURIWorkspaces, [{ id: workspace.id, configURIPath: URI.file(workspacePath).toString() }]); + + assertEqualUris(service.getWorkspaceBackups().map(w => w.workspace.configPath), [workspace.configPath]); + }); }); + suite('loadSync', () => { test('getFolderBackupPaths() should return [] when workspaces.json doesn\'t exist', () => { assertEqualUris(service.getFolderBackupPaths(), []); @@ -387,10 +422,32 @@ suite('BackupMainService', () => { assert.deepEqual(service.getWorkspaceBackups(), []); }); + test('getWorkspaceBackups() should return [] when rootURIWorkspaces in workspaces.json is not a object array', async () => { + fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{}}'); + await service.initialize(); + assert.deepEqual(service.getWorkspaceBackups(), []); + fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{"foo": ["bar"]}}'); + await service.initialize(); + assert.deepEqual(service.getWorkspaceBackups(), []); + fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{"foo": []}}'); + await service.initialize(); + assert.deepEqual(service.getWorkspaceBackups(), []); + fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":{"foo": "bar"}}'); + await service.initialize(); + assert.deepEqual(service.getWorkspaceBackups(), []); + fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":"foo"}'); + await service.initialize(); + assert.deepEqual(service.getWorkspaceBackups(), []); + fs.writeFileSync(backupWorkspacesPath, '{"rootURIWorkspaces":1}'); + await service.initialize(); + assert.deepEqual(service.getWorkspaceBackups(), []); + }); + test('getWorkspaceBackups() should return [] when files.hotExit = "onExitAndWindowClose"', async () => { - service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath.toUpperCase())); + const upperFooPath = fooFile.fsPath.toUpperCase(); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(upperFooPath)); assert.equal(service.getWorkspaceBackups().length, 1); - assert.deepEqual(service.getWorkspaceBackups().map(r => r.configPath), [fooFile.fsPath.toUpperCase()]); + assertEqualUris(service.getWorkspaceBackups().map(r => r.workspace.configPath), [URI.file(upperFooPath)]); configService.setUserConfiguration('files.hotExit', HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE); await service.initialize(); assert.deepEqual(service.getWorkspaceBackups(), []); @@ -447,7 +504,7 @@ suite('BackupMainService', () => { await ensureFolderExists(existingTestFolder1); const workspacesJson: IBackupWorkspacesFormat = { - rootWorkspaces: [], + rootURIWorkspaces: [], folderURIWorkspaces: [existingTestFolder1.toString(), existingTestFolder1.toString()], emptyWorkspaceInfos: [] }; @@ -464,7 +521,7 @@ suite('BackupMainService', () => { await ensureFolderExists(existingTestFolder1); const workspacesJson: IBackupWorkspacesFormat = { - rootWorkspaces: [], + rootURIWorkspaces: [], folderURIWorkspaces: [existingTestFolder1.toString(), existingTestFolder1.toString().toLowerCase()], emptyWorkspaceInfos: [] }; @@ -476,18 +533,17 @@ suite('BackupMainService', () => { }); test('should ignore duplicates on Windows and Mac (root workspace)', async () => { - if (platform.isLinux) { - return; // TODO:Martin #54483 fix tests - } const workspacePath = path.join(parentDir, 'Foo.code-workspace'); + const workspacePath1 = path.join(parentDir, 'FOO.code-workspace'); + const workspacePath2 = path.join(parentDir, 'foo.code-workspace'); const workspace1 = await ensureWorkspaceExists(toWorkspace(workspacePath)); - const workspace2 = await ensureWorkspaceExists(toWorkspace(workspacePath.toUpperCase())); - const workspace3 = await ensureWorkspaceExists(toWorkspace(workspacePath.toLowerCase())); + const workspace2 = await ensureWorkspaceExists(toWorkspace(workspacePath1)); + const workspace3 = await ensureWorkspaceExists(toWorkspace(workspacePath2)); const workspacesJson: IBackupWorkspacesFormat = { - rootWorkspaces: [workspace1, workspace2, workspace3], + rootURIWorkspaces: [workspace1, workspace2, workspace3].map(toSerializedWorkspace), folderURIWorkspaces: [], emptyWorkspaceInfos: [] }; @@ -496,11 +552,11 @@ suite('BackupMainService', () => { const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json = JSON.parse(buffer); - assert.equal(json.rootWorkspaces.length, platform.isLinux ? 3 : 1); + assert.equal(json.rootURIWorkspaces.length, platform.isLinux ? 3 : 1); if (platform.isLinux) { - assert.deepEqual(json.rootWorkspaces.map(r => r.configPath), [workspacePath, workspacePath.toUpperCase(), workspacePath.toLowerCase()]); + assert.deepEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [URI.file(workspacePath).toString(), URI.file(workspacePath1).toString(), URI.file(workspacePath2).toString()]); } else { - assert.deepEqual(json.rootWorkspaces.map(r => r.configPath), [workspacePath], 'should return the first duplicated entry'); + assert.deepEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [URI.file(workspacePath).toString()], 'should return the first duplicated entry'); } }); }); @@ -516,21 +572,21 @@ suite('BackupMainService', () => { }); test('should persist paths to workspaces.json (root workspace)', async () => { - const ws1 = toWorkspace(fooFile.fsPath); + const ws1 = toWorkspaceBackupInfo(fooFile.fsPath); service.registerWorkspaceBackupSync(ws1); - const ws2 = toWorkspace(barFile.fsPath); + const ws2 = toWorkspaceBackupInfo(barFile.fsPath); service.registerWorkspaceBackupSync(ws2); - assert.deepEqual(service.getWorkspaceBackups().map(b => b.configPath), [fooFile.fsPath, barFile.fsPath]); - assert.equal(ws1.id, service.getWorkspaceBackups()[0].id); - assert.equal(ws2.id, service.getWorkspaceBackups()[1].id); + assertEqualUris(service.getWorkspaceBackups().map(b => b.workspace.configPath), [fooFile, barFile]); + assert.equal(ws1.workspace.id, service.getWorkspaceBackups()[0].workspace.id); + assert.equal(ws2.workspace.id, service.getWorkspaceBackups()[1].workspace.id); const buffer = await pfs.readFile(backupWorkspacesPath, 'utf-8'); const json = JSON.parse(buffer); - assert.deepEqual(json.rootWorkspaces.map(b => b.configPath), [fooFile.fsPath, barFile.fsPath]); - assert.equal(ws1.id, json.rootWorkspaces[0].id); - assert.equal(ws2.id, json.rootWorkspaces[1].id); + assert.deepEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [fooFile.toString(), barFile.toString()]); + assert.equal(ws1.workspace.id, json.rootURIWorkspaces[0].id); + assert.equal(ws2.workspace.id, json.rootURIWorkspaces[1].id); }); }); @@ -544,11 +600,12 @@ suite('BackupMainService', () => { }); test('should always store the workspace path in workspaces.json using the case given, regardless of whether the file system is case-sensitive (root workspace)', () => { - service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath.toUpperCase())); - assert.deepEqual(service.getWorkspaceBackups().map(b => b.configPath), [fooFile.fsPath.toUpperCase()]); + const upperFooPath = fooFile.fsPath.toUpperCase(); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(upperFooPath)); + assertEqualUris(service.getWorkspaceBackups().map(b => b.workspace.configPath), [URI.file(upperFooPath)]); return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => { const json = JSON.parse(buffer); - assert.deepEqual(json.rootWorkspaces.map(b => b.configPath), [fooFile.fsPath.toUpperCase()]); + assert.deepEqual(json.rootURIWorkspaces.map(b => b.configURIPath), [URI.file(upperFooPath).toString()]); }); }); @@ -569,25 +626,25 @@ suite('BackupMainService', () => { }); test('should remove folder workspaces from workspaces.json (root workspace)', () => { - const ws1 = toWorkspace(fooFile.fsPath); + const ws1 = toWorkspaceBackupInfo(fooFile.fsPath); service.registerWorkspaceBackupSync(ws1); - const ws2 = toWorkspace(barFile.fsPath); + const ws2 = toWorkspaceBackupInfo(barFile.fsPath); service.registerWorkspaceBackupSync(ws2); - service.unregisterWorkspaceBackupSync(ws1); + service.unregisterWorkspaceBackupSync(ws1.workspace); return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => { const json = JSON.parse(buffer); - assert.deepEqual(json.rootWorkspaces.map(r => r.configPath), [barFile.fsPath]); - service.unregisterWorkspaceBackupSync(ws2); + assert.deepEqual(json.rootURIWorkspaces.map(r => r.configURIPath), [barFile.toString()]); + service.unregisterWorkspaceBackupSync(ws2.workspace); return pfs.readFile(backupWorkspacesPath, 'utf-8').then(content => { const json2 = JSON.parse(content); - assert.deepEqual(json2.rootWorkspaces, []); + assert.deepEqual(json2.rootURIWorkspaces, []); }); }); }); test('should remove empty workspaces from workspaces.json', () => { - service.registerEmptyWindowBackupSync({ backupFolder: 'foo' }); - service.registerEmptyWindowBackupSync({ backupFolder: 'bar' }); + service.registerEmptyWindowBackupSync('foo'); + service.registerEmptyWindowBackupSync('bar'); service.unregisterEmptyWindowBackupSync('foo'); return pfs.readFile(backupWorkspacesPath, 'utf-8').then(buffer => { const json = JSON.parse(buffer); @@ -604,7 +661,7 @@ suite('BackupMainService', () => { await ensureFolderExists(existingTestFolder1); // make sure backup folder exists, so the folder is not removed on loadSync - const workspacesJson: IBackupWorkspacesFormat = { rootWorkspaces: [], folderURIWorkspaces: [existingTestFolder1.toString()], emptyWorkspaceInfos: [] }; + const workspacesJson: IBackupWorkspacesFormat = { rootURIWorkspaces: [], folderURIWorkspaces: [existingTestFolder1.toString()], emptyWorkspaceInfos: [] }; await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); await service.initialize(); service.unregisterFolderBackupSync(barFile); @@ -646,8 +703,8 @@ suite('BackupMainService', () => { }); test('should handle case insensitive paths properly (registerWindowForBackupsSync) (root workspace)', () => { - service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath)); - service.registerWorkspaceBackupSync(toWorkspace(fooFile.fsPath.toUpperCase())); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath)); + service.registerWorkspaceBackupSync(toWorkspaceBackupInfo(fooFile.fsPath.toUpperCase())); if (platform.isLinux) { assert.equal(service.getWorkspaceBackups().length, 2); diff --git a/src/vs/platform/widget/browser/contextScopedHistoryWidget.ts b/src/vs/platform/browser/contextScopedHistoryWidget.ts similarity index 82% rename from src/vs/platform/widget/browser/contextScopedHistoryWidget.ts rename to src/vs/platform/browser/contextScopedHistoryWidget.ts index b6abb4af81..3e36c8c5ca 100644 --- a/src/vs/platform/widget/browser/contextScopedHistoryWidget.ts +++ b/src/vs/platform/browser/contextScopedHistoryWidget.ts @@ -3,11 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IContextKeyService, ContextKeyDefinedExpr, ContextKeyExpr, ContextKeyAndExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, ContextKeyDefinedExpr, ContextKeyExpr, ContextKeyAndExpr, ContextKeyEqualsExpr, RawContextKey, IContextKey, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; import { HistoryInputBox, IHistoryInputOptions } from 'vs/base/browser/ui/inputbox/inputBox'; import { FindInput, IFindInputOptions } from 'vs/base/browser/ui/findinput/findInput'; import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; -import { IContextScopedWidget, getContextScopedWidget, createWidgetScopedContextKeyService, bindContextScopedWidget } from 'vs/platform/widget/common/contextScopedWidget'; import { IHistoryNavigationWidget } from 'vs/base/browser/history'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; @@ -15,10 +14,24 @@ import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; export const HistoryNavigationWidgetContext = 'historyNavigationWidget'; export const HistoryNavigationEnablementContext = 'historyNavigationEnabled'; -export interface IContextScopedHistoryNavigationWidget extends IContextScopedWidget { +function bindContextScopedWidget(contextKeyService: IContextKeyService, widget: IContextScopedWidget, contextKey: string): void { + new RawContextKey(contextKey, widget).bindTo(contextKeyService); +} +function createWidgetScopedContextKeyService(contextKeyService: IContextKeyService, widget: IContextScopedWidget): IContextKeyService { + return contextKeyService.createScoped(widget.target); +} + +function getContextScopedWidget(contextKeyService: IContextKeyService, contextKey: string): T | undefined { + return contextKeyService.getContext(document.activeElement).getValue(contextKey); +} + +interface IContextScopedWidget { + readonly target: IContextKeyServiceTarget; +} + +interface IContextScopedHistoryNavigationWidget extends IContextScopedWidget { historyNavigator: IHistoryNavigationWidget; - } export function createAndBindHistoryNavigationWidgetScopedContextKeyService(contextKeyService: IContextKeyService, widget: IContextScopedHistoryNavigationWidget): { scopedContextKeyService: IContextKeyService, historyNavigationEnablement: IContextKey } { diff --git a/src/vs/platform/clipboard/common/clipboardService.ts b/src/vs/platform/clipboard/common/clipboardService.ts index 77e05509d6..cb62264efd 100644 --- a/src/vs/platform/clipboard/common/clipboardService.ts +++ b/src/vs/platform/clipboard/common/clipboardService.ts @@ -15,12 +15,12 @@ export interface IClipboardService { /** * Writes text to the system clipboard. */ - writeText(text: string): void; + writeText(text: string, type?: string): void; /** * Reads the content of the clipboard in plain text */ - readText(): string; + readText(type?: string): string; /** * Reads text from the system find pasteboard. diff --git a/src/vs/platform/clipboard/electron-browser/clipboardService.ts b/src/vs/platform/clipboard/electron-browser/clipboardService.ts index 9985118fef..ffcf5c600f 100644 --- a/src/vs/platform/clipboard/electron-browser/clipboardService.ts +++ b/src/vs/platform/clipboard/electron-browser/clipboardService.ts @@ -10,20 +10,19 @@ import { isMacintosh } from 'vs/base/common/platform'; export class ClipboardService implements IClipboardService { - // Clipboard format for files - private static FILE_FORMAT = 'code/file-list'; + private static FILE_FORMAT = 'code/file-list'; // Clipboard format for files _serviceBrand: any; - public writeText(text: string): void { - clipboard.writeText(text); + writeText(text: string, type?: string): void { + clipboard.writeText(text, type); } - public readText(): string { - return clipboard.readText(); + readText(type?: string): string { + return clipboard.readText(type); } - public readFindText(): string { + readFindText(): string { if (isMacintosh) { return clipboard.readFindText(); } @@ -31,23 +30,23 @@ export class ClipboardService implements IClipboardService { return ''; } - public writeFindText(text: string): void { + writeFindText(text: string): void { if (isMacintosh) { clipboard.writeFindText(text); } } - public writeResources(resources: URI[]): void { + writeResources(resources: URI[]): void { if (resources.length) { clipboard.writeBuffer(ClipboardService.FILE_FORMAT, this.resourcesToBuffer(resources)); } } - public readResources(): URI[] { + readResources(): URI[] { return this.bufferToResources(clipboard.readBuffer(ClipboardService.FILE_FORMAT)); } - public hasResources(): boolean { + hasResources(): boolean { return clipboard.has(ClipboardService.FILE_FORMAT); } diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index d86e9f0335..693d88f021 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -8,6 +8,7 @@ import { TypeConstraint, validateConstraints } from 'vs/base/common/types'; import { ServicesAccessor, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; import { LinkedList } from 'vs/base/common/linkedList'; +import { IJSONSchema } from 'vs/base/common/jsonSchema'; export const ICommandService = createDecorator('commandService'); @@ -37,7 +38,7 @@ export interface ICommand { export interface ICommandHandlerDescription { description: string; - args: { name: string; description?: string; constraint?: TypeConstraint; }[]; + args: { name: string; description?: string; constraint?: TypeConstraint; schema?: IJSONSchema; }[]; returns?: string; } @@ -132,7 +133,7 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR export const NullCommandService: ICommandService = { _serviceBrand: undefined, onWillExecuteCommand: () => ({ dispose: () => { } }), - executeCommand() { - return Promise.resolve(undefined); + executeCommand() { + return Promise.resolve(undefined); } }; diff --git a/src/vs/platform/configuration/common/configuration.ts b/src/vs/platform/configuration/common/configuration.ts index a26a4a0739..c6eb1d723a 100644 --- a/src/vs/platform/configuration/common/configuration.ts +++ b/src/vs/platform/configuration/common/configuration.ts @@ -83,8 +83,7 @@ export interface IConfigurationService { updateValue(key: string, value: any, target: ConfigurationTarget): Promise; updateValue(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget, donotNotifyError?: boolean): Promise; - reloadConfiguration(): Promise; - reloadConfiguration(folder: IWorkspaceFolder): Promise; + reloadConfiguration(folder?: IWorkspaceFolder): Promise; inspect(key: string, overrides?: IConfigurationOverrides): { default: T, @@ -280,3 +279,16 @@ export function overrideIdentifierFromKey(key: string): string { export function keyFromOverrideIdentifier(overrideIdentifier: string): string { return `[${overrideIdentifier}]`; } + +export function getMigratedSettingValue(configurationService: IConfigurationService, currentSettingName: string, legacySettingName: string): T { + const setting = configurationService.inspect(currentSettingName); + const legacySetting = configurationService.inspect(legacySettingName); + + if (typeof setting.user !== 'undefined' || typeof setting.workspace !== 'undefined' || typeof setting.workspaceFolder !== 'undefined') { + return setting.value; + } else if (typeof legacySetting.user !== 'undefined' || typeof legacySetting.workspace !== 'undefined' || typeof legacySetting.workspaceFolder !== 'undefined') { + return legacySetting.value; + } else { + return setting.default; + } +} diff --git a/src/vs/platform/configuration/common/configurationModels.ts b/src/vs/platform/configuration/common/configurationModels.ts index a234aa7981..edff80c3b4 100644 --- a/src/vs/platform/configuration/common/configurationModels.ts +++ b/src/vs/platform/configuration/common/configurationModels.ts @@ -292,7 +292,7 @@ export class Configuration { private _freeze: boolean = true) { } - getValue(section: string | undefined, overrides: IConfigurationOverrides, workspace: Workspace | null): any { + getValue(section: string | undefined, overrides: IConfigurationOverrides, workspace: Workspace | undefined): any { const consolidateConfigurationModel = this.getConsolidateConfigurationModel(overrides, workspace); return consolidateConfigurationModel.getValue(section); } @@ -320,7 +320,7 @@ export class Configuration { } } - inspect(key: string, overrides: IConfigurationOverrides, workspace: Workspace | null): { + inspect(key: string, overrides: IConfigurationOverrides, workspace: Workspace | undefined): { default: C, user: C, workspace?: C, @@ -336,12 +336,12 @@ export class Configuration { user: overrides.overrideIdentifier ? this._userConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._userConfiguration.freeze().getValue(key), workspace: workspace ? overrides.overrideIdentifier ? this._workspaceConfiguration.freeze().override(overrides.overrideIdentifier).getValue(key) : this._workspaceConfiguration.freeze().getValue(key) : undefined, //Check on workspace exists or not because _workspaceConfiguration is never null workspaceFolder: folderConfigurationModel ? overrides.overrideIdentifier ? folderConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : folderConfigurationModel.freeze().getValue(key) : undefined, - memory: overrides.overrideIdentifier ? memoryConfigurationModel.freeze().override(overrides.overrideIdentifier).getValue(key) : memoryConfigurationModel.freeze().getValue(key), + memory: overrides.overrideIdentifier ? memoryConfigurationModel.override(overrides.overrideIdentifier).getValue(key) : memoryConfigurationModel.getValue(key), value: consolidateConfigurationModel.getValue(key) }; } - keys(workspace: Workspace | null): { + keys(workspace: Workspace | undefined): { default: string[]; user: string[]; workspace: string[]; @@ -400,12 +400,12 @@ export class Configuration { return this._folderConfigurations; } - private getConsolidateConfigurationModel(overrides: IConfigurationOverrides, workspace: Workspace | null): ConfigurationModel { + private getConsolidateConfigurationModel(overrides: IConfigurationOverrides, workspace: Workspace | undefined): ConfigurationModel { let configurationModel = this.getConsolidatedConfigurationModelForResource(overrides, workspace); return overrides.overrideIdentifier ? configurationModel.override(overrides.overrideIdentifier) : configurationModel; } - private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides, workspace: Workspace | null): ConfigurationModel { + private getConsolidatedConfigurationModelForResource({ resource }: IConfigurationOverrides, workspace: Workspace | undefined): ConfigurationModel { let consolidateConfiguration = this.getWorkspaceConsolidatedConfiguration(); if (workspace && resource) { @@ -450,7 +450,7 @@ export class Configuration { return folderConsolidatedConfiguration; } - private getFolderConfigurationModelForResource(resource: URI | null | undefined, workspace: Workspace | null): ConfigurationModel | null { + private getFolderConfigurationModelForResource(resource: URI | null | undefined, workspace: Workspace | undefined): ConfigurationModel | null { if (workspace && resource) { const root = workspace.getFolder(resource); if (root) { @@ -486,7 +486,7 @@ export class Configuration { }; } - allKeys(workspace: Workspace): string[] { + allKeys(workspace: Workspace | undefined): string[] { let keys = this.keys(workspace); let all = [...keys.default]; const addKeys = (keys) => { diff --git a/src/vs/platform/configuration/node/configurationService.ts b/src/vs/platform/configuration/node/configurationService.ts index 0316296893..ff86837d30 100644 --- a/src/vs/platform/configuration/node/configurationService.ts +++ b/src/vs/platform/configuration/node/configurationService.ts @@ -55,7 +55,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe getValue(arg1?: any, arg2?: any): any { const section = typeof arg1 === 'string' ? arg1 : undefined; const overrides = isConfigurationOverrides(arg1) ? arg1 : isConfigurationOverrides(arg2) ? arg2 : {}; - return this.configuration.getValue(section, overrides, null); + return this.configuration.getValue(section, overrides, undefined); } updateValue(key: string, value: any): Promise; @@ -73,7 +73,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe workspaceFolder?: T value: T } { - return this.configuration.inspect(key, {}, null); + return this.configuration.inspect(key, {}, undefined); } keys(): { @@ -82,7 +82,7 @@ export class ConfigurationService extends Disposable implements IConfigurationSe workspace: string[]; workspaceFolder: string[]; } { - return this.configuration.keys(null); + return this.configuration.keys(undefined); } reloadConfiguration(folder?: IWorkspaceFolder): Promise { diff --git a/src/vs/platform/configuration/test/common/configurationModels.test.ts b/src/vs/platform/configuration/test/common/configurationModels.test.ts index 0648d82cb5..2abed07642 100644 --- a/src/vs/platform/configuration/test/common/configurationModels.test.ts +++ b/src/vs/platform/configuration/test/common/configurationModels.test.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, ConfigurationModelParser } from 'vs/platform/configuration/common/configurationModels'; +import { ConfigurationModel, DefaultConfigurationModel, ConfigurationChangeEvent, ConfigurationModelParser, Configuration } from 'vs/platform/configuration/common/configurationModels'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { URI } from 'vs/base/common/uri'; @@ -466,4 +466,30 @@ suite('ConfigurationChangeEvent', () => { assert.ok(actual.affectsConfiguration('[markdown]', URI.file('file2'))); }); +}); + +suite('Configuration', () => { + + test('Test update value', () => { + const parser = new ConfigurationModelParser('test'); + parser.parse(JSON.stringify({ 'a': 1 })); + const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel()); + + testObject.updateValue('a', 2); + + assert.equal(testObject.getValue('a', {}, undefined), 2); + }); + + test('Test update value after inspect', () => { + const parser = new ConfigurationModelParser('test'); + parser.parse(JSON.stringify({ 'a': 1 })); + const testObject: Configuration = new Configuration(parser.configurationModel, new ConfigurationModel()); + + testObject.inspect('a', {}, undefined); + testObject.updateValue('a', 2); + + assert.equal(testObject.getValue('a', {}, undefined), 2); + }); + + }); \ No newline at end of file diff --git a/src/vs/platform/configuration/test/node/configurationService.test.ts b/src/vs/platform/configuration/test/node/configurationService.test.ts index ce984a9073..fbd0dfa764 100644 --- a/src/vs/platform/configuration/test/node/configurationService.test.ts +++ b/src/vs/platform/configuration/test/node/configurationService.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as os from 'os'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as fs from 'fs'; import { Registry } from 'vs/platform/registry/common/platform'; diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index 76812920ac..871b34b3d9 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -20,6 +20,14 @@ export const enum ContextKeyExprType { // } +export interface IContextKeyExprMapper { + mapDefined(key: string): ContextKeyDefinedExpr; + mapNot(key: string): ContextKeyNotExpr; + mapEquals(key: string, value: any): ContextKeyEqualsExpr; + mapNotEquals(key: string, value: any): ContextKeyNotEqualsExpr; + mapRegex(key: string, regexp: RegExp | null): ContextKeyRegexExpr; +} + export abstract class ContextKeyExpr { public static has(key: string): ContextKeyExpr { @@ -55,42 +63,42 @@ export abstract class ContextKeyExpr { } // - public static deserialize(serialized: string | null | undefined): ContextKeyExpr | null { + public static deserialize(serialized: string | null | undefined, strict: boolean = false): ContextKeyExpr | null { if (!serialized) { return null; } let pieces = serialized.split('&&'); - let result = new ContextKeyAndExpr(pieces.map(p => this._deserializeOne(p))); + let result = new ContextKeyAndExpr(pieces.map(p => this._deserializeOne(p, strict))); return result.normalize(); } - private static _deserializeOne(serializedOne: string): ContextKeyExpr { + private static _deserializeOne(serializedOne: string, strict: boolean): ContextKeyExpr { serializedOne = serializedOne.trim(); if (serializedOne.indexOf('!=') >= 0) { let pieces = serializedOne.split('!='); - return new ContextKeyNotEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1])); + return new ContextKeyNotEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); } if (serializedOne.indexOf('==') >= 0) { let pieces = serializedOne.split('=='); - return new ContextKeyEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1])); + return new ContextKeyEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); } if (serializedOne.indexOf('=~') >= 0) { let pieces = serializedOne.split('=~'); - return new ContextKeyRegexExpr(pieces[0].trim(), this._deserializeRegexValue(pieces[1])); + return new ContextKeyRegexExpr(pieces[0].trim(), this._deserializeRegexValue(pieces[1], strict)); } // {{SQL CARBON EDIT}} if (serializedOne.indexOf('>=') >= 0) { let pieces = serializedOne.split('>='); - return new ContextKeyGreaterThanEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1])); + return new ContextKeyGreaterThanEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); } if (serializedOne.indexOf('<=') >= 0) { let pieces = serializedOne.split('<='); - return new ContextKeyLessThanEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1])); + return new ContextKeyLessThanEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1], strict)); } // @@ -101,7 +109,7 @@ export abstract class ContextKeyExpr { return new ContextKeyDefinedExpr(serializedOne); } - private static _deserializeValue(serializedValue: string): any { + private static _deserializeValue(serializedValue: string, strict: boolean): any { serializedValue = serializedValue.trim(); if (serializedValue === 'true') { @@ -120,17 +128,25 @@ export abstract class ContextKeyExpr { return serializedValue; } - private static _deserializeRegexValue(serializedValue: string): RegExp | null { + private static _deserializeRegexValue(serializedValue: string, strict: boolean): RegExp | null { if (isFalsyOrWhitespace(serializedValue)) { - console.warn('missing regexp-value for =~-expression'); + if (strict) { + throw new Error('missing regexp-value for =~-expression'); + } else { + console.warn('missing regexp-value for =~-expression'); + } return null; } let start = serializedValue.indexOf('/'); let end = serializedValue.lastIndexOf('/'); if (start === end || start < 0 /* || to < 0 */) { - console.warn(`bad regexp-value '${serializedValue}', missing /-enclosure`); + if (strict) { + throw new Error(`bad regexp-value '${serializedValue}', missing /-enclosure`); + } else { + console.warn(`bad regexp-value '${serializedValue}', missing /-enclosure`); + } return null; } @@ -139,7 +155,11 @@ export abstract class ContextKeyExpr { try { return new RegExp(value, caseIgnoreFlag); } catch (e) { - console.warn(`bad regexp-value '${serializedValue}', parse error: ${e}`); + if (strict) { + throw new Error(`bad regexp-value '${serializedValue}', parse error: ${e}`); + } else { + console.warn(`bad regexp-value '${serializedValue}', parse error: ${e}`); + } return null; } } @@ -150,6 +170,7 @@ export abstract class ContextKeyExpr { public abstract normalize(): ContextKeyExpr | null; public abstract serialize(): string; public abstract keys(): string[]; + public abstract map(mapFnc: IContextKeyExprMapper): ContextKeyExpr; } function cmp(a: ContextKeyExpr, b: ContextKeyExpr): number { @@ -220,10 +241,14 @@ export class ContextKeyDefinedExpr implements ContextKeyExpr { public keys(): string[] { return [this.key]; } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + return mapFnc.mapDefined(this.key); + } } export class ContextKeyEqualsExpr implements ContextKeyExpr { - constructor(private key: string, private value: any) { + constructor(private readonly key: string, private readonly value: any) { } public getType(): ContextKeyExprType { @@ -281,6 +306,10 @@ export class ContextKeyEqualsExpr implements ContextKeyExpr { public keys(): string[] { return [this.key]; } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + return mapFnc.mapEquals(this.key, this.value); + } } export class ContextKeyNotEqualsExpr implements ContextKeyExpr { @@ -342,6 +371,10 @@ export class ContextKeyNotEqualsExpr implements ContextKeyExpr { public keys(): string[] { return [this.key]; } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + return mapFnc.mapNotEquals(this.key, this.value); + } } export class ContextKeyNotExpr implements ContextKeyExpr { @@ -384,6 +417,10 @@ export class ContextKeyNotExpr implements ContextKeyExpr { public keys(): string[] { return [this.key]; } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + return mapFnc.mapNot(this.key); + } } export class ContextKeyRegexExpr implements ContextKeyExpr { @@ -442,6 +479,10 @@ export class ContextKeyRegexExpr implements ContextKeyExpr { public keys(): string[] { return [this.key]; } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + return mapFnc.mapRegex(this.key, this.regexp); + } } export class ContextKeyAndExpr implements ContextKeyExpr { @@ -541,6 +582,10 @@ export class ContextKeyAndExpr implements ContextKeyExpr { } return result; } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + return new ContextKeyAndExpr(this.expr.map(expr => expr.map(mapFnc))); + } } // {{SQL CARBON EDIT}} @@ -592,6 +637,10 @@ export class ContextKeyGreaterThanEqualsExpr implements ContextKeyExpr { public keys(): string[] { return [this.key]; } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + return mapFnc.mapEquals(this.key, this.value); + } } // {{SQL CARBON EDIT}} @@ -643,6 +692,10 @@ export class ContextKeyLessThanEqualsExpr implements ContextKeyExpr { public keys(): string[] { return [this.key]; } + + public map(mapFnc: IContextKeyExprMapper): ContextKeyExpr { + return mapFnc.mapEquals(this.key, this.value); + } } export class RawContextKey extends ContextKeyDefinedExpr { diff --git a/src/vs/platform/workbench/common/contextkeys.ts b/src/vs/platform/contextkey/common/contextkeys.ts similarity index 64% rename from src/vs/platform/workbench/common/contextkeys.ts rename to src/vs/platform/contextkey/common/contextkeys.ts index 92a23bd899..9bda9fd729 100644 --- a/src/vs/platform/workbench/common/contextkeys.ts +++ b/src/vs/platform/contextkey/common/contextkeys.ts @@ -4,10 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { isMacintosh, isLinux, isWindows } from 'vs/base/common/platform'; export const InputFocusedContextKey = 'inputFocus'; export const InputFocusedContext = new RawContextKey(InputFocusedContextKey, false); -export const IsMacContext = new RawContextKey('isMac', isMacintosh); -export const IsLinuxContext = new RawContextKey('isLinux', isLinux); -export const IsWindowsContext = new RawContextKey('isWindows', isWindows); diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.css b/src/vs/platform/contextview/browser/contextMenuHandler.css index 998f56a0cc..58a2027bf7 100644 --- a/src/vs/platform/contextview/browser/contextMenuHandler.css +++ b/src/vs/platform/contextview/browser/contextMenuHandler.css @@ -6,3 +6,12 @@ .context-view .monaco-menu { min-width: 130px; } + +.context-view-block { + position: fixed; + left:0; + top:0; + z-index: -1; + width: 100%; + height: 100%; +} \ No newline at end of file diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.ts b/src/vs/platform/contextview/browser/contextMenuHandler.ts index 77e8053735..0b2647e628 100644 --- a/src/vs/platform/contextview/browser/contextMenuHandler.ts +++ b/src/vs/platform/contextview/browser/contextMenuHandler.ts @@ -16,25 +16,27 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextMenuDelegate } from 'vs/base/browser/contextmenu'; -import { addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, $, removeNode } from 'vs/base/browser/dom'; import { attachMenuStyler } from 'vs/platform/theme/common/styler'; import { domEvent } from 'vs/base/browser/event'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export class ContextMenuHandler { private element: HTMLElement | null; private elementDisposable: IDisposable; private menuContainerElement: HTMLElement | null; private focusToReturn: HTMLElement; + private block: HTMLElement | null; constructor( - element: HTMLElement, + private layoutService: ILayoutService, private contextViewService: IContextViewService, private telemetryService: ITelemetryService, private notificationService: INotificationService, private keybindingService: IKeybindingService, private themeService: IThemeService ) { - this.setContainer(element); + this.setContainer(this.layoutService.container); } setContainer(container: HTMLElement | null): void { @@ -73,12 +75,16 @@ export class ContextMenuHandler { container.className += ' ' + className; } + // Render invisible div to block mouse interaction in the rest of the UI + if (this.layoutService.hasWorkbench) { + this.block = container.appendChild($('.context-view-block')); + } + const menuDisposables: IDisposable[] = []; const actionRunner = delegate.actionRunner || new ActionRunner(); actionRunner.onDidBeforeRun(this.onActionRun, this, menuDisposables); actionRunner.onDidRun(this.onDidActionRun, this, menuDisposables); - menu = new Menu(container, actions, { actionItemProvider: delegate.getActionItem, context: delegate.getActionsContext ? delegate.getActionsContext() : null, @@ -106,6 +112,11 @@ export class ContextMenuHandler { delegate.onHide(!!didCancel); } + if (this.block) { + removeNode(this.block); + this.block = null; + } + if (this.focusToReturn) { this.focusToReturn.focus(); } diff --git a/src/vs/platform/contextview/browser/contextMenuService.ts b/src/vs/platform/contextview/browser/contextMenuService.ts index 2d7f8201c5..dbdb8d4f23 100644 --- a/src/vs/platform/contextview/browser/contextMenuService.ts +++ b/src/vs/platform/contextview/browser/contextMenuService.ts @@ -12,6 +12,7 @@ import { IContextMenuDelegate } from 'vs/base/browser/contextmenu'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Disposable } from 'vs/base/common/lifecycle'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export class ContextMenuService extends Disposable implements IContextMenuService { _serviceBrand: any; @@ -22,7 +23,7 @@ export class ContextMenuService extends Disposable implements IContextMenuServic private contextMenuHandler: ContextMenuHandler; constructor( - container: HTMLElement, + @ILayoutService layoutService: ILayoutService, @ITelemetryService telemetryService: ITelemetryService, @INotificationService notificationService: INotificationService, @IContextViewService contextViewService: IContextViewService, @@ -31,7 +32,7 @@ export class ContextMenuService extends Disposable implements IContextMenuServic ) { super(); - this.contextMenuHandler = this._register(new ContextMenuHandler(container, contextViewService, telemetryService, notificationService, keybindingService, themeService)); + this.contextMenuHandler = this._register(new ContextMenuHandler(layoutService, contextViewService, telemetryService, notificationService, keybindingService, themeService)); } dispose(): void { diff --git a/src/vs/platform/contextview/browser/contextViewService.ts b/src/vs/platform/contextview/browser/contextViewService.ts index 8fc7ca27cf..eb6ea4c22b 100644 --- a/src/vs/platform/contextview/browser/contextViewService.ts +++ b/src/vs/platform/contextview/browser/contextViewService.ts @@ -5,9 +5,8 @@ import { IContextViewService, IContextViewDelegate } from './contextView'; import { ContextView } from 'vs/base/browser/ui/contextview/contextview'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ILogService } from 'vs/platform/log/common/log'; import { Disposable } from 'vs/base/common/lifecycle'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export class ContextViewService extends Disposable implements IContextViewService { _serviceBrand: any; @@ -15,24 +14,23 @@ export class ContextViewService extends Disposable implements IContextViewServic private contextView: ContextView; constructor( - container: HTMLElement, - @ITelemetryService telemetryService: ITelemetryService, - @ILogService private readonly logService: ILogService + @ILayoutService readonly layoutService: ILayoutService ) { super(); - this.contextView = this._register(new ContextView(container)); + this.contextView = this._register(new ContextView(layoutService.container)); + this.layout(); + + this._register(layoutService.onLayout(() => this.layout())); } // ContextView setContainer(container: HTMLElement): void { - this.logService.trace('ContextViewService#setContainer'); this.contextView.setContainer(container); } showContextView(delegate: IContextViewDelegate): void { - this.logService.trace('ContextViewService#showContextView'); this.contextView.show(delegate); } @@ -41,7 +39,6 @@ export class ContextViewService extends Disposable implements IContextViewServic } hideContextView(data?: any): void { - this.logService.trace('ContextViewService#hideContextView'); this.contextView.hide(data); } } \ No newline at end of file diff --git a/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts b/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts index 28a89cb146..5b72e342d7 100644 --- a/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/electron-main/diagnosticsService.ts @@ -3,19 +3,19 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { WorkspaceStats, collectWorkspaceStats, collectLaunchConfigs, WorkspaceStatItem } from 'vs/base/node/stats'; import { IMainProcessInfo } from 'vs/platform/launch/electron-main/launchService'; import { ProcessItem, listProcesses } from 'vs/base/node/ps'; -import product from 'vs/platform/node/product'; -import pkg from 'vs/platform/node/package'; +import product from 'vs/platform/product/node/product'; +import pkg from 'vs/platform/product/node/package'; import * as os from 'os'; import { virtualMachineHint } from 'vs/base/node/id'; import { repeat, pad } from 'vs/base/common/strings'; import { isWindows } from 'vs/base/common/platform'; import { app } from 'electron'; -import { basename } from 'path'; +import { basename, join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { readdir, stat } from 'fs'; export const ID = 'diagnosticsService'; export const IDiagnosticsService = createDecorator(ID); @@ -109,11 +109,6 @@ export class DiagnosticsService implements IDiagnosticsService { } workspaceInfoMessages.push(`| Folder (${basename(folder)}): ${countMessage}`); workspaceInfoMessages.push(this.formatWorkspaceStats(stats)); - - const launchConfigs = await collectLaunchConfigs(folder); - if (launchConfigs.length > 0) { - workspaceInfoMessages.push(this.formatLaunchConfigs(launchConfigs)); - } })); } else { workspaceInfoMessages.push(`| Folder (${folderUri.toString()}): RPerformance stats not available.`); @@ -196,11 +191,6 @@ export class DiagnosticsService implements IDiagnosticsService { console.log(`| Folder (${basename(folder)}): ${countMessage}`); console.log(this.formatWorkspaceStats(stats)); - await collectLaunchConfigs(folder).then(launchConfigs => { - if (launchConfigs.length > 0) { - console.log(this.formatLaunchConfigs(launchConfigs)); - } - }); }).catch(error => { console.log(`| Error: Unable to collect workspace stats for folder ${folder} (${error.toString()})`); })); @@ -257,17 +247,14 @@ export class DiagnosticsService implements IDiagnosticsService { output.push(line); } - return output.join('\n'); - } - - private formatLaunchConfigs(configs: WorkspaceStatItem[]): string { - const output: string[] = []; - let line = '| Launch Configs:'; - configs.forEach(each => { - const item = each.count > 1 ? ` ${each.name}(${each.count})` : ` ${each.name}`; - line += item; - }); - output.push(line); + // if (workspaceStats.launchConfigFiles.length > 0) { + // let line = '| Launch Configs:'; + // workspaceStats.launchConfigFiles.forEach(each => { + // const item = each.count > 1 ? ` ${each.name}(${each.count})` : ` ${each.name}`; + // line += item; + // }); + // output.push(line); + // } return output.join('\n'); } @@ -318,3 +305,197 @@ export class DiagnosticsService implements IDiagnosticsService { } } } + +interface WorkspaceStatItem { + name: string; + count: number; +} + +interface WorkspaceStats { + fileTypes: WorkspaceStatItem[]; + configFiles: WorkspaceStatItem[]; + fileCount: number; + maxFilesReached: boolean; + // launchConfigFiles: WorkspaceStatItem[]; +} + +function asSortedItems(map: Map): WorkspaceStatItem[] { + const a: WorkspaceStatItem[] = []; + map.forEach((value, index) => a.push({ name: index, count: value })); + return a.sort((a, b) => b.count - a.count); +} + +// function collectLaunchConfigs(folder: string): Promise { +// const launchConfigs = new Map(); + +// const launchConfig = join(folder, '.vscode', 'launch.json'); +// return new Promise((resolve, reject) => { +// exists(launchConfig, (doesExist) => { +// if (doesExist) { +// readFile(launchConfig, (err, contents) => { +// if (err) { +// return resolve([]); +// } + +// const errors: ParseError[] = []; +// const json = parse(contents.toString(), errors); +// if (errors.length) { +// console.log(`Unable to parse ${launchConfig}`); +// return resolve([]); +// } + +// if (json['configurations']) { +// for (const each of json['configurations']) { +// const type = each['type']; +// if (type) { +// if (launchConfigs.has(type)) { +// launchConfigs.set(type, launchConfigs.get(type)! + 1); +// } else { +// launchConfigs.set(type, 1); +// } +// } +// } +// } + +// return resolve(asSortedItems(launchConfigs)); +// }); +// } else { +// return resolve([]); +// } +// }); +// }); +// } + +function collectWorkspaceStats(folder: string, filter: string[]): Promise { + const configFilePatterns = [ + { 'tag': 'grunt.js', 'pattern': /^gruntfile\.js$/i }, + { 'tag': 'gulp.js', 'pattern': /^gulpfile\.js$/i }, + { 'tag': 'tsconfig.json', 'pattern': /^tsconfig\.json$/i }, + { 'tag': 'package.json', 'pattern': /^package\.json$/i }, + { 'tag': 'jsconfig.json', 'pattern': /^jsconfig\.json$/i }, + { 'tag': 'tslint.json', 'pattern': /^tslint\.json$/i }, + { 'tag': 'eslint.json', 'pattern': /^eslint\.json$/i }, + { 'tag': 'tasks.json', 'pattern': /^tasks\.json$/i }, + { 'tag': 'launch.json', 'pattern': /^launch\.json$/i }, + { 'tag': 'settings.json', 'pattern': /^settings\.json$/i }, + { 'tag': 'webpack.config.js', 'pattern': /^webpack\.config\.js$/i }, + { 'tag': 'project.json', 'pattern': /^project\.json$/i }, + { 'tag': 'makefile', 'pattern': /^makefile$/i }, + { 'tag': 'sln', 'pattern': /^.+\.sln$/i }, + { 'tag': 'csproj', 'pattern': /^.+\.csproj$/i }, + { 'tag': 'cmake', 'pattern': /^.+\.cmake$/i } + ]; + + const fileTypes = new Map(); + const configFiles = new Map(); + + const MAX_FILES = 20000; + + function walk(dir: string, filter: string[], token, done: (allFiles: string[]) => void): void { + let results: string[] = []; + readdir(dir, async (err, files) => { + // Ignore folders that can't be read + if (err) { + return done(results); + } + + let pending = files.length; + if (pending === 0) { + return done(results); + } + + for (const file of files) { + if (token.maxReached) { + return done(results); + } + + stat(join(dir, file), (err, stats) => { + // Ignore files that can't be read + if (err) { + if (--pending === 0) { + return done(results); + } + } else { + if (stats.isDirectory()) { + if (filter.indexOf(file) === -1) { + walk(join(dir, file), filter, token, (res: string[]) => { + results = results.concat(res); + + if (--pending === 0) { + return done(results); + } + }); + } else { + if (--pending === 0) { + done(results); + } + } + } else { + if (token.count >= MAX_FILES) { + token.maxReached = true; + } + + token.count++; + results.push(file); + + if (--pending === 0) { + done(results); + } + } + } + }); + } + }); + } + + const addFileType = (fileType: string) => { + if (fileTypes.has(fileType)) { + fileTypes.set(fileType, fileTypes.get(fileType)! + 1); + } + else { + fileTypes.set(fileType, 1); + } + }; + + const addConfigFiles = (fileName: string) => { + for (const each of configFilePatterns) { + if (each.pattern.test(fileName)) { + if (configFiles.has(each.tag)) { + configFiles.set(each.tag, configFiles.get(each.tag)! + 1); + } else { + configFiles.set(each.tag, 1); + } + } + } + }; + + const acceptFile = (name: string) => { + if (name.lastIndexOf('.') >= 0) { + const suffix: string | undefined = name.split('.').pop(); + if (suffix) { + addFileType(suffix); + } + } + addConfigFiles(name); + }; + + const token: { count: number, maxReached: boolean } = { count: 0, maxReached: false }; + + return new Promise((resolve, reject) => { + walk(folder, filter, token, async (files) => { + files.forEach(acceptFile); + + // TODO@rachel commented out due to severe performance issues + // see https://github.com/Microsoft/vscode/issues/70563 + // const launchConfigs = await collectLaunchConfigs(folder); + + resolve({ + configFiles: asSortedItems(configFiles), + fileTypes: asSortedItems(fileTypes), + fileCount: token.count, + maxFilesReached: token.maxReached, + // launchConfigFiles: launchConfigs + }); + }); + }); +} \ No newline at end of file diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 1ce4732c3e..f75917be28 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -6,7 +6,7 @@ import Severity from 'vs/base/common/severity'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; -import { basename } from 'vs/base/common/paths'; +import { basename } from 'vs/base/common/resources'; import { localize } from 'vs/nls'; import { FileFilter } from 'vs/platform/windows/common/windows'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; @@ -66,6 +66,12 @@ export interface ISaveDialogOptions { * A human-readable string for the ok button */ saveLabel?: string; + + /** + * Specifies a list of schemas for the file systems the user can save to. If not specified, uses the schema of the defaultURI or, if also not specified, + * the schema of the current window. + */ + availableFileSystems?: string[]; } export interface IOpenDialogOptions { @@ -104,6 +110,12 @@ export interface IOpenDialogOptions { * like "TypeScript", and an array of extensions. */ filters?: FileFilter[]; + + /** + * Specifies a list of schemas for the file systems the user can load from. If not specified, uses the schema of the defaultURI or, if also not available, + * the schema of the current window. + */ + availableFileSystems?: string[]; } @@ -150,21 +162,21 @@ export interface IFileDialogService { /** * The default path for a new file based on previously used files. - * @param schemeFilter The scheme of the file path. + * @param schemeFilter The scheme of the file path. If no filter given, the scheme of the current window is used. */ - defaultFilePath(schemeFilter: string): URI | undefined; + defaultFilePath(schemeFilter?: string): URI | undefined; /** * The default path for a new folder based on previously used folders. - * @param schemeFilter The scheme of the folder path. + * @param schemeFilter The scheme of the folder path. If no filter given, the scheme of the current window is used. */ - defaultFolderPath(schemeFilter: string): URI | undefined; + defaultFolderPath(schemeFilter?: string): URI | undefined; /** * The default path for a new workspace based on previously used workspaces. - * @param schemeFilter The scheme of the workspace path. + * @param schemeFilter The scheme of the workspace path. If no filter given, the scheme of the current window is used. */ - defaultWorkspacePath(schemeFilter: string): URI | undefined; + defaultWorkspacePath(schemeFilter?: string): URI | undefined; /** * Shows a file-folder selection dialog and opens the selected entry. @@ -202,7 +214,7 @@ const MAX_CONFIRM_FILES = 10; export function getConfirmMessage(start: string, resourcesToConfirm: URI[]): string { const message = [start]; message.push(''); - message.push(...resourcesToConfirm.slice(0, MAX_CONFIRM_FILES).map(r => basename(r.fsPath))); + message.push(...resourcesToConfirm.slice(0, MAX_CONFIRM_FILES).map(r => basename(r))); if (resourcesToConfirm.length > MAX_CONFIRM_FILES) { if (resourcesToConfirm.length - MAX_CONFIRM_FILES === 1) { diff --git a/src/vs/platform/dialogs/node/dialogIpc.ts b/src/vs/platform/dialogs/node/dialogIpc.ts index f6095736dd..4c79dfa81d 100644 --- a/src/vs/platform/dialogs/node/dialogIpc.ts +++ b/src/vs/platform/dialogs/node/dialogIpc.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { IDialogService, IConfirmation, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; import { Event } from 'vs/base/common/event'; diff --git a/src/vs/platform/download/common/download.ts b/src/vs/platform/download/common/download.ts index 789c9cefac..c0a3e02362 100644 --- a/src/vs/platform/download/common/download.ts +++ b/src/vs/platform/download/common/download.ts @@ -13,6 +13,6 @@ export interface IDownloadService { _serviceBrand: any; - download(uri: URI, to: string, cancellationToken?: CancellationToken): Promise; + download(uri: URI, to?: string, cancellationToken?: CancellationToken): Promise; -} \ No newline at end of file +} diff --git a/src/vs/platform/download/node/downloadIpc.ts b/src/vs/platform/download/node/downloadIpc.ts index 62847f8814..e363268147 100644 --- a/src/vs/platform/download/node/downloadIpc.ts +++ b/src/vs/platform/download/node/downloadIpc.ts @@ -4,17 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as fs from 'fs'; -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event, Emitter } from 'vs/base/common/event'; import { IDownloadService } from 'vs/platform/download/common/download'; import { mkdirp } from 'vs/base/node/pfs'; import { IURITransformer } from 'vs/base/common/uriIpc'; +import { tmpdir } from 'os'; +import { generateUuid } from 'vs/base/common/uuid'; -export type UploadResponse = Buffer | string | undefined; +type UploadResponse = Buffer | string | undefined; -export function upload(uri: URI): Event { +function upload(uri: URI): Event { const stream = new Emitter(); const readstream = fs.createReadStream(uri.fsPath); readstream.on('data', data => stream.fire(data)); @@ -46,21 +48,21 @@ export class DownloadServiceChannelClient implements IDownloadService { constructor(private channel: IChannel, private getUriTransformer: () => IURITransformer) { } - download(from: URI, to: string): Promise { + download(from: URI, to: string = path.join(tmpdir(), generateUuid())): Promise { from = this.getUriTransformer().transformOutgoingURI(from); const dirName = path.dirname(to); let out: fs.WriteStream; - return new Promise((c, e) => { + return new Promise((c, e) => { return mkdirp(dirName) .then(() => { out = fs.createWriteStream(to); - out.once('close', () => c()); + out.once('close', () => c(to)); out.once('error', e); const uploadStream = this.channel.listen('upload', from); const disposable = uploadStream(result => { if (result === undefined) { disposable.dispose(); - out.end(c); + out.end(() => c(to)); } else if (Buffer.isBuffer(result)) { out.write(result); } else if (typeof result === 'string') { diff --git a/src/vs/platform/download/node/downloadService.ts b/src/vs/platform/download/node/downloadService.ts index 295cff7741..12ecc77fd6 100644 --- a/src/vs/platform/download/node/downloadService.ts +++ b/src/vs/platform/download/node/downloadService.ts @@ -10,6 +10,9 @@ import { copy } from 'vs/base/node/pfs'; import { IRequestService } from 'vs/platform/request/node/request'; import { asText, download } from 'vs/base/node/request'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { join } from 'vs/base/common/path'; +import { tmpdir } from 'os'; +import { generateUuid } from 'vs/base/common/uuid'; export class DownloadService implements IDownloadService { @@ -19,18 +22,18 @@ export class DownloadService implements IDownloadService { @IRequestService private readonly requestService: IRequestService ) { } - download(uri: URI, target: string, cancellationToken: CancellationToken = CancellationToken.None): Promise { + download(uri: URI, target: string = join(tmpdir(), generateUuid()), cancellationToken: CancellationToken = CancellationToken.None): Promise { if (uri.scheme === Schemas.file) { - return copy(uri.fsPath, target); + return copy(uri.fsPath, target).then(() => target); } const options = { type: 'GET', url: uri.toString() }; return this.requestService.request(options, cancellationToken) .then(context => { if (context.res.statusCode === 200) { - return download(target, context); + return download(target, context).then(() => target); } return asText(context) .then(message => Promise.reject(new Error(`Expected 200, got back ${context.res.statusCode} instead.\n\n${message}`))); }); } -} \ No newline at end of file +} diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts index edb131301d..ae2c035408 100644 --- a/src/vs/platform/driver/electron-browser/driver.ts +++ b/src/vs/platform/driver/electron-browser/driver.ts @@ -3,10 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IWindowDriver, IElement, WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/node/driver'; -import { IPCClient } from 'vs/base/parts/ipc/node/ipc'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { getTopLeftOffset, getClientArea } from 'vs/base/browser/dom'; import * as electron from 'electron'; import { IWindowService } from 'vs/platform/windows/common/windows'; @@ -214,25 +214,24 @@ class WindowDriver implements IWindowDriver { } } -export async function registerWindowDriver( - client: IPCClient, - windowId: number, - instantiationService: IInstantiationService -): Promise { +export async function registerWindowDriver(accessor: ServicesAccessor): Promise { + const instantiationService = accessor.get(IInstantiationService); + const mainProcessService = accessor.get(IMainProcessService); + const windowService = accessor.get(IWindowService); + const windowDriver = instantiationService.createInstance(WindowDriver); const windowDriverChannel = new WindowDriverChannel(windowDriver); - client.registerChannel('windowDriver', windowDriverChannel); + mainProcessService.registerChannel('windowDriver', windowDriverChannel); - const windowDriverRegistryChannel = client.getChannel('windowDriverRegistry'); + const windowDriverRegistryChannel = mainProcessService.getChannel('windowDriverRegistry'); const windowDriverRegistry = new WindowDriverRegistryChannelClient(windowDriverRegistryChannel); - await windowDriverRegistry.registerWindowDriver(windowId); + await windowDriverRegistry.registerWindowDriver(windowService.getCurrentWindowId()); // const options = await windowDriverRegistry.registerWindowDriver(windowId); // if (options.verbose) { // windowDriver.openDevTools(); // } - const disposable = toDisposable(() => windowDriverRegistry.reloadWindowDriver(windowId)); - return combinedDisposable([disposable, client]); + return toDisposable(() => windowDriverRegistry.reloadWindowDriver(windowService.getCurrentWindowId())); } diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index d3f808abb1..7b37a90c0a 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -57,9 +57,11 @@ export class Driver implements IDriver, IWindowDriverRegistry { await this.whenUnfrozen(windowId); const window = this.windowsService.getWindowById(windowId); + if (!window) { + throw new Error('Invalid window'); + } const webContents = window.win.webContents; const image = await new Promise(c => webContents.capturePage(c)); - return image.toPNG().toString('base64'); } @@ -67,6 +69,9 @@ export class Driver implements IDriver, IWindowDriverRegistry { await this.whenUnfrozen(windowId); const window = this.windowsService.getWindowById(windowId); + if (!window) { + throw new Error('Invalid window'); + } this.reloadingWindowIds.add(windowId); this.windowsService.reload(window); } @@ -78,16 +83,10 @@ export class Driver implements IDriver, IWindowDriverRegistry { async dispatchKeybinding(windowId: number, keybinding: string): Promise { await this.whenUnfrozen(windowId); - const [first, second] = KeybindingParser.parseUserBinding(keybinding); + const parts = KeybindingParser.parseUserBinding(keybinding); - if (!first) { - return; - } - - await this._dispatchKeybinding(windowId, first); - - if (second) { - await this._dispatchKeybinding(windowId, second); + for (let part of parts) { + await this._dispatchKeybinding(windowId, part); } } @@ -97,9 +96,12 @@ export class Driver implements IDriver, IWindowDriverRegistry { } const window = this.windowsService.getWindowById(windowId); + if (!window) { + throw new Error('Invalid window'); + } const webContents = window.win.webContents; const noModifiedKeybinding = new SimpleKeybinding(false, false, false, false, keybinding.keyCode); - const resolvedKeybinding = new USLayoutResolvedKeybinding(noModifiedKeybinding, OS); + const resolvedKeybinding = new USLayoutResolvedKeybinding(noModifiedKeybinding.toChord(), OS); const keyCode = resolvedKeybinding.getElectronAccelerator(); const modifiers: string[] = []; diff --git a/src/vs/platform/driver/node/driver.ts b/src/vs/platform/driver/node/driver.ts index 5ff791080d..df4f9f80df 100644 --- a/src/vs/platform/driver/node/driver.ts +++ b/src/vs/platform/driver/node/driver.ts @@ -5,7 +5,7 @@ import { connect as connectNet, Client } from 'vs/base/parts/ipc/node/ipc.net'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; export const ID = 'driverService'; diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index 46d65cadad..0273098c28 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -109,6 +109,12 @@ export interface IEditorOptions { * in the background. */ readonly inactive?: boolean; + + /** + * Will not show an error in case opening the editor fails and thus allows to show a custom error + * message as needed. By default, an error will be presented as notification if opening was not possible. + */ + readonly ignoreError?: boolean; } export interface ITextEditorSelection { diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 4a4da2e1ac..6d40a237f6 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -37,13 +37,13 @@ export interface ParsedArgs { logExtensionHostCommunication?: boolean; 'extensions-dir'?: string; 'builtin-extensions-dir'?: string; - extensionDevelopmentPath?: string; - extensionTestsPath?: string; - debugPluginHost?: string; - debugBrkPluginHost?: string; + extensionDevelopmentPath?: string; // either a local path or a URI + extensionTestsPath?: string; // either a local path or a URI + 'inspect-extensions'?: string; + 'inspect-brk-extensions'?: string; debugId?: string; - debugSearch?: string; - debugBrkSearch?: string; + 'inspect-search'?: string; + 'inspect-brk-search'?: string; 'disable-extensions'?: boolean; 'disable-extension'?: string | string[]; 'list-extensions'?: boolean; @@ -62,13 +62,14 @@ export interface ParsedArgs { 'disable-updates'?: string; 'disable-crash-reporter'?: string; 'skip-add-to-recently-opened'?: boolean; - 'max-memory'?: number; + 'max-memory'?: string; 'file-write'?: boolean; 'file-chmod'?: boolean; 'upload-logs'?: string; 'driver'?: string; 'driver-verbose'?: boolean; remote?: string; + 'nodeless'?: boolean; // TODO@ben revisit electron5 nodeless support // {{SQL CARBON EDIT}} aad?: boolean; database?: string; @@ -117,14 +118,14 @@ export interface IEnvironmentService { backupHome: string; backupWorkspacesPath: string; - workspacesHome: string; + untitledWorkspacesHome: URI; isExtensionDevelopment: boolean; disableExtensions: boolean | string[]; builtinExtensionsPath: string; extensionsPath: string; extensionDevelopmentLocationURI?: URI; - extensionTestsPath?: string; + extensionTestsLocationURI?: URI; debugExtensionHost: IExtensionHostDebugParams; debugSearch: IDebugParams; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index ad4cd58c09..944d35a004 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -22,6 +22,7 @@ export interface Option { id: string; type: 'boolean' | 'string'; alias?: string; + deprecates?: string; // old deprecated id args?: string | string[]; description?: string; cat?: keyof HelpCategories; @@ -41,7 +42,7 @@ export const options: Option[] = [ { id: 'folder-uri', type: 'string', cat: 'o', args: 'uri', description: localize('folderUri', "Opens a window with given folder uri(s)") }, { id: 'file-uri', type: 'string', cat: 'o', args: 'uri', description: localize('fileUri', "Opens a window with given file uri(s)") }, - { id: 'extensions-dir', type: 'string', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") }, + { id: 'extensions-dir', type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") }, { id: 'list-extensions', type: 'boolean', cat: 'e', description: localize('listExtensions', "List the installed extensions.") }, { id: 'show-versions', type: 'boolean', cat: 'e', description: localize('showVersions', "Show versions of installed extensions, when using --list-extension.") }, { id: 'install-extension', type: 'string', cat: 'e', args: 'extension-id', description: localize('installExtension', "Installs or updates the extension. Use `--force` argument to avoid prompts.") }, @@ -53,21 +54,21 @@ export const options: Option[] = [ { id: 'status', type: 'boolean', alias: 's', cat: 't', description: localize('status', "Print process usage and diagnostics information.") }, { id: 'prof-modules', type: 'boolean', alias: 'p', cat: 't', description: localize('prof-modules', "Capture performance markers while loading JS modules and print them with 'F1 > Developer: Startup Performance") }, { id: 'prof-startup', type: 'boolean', cat: 't', description: localize('prof-startup', "Run CPU profiler during startup") }, - { id: 'disable-extensions', type: 'boolean', cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") }, + { id: 'disable-extensions', type: 'boolean', deprecates: 'disableExtensions', cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") }, { id: 'disable-extension', type: 'string', cat: 't', args: 'extension-id', description: localize('disableExtension', "Disable an extension.") }, - { id: 'inspect-extensions', type: 'string', args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") }, - { id: 'inspect-brk-search', type: 'string', args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") }, + { id: 'inspect-extensions', type: 'string', deprecates: 'debugPluginHost', args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") }, + { id: 'inspect-brk-extensions', type: 'string', deprecates: 'debugBrkPluginHost', args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") }, { id: 'disable-gpu', type: 'boolean', cat: 't', description: localize('disableGPU', "Disable GPU hardware acceleration.") }, { id: 'upload-logs', type: 'string', cat: 't', description: localize('uploadLogs', "Uploads logs from current session to a secure endpoint.") }, - { id: 'max-memory', type: 'boolean', cat: 't', description: localize('maxMemory', "Max memory size for a window (in Mbytes).") }, + { id: 'max-memory', type: 'string', cat: 't', description: localize('maxMemory', "Max memory size for a window (in Mbytes).") }, { id: 'remote', type: 'string' }, { id: 'extensionDevelopmentPath', type: 'string' }, { id: 'extensionTestsPath', type: 'string' }, { id: 'debugId', type: 'string' }, - { id: 'inspect-search', type: 'string' }, - { id: 'inspect-brk-extensions', type: 'string' }, + { id: 'inspect-search', type: 'string', deprecates: 'debugSearch' }, + { id: 'inspect-brk-search', type: 'string', deprecates: 'debugBrkSearch' }, { id: 'export-default-configuration', type: 'string' }, { id: 'install-source', type: 'string' }, { id: 'driver', type: 'string' }, @@ -91,9 +92,7 @@ export const options: Option[] = [ { id: 'trace-category-filter', type: 'string' }, { id: 'trace-options', type: 'string' }, { id: 'prof-code-loading', type: 'boolean' }, - - { id: 'debugPluginHost', type: 'string', alias: 'inspect-extensions' }, - { id: 'debugBrkPluginHost', type: 'string', alias: 'inspect-brk-extensions' }, + { id: 'nodeless', type: 'boolean' }, // TODO@ben revisit electron5 nodeless support // {{SQL CARBON EDIT}} { id: 'database', type: 'string', alias: 'D' }, @@ -110,13 +109,22 @@ export function parseArgs(args: string[], isOptionSupported = (_: Option) => tru const string: string[] = []; const boolean: string[] = []; for (let o of options) { - if (o.alias && isOptionSupported(o)) { - alias[o.id] = o.alias; + if (isOptionSupported(o)) { + if (o.alias) { + alias[o.id] = o.alias; + } } + if (o.type === 'string') { string.push(o.id); + if (o.deprecates) { + string.push(o.deprecates); + } } else if (o.type === 'boolean') { boolean.push(o.id); + if (o.deprecates) { + boolean.push(o.deprecates); + } } } // remote aliases to avoid confusion @@ -125,6 +133,10 @@ export function parseArgs(args: string[], isOptionSupported = (_: Option) => tru if (o.alias) { delete parsedArgs[o.alias]; } + if (o.deprecates && parsedArgs.hasOwnProperty(o.deprecates) && !parsedArgs[o.id]) { + parsedArgs[o.id] = parsedArgs[o.deprecates]; + delete parsedArgs[o.deprecates]; + } } return parsedArgs; } @@ -237,3 +249,17 @@ export function hasArgs(arg: string | string[] | undefined): boolean { } return false; } + +export function addArg(argv: string[], ...args: string[]): string[] { + const endOfArgsMarkerIndex = argv.indexOf('--'); + if (endOfArgsMarkerIndex === -1) { + argv.push(...args); + } else { + // if the we have an argument "--" (end of argument marker) + // we cannot add arguments at the end. rather, we add + // arguments before the "--" marker. + argv.splice(endOfArgsMarkerIndex, 0, ...args); + } + + return argv; +} diff --git a/src/vs/platform/environment/node/argvHelper.ts b/src/vs/platform/environment/node/argvHelper.ts index 53c2edbe3f..20a0f94943 100644 --- a/src/vs/platform/environment/node/argvHelper.ts +++ b/src/vs/platform/environment/node/argvHelper.ts @@ -4,12 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { tmpdir } from 'os'; import { firstIndex } from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; import { ParsedArgs } from '../common/environment'; import { MIN_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/common/files'; import { parseArgs } from 'vs/platform/environment/node/argv'; - +import { join } from 'vs/base/common/path'; +import { writeFile } from 'vs/base/node/pfs'; function validate(args: ParsedArgs): ParsedArgs { if (args.goto) { @@ -17,7 +19,7 @@ function validate(args: ParsedArgs): ParsedArgs { } if (args['max-memory']) { - assert(args['max-memory'] >= MIN_MAX_MEMORY_SIZE_MB, `The max-memory argument cannot be specified lower than ${MIN_MAX_MEMORY_SIZE_MB} MB.`); + assert(parseInt(args['max-memory']) >= MIN_MAX_MEMORY_SIZE_MB, `The max-memory argument cannot be specified lower than ${MIN_MAX_MEMORY_SIZE_MB} MB.`); } return args; @@ -57,4 +59,22 @@ export function parseCLIProcessArgv(processArgv: string[]): ParsedArgs { } return validate(parseArgs(args)); -} \ No newline at end of file +} + +export function createWaitMarkerFile(verbose?: boolean): Promise { + const randomWaitMarkerPath = join(tmpdir(), Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10)); + + return writeFile(randomWaitMarkerPath, '').then(() => { + if (verbose) { + console.log(`Marker file for --wait created: ${randomWaitMarkerPath}`); + } + + return randomWaitMarkerPath; + }, error => { + if (verbose) { + console.error(`Failed to create marker file for --wait: ${error}`); + } + + return Promise.resolve(undefined); + }); +} diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index f60aea299e..ca3554de17 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -7,10 +7,10 @@ import { IEnvironmentService, ParsedArgs, IDebugParams, IExtensionHostDebugParam import * as crypto from 'crypto'; import * as paths from 'vs/base/node/paths'; import * as os from 'os'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { memoize } from 'vs/base/common/decorators'; -import pkg from 'vs/platform/node/package'; -import product from 'vs/platform/node/product'; +import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/node/product'; import { toLocalISOString } from 'vs/base/common/date'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { getPathFromAmdModule } from 'vs/base/common/amd'; @@ -135,7 +135,7 @@ export class EnvironmentService implements IEnvironmentService { get backupWorkspacesPath(): string { return path.join(this.backupHome, 'workspaces.json'); } @memoize - get workspacesHome(): string { return path.join(this.userDataPath, 'Workspaces'); } + get untitledWorkspacesHome(): URI { return URI.file(path.join(this.userDataPath, 'Workspaces')); } @memoize get installSourcePath(): string { return path.join(this.userDataPath, 'installSource'); } @@ -184,7 +184,16 @@ export class EnvironmentService implements IEnvironmentService { } @memoize - get extensionTestsPath(): string | undefined { return this._args.extensionTestsPath ? path.normalize(this._args.extensionTestsPath) : this._args.extensionTestsPath; } + get extensionTestsLocationURI(): URI | undefined { + const s = this._args.extensionTestsPath; + if (s) { + if (/^[^:/?#]+?:\/\//.test(s)) { + return URI.parse(s); + } + return URI.file(path.normalize(s)); + } + return undefined; + } get disableExtensions(): boolean | string[] { if (this._args['disable-extensions']) { @@ -250,11 +259,11 @@ export class EnvironmentService implements IEnvironmentService { } export function parseExtensionHostPort(args: ParsedArgs, isBuild: boolean): IExtensionHostDebugParams { - return parseDebugPort(args.debugPluginHost, args.debugBrkPluginHost, 5870, isBuild, args.debugId); + return parseDebugPort(args['inspect-extensions'], args['inspect-brk-extensions'], 5870, isBuild, args.debugId); } export function parseSearchPort(args: ParsedArgs, isBuild: boolean): IDebugParams { - return parseDebugPort(args.debugSearch, args.debugBrkSearch, 5876, isBuild); + return parseDebugPort(args['inspect-search'], args['inspect-brk-search'], 5876, isBuild); } export function parseDebugPort(debugArg: string | undefined, debugBrkArg: string | undefined, defaultBuildPort: number, isBuild: boolean, debugId?: string): IExtensionHostDebugParams { diff --git a/src/vs/platform/environment/test/node/environmentService.test.ts b/src/vs/platform/environment/test/node/environmentService.test.ts index 80b10298e9..08c751d26b 100644 --- a/src/vs/platform/environment/test/node/environmentService.test.ts +++ b/src/vs/platform/environment/test/node/environmentService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { parseArgs } from 'vs/platform/environment/node/argv'; import { parseExtensionHostPort, parseUserDataDir } from 'vs/platform/environment/node/environmentService'; @@ -19,6 +19,12 @@ suite('EnvironmentService', () => { assert.deepEqual(parse(['--debugBrkPluginHost']), { port: null, break: false, debugId: undefined }); assert.deepEqual(parse(['--debugBrkPluginHost=5678']), { port: 5678, break: true, debugId: undefined }); assert.deepEqual(parse(['--debugPluginHost=1234', '--debugBrkPluginHost=5678', '--debugId=7']), { port: 5678, break: true, debugId: '7' }); + + assert.deepEqual(parse(['--inspect-extensions']), { port: null, break: false, debugId: undefined }); + assert.deepEqual(parse(['--inspect-extensions=1234']), { port: 1234, break: false, debugId: undefined }); + assert.deepEqual(parse(['--inspect-brk-extensions']), { port: null, break: false, debugId: undefined }); + assert.deepEqual(parse(['--inspect-brk-extensions=5678']), { port: 5678, break: true, debugId: undefined }); + assert.deepEqual(parse(['--inspect-extensions=1234', '--inspect-brk-extensions=5678', '--debugId=7']), { port: 5678, break: true, debugId: '7' }); }); test('parseExtensionHostPort when unbuilt', () => { @@ -30,6 +36,12 @@ suite('EnvironmentService', () => { assert.deepEqual(parse(['--debugBrkPluginHost']), { port: 5870, break: false, debugId: undefined }); assert.deepEqual(parse(['--debugBrkPluginHost=5678']), { port: 5678, break: true, debugId: undefined }); assert.deepEqual(parse(['--debugPluginHost=1234', '--debugBrkPluginHost=5678', '--debugId=7']), { port: 5678, break: true, debugId: '7' }); + + assert.deepEqual(parse(['--inspect-extensions']), { port: 5870, break: false, debugId: undefined }); + assert.deepEqual(parse(['--inspect-extensions=1234']), { port: 1234, break: false, debugId: undefined }); + assert.deepEqual(parse(['--inspect-brk-extensions']), { port: 5870, break: false, debugId: undefined }); + assert.deepEqual(parse(['--inspect-brk-extensions=5678']), { port: 5678, break: true, debugId: undefined }); + assert.deepEqual(parse(['--inspect-extensions=1234', '--inspect-brk-extensions=5678', '--debugId=7']), { port: 5678, break: true, debugId: '7' }); }); test('userDataPath', () => { diff --git a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts index 0ad32bbdeb..f9feeb721f 100644 --- a/src/vs/platform/extensionManagement/common/extensionEnablementService.ts +++ b/src/vs/platform/extensionManagement/common/extensionEnablementService.ts @@ -385,5 +385,4 @@ class StorageManager extends Disposable { this.storageService.remove(key, scope); } } - -} \ No newline at end of file +} diff --git a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts index 51682f07c5..54834210d4 100644 --- a/src/vs/platform/extensionManagement/node/extensionGalleryService.ts +++ b/src/vs/platform/extensionManagement/node/extensionGalleryService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { tmpdir } from 'os'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { distinct } from 'vs/base/common/arrays'; import { getErrorMessage, isPromiseCanceledError, canceled } from 'vs/base/common/errors'; import { StatisticType, IGalleryExtension, IExtensionGalleryService, IGalleryExtensionAsset, IQueryOptions, SortBy, SortOrder, IExtensionIdentifier, IReportedExtension, InstallOperation, ITranslation, IGalleryExtensionVersion, IGalleryExtensionAssets, isIExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -14,8 +14,8 @@ import { IRequestService } from 'vs/platform/request/node/request'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IPager } from 'vs/base/common/paging'; import { IRequestOptions, IRequestContext, download, asJson, asText } from 'vs/base/node/request'; -import pkg from 'vs/platform/node/package'; -import product from 'vs/platform/node/product'; +import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/node/product'; import { isEngineValid } from 'vs/platform/extensions/node/extensionValidator'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { readFile } from 'vs/base/node/pfs'; @@ -24,7 +24,7 @@ import { generateUuid, isUUID } from 'vs/base/common/uuid'; import { values } from 'vs/base/common/map'; // {{SQL CARBON EDIT}} import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/workbench/parts/extensions/common/extensions'; +import { ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/workbench/contrib/extensions/common/extensions'; // {{SQL CARBON EDIT}} - End import { CancellationToken } from 'vs/base/common/cancellation'; import { ILogService } from 'vs/platform/log/common/log'; @@ -412,8 +412,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { .withFlags(Flags.IncludeAssetUri, Flags.IncludeStatistics, Flags.IncludeFiles, Flags.IncludeVersionProperties, Flags.ExcludeNonValidated) .withPage(1, 1) .withFilter(FilterType.Target, 'Microsoft.VisualStudio.Code') - .withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished)) - .withAssetTypes(AssetType.Manifest, AssetType.VSIX); + .withFilter(FilterType.ExcludeWithFlags, flagsToString(Flags.Unpublished)); if (uuid) { query = query.withFilter(FilterType.ExtensionId, uuid); @@ -914,7 +913,7 @@ export class ExtensionGalleryService implements IExtensionGalleryService { const headers = { 'Accept-Encoding': 'gzip' }; return this.getAsset(manifest, { headers }) .then(context => asJson(context)) - .then(manifest => manifest ? manifest.engines.vscode : Promise.reject('Error while reading manifest')); + .then(manifest => manifest ? manifest.engines.vscode : Promise.reject('Error while reading manifest')); } private getLastValidExtensionVersionReccursively(extension: IRawGalleryExtension, versions: IRawGalleryExtensionVersion[]): Promise { @@ -979,7 +978,7 @@ export function resolveMarketplaceHeaders(environmentService: IEnvironmentServic const marketplaceMachineIdFile = path.join(environmentService.userDataPath, 'machineid'); return readFile(marketplaceMachineIdFile, 'utf8') - .then(contents => isUUID(contents) ? contents : Promise.resolve(null), () => Promise.resolve(null) /* error reading ID file */) + .then(contents => isUUID(contents) ? contents : null, () => null /* error reading ID file */) .then(uuid => { if (!uuid) { uuid = generateUuid(); @@ -995,4 +994,4 @@ export function resolveMarketplaceHeaders(environmentService: IEnvironmentServic 'X-Market-User-Id': uuid }; }); -} +} \ No newline at end of file diff --git a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts index 875fb1f0cc..b3bbe04116 100644 --- a/src/vs/platform/extensionManagement/node/extensionLifecycle.ts +++ b/src/vs/platform/extensionManagement/node/extensionLifecycle.ts @@ -8,7 +8,7 @@ import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensio import { ILogService } from 'vs/platform/log/common/log'; import { fork, ChildProcess } from 'child_process'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { posix } from 'path'; +import { join } from 'vs/base/common/path'; import { Limiter } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; @@ -45,7 +45,7 @@ export class ExtensionsLifecycle extends Disposable { this.logService.warn(extension.identifier.id, extension.manifest.version, `${scriptKey} should be a node script`); return null; } - return { script: posix.join(extension.location.fsPath, script[1]), args: script.slice(2) || [] }; + return { script: join(extension.location.fsPath, script[1]), args: script.slice(2) || [] }; } return null; } @@ -130,6 +130,6 @@ export class ExtensionsLifecycle extends Disposable { } private getExtensionStoragePath(extension: ILocalExtension): string { - return posix.join(this.environmentService.globalStorageHome, extension.identifier.id.toLowerCase()); + return join(this.environmentService.globalStorageHome, extension.identifier.id.toLowerCase()); } } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementIpc.ts b/src/vs/platform/extensionManagement/node/extensionManagementIpc.ts index 0ab6f4d065..97ca819612 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementIpc.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementIpc.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension } from '../common/extensionManagement'; import { Event } from 'vs/base/common/event'; import { URI, UriComponents } from 'vs/base/common/uri'; diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 7261631131..f071a37ae7 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { assign } from 'vs/base/common/objects'; import { toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { flatten } from 'vs/base/common/arrays'; -import { extract, ExtractError, zip, IFile } from 'vs/platform/node/zip'; +import { extract, ExtractError, zip, IFile } from 'vs/base/node/zip'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IGalleryMetadata, @@ -24,11 +24,11 @@ import { import { areSameExtensions, getGalleryExtensionId, groupByExtension, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localizeManifest } from '../common/extensionNls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { Limiter, always, createCancelablePromise, CancelablePromise, Queue } from 'vs/base/common/async'; +import { Limiter, createCancelablePromise, CancelablePromise, Queue } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import * as semver from 'semver'; import { URI } from 'vs/base/common/uri'; -import pkg from 'vs/platform/node/package'; +import pkg from 'vs/platform/product/node/package'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { ILogService } from 'vs/platform/log/common/log'; import { ExtensionsManifestCache } from 'vs/platform/extensionManagement/node/extensionsManifestCache'; @@ -44,12 +44,12 @@ import { Schemas } from 'vs/base/common/network'; import { CancellationToken } from 'vs/base/common/cancellation'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; -import { IExtensionManifest, ExtensionType, ExtensionIdentifierWithVersion } from 'vs/platform/extensions/common/extensions'; +import { IExtensionManifest, ExtensionType, ExtensionIdentifierWithVersion, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { isUIExtension } from 'vs/platform/extensions/node/extensionsUtil'; // {{SQL CARBON EDIT} -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem'; const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser'; @@ -318,7 +318,7 @@ export class ExtensionManagementService extends Disposable implements IExtension this.downloadInstallableExtension(extension, operation) .then(installableExtension => this.installExtension(installableExtension, ExtensionType.User, cancellationToken) - .then(local => always(pfs.rimraf(installableExtension.zipPath), () => null).then(() => local))) + .then(local => pfs.rimraf(installableExtension.zipPath).finally(() => null).then(() => local))) .then(local => this.installDependenciesAndPackExtensions(local, existingExtension) .then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error)))) .then( @@ -357,7 +357,7 @@ export class ExtensionManagementService extends Disposable implements IExtension if (this.remote) { const manifest = await this.galleryService.getManifest(extension, CancellationToken.None); - if (manifest && isUIExtension(manifest, this.configurationService)) { + if (manifest && isUIExtension(manifest, [], this.configurationService) && !isLanguagePackExtension(manifest)) { return Promise.reject(new Error(nls.localize('notSupportedUIExtension', "Can't install extension {0} since UI Extensions are not supported", extension.identifier.id))); } } @@ -476,7 +476,7 @@ export class ExtensionManagementService extends Disposable implements IExtension () => this.logService.info('Renamed to', renamePath), e => { this.logService.info('Rename failed. Deleting from extracted location', extractPath); - return always(pfs.rimraf(extractPath), () => null).then(() => Promise.reject(e)); + return pfs.rimraf(extractPath).finally(() => null).then(() => Promise.reject(e)); })); } @@ -484,10 +484,10 @@ export class ExtensionManagementService extends Disposable implements IExtension this.logService.trace(`Started extracting the extension from ${zipPath} to ${extractPath}`); return pfs.rimraf(extractPath) .then( - () => extract(zipPath, extractPath, { sourcePath: 'extension', overwrite: true }, this.logService, token) + () => extract(zipPath, extractPath, { sourcePath: 'extension', overwrite: true }, token) .then( () => this.logService.info(`Extracted extension to ${extractPath}:`, identifier.id), - e => always(pfs.rimraf(extractPath), () => null) + e => pfs.rimraf(extractPath).finally(() => null) .then(() => Promise.reject(new ExtensionManagementError(e.message, e instanceof ExtractError && e.type ? e.type : INSTALL_ERROR_EXTRACTING)))), e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING))); } @@ -528,7 +528,7 @@ export class ExtensionManagementService extends Disposable implements IExtension return Promise.all(extensionsToInstall.map(async e => { if (this.remote) { const manifest = await this.galleryService.getManifest(e, CancellationToken.None); - if (manifest && isUIExtension(manifest, this.configurationService)) { + if (manifest && isUIExtension(manifest, [], this.configurationService) && !isLanguagePackExtension(manifest)) { this.logService.info('Ignored installing the UI dependency', e.identifier.id); return; } @@ -981,4 +981,4 @@ export class ExtensionManagementService extends Disposable implements IExtension */ this.telemetryService.publicLog(eventName, assign(extensionData, { success: !error, duration, errorcode })); } -} \ No newline at end of file +} diff --git a/src/vs/platform/extensionManagement/node/extensionManagementUtil.ts b/src/vs/platform/extensionManagement/node/extensionManagementUtil.ts index 2cdc427c1d..ee2e0324b4 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementUtil.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementUtil.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { buffer } from 'vs/platform/node/zip'; +import { buffer } from 'vs/base/node/zip'; import { localize } from 'vs/nls'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; diff --git a/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts b/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts index c72c637137..cee86987a2 100644 --- a/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts +++ b/src/vs/platform/extensionManagement/node/extensionsManifestCache.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { join } from 'path'; +import { join } from 'vs/base/common/path'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionManagementService, DidInstallExtensionEvent, DidUninstallExtensionEvent } from 'vs/platform/extensionManagement/common/extensionManagement'; import { MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE } from 'vs/platform/extensions/common/extensions'; diff --git a/src/vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test.ts b/src/vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test.ts index 9489ce4881..ca1813fe5e 100644 --- a/src/vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test.ts +++ b/src/vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test.ts @@ -10,8 +10,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { Emitter } from 'vs/base/common/event'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; import { IExtensionContributions, ExtensionType, IExtension } from 'vs/platform/extensions/common/extensions'; import { isUndefinedOrNull } from 'vs/base/common/types'; @@ -24,7 +23,7 @@ function storageService(instantiationService: TestInstantiationService): IStorag getWorkbenchState: () => WorkbenchState.FOLDER, }); } - service = instantiationService.stub(IStorageService, new TestStorageService()); + service = instantiationService.stub(IStorageService, new InMemoryStorageService()); } return service; } diff --git a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts index 4e1b978ca9..69cf076df3 100644 --- a/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts +++ b/src/vs/platform/extensionManagement/test/node/extensionGalleryService.test.ts @@ -8,8 +8,8 @@ import * as os from 'os'; import * as extfs from 'vs/base/node/extfs'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { parseArgs } from 'vs/platform/environment/node/argv'; -import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices'; -import { join } from 'path'; +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; +import { join } from 'vs/base/common/path'; import { mkdirp } from 'vs/base/node/pfs'; // {{SQL CARBON EDIT}} import { resolveMarketplaceHeaders, ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 7d4058a448..a9f503039c 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -159,6 +159,7 @@ export interface IExtensionManifest { readonly repository?: { url: string; }; readonly bugs?: { url: string; }; readonly enableProposedApi?: boolean; + readonly api?: string; } export const enum ExtensionType { @@ -227,3 +228,16 @@ export class ExtensionIdentifier { return id._lower; } } + +export interface IExtensionDescription extends IExtensionManifest { + readonly identifier: ExtensionIdentifier; + readonly uuid?: string; + readonly isBuiltin: boolean; + readonly isUnderDevelopment: boolean; + readonly extensionLocation: URI; + enableProposedApi?: boolean; +} + +export function isLanguagePackExtension(manifest: IExtensionManifest): boolean { + return manifest.contributes && manifest.contributes.localizations ? manifest.contributes.localizations.length > 0 : false; +} \ No newline at end of file diff --git a/src/vs/platform/extensions/node/extensionValidator.ts b/src/vs/platform/extensions/node/extensionValidator.ts index a4cbab7ce0..ecc3a48b5c 100644 --- a/src/vs/platform/extensions/node/extensionValidator.ts +++ b/src/vs/platform/extensions/node/extensionValidator.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import pkg from 'vs/platform/node/package'; +import pkg from 'vs/platform/product/node/package'; export interface IParsedVersion { hasCaret: boolean; diff --git a/src/vs/platform/extensions/node/extensionsUtil.ts b/src/vs/platform/extensions/node/extensionsUtil.ts index 1434f5cbec..0d36fbe606 100644 --- a/src/vs/platform/extensions/node/extensionsUtil.ts +++ b/src/vs/platform/extensions/node/extensionsUtil.ts @@ -7,9 +7,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; import { getGalleryExtensionId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { isNonEmptyArray } from 'vs/base/common/arrays'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; -export function isUIExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean { +export function isUIExtension(manifest: IExtensionManifest, uiContributions: string[], configurationService: IConfigurationService): boolean { const extensionId = getGalleryExtensionId(manifest.publisher, manifest.name); const configuredUIExtensions = configurationService.getValue('_workbench.uiExtensions') || []; if (configuredUIExtensions.length) { @@ -30,8 +30,10 @@ export function isUIExtension(manifest: IExtensionManifest, configurationService if (manifest.main) { return false; } - if (manifest.contributes && isNonEmptyArray(manifest.contributes.debuggers)) { - return false; + if (manifest.contributes) { + if (!uiContributions.length || Object.keys(manifest.contributes).some(contribution => uiContributions.indexOf(contribution) === -1)) { + return false; + } } // Default is UI Extension return true; diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 28644bc5d2..6794692d95 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as paths from 'vs/base/common/paths'; +import { sep } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import * as glob from 'vs/base/common/glob'; import { isLinux } from 'vs/base/common/platform'; @@ -17,7 +17,12 @@ import { isUndefinedOrNull } from 'vs/base/common/types'; export const IFileService = createDecorator('fileService'); export interface IResourceEncodings { - getWriteEncoding(resource: URI, preferredEncoding?: string): string; + getWriteEncoding(resource: URI, preferredEncoding?: string): IResourceEncoding; +} + +export interface IResourceEncoding { + encoding: string; + hasBOM: boolean; } export interface IFileService { @@ -385,8 +390,8 @@ export function isParent(path: string, candidate: string, ignoreCase?: boolean): return false; } - if (candidate.charAt(candidate.length - 1) !== paths.nativeSep) { - candidate += paths.nativeSep; + if (candidate.charAt(candidate.length - 1) !== sep) { + candidate += sep; } if (ignoreCase) { @@ -419,7 +424,7 @@ export interface IBaseStat { * A unique identifier thet represents the * current state of the file or directory. */ - etag: string; + etag?: string; /** * The resource is readonly. @@ -455,7 +460,7 @@ export interface IFileStat extends IBaseStat { } export interface IResolveFileResult { - stat: IFileStat; + stat?: IFileStat; success: boolean; } diff --git a/src/vs/platform/files/node/files.ts b/src/vs/platform/files/node/fileConstants.ts similarity index 100% rename from src/vs/platform/files/node/files.ts rename to src/vs/platform/files/node/fileConstants.ts diff --git a/src/vs/platform/files/test/files.test.ts b/src/vs/platform/files/test/files.test.ts index b95f00e7a7..293ca20dbc 100644 --- a/src/vs/platform/files/test/files.test.ts +++ b/src/vs/platform/files/test/files.test.ts @@ -5,36 +5,33 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { join, isEqual, isEqualOrParent } from 'vs/base/common/paths'; +import { isEqual, isEqualOrParent } from 'vs/base/common/extpath'; import { FileChangeType, FileChangesEvent, isParent } from 'vs/platform/files/common/files'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; +import { toResource } from 'vs/base/test/common/utils'; suite('Files', () => { - function toResource(path) { - return URI.file(join('C:\\', path)); - } - - test('FileChangesEvent', () => { + test('FileChangesEvent', function () { let changes = [ - { resource: URI.file(join('C:\\', '/foo/updated.txt')), type: FileChangeType.UPDATED }, - { resource: URI.file(join('C:\\', '/foo/otherupdated.txt')), type: FileChangeType.UPDATED }, - { resource: URI.file(join('C:\\', '/added.txt')), type: FileChangeType.ADDED }, - { resource: URI.file(join('C:\\', '/bar/deleted.txt')), type: FileChangeType.DELETED }, - { resource: URI.file(join('C:\\', '/bar/folder')), type: FileChangeType.DELETED } + { resource: toResource.call(this, '/foo/updated.txt'), type: FileChangeType.UPDATED }, + { resource: toResource.call(this, '/foo/otherupdated.txt'), type: FileChangeType.UPDATED }, + { resource: toResource.call(this, '/added.txt'), type: FileChangeType.ADDED }, + { resource: toResource.call(this, '/bar/deleted.txt'), type: FileChangeType.DELETED }, + { resource: toResource.call(this, '/bar/folder'), type: FileChangeType.DELETED } ]; let r1 = new FileChangesEvent(changes); - assert(!r1.contains(toResource('/foo'), FileChangeType.UPDATED)); - assert(r1.contains(toResource('/foo/updated.txt'), FileChangeType.UPDATED)); - assert(!r1.contains(toResource('/foo/updated.txt'), FileChangeType.ADDED)); - assert(!r1.contains(toResource('/foo/updated.txt'), FileChangeType.DELETED)); + assert(!r1.contains(toResource.call(this, '/foo'), FileChangeType.UPDATED)); + assert(r1.contains(toResource.call(this, '/foo/updated.txt'), FileChangeType.UPDATED)); + assert(!r1.contains(toResource.call(this, '/foo/updated.txt'), FileChangeType.ADDED)); + assert(!r1.contains(toResource.call(this, '/foo/updated.txt'), FileChangeType.DELETED)); - assert(r1.contains(toResource('/bar/folder'), FileChangeType.DELETED)); - assert(r1.contains(toResource('/bar/folder/somefile'), FileChangeType.DELETED)); - assert(r1.contains(toResource('/bar/folder/somefile/test.txt'), FileChangeType.DELETED)); - assert(!r1.contains(toResource('/bar/folder2/somefile'), FileChangeType.DELETED)); + assert(r1.contains(toResource.call(this, '/bar/folder'), FileChangeType.DELETED)); + assert(r1.contains(toResource.call(this, '/bar/folder/somefile'), FileChangeType.DELETED)); + assert(r1.contains(toResource.call(this, '/bar/folder/somefile/test.txt'), FileChangeType.DELETED)); + assert(!r1.contains(toResource.call(this, '/bar/folder2/somefile'), FileChangeType.DELETED)); assert.strictEqual(5, r1.changes.length); assert.strictEqual(1, r1.getAdded().length); diff --git a/src/vs/platform/history/common/history.ts b/src/vs/platform/history/common/history.ts index 0e4eb79e6c..41307e1304 100644 --- a/src/vs/platform/history/common/history.ts +++ b/src/vs/platform/history/common/history.ts @@ -3,27 +3,57 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IPath } from 'vs/platform/windows/common/windows'; import { Event as CommonEvent } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; +import { IPath } from 'vs/platform/windows/common/windows'; export const IHistoryMainService = createDecorator('historyMainService'); export interface IRecentlyOpened { - workspaces: Array; - files: URI[]; + workspaces: Array; + files: IRecentFile[]; } +export type IRecent = IRecentWorkspace | IRecentFolder | IRecentFile; + +export interface IRecentWorkspace { + workspace: IWorkspaceIdentifier; + label?: string; +} + +export interface IRecentFolder { + folderUri: ISingleFolderWorkspaceIdentifier; + label?: string; +} + +export interface IRecentFile { + fileUri: URI; + label?: string; +} + +export function isRecentWorkspace(curr: IRecent): curr is IRecentWorkspace { + return !!curr['workspace']; +} + +export function isRecentFolder(curr: IRecent): curr is IRecentFolder { + return !!curr['folderUri']; +} + +export function isRecentFile(curr: IRecent): curr is IRecentFile { + return !!curr['fileUri']; +} + + export interface IHistoryMainService { _serviceBrand: any; onRecentlyOpenedChange: CommonEvent; - addRecentlyOpened(workspaces: undefined | Array, files: URI[]): void; - getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened; - removeFromRecentlyOpened(paths: Array): void; + addRecentlyOpened(recents: IRecent[]): void; + getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier, currentFolder?: ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened; + removeFromRecentlyOpened(paths: URI[]): void; clearRecentlyOpened(): void; updateWindowsJumpList(): void; diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index 3d4c3f3454..6eb3cae891 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -11,26 +11,17 @@ import { ILogService } from 'vs/platform/log/common/log'; import { getBaseLabel, getPathLabel } from 'vs/base/common/labels'; import { IPath } from 'vs/platform/windows/common/windows'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; -import { isWindows, isMacintosh, isLinux } from 'vs/base/common/platform'; -import { IWorkspaceIdentifier, IWorkspacesMainService, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IHistoryMainService, IRecentlyOpened } from 'vs/platform/history/common/history'; -import { isEqual } from 'vs/base/common/paths'; -import { RunOnceScheduler } from 'vs/base/common/async'; -import { getComparisonKey, isEqual as areResourcesEqual, dirname } from 'vs/base/common/resources'; -import { URI, UriComponents } from 'vs/base/common/uri'; +import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { IWorkspaceIdentifier, IWorkspacesMainService, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IHistoryMainService, IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile } from 'vs/platform/history/common/history'; +import { ThrottledDelayer } from 'vs/base/common/async'; +import { isEqual as areResourcesEqual, dirname, originalFSPath } from 'vs/base/common/resources'; +import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { getSimpleWorkspaceLabel } from 'vs/platform/label/common/label'; - -interface ISerializedRecentlyOpened { - workspaces2: Array; // IWorkspaceIdentifier or URI.toString() - files2: string[]; // files as URI.toString() -} - -interface ILegacySerializedRecentlyOpened { - workspaces: Array; // legacy (UriComponents was also supported for a few insider builds) - files: string[]; // files as paths -} +import { toStoreData, restoreRecentlyOpened, RecentlyOpenedStorageData } from 'vs/platform/history/electron-main/historyStorage'; +import { exists } from 'vs/base/node/pfs'; export class HistoryMainService implements IHistoryMainService { @@ -45,7 +36,7 @@ export class HistoryMainService implements IHistoryMainService { private _onRecentlyOpenedChange = new Emitter(); onRecentlyOpenedChange: CommonEvent = this._onRecentlyOpenedChange.event; - private macOSRecentDocumentsUpdater: RunOnceScheduler; + private macOSRecentDocumentsUpdater: ThrottledDelayer; constructor( @IStateService private readonly stateService: IStateService, @@ -53,113 +44,78 @@ export class HistoryMainService implements IHistoryMainService { @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @IEnvironmentService private readonly environmentService: IEnvironmentService ) { - this.macOSRecentDocumentsUpdater = new RunOnceScheduler(() => this.updateMacOSRecentDocuments(), 800); + this.macOSRecentDocumentsUpdater = new ThrottledDelayer(800); } - addRecentlyOpened(workspaces: Array, files: URI[]): void { - if ((workspaces && workspaces.length > 0) || (files && files.length > 0)) { - const mru = this.getRecentlyOpened(); - - // Workspaces - if (Array.isArray(workspaces)) { - workspaces.forEach(workspace => { - const isUntitledWorkspace = !isSingleFolderWorkspaceIdentifier(workspace) && this.workspacesMainService.isUntitledWorkspace(workspace); - if (isUntitledWorkspace) { - return; // only store saved workspaces - } - - mru.workspaces.unshift(workspace); - mru.workspaces = arrays.distinct(mru.workspaces, workspace => this.distinctFn(workspace)); - - // We do not add to recent documents here because on Windows we do this from a custom - // JumpList and on macOS we fill the recent documents in one go from all our data later. - }); - } - - // Files - if (Array.isArray(files)) { - files.forEach((fileUri) => { - mru.files.unshift(fileUri); - mru.files = arrays.distinct(mru.files, file => this.distinctFn(file)); + addRecentlyOpened(newlyAdded: IRecent[]): void { + const workspaces: Array = []; + const files: IRecentFile[] = []; + for (let curr of newlyAdded) { + if (isRecentWorkspace(curr)) { + if (!this.workspacesMainService.isUntitledWorkspace(curr.workspace) && indexOfWorkspace(workspaces, curr.workspace) === -1) { + workspaces.push(curr); + } + } else if (isRecentFolder(curr)) { + if (indexOfFolder(workspaces, curr.folderUri) === -1) { + workspaces.push(curr); + } + } else { + if (indexOfFile(files, curr.fileUri) === -1) { + files.push(curr); // Add to recent documents (Windows only, macOS later) - if (isWindows && fileUri.scheme === Schemas.file) { - app.addRecentDocument(fileUri.fsPath); + if (isWindows && curr.fileUri.scheme === Schemas.file) { + app.addRecentDocument(curr.fileUri.fsPath); } - }); + } } + } - // Make sure its bounded - mru.workspaces = mru.workspaces.slice(0, HistoryMainService.MAX_TOTAL_RECENT_ENTRIES); - mru.files = mru.files.slice(0, HistoryMainService.MAX_TOTAL_RECENT_ENTRIES); + this.addEntriesFromStorage(workspaces, files); - this.saveRecentlyOpened(mru); - this._onRecentlyOpenedChange.fire(); + if (workspaces.length > HistoryMainService.MAX_TOTAL_RECENT_ENTRIES) { + workspaces.length = HistoryMainService.MAX_TOTAL_RECENT_ENTRIES; + } + if (files.length > HistoryMainService.MAX_TOTAL_RECENT_ENTRIES) { + files.length = HistoryMainService.MAX_TOTAL_RECENT_ENTRIES; + } - // Schedule update to recent documents on macOS dock - if (isMacintosh) { - this.macOSRecentDocumentsUpdater.schedule(); - } + this.saveRecentlyOpened({ workspaces, files }); + this._onRecentlyOpenedChange.fire(); + + // Schedule update to recent documents on macOS dock + if (isMacintosh) { + this.macOSRecentDocumentsUpdater.trigger(() => this.updateMacOSRecentDocuments()); } } - removeFromRecentlyOpened(pathsToRemove: Array): void { + removeFromRecentlyOpened(toRemove: URI[]): void { + const keep = (recent: IRecent) => { + const uri = location(recent); + for (const r of toRemove) { + if (areResourcesEqual(r, uri)) { + return false; + } + } + return true; + }; + const mru = this.getRecentlyOpened(); - let update = false; + const workspaces = mru.workspaces.filter(keep); + const files = mru.files.filter(keep); - pathsToRemove.forEach(pathToRemove => { - - // Remove workspace - let index = arrays.firstIndex(mru.workspaces, workspace => { - if (isWorkspaceIdentifier(pathToRemove)) { - return isWorkspaceIdentifier(workspace) && isEqual(pathToRemove.configPath, workspace.configPath, !isLinux /* ignorecase */); - } - if (isSingleFolderWorkspaceIdentifier(pathToRemove)) { - return isSingleFolderWorkspaceIdentifier(workspace) && areResourcesEqual(pathToRemove, workspace); - } - if (typeof pathToRemove === 'string') { - if (isSingleFolderWorkspaceIdentifier(workspace)) { - return workspace.scheme === Schemas.file && isEqual(pathToRemove, workspace.fsPath, !isLinux /* ignorecase */); - } - if (isWorkspaceIdentifier(workspace)) { - return isEqual(pathToRemove, workspace.configPath, !isLinux /* ignorecase */); - } - } - return false; - }); - if (index >= 0) { - mru.workspaces.splice(index, 1); - update = true; - } - - // Remove file - index = arrays.firstIndex(mru.files, file => { - if (pathToRemove instanceof URI) { - return areResourcesEqual(file, pathToRemove); - } else if (typeof pathToRemove === 'string') { - return isEqual(file.fsPath, pathToRemove, !isLinux /* ignorecase */); - } - return false; - }); - - if (index >= 0) { - mru.files.splice(index, 1); - update = true; - } - }); - - if (update) { - this.saveRecentlyOpened(mru); + if (workspaces.length !== mru.workspaces.length || files.length !== mru.files.length) { + this.saveRecentlyOpened({ files, workspaces }); this._onRecentlyOpenedChange.fire(); // Schedule update to recent documents on macOS dock if (isMacintosh) { - this.macOSRecentDocumentsUpdater.schedule(); + this.macOSRecentDocumentsUpdater.trigger(() => this.updateMacOSRecentDocuments()); } } } - private updateMacOSRecentDocuments(): void { + private async updateMacOSRecentDocuments(): Promise { if (!isMacintosh) { return; } @@ -173,27 +129,26 @@ export class HistoryMainService implements IHistoryMainService { const mru = this.getRecentlyOpened(); // Fill in workspaces - let entries = 0; - for (let i = 0; i < mru.workspaces.length && entries < HistoryMainService.MAX_MACOS_DOCK_RECENT_FOLDERS; i++) { - const workspace = mru.workspaces[i]; - if (isSingleFolderWorkspaceIdentifier(workspace)) { - if (workspace.scheme === Schemas.file) { - app.addRecentDocument(workspace.fsPath); + for (let i = 0, entries = 0; i < mru.workspaces.length && entries < HistoryMainService.MAX_MACOS_DOCK_RECENT_FOLDERS; i++) { + const loc = location(mru.workspaces[i]); + if (loc.scheme === Schemas.file) { + const workspacePath = originalFSPath(loc); + if (await exists(workspacePath)) { + app.addRecentDocument(workspacePath); entries++; } - } else { - app.addRecentDocument(workspace.configPath); - entries++; } } // Fill in files - entries = 0; - for (let i = 0; i < mru.files.length && entries < HistoryMainService.MAX_MACOS_DOCK_RECENT_FILES; i++) { - const file = mru.files[i]; - if (file.scheme === Schemas.file) { - app.addRecentDocument(file.fsPath); - entries++; + for (let i = 0, entries = 0; i < mru.files.length && entries < HistoryMainService.MAX_MACOS_DOCK_RECENT_FILES; i++) { + const loc = location(mru.files[i]); + if (loc.scheme === Schemas.file) { + const filePath = originalFSPath(loc); + if (await exists(filePath)) { + app.addRecentDocument(filePath); + entries++; + } } } } @@ -206,108 +161,61 @@ export class HistoryMainService implements IHistoryMainService { this._onRecentlyOpenedChange.fire(); } - getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened { - let workspaces: Array; - let files: URI[]; + getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier, currentFolder?: ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened { - // Get from storage - const storedRecents = this.getRecentlyOpenedFromStorage(); - if (storedRecents) { - workspaces = storedRecents.workspaces || []; - files = storedRecents.files || []; - } else { - workspaces = []; - files = []; - } + const workspaces: Array = []; + const files: IRecentFile[] = []; // Add current workspace to beginning if set - if (currentWorkspace) { - workspaces.unshift(currentWorkspace); + if (currentWorkspace && !this.workspacesMainService.isUntitledWorkspace(currentWorkspace)) { + workspaces.push({ workspace: currentWorkspace }); + } + if (currentFolder) { + workspaces.push({ folderUri: currentFolder }); } // Add currently files to open to the beginning if any if (currentFiles) { - files.unshift(...arrays.coalesce(currentFiles.map(f => f.fileUri))); + for (let currentFile of currentFiles) { + const fileUri = currentFile.fileUri; + if (fileUri && indexOfFile(files, fileUri) === -1) { + files.push({ fileUri }); + } + } } - - // Clear those dupes - workspaces = arrays.distinct(workspaces, workspace => this.distinctFn(workspace)); - files = arrays.distinct(files, file => this.distinctFn(file)); - - // Hide untitled workspaces - workspaces = workspaces.filter(workspace => isSingleFolderWorkspaceIdentifier(workspace) || !this.workspacesMainService.isUntitledWorkspace(workspace)); + this.addEntriesFromStorage(workspaces, files); return { workspaces, files }; } - private distinctFn(workspaceOrFile: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI): string { - if (workspaceOrFile instanceof URI) { - return getComparisonKey(workspaceOrFile); + private addEntriesFromStorage(workspaces: Array, files: IRecentFile[]) { + // Get from storage + let recents = this.getRecentlyOpenedFromStorage(); + for (let recent of recents.workspaces) { + let index = isRecentFolder(recent) ? indexOfFolder(workspaces, recent.folderUri) : indexOfWorkspace(workspaces, recent.workspace); + if (index >= 0) { + workspaces[index].label = workspaces[index].label || recent.label; + } else { + workspaces.push(recent); + } + } + for (let recent of recents.files) { + let index = indexOfFile(files, recent.fileUri); + if (index >= 0) { + files[index].label = files[index].label || recent.label; + } else { + files.push(recent); + } } - - return workspaceOrFile.id; } private getRecentlyOpenedFromStorage(): IRecentlyOpened { - const storedRecents = this.stateService.getItem(HistoryMainService.recentlyOpenedStorageKey); - const result: IRecentlyOpened = { workspaces: [], files: [] }; - if (storedRecents) { - if (Array.isArray(storedRecents.workspaces2)) { - for (const workspace of storedRecents.workspaces2) { - if (isWorkspaceIdentifier(workspace)) { - result.workspaces.push(workspace); - } else if (typeof workspace === 'string') { - result.workspaces.push(URI.parse(workspace)); - } - } - } else if (Array.isArray(storedRecents.workspaces)) { - // TODO@martin legacy support can be removed at some point (6 month?) - // format of 1.25 and before - for (const workspace of storedRecents.workspaces) { - if (typeof workspace === 'string') { - result.workspaces.push(URI.file(workspace)); - } else if (isWorkspaceIdentifier(workspace)) { - result.workspaces.push(workspace); - } else if (workspace && typeof workspace.path === 'string' && typeof workspace.scheme === 'string') { - // added by 1.26-insiders - result.workspaces.push(URI.revive(workspace)); - } - } - } - - if (Array.isArray(storedRecents.files2)) { - for (const file of storedRecents.files2) { - if (typeof file === 'string') { - result.files.push(URI.parse(file)); - } - } - } else if (Array.isArray(storedRecents.files)) { - for (const file of storedRecents.files) { - if (typeof file === 'string') { - result.files.push(URI.file(file)); - } - } - } - } - - return result; + const storedRecents = this.stateService.getItem(HistoryMainService.recentlyOpenedStorageKey); + return restoreRecentlyOpened(storedRecents); } private saveRecentlyOpened(recent: IRecentlyOpened): void { - const serialized: ISerializedRecentlyOpened = { workspaces2: [], files2: [] }; - - for (const workspace of recent.workspaces) { - if (isSingleFolderWorkspaceIdentifier(workspace)) { - serialized.workspaces2.push(workspace.toString()); - } else { - serialized.workspaces2.push(workspace); - } - } - - for (const file of recent.files) { - serialized.files2.push(file.toString()); - } - + const serialized = toStoreData(recent); this.stateService.setItem(HistoryMainService.recentlyOpenedStorageKey, serialized); } @@ -341,18 +249,13 @@ export class HistoryMainService implements IHistoryMainService { // so we need to update our list of recent paths with the choice of the user to not add them again // Also: Windows will not show our custom category at all if there is any entry which was removed // by the user! See https://github.com/Microsoft/vscode/issues/15052 - let toRemove: Array = []; + let toRemove: URI[] = []; for (let item of app.getJumpListSettings().removedItems) { const args = item.args; if (args) { - const match = /^--folder-uri\s+"([^"]+)"$/.exec(args); + const match = /^--(folder|file)-uri\s+"([^"]+)"$/.exec(args); if (match) { - if (args[0] === '-') { - toRemove.push(URI.parse(match[1])); - } else { - let configPath = match[1]; - toRemove.push({ id: this.workspacesMainService.getWorkspaceId(configPath), configPath }); - } + toRemove.push(URI.parse(match[2])); } } } @@ -362,17 +265,18 @@ export class HistoryMainService implements IHistoryMainService { jumpList.push({ type: 'custom', name: nls.localize('recentFolders', "Recent Workspaces"), - items: arrays.coalesce(this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(workspace => { - const title = getSimpleWorkspaceLabel(workspace, this.environmentService.workspacesHome); + items: arrays.coalesce(this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(recent => { + const workspace = isRecentWorkspace(recent) ? recent.workspace : recent.folderUri; + const title = recent.label || getSimpleWorkspaceLabel(workspace, this.environmentService.untitledWorkspacesHome); let description; let args; if (isSingleFolderWorkspaceIdentifier(workspace)) { const parentFolder = dirname(workspace); - description = parentFolder ? nls.localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(parentFolder, this.environmentService)) : getBaseLabel(workspace); + description = nls.localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(parentFolder, this.environmentService)); args = `--folder-uri "${workspace.toString()}"`; } else { description = nls.localize('codeWorkspace', "Code Workspace"); - args = `"${workspace.configPath}"`; + args = `--file-uri "${workspace.configPath.toString()}"`; } return { type: 'task', @@ -399,3 +303,25 @@ export class HistoryMainService implements IHistoryMainService { } } } + +function location(recent: IRecent): URI { + if (isRecentFolder(recent)) { + return recent.folderUri; + } + if (isRecentFile(recent)) { + return recent.fileUri; + } + return recent.workspace.configPath; +} + +function indexOfWorkspace(arr: IRecent[], workspace: IWorkspaceIdentifier): number { + return arrays.firstIndex(arr, w => isRecentWorkspace(w) && w.workspace.id === workspace.id); +} + +function indexOfFolder(arr: IRecent[], folderURI: ISingleFolderWorkspaceIdentifier): number { + return arrays.firstIndex(arr, f => isRecentFolder(f) && areResourcesEqual(f.folderUri, folderURI)); +} + +function indexOfFile(arr: IRecentFile[], fileURI: URI): number { + return arrays.firstIndex(arr, f => areResourcesEqual(f.fileUri, fileURI)); +} \ No newline at end of file diff --git a/src/vs/platform/history/electron-main/historyStorage.ts b/src/vs/platform/history/electron-main/historyStorage.ts new file mode 100644 index 0000000000..396638ce30 --- /dev/null +++ b/src/vs/platform/history/electron-main/historyStorage.ts @@ -0,0 +1,113 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { UriComponents, URI } from 'vs/base/common/uri'; +import { IRecentlyOpened, isRecentFolder } from 'vs/platform/history/common/history'; + +interface ISerializedRecentlyOpened { + workspaces3: Array; // workspace or URI.toString() // added in 1.32 + workspaceLabels?: Array; // added in 1.33 + files2: string[]; // files as URI.toString() // added in 1.32 + fileLabels?: Array; // added in 1.33 +} + +interface ILegacySerializedRecentlyOpened { + workspaces2: Array; // legacy, configPath as file path + workspaces: Array; // legacy (UriComponents was also supported for a few insider builds) + files: string[]; // files as paths +} + +interface ISerializedWorkspace { id: string; configURIPath: string; } +interface ILegacySerializedWorkspace { id: string; configPath: string; } + +export type RecentlyOpenedStorageData = object; + +export function restoreRecentlyOpened(data: RecentlyOpenedStorageData | undefined): IRecentlyOpened { + const result: IRecentlyOpened = { workspaces: [], files: [] }; + if (data) { + const storedRecents = data as ISerializedRecentlyOpened & ILegacySerializedRecentlyOpened; + if (Array.isArray(storedRecents.workspaces3)) { + for (let i = 0; i < storedRecents.workspaces3.length; i++) { + const workspace = storedRecents.workspaces3[i]; + const label: string | undefined = (Array.isArray(storedRecents.workspaceLabels) && storedRecents.workspaceLabels[i]) || undefined; + if (typeof workspace === 'object' && typeof workspace.id === 'string' && typeof workspace.configURIPath === 'string') { + result.workspaces.push({ label, workspace: { id: workspace.id, configPath: URI.parse(workspace.configURIPath) } }); + } else if (typeof workspace === 'string') { + result.workspaces.push({ label, folderUri: URI.parse(workspace) }); + } + } + } else if (Array.isArray(storedRecents.workspaces2)) { + for (const workspace of storedRecents.workspaces2) { + if (typeof workspace === 'object' && typeof workspace.id === 'string' && typeof workspace.configPath === 'string') { + result.workspaces.push({ workspace: { id: workspace.id, configPath: URI.file(workspace.configPath) } }); + } else if (typeof workspace === 'string') { + result.workspaces.push({ folderUri: URI.parse(workspace) }); + } + } + } else if (Array.isArray(storedRecents.workspaces)) { + // TODO@martin legacy support can be removed at some point (6 month?) + // format of 1.25 and before + for (const workspace of storedRecents.workspaces) { + if (typeof workspace === 'string') { + result.workspaces.push({ folderUri: URI.file(workspace) }); + } else if (typeof workspace === 'object' && typeof workspace['id'] === 'string' && typeof workspace['configPath'] === 'string') { + result.workspaces.push({ workspace: { id: workspace['id'], configPath: URI.file(workspace['configPath']) } }); + } else if (workspace && typeof workspace['path'] === 'string' && typeof workspace['scheme'] === 'string') { + // added by 1.26-insiders + result.workspaces.push({ folderUri: URI.revive(workspace) }); + } + } + } + + if (Array.isArray(storedRecents.files2)) { + for (let i = 0; i < storedRecents.files2.length; i++) { + const file = storedRecents.files2[i]; + const label: string | undefined = (Array.isArray(storedRecents.fileLabels) && storedRecents.fileLabels[i]) || undefined; + if (typeof file === 'string') { + result.files.push({ label, fileUri: URI.parse(file) }); + } + } + } else if (Array.isArray(storedRecents.files)) { + for (const file of storedRecents.files) { + if (typeof file === 'string') { + result.files.push({ fileUri: URI.file(file) }); + } + } + } + } + + return result; +} + +export function toStoreData(recents: IRecentlyOpened): RecentlyOpenedStorageData { + const serialized: ISerializedRecentlyOpened = { workspaces3: [], files2: [] }; + + let hasLabel = false; + const workspaceLabels: (string | null)[] = []; + for (const recent of recents.workspaces) { + if (isRecentFolder(recent)) { + serialized.workspaces3.push(recent.folderUri.toString()); + } else { + serialized.workspaces3.push({ id: recent.workspace.id, configURIPath: recent.workspace.configPath.toString() }); + } + workspaceLabels.push(recent.label || null); + hasLabel = hasLabel || !!recent.label; + } + if (hasLabel) { + serialized.workspaceLabels = workspaceLabels; + } + + hasLabel = false; + const fileLabels: (string | null)[] = []; + for (const recent of recents.files) { + serialized.files2.push(recent.fileUri.toString()); + fileLabels.push(recent.label || null); + hasLabel = hasLabel || !!recent.label; + } + if (hasLabel) { + serialized.fileLabels = fileLabels; + } + + return serialized; +} diff --git a/src/vs/platform/history/test/electron-main/historyStorage.test.ts b/src/vs/platform/history/test/electron-main/historyStorage.test.ts new file mode 100644 index 0000000000..202506a92e --- /dev/null +++ b/src/vs/platform/history/test/electron-main/historyStorage.test.ts @@ -0,0 +1,223 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as assert from 'assert'; +import * as os from 'os'; +import * as path from 'vs/base/common/path'; + +import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { URI } from 'vs/base/common/uri'; +import { IRecentlyOpened, isRecentFolder, IRecentFolder, IRecentWorkspace } from 'vs/platform/history/common/history'; +import { toStoreData, restoreRecentlyOpened } from 'vs/platform/history/electron-main/historyStorage'; + +function toWorkspace(uri: URI): IWorkspaceIdentifier { + return { + id: '1234', + configPath: uri + }; +} +function assertEqualURI(u1: URI | undefined, u2: URI | undefined, message?: string): void { + assert.equal(u1 && u1.toString(), u2 && u2.toString(), message); +} + +function assertEqualWorkspace(w1: IWorkspaceIdentifier | undefined, w2: IWorkspaceIdentifier | undefined, message?: string): void { + if (!w1 || !w2) { + assert.equal(w1, w2, message); + return; + } + assert.equal(w1.id, w2.id, message); + assertEqualURI(w1.configPath, w2.configPath, message); +} + +function assertEqualRecentlyOpened(actual: IRecentlyOpened, expected: IRecentlyOpened, message?: string) { + assert.equal(actual.files.length, expected.files.length, message); + for (let i = 0; i < actual.files.length; i++) { + assertEqualURI(actual.files[i].fileUri, expected.files[i].fileUri, message); + assert.equal(actual.files[i].label, expected.files[i].label); + } + assert.equal(actual.workspaces.length, expected.workspaces.length, message); + for (let i = 0; i < actual.workspaces.length; i++) { + let expectedRecent = expected.workspaces[i]; + let actualRecent = actual.workspaces[i]; + if (isRecentFolder(actualRecent)) { + assertEqualURI(actualRecent.folderUri, (expectedRecent).folderUri, message); + } else { + assertEqualWorkspace(actualRecent.workspace, (expectedRecent).workspace, message); + } + assert.equal(actualRecent.label, expectedRecent.label); + } +} + +function assertRestoring(state: IRecentlyOpened, message?: string) { + const stored = toStoreData(state); + const restored = restoreRecentlyOpened(stored); + assertEqualRecentlyOpened(state, restored, message); +} + +const testWSPath = URI.file(path.join(os.tmpdir(), 'windowStateTest', 'test.code-workspace')); +const testFileURI = URI.file(path.join(os.tmpdir(), 'windowStateTest', 'testFile.txt')); +const testFolderURI = URI.file(path.join(os.tmpdir(), 'windowStateTest', 'testFolder')); + +const testRemoteFolderURI = URI.parse('foo://bar/c/e'); +const testRemoteFileURI = URI.parse('foo://bar/c/d.txt'); +const testRemoteWSURI = URI.parse('foo://bar/c/test.code-workspace'); + +suite('History Storage', () => { + test('storing and restoring', () => { + let ro: IRecentlyOpened; + ro = { + files: [], + workspaces: [] + }; + assertRestoring(ro, 'empty'); + ro = { + files: [{ fileUri: testFileURI }], + workspaces: [] + }; + assertRestoring(ro, 'file'); + ro = { + files: [], + workspaces: [{ folderUri: testFolderURI }] + }; + assertRestoring(ro, 'folder'); + ro = { + files: [], + workspaces: [{ workspace: toWorkspace(testWSPath) }, { folderUri: testFolderURI }] + }; + assertRestoring(ro, 'workspaces and folders'); + + ro = { + files: [{ fileUri: testRemoteFileURI }], + workspaces: [{ workspace: toWorkspace(testRemoteWSURI) }, { folderUri: testRemoteFolderURI }] + }; + assertRestoring(ro, 'remote workspaces and folders'); + ro = { + files: [{ label: 'abc', fileUri: testFileURI }], + workspaces: [{ label: 'def', workspace: toWorkspace(testWSPath) }, { folderUri: testRemoteFolderURI }] + }; + assertRestoring(ro, 'labels'); + }); + + test('open 1_25', () => { + const v1_25_win = `{ + "workspaces": [ + { + "id": "2fa677dbdf5f771e775af84dea9feaea", + "configPath": "C:\\\\workspaces\\\\testing\\\\test.code-workspace" + }, + "C:\\\\workspaces\\\\testing\\\\test-ext", + { + "id": "d87a0241f8abc86b95c4e5481ebcbf56", + "configPath": "C:\\\\workspaces\\\\test.code-workspace" + } + ], + "files": [ + "C:\\\\workspaces\\\\test.code-workspace", + "C:\\\\workspaces\\\\testing\\\\test-ext\\\\.gitignore" + ] + }`; + + let actual = restoreRecentlyOpened(JSON.parse(v1_25_win)); + let expected: IRecentlyOpened = { + files: [{ fileUri: URI.file('C:\\workspaces\\test.code-workspace') }, { fileUri: URI.file('C:\\workspaces\\testing\\test-ext\\.gitignore') }], + workspaces: [ + { workspace: { id: '2fa677dbdf5f771e775af84dea9feaea', configPath: URI.file('C:\\workspaces\\testing\\test.code-workspace') } }, + { folderUri: URI.file('C:\\workspaces\\testing\\test-ext') }, + { workspace: { id: 'd87a0241f8abc86b95c4e5481ebcbf56', configPath: URI.file('C:\\workspaces\\test.code-workspace') } } + ] + }; + + assertEqualRecentlyOpened(actual, expected, 'v1_31_win'); + }); + + test('open 1_31', () => { + const v1_31_win = `{ + "workspaces2": [ + "file:///c%3A/workspaces/testing/test-ext", + "file:///c%3A/WINDOWS/system32", + { + "id": "d87a0241f8abc86b95c4e5481ebcbf56", + "configPath": "c:\\\\workspaces\\\\test.code-workspace" + } + ], + "files2": [ + "file:///c%3A/workspaces/vscode/.yarnrc" + ] + }`; + + let actual = restoreRecentlyOpened(JSON.parse(v1_31_win)); + let expected: IRecentlyOpened = { + files: [{ fileUri: URI.parse('file:///c%3A/workspaces/vscode/.yarnrc') }], + workspaces: [ + { folderUri: URI.parse('file:///c%3A/workspaces/testing/test-ext') }, + { folderUri: URI.parse('file:///c%3A/WINDOWS/system32') }, + { workspace: { id: 'd87a0241f8abc86b95c4e5481ebcbf56', configPath: URI.file('c:\\workspaces\\test.code-workspace') } } + ] + }; + + assertEqualRecentlyOpened(actual, expected, 'v1_31_win'); + }); + + test('open 1_32', () => { + const v1_32 = `{ + "workspaces3": [ + { + "id": "53b714b46ef1a2d4346568b4f591028c", + "configURIPath": "file:///home/user/workspaces/testing/custom.code-workspace" + }, + "file:///home/user/workspaces/testing/folding" + ], + "files2": [ + "file:///home/user/.config/code-oss-dev/storage.json" + ] + }`; + + let windowsState = restoreRecentlyOpened(JSON.parse(v1_32)); + let expected: IRecentlyOpened = { + files: [{ fileUri: URI.parse('file:///home/user/.config/code-oss-dev/storage.json') }], + workspaces: [ + { workspace: { id: '53b714b46ef1a2d4346568b4f591028c', configPath: URI.parse('file:///home/user/workspaces/testing/custom.code-workspace') } }, + { folderUri: URI.parse('file:///home/user/workspaces/testing/folding') } + ] + }; + + assertEqualRecentlyOpened(windowsState, expected, 'v1_32'); + }); + + test('open 1_33', () => { + const v1_33 = `{ + "workspaces3": [ + { + "id": "53b714b46ef1a2d4346568b4f591028c", + "configURIPath": "file:///home/user/workspaces/testing/custom.code-workspace" + }, + "file:///home/user/workspaces/testing/folding" + ], + "files2": [ + "file:///home/user/.config/code-oss-dev/storage.json" + ], + "workspaceLabels": [ + null, + "abc" + ], + "fileLabels": [ + "def" + ] + }`; + + let windowsState = restoreRecentlyOpened(JSON.parse(v1_33)); + let expected: IRecentlyOpened = { + files: [{ label: 'def', fileUri: URI.parse('file:///home/user/.config/code-oss-dev/storage.json') }], + workspaces: [ + { workspace: { id: '53b714b46ef1a2d4346568b4f591028c', configPath: URI.parse('file:///home/user/workspaces/testing/custom.code-workspace') } }, + { label: 'abc', folderUri: URI.parse('file:///home/user/workspaces/testing/folding') } + ] + }; + + assertEqualRecentlyOpened(windowsState, expected, 'v1_33'); + + }); + + +}); diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index e023c84808..5e009dd97a 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -9,10 +9,17 @@ import { Graph } from 'vs/platform/instantiation/common/graph'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ServiceIdentifier, IInstantiationService, ServicesAccessor, _util, optional } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { IdleValue } from 'vs/base/common/async'; // TRACING const _enableTracing = false; +// PROXY +// Ghetto-declare of the global Proxy object. This isn't the proper way +// but allows us to run this code in the browser without IE11. +declare var Proxy: any; +const _canUseProxy = typeof Proxy === 'function'; + export class InstantiationService implements IInstantiationService { _serviceBrand: any; @@ -151,7 +158,8 @@ export class InstantiationService implements IInstantiationService { // TODO@joh use the graph to find a cycle // a weak heuristic for cycle checks - if (count++ > 100) { + // {{SQL CARBON EDIT}} we hit ~102 with our services; when they implement graph cycle; we can remove + if (count++ > 200) { throwCycleError(); } @@ -205,8 +213,26 @@ export class InstantiationService implements IInstantiationService { } } - protected _createServiceInstance(ctor: any, args: any[] = [], _supportsDelayedInstantiation: boolean, _trace: Trace): T { - return this._createInstance(ctor, args, _trace); + private _createServiceInstance(ctor: any, args: any[] = [], _supportsDelayedInstantiation: boolean, _trace: Trace): T { + if (!_supportsDelayedInstantiation || !_canUseProxy) { + // eager instantiation or no support JS proxies (e.g. IE11) + return this._createInstance(ctor, args, _trace); + + } else { + // Return a proxy object that's backed by an idle value. That + // strategy is to instantiate services in our idle time or when actually + // needed but not when injected into a consumer + const idle = new IdleValue(() => this._createInstance(ctor, args, _trace)); + return new Proxy(Object.create(null), { + get(_target: T, prop: PropertyKey): any { + return idle.getValue()[prop]; + }, + set(_target: T, p: PropertyKey, value: any): boolean { + idle.getValue()[p] = value; + return true; + } + }); + } } } diff --git a/src/vs/platform/instantiation/node/instantiationService.ts b/src/vs/platform/instantiation/node/instantiationService.ts deleted file mode 100644 index f91f51e704..0000000000 --- a/src/vs/platform/instantiation/node/instantiationService.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { IdleValue } from 'vs/base/common/async'; -import { InstantiationService as BaseInstantiationService } from 'vs/platform/instantiation/common/instantiationService'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; - -// this is in the /node/-layer because it depends on Proxy which isn't available -// in IE11 and therefore not in the /common/-layer - -export class InstantiationService extends BaseInstantiationService { - - createChild(services: ServiceCollection): IInstantiationService { - return new InstantiationService(services, this._strict, this); - } - - protected _createServiceInstance(ctor: any, args: any[] = [], supportsDelayedInstantiation: boolean, _trace): T { - if (supportsDelayedInstantiation) { - return InstantiationService._newIdleProxyService(() => super._createServiceInstance(ctor, args, supportsDelayedInstantiation, _trace)); - } else { - return super._createServiceInstance(ctor, args, supportsDelayedInstantiation, _trace); - } - } - - private static _newIdleProxyService(executor: () => T): T { - const idle = new IdleValue(executor); - return new Proxy(Object.create(null), { - get(_target: T, prop: PropertyKey): any { - return idle.getValue()[prop]; - }, - set(_target: T, p: PropertyKey, value: any): boolean { - idle.getValue()[p] = value; - return true; - } - }); - } -} diff --git a/src/vs/platform/ipc/electron-browser/mainProcessService.ts b/src/vs/platform/ipc/electron-browser/mainProcessService.ts new file mode 100644 index 0000000000..97d20f2e77 --- /dev/null +++ b/src/vs/platform/ipc/electron-browser/mainProcessService.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Client } from 'vs/base/parts/ipc/electron-browser/ipc.electron-browser'; +import { Disposable } from 'vs/base/common/lifecycle'; + +export const IMainProcessService = createDecorator('mainProcessService'); + +export interface IMainProcessService { + + _serviceBrand: ServiceIdentifier; + + getChannel(channelName: string): IChannel; + + registerChannel(channelName: string, channel: IServerChannel): void; +} + +export class MainProcessService extends Disposable implements IMainProcessService { + + _serviceBrand: ServiceIdentifier; + + private mainProcessConnection: Client; + + constructor( + windowId: number + ) { + super(); + + this.mainProcessConnection = this._register(new Client(`window:${windowId}`)); + } + + getChannel(channelName: string): IChannel { + return this.mainProcessConnection.getChannel(channelName); + } + + registerChannel(channelName: string, channel: IServerChannel): void { + this.mainProcessConnection.registerChannel(channelName, channel); + } +} diff --git a/src/vs/platform/ipc/electron-browser/sharedProcessService.ts b/src/vs/platform/ipc/electron-browser/sharedProcessService.ts new file mode 100644 index 0000000000..1bf8d34da4 --- /dev/null +++ b/src/vs/platform/ipc/electron-browser/sharedProcessService.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { Client, connect } from 'vs/base/parts/ipc/node/ipc.net'; +import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { getDelayedChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; + +export const ISharedProcessService = createDecorator('sharedProcessService'); + +export interface ISharedProcessService { + + _serviceBrand: ServiceIdentifier; + + getChannel(channelName: string): IChannel; + + registerChannel(channelName: string, channel: IServerChannel): void; +} + +export class SharedProcessService implements ISharedProcessService { + + _serviceBrand: ServiceIdentifier; + + private withSharedProcessConnection: Promise>; + + constructor( + @IWindowsService windowsService: IWindowsService, + @IWindowService windowService: IWindowService, + @IEnvironmentService environmentService: IEnvironmentService + ) { + this.withSharedProcessConnection = windowsService.whenSharedProcessReady() + .then(() => connect(environmentService.sharedIPCHandle, `window:${windowService.getCurrentWindowId()}`)); + } + + getChannel(channelName: string): IChannel { + return getDelayedChannel(this.withSharedProcessConnection.then(connection => connection.getChannel(channelName))); + } + + registerChannel(channelName: string, channel: IServerChannel): void { + this.withSharedProcessConnection.then(connection => connection.registerChannel(channelName, channel)); + } +} diff --git a/src/vs/platform/issue/electron-browser/issueService.ts b/src/vs/platform/issue/electron-browser/issueService.ts new file mode 100644 index 0000000000..29526af74a --- /dev/null +++ b/src/vs/platform/issue/electron-browser/issueService.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IIssueService, IssueReporterData, ProcessExplorerData } from 'vs/platform/issue/common/issue'; +import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; + +export class IssueService implements IIssueService { + + _serviceBrand: ServiceIdentifier; + + private channel: IChannel; + + constructor(@IMainProcessService mainProcessService: IMainProcessService) { + this.channel = mainProcessService.getChannel('issue'); + } + + openReporter(data: IssueReporterData): Promise { + return this.channel.call('openIssueReporter', data); + } + + openProcessExplorer(data: ProcessExplorerData): Promise { + return this.channel.call('openProcessExplorer', data); + } +} diff --git a/src/vs/platform/issue/node/issueIpc.ts b/src/vs/platform/issue/node/issueIpc.ts index 1af5b1398a..86fc7372ab 100644 --- a/src/vs/platform/issue/node/issueIpc.ts +++ b/src/vs/platform/issue/node/issueIpc.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; -import { IIssueService, IssueReporterData, ProcessExplorerData } from '../common/issue'; +import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; +import { IIssueService } from 'vs/platform/issue/common/issue'; export class IssueChannel implements IServerChannel { @@ -25,19 +25,4 @@ export class IssueChannel implements IServerChannel { throw new Error(`Call not found: ${command}`); } -} - -export class IssueChannelClient implements IIssueService { - - _serviceBrand: any; - - constructor(private channel: IChannel) { } - - openReporter(data: IssueReporterData): Promise { - return this.channel.call('openIssueReporter', data); - } - - openProcessExplorer(data: ProcessExplorerData): Promise { - return this.channel.call('openProcessExplorer', data); - } } \ No newline at end of file diff --git a/src/vs/platform/jsonschemas/common/jsonContributionRegistry.ts b/src/vs/platform/jsonschemas/common/jsonContributionRegistry.ts index b674e57813..f9a56e29dc 100644 --- a/src/vs/platform/jsonschemas/common/jsonContributionRegistry.ts +++ b/src/vs/platform/jsonschemas/common/jsonContributionRegistry.ts @@ -12,7 +12,7 @@ export const Extensions = { }; export interface ISchemaContributions { - schemas?: { [id: string]: IJSONSchema }; + schemas: { [id: string]: IJSONSchema }; } export interface IJSONContributionRegistry { diff --git a/src/vs/platform/keybinding/common/abstractKeybindingService.ts b/src/vs/platform/keybinding/common/abstractKeybindingService.ts index 14b8710036..0f94f131ac 100644 --- a/src/vs/platform/keybinding/common/abstractKeybindingService.ts +++ b/src/vs/platform/keybinding/common/abstractKeybindingService.ts @@ -17,6 +17,7 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { withNullAsUndefined } from 'vs/base/common/types'; interface CurrentChord { keypress: string; @@ -70,6 +71,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK public abstract resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[]; public abstract resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding; public abstract resolveUserBinding(userBinding: string): ResolvedKeybinding[]; + public abstract _dumpDebugInfo(): string; public getDefaultKeybindingsContent(): string { return ''; @@ -98,7 +100,7 @@ export abstract class AbstractKeybindingService extends Disposable implements IK if (!result) { return undefined; } - return result.resolvedKeybinding || undefined; + return withNullAsUndefined(result.resolvedKeybinding); } public dispatchEvent(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean { diff --git a/src/vs/platform/keybinding/common/baseResolvedKeybinding.ts b/src/vs/platform/keybinding/common/baseResolvedKeybinding.ts new file mode 100644 index 0000000000..acfe5d8d85 --- /dev/null +++ b/src/vs/platform/keybinding/common/baseResolvedKeybinding.ts @@ -0,0 +1,78 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { OperatingSystem } from 'vs/base/common/platform'; +import { illegalArgument } from 'vs/base/common/errors'; +import { Modifiers, UILabelProvider, AriaLabelProvider, ElectronAcceleratorLabelProvider, UserSettingsLabelProvider } from 'vs/base/common/keybindingLabels'; +import { ResolvedKeybinding, ResolvedKeybindingPart } from 'vs/base/common/keyCodes'; + +export abstract class BaseResolvedKeybinding extends ResolvedKeybinding { + + protected readonly _os: OperatingSystem; + protected readonly _parts: T[]; + + constructor(os: OperatingSystem, parts: T[]) { + super(); + if (parts.length === 0) { + throw illegalArgument(`parts`); + } + this._os = os; + this._parts = parts; + } + + public getLabel(): string | null { + return UILabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getLabel(keybinding)); + } + + public getAriaLabel(): string | null { + return AriaLabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getAriaLabel(keybinding)); + } + + public getElectronAccelerator(): string | null { + if (this._parts.length > 1) { + // Electron cannot handle chords + return null; + } + return ElectronAcceleratorLabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getElectronAccelerator(keybinding)); + } + + public getUserSettingsLabel(): string | null { + return UserSettingsLabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getUserSettingsLabel(keybinding)); + } + + public isWYSIWYG(): boolean { + return this._parts.every((keybinding) => this._isWYSIWYG(keybinding)); + } + + public isChord(): boolean { + return (this._parts.length > 1); + } + + public getParts(): ResolvedKeybindingPart[] { + return this._parts.map((keybinding) => this._getPart(keybinding)); + } + + private _getPart(keybinding: T): ResolvedKeybindingPart { + return new ResolvedKeybindingPart( + keybinding.ctrlKey, + keybinding.shiftKey, + keybinding.altKey, + keybinding.metaKey, + this._getLabel(keybinding), + this._getAriaLabel(keybinding) + ); + } + + public getDispatchParts(): (string | null)[] { + return this._parts.map((keybinding) => this._getDispatchPart(keybinding)); + } + + protected abstract _getLabel(keybinding: T): string | null; + protected abstract _getAriaLabel(keybinding: T): string | null; + protected abstract _getElectronAccelerator(keybinding: T): string | null; + protected abstract _getUserSettingsLabel(keybinding: T): string | null; + protected abstract _isWYSIWYG(keybinding: T): boolean; + protected abstract _getDispatchPart(keybinding: T): string | null; +} diff --git a/src/vs/platform/keybinding/common/keybinding.ts b/src/vs/platform/keybinding/common/keybinding.ts index 95b060d3e9..41c0a6ca6e 100644 --- a/src/vs/platform/keybinding/common/keybinding.ts +++ b/src/vs/platform/keybinding/common/keybinding.ts @@ -28,6 +28,8 @@ export interface IKeybindingEvent { } export interface IKeyboardEvent { + readonly _standardKeyboardEventBrand: true; + readonly ctrlKey: boolean; readonly shiftKey: boolean; readonly altKey: boolean; @@ -89,5 +91,7 @@ export interface IKeybindingService { * text box. *Note* that the results of this function can be incorrect. */ mightProducePrintableCharacter(event: IKeyboardEvent): boolean; + + _dumpDebugInfo(): string; } diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index de25aa60b1..f8680b7349 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -40,12 +40,13 @@ export class KeybindingResolver { this._keybindings = KeybindingResolver.combine(defaultKeybindings, overrides); for (let i = 0, len = this._keybindings.length; i < len; i++) { let k = this._keybindings[i]; - if (k.keypressFirstPart === null) { + if (k.keypressParts.length === 0) { // unbound continue; } - this._addKeyPress(k.keypressFirstPart, k); + // TODO@chords + this._addKeyPress(k.keypressParts[0], k); } } @@ -53,10 +54,12 @@ export class KeybindingResolver { if (defaultKb.command !== command) { return false; } - if (keypressFirstPart && defaultKb.keypressFirstPart !== keypressFirstPart) { + // TODO@chords + if (keypressFirstPart && defaultKb.keypressParts[0] !== keypressFirstPart) { return false; } - if (keypressChordPart && defaultKb.keypressChordPart !== keypressChordPart) { + // TODO@chords + if (keypressChordPart && defaultKb.keypressParts[1] !== keypressChordPart) { return false; } if (when) { @@ -84,8 +87,9 @@ export class KeybindingResolver { } const command = override.command.substr(1); - const keypressFirstPart = override.keypressFirstPart; - const keypressChordPart = override.keypressChordPart; + // TODO@chords + const keypressFirstPart = override.keypressParts[0]; + const keypressChordPart = override.keypressParts[1]; const when = override.when; for (let j = defaults.length - 1; j >= 0; j--) { if (this._isTargetedForRemoval(defaults[j], keypressFirstPart, keypressChordPart, command, when)) { @@ -114,10 +118,11 @@ export class KeybindingResolver { continue; } - const conflictIsChord = (conflict.keypressChordPart !== null); - const itemIsChord = (item.keypressChordPart !== null); + const conflictIsChord = (conflict.keypressParts.length > 1); + const itemIsChord = (item.keypressParts.length > 1); - if (conflictIsChord && itemIsChord && conflict.keypressChordPart !== item.keypressChordPart) { + // TODO@chords + if (conflictIsChord && itemIsChord && conflict.keypressParts[1] !== item.keypressParts[1]) { // The conflict only shares the chord start with this command continue; } @@ -247,7 +252,8 @@ export class KeybindingResolver { lookupMap = []; for (let i = 0, len = candidates.length; i < len; i++) { let candidate = candidates[i]; - if (candidate.keypressChordPart === keypress) { + // TODO@chords + if (candidate.keypressParts[1] === keypress) { lookupMap.push(candidate); } } @@ -266,7 +272,8 @@ export class KeybindingResolver { return null; } - if (currentChord === null && result.keypressChordPart !== null) { + // TODO@chords + if (currentChord === null && result.keypressParts.length > 1 && result.keypressParts[1] !== null) { return { enterChord: true, commandId: null, @@ -307,29 +314,31 @@ export class KeybindingResolver { public static getAllUnboundCommands(boundCommands: Map): string[] { const unboundCommands: string[] = []; const seenMap: Map = new Map(); - const addCommand = id => { + const addCommand = (id: string, includeCommandWithArgs: boolean) => { if (seenMap.has(id)) { return; } - seenMap.set(id); + seenMap.set(id, true); if (id[0] === '_' || id.indexOf('vscode.') === 0) { // private command return; } if (boundCommands.get(id) === true) { return; } - const command = CommandsRegistry.getCommand(id); - if (command && typeof command.description === 'object' - && isNonEmptyArray((command.description).args)) { // command with args - return; + if (!includeCommandWithArgs) { + const command = CommandsRegistry.getCommand(id); + if (command && typeof command.description === 'object' + && isNonEmptyArray((command.description).args)) { // command with args + return; + } } unboundCommands.push(id); }; for (const id in MenuRegistry.getCommands()) { - addCommand(id); + addCommand(id, true); } for (const id in CommandsRegistry.getCommands()) { - addCommand(id); + addCommand(id, false); } return unboundCommands; diff --git a/src/vs/platform/keybinding/common/keybindingsRegistry.ts b/src/vs/platform/keybinding/common/keybindingsRegistry.ts index 1a6e3d0c19..b78a26b6f4 100644 --- a/src/vs/platform/keybinding/common/keybindingsRegistry.ts +++ b/src/vs/platform/keybinding/common/keybindingsRegistry.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyCode, Keybinding, KeybindingType, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; +import { KeyCode, Keybinding, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { CommandsRegistry, ICommandHandler, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -19,7 +19,7 @@ export interface IKeybindingItem { } export interface IKeybindings { - primary: number; + primary?: number; secondary?: number[]; win?: { primary: number; @@ -210,11 +210,7 @@ class KeybindingsRegistryImpl implements IKeybindingsRegistry { private _registerDefaultKeybinding(keybinding: Keybinding, commandId: string, commandArgs: any, weight1: number, weight2: number, when: ContextKeyExpr | null | undefined): void { if (OS === OperatingSystem.Windows) { - if (keybinding.type === KeybindingType.Chord) { - this._assertNoCtrlAlt(keybinding.firstPart, commandId); - } else { - this._assertNoCtrlAlt(keybinding, commandId); - } + this._assertNoCtrlAlt(keybinding.parts[0], commandId); } this._coreKeybindings.push({ keybinding: keybinding, diff --git a/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts b/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts index a17cdc1f6e..6ce44fd688 100644 --- a/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts +++ b/src/vs/platform/keybinding/common/resolvedKeybindingItem.ts @@ -11,8 +11,7 @@ export class ResolvedKeybindingItem { _resolvedKeybindingItemBrand: void; public readonly resolvedKeybinding: ResolvedKeybinding | null; - public readonly keypressFirstPart: string | null; - public readonly keypressChordPart: string | null; + public readonly keypressParts: string[]; public readonly bubble: boolean; public readonly command: string | null; public readonly commandArgs: any; @@ -21,14 +20,7 @@ export class ResolvedKeybindingItem { constructor(resolvedKeybinding: ResolvedKeybinding | null, command: string | null, commandArgs: any, when: ContextKeyExpr | null, isDefault: boolean) { this.resolvedKeybinding = resolvedKeybinding; - if (resolvedKeybinding) { - let [keypressFirstPart, keypressChordPart] = resolvedKeybinding.getDispatchParts(); - this.keypressFirstPart = keypressFirstPart; - this.keypressChordPart = keypressChordPart; - } else { - this.keypressFirstPart = null; - this.keypressChordPart = null; - } + this.keypressParts = resolvedKeybinding ? removeElementsAfterNulls(resolvedKeybinding.getDispatchParts()) : []; this.bubble = (command ? command.charCodeAt(0) === CharCode.Caret : false); this.command = this.bubble ? command!.substr(1) : command; this.commandArgs = commandArgs; @@ -36,3 +28,16 @@ export class ResolvedKeybindingItem { this.isDefault = isDefault; } } + +export function removeElementsAfterNulls(arr: (T | null)[]): T[] { + let result: T[] = []; + for (let i = 0, len = arr.length; i < len; i++) { + const element = arr[i]; + if (!element) { + // stop processing at first encountered null + return result; + } + result.push(element); + } + return result; +} diff --git a/src/vs/platform/keybinding/common/usLayoutResolvedKeybinding.ts b/src/vs/platform/keybinding/common/usLayoutResolvedKeybinding.ts index d56259028a..283c0676f1 100644 --- a/src/vs/platform/keybinding/common/usLayoutResolvedKeybinding.ts +++ b/src/vs/platform/keybinding/common/usLayoutResolvedKeybinding.ts @@ -3,31 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { KeyCode, KeyCodeUtils, Keybinding, KeybindingType, ResolvedKeybinding, ResolvedKeybindingPart, SimpleKeybinding } from 'vs/base/common/keyCodes'; -import { AriaLabelProvider, ElectronAcceleratorLabelProvider, UILabelProvider, UserSettingsLabelProvider } from 'vs/base/common/keybindingLabels'; +import { KeyCode, KeyCodeUtils, Keybinding, SimpleKeybinding } from 'vs/base/common/keyCodes'; import { OperatingSystem } from 'vs/base/common/platform'; +import { BaseResolvedKeybinding } from 'vs/platform/keybinding/common/baseResolvedKeybinding'; /** * Do not instantiate. Use KeybindingService to get a ResolvedKeybinding seeded with information about the current kb layout. */ -export class USLayoutResolvedKeybinding extends ResolvedKeybinding { +export class USLayoutResolvedKeybinding extends BaseResolvedKeybinding { - private readonly _os: OperatingSystem; - private readonly _firstPart: SimpleKeybinding; - private readonly _chordPart: SimpleKeybinding | null; - - constructor(actual: Keybinding, OS: OperatingSystem) { - super(); - this._os = OS; - if (!actual) { - throw new Error(`Invalid USLayoutResolvedKeybinding`); - } else if (actual.type === KeybindingType.Chord) { - this._firstPart = actual.firstPart; - this._chordPart = actual.chordPart; - } else { - this._firstPart = actual; - this._chordPart = null; - } + constructor(actual: Keybinding, os: OperatingSystem) { + super(os, actual.parts); } private _keyCodeToUILabel(keyCode: KeyCode): string { @@ -46,38 +32,20 @@ export class USLayoutResolvedKeybinding extends ResolvedKeybinding { return KeyCodeUtils.toString(keyCode); } - private _getUILabelForKeybinding(keybinding: SimpleKeybinding | null): string | null { - if (!keybinding) { - return null; - } + protected _getLabel(keybinding: SimpleKeybinding): string | null { if (keybinding.isDuplicateModifierCase()) { return ''; } return this._keyCodeToUILabel(keybinding.keyCode); } - public getLabel(): string | null { - let firstPart = this._getUILabelForKeybinding(this._firstPart); - let chordPart = this._getUILabelForKeybinding(this._chordPart); - return UILabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, this._os); - } - - private _getAriaLabelForKeybinding(keybinding: SimpleKeybinding | null): string | null { - if (!keybinding) { - return null; - } + protected _getAriaLabel(keybinding: SimpleKeybinding): string | null { if (keybinding.isDuplicateModifierCase()) { return ''; } return KeyCodeUtils.toString(keybinding.keyCode); } - public getAriaLabel(): string | null { - let firstPart = this._getAriaLabelForKeybinding(this._firstPart); - let chordPart = this._getAriaLabelForKeybinding(this._chordPart); - return AriaLabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, this._os); - } - private _keyCodeToElectronAccelerator(keyCode: KeyCode): string | null { if (keyCode >= KeyCode.NUMPAD_0 && keyCode <= KeyCode.NUMPAD_DIVIDE) { // Electron cannot handle numpad keys @@ -98,73 +66,27 @@ export class USLayoutResolvedKeybinding extends ResolvedKeybinding { return KeyCodeUtils.toString(keyCode); } - private _getElectronAcceleratorLabelForKeybinding(keybinding: SimpleKeybinding | null): string | null { - if (!keybinding) { - return null; - } + protected _getElectronAccelerator(keybinding: SimpleKeybinding): string | null { if (keybinding.isDuplicateModifierCase()) { return null; } return this._keyCodeToElectronAccelerator(keybinding.keyCode); } - public getElectronAccelerator(): string | null { - if (this._chordPart !== null) { - // Electron cannot handle chords - return null; - } - - let firstPart = this._getElectronAcceleratorLabelForKeybinding(this._firstPart); - return ElectronAcceleratorLabelProvider.toLabel(this._firstPart, firstPart, null, null, this._os); - } - - private _getUserSettingsLabelForKeybinding(keybinding: SimpleKeybinding | null): string | null { - if (!keybinding) { - return null; - } + protected _getUserSettingsLabel(keybinding: SimpleKeybinding): string | null { if (keybinding.isDuplicateModifierCase()) { return ''; } - return KeyCodeUtils.toUserSettingsUS(keybinding.keyCode); - } - - public getUserSettingsLabel(): string | null { - let firstPart = this._getUserSettingsLabelForKeybinding(this._firstPart); - let chordPart = this._getUserSettingsLabelForKeybinding(this._chordPart); - let result = UserSettingsLabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, this._os); + const result = KeyCodeUtils.toUserSettingsUS(keybinding.keyCode); return (result ? result.toLowerCase() : result); } - public isWYSIWYG(): boolean { + protected _isWYSIWYG(): boolean { return true; } - public isChord(): boolean { - return (this._chordPart ? true : false); - } - - public getParts(): [ResolvedKeybindingPart, ResolvedKeybindingPart | null] { - return [ - this._toResolvedKeybindingPart(this._firstPart), - this._chordPart ? this._toResolvedKeybindingPart(this._chordPart) : null - ]; - } - - private _toResolvedKeybindingPart(keybinding: SimpleKeybinding): ResolvedKeybindingPart { - return new ResolvedKeybindingPart( - keybinding.ctrlKey, - keybinding.shiftKey, - keybinding.altKey, - keybinding.metaKey, - this._getUILabelForKeybinding(keybinding), - this._getAriaLabelForKeybinding(keybinding) - ); - } - - public getDispatchParts(): [string | null, string | null] { - let firstPart = this._firstPart ? USLayoutResolvedKeybinding.getDispatchStr(this._firstPart) : null; - let chordPart = this._chordPart ? USLayoutResolvedKeybinding.getDispatchStr(this._chordPart) : null; - return [firstPart, chordPart]; + protected _getDispatchPart(keybinding: SimpleKeybinding): string | null { + return USLayoutResolvedKeybinding.getDispatchStr(keybinding); } public static getDispatchStr(keybinding: SimpleKeybinding): string | null { diff --git a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts index 0d0830d17a..777ab2d2aa 100644 --- a/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts +++ b/src/vs/platform/keybinding/test/common/abstractKeybindingService.test.ts @@ -61,7 +61,7 @@ suite('AbstractKeybindingService', () => { keyboardEvent.altKey, keyboardEvent.metaKey, keyboardEvent.keyCode - ); + ).toChord(); return this.resolveKeybinding(keybinding)[0]; } @@ -72,6 +72,7 @@ suite('AbstractKeybindingService', () => { public testDispatch(kb: number): boolean { const keybinding = createSimpleKeybinding(kb, OS); return this._dispatch({ + _standardKeyboardEventBrand: true, ctrlKey: keybinding.ctrlKey, shiftKey: keybinding.shiftKey, altKey: keybinding.altKey, @@ -80,6 +81,10 @@ suite('AbstractKeybindingService', () => { code: null! }, null!); } + + public _dumpDebugInfo(): string { + return ''; + } } let createTestKeybindingService: (items: ResolvedKeybindingItem[], contextValue?: any) => TestKeybindingService = null!; diff --git a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts index 0677469bd3..15b318b08c 100644 --- a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { KeyChord, KeyCode, KeyMod, KeybindingType, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; +import { KeyChord, KeyCode, KeyMod, SimpleKeybinding, createKeybinding, createSimpleKeybinding } from 'vs/base/common/keyCodes'; import { OS } from 'vs/base/common/platform'; import { ContextKeyAndExpr, ContextKeyExpr, IContext } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; @@ -37,7 +37,7 @@ suite('KeybindingResolver', () => { test('resolve key', function () { let keybinding = KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z; - let runtimeKeybinding = createKeybinding(keybinding, OS); + let runtimeKeybinding = createSimpleKeybinding(keybinding, OS); let contextRules = ContextKeyExpr.equals('bar', 'baz'); let keybindingItem = kbItem(keybinding, 'yes', null, contextRules, true); @@ -45,19 +45,19 @@ suite('KeybindingResolver', () => { assert.equal(KeybindingResolver.contextMatchesRules(createContext({ bar: 'bz' }), contextRules), false); let resolver = new KeybindingResolver([keybindingItem], []); - assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(runtimeKeybinding))!.commandId, 'yes'); - assert.equal(resolver.resolve(createContext({ bar: 'bz' }), null, getDispatchStr(runtimeKeybinding)), null); + assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(runtimeKeybinding))!.commandId, 'yes'); + assert.equal(resolver.resolve(createContext({ bar: 'bz' }), null, getDispatchStr(runtimeKeybinding)), null); }); test('resolve key with arguments', function () { let commandArgs = { text: 'no' }; let keybinding = KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z; - let runtimeKeybinding = createKeybinding(keybinding, OS); + let runtimeKeybinding = createSimpleKeybinding(keybinding, OS); let contextRules = ContextKeyExpr.equals('bar', 'baz'); let keybindingItem = kbItem(keybinding, 'yes', commandArgs, contextRules, true); let resolver = new KeybindingResolver([keybindingItem], []); - assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(runtimeKeybinding))!.commandArgs, commandArgs); + assert.equal(resolver.resolve(createContext({ bar: 'baz' }), null, getDispatchStr(runtimeKeybinding))!.commandArgs, commandArgs); }); test('KeybindingResolver.combine simple 1', function () { @@ -346,24 +346,24 @@ suite('KeybindingResolver', () => { let testResolve = (ctx: IContext, _expectedKey: number, commandId: string) => { const expectedKey = createKeybinding(_expectedKey, OS)!; - if (expectedKey.type === KeybindingType.Chord) { - let firstPart = getDispatchStr(expectedKey.firstPart); - let chordPart = getDispatchStr(expectedKey.chordPart); - - let result = resolver.resolve(ctx, null, firstPart)!; - assert.ok(result !== null, 'Enters chord for ' + commandId); - assert.equal(result.commandId, null, 'Enters chord for ' + commandId); - assert.equal(result.enterChord, true, 'Enters chord for ' + commandId); - - result = resolver.resolve(ctx, firstPart, chordPart)!; - assert.ok(result !== null, 'Enters chord for ' + commandId); - assert.equal(result.commandId, commandId, 'Finds chorded command ' + commandId); - assert.equal(result.enterChord, false, 'Finds chorded command ' + commandId); - } else { - let result = resolver.resolve(ctx, null, getDispatchStr(expectedKey))!; - assert.ok(result !== null, 'Finds command ' + commandId); - assert.equal(result.commandId, commandId, 'Finds command ' + commandId); - assert.equal(result.enterChord, false, 'Finds command ' + commandId); + let previousPart: (string | null) = null; + for (let i = 0, len = expectedKey.parts.length; i < len; i++) { + let part = getDispatchStr(expectedKey.parts[i]); + let result = resolver.resolve(ctx, previousPart, part); + if (i === len - 1) { + // if it's the final part, then we should find a valid command, + // and there should not be a chord. + assert.ok(result !== null, `Enters chord for ${commandId} at part ${i}`); + assert.equal(result!.commandId, commandId, `Enters chord for ${commandId} at part ${i}`); + assert.equal(result!.enterChord, false, `Enters chord for ${commandId} at part ${i}`); + } else { + // if it's not the final part, then we should not find a valid command, + // and there should be a chord. + assert.ok(result !== null, `Enters chord for ${commandId} at part ${i}`); + assert.equal(result!.commandId, null, `Enters chord for ${commandId} at part ${i}`); + assert.equal(result!.enterChord, true, `Enters chord for ${commandId} at part ${i}`); + } + previousPart = part; } }; diff --git a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts index d54740927a..c413d594b5 100644 --- a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts +++ b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts @@ -98,7 +98,7 @@ export class MockKeybindingService implements IKeybindingService { keyboardEvent.metaKey, keyboardEvent.keyCode ); - return this.resolveKeybinding(keybinding)[0]; + return this.resolveKeybinding(keybinding.toChord())[0]; } public resolveUserBinding(userBinding: string): ResolvedKeybinding[] { @@ -132,4 +132,8 @@ export class MockKeybindingService implements IKeybindingService { public mightProducePrintableCharacter(e: IKeyboardEvent): boolean { return false; } + + public _dumpDebugInfo(): string { + return ''; + } } diff --git a/src/vs/platform/label/common/label.ts b/src/vs/platform/label/common/label.ts index e6f209805b..25067865dc 100644 --- a/src/vs/platform/label/common/label.ts +++ b/src/vs/platform/label/common/label.ts @@ -8,12 +8,9 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; import { IWorkspace } from 'vs/platform/workspace/common/workspace'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { basename as resourceBasename } from 'vs/base/common/resources'; -import { isLinux } from 'vs/base/common/platform'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import { localize } from 'vs/nls'; -import { isParent } from 'vs/platform/files/common/files'; -import { basename } from 'vs/base/common/paths'; +import { isEqualOrParent, basename } from 'vs/base/common/resources'; export interface ILabelService { _serviceBrand: any; @@ -22,9 +19,10 @@ export interface ILabelService { * If relative is passed returns a label relative to the workspace root that the uri belongs to. * If noPrefix is passed does not tildify the label and also does not prepand the root name for relative labels in a multi root scenario. */ - getUriLabel(resource: URI, options?: { relative?: boolean, noPrefix?: boolean }): string; + getUriLabel(resource: URI, options?: { relative?: boolean, noPrefix?: boolean, endWithSeparator?: boolean }): string; getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string; - getHostLabel(): string; + getHostLabel(scheme: string, authority?: string): string; + getSeparator(scheme: string, authority?: string): '/' | '\\'; registerFormatter(formatter: ResourceLabelFormatter): IDisposable; onDidChangeFormatters: Event; } @@ -32,6 +30,7 @@ export interface ILabelService { export interface ResourceLabelFormatter { scheme: string; authority?: string; + priority?: boolean; formatting: ResourceLabelFormatting; } @@ -46,12 +45,12 @@ export interface ResourceLabelFormatting { const LABEL_SERVICE_ID = 'label'; -export function getSimpleWorkspaceLabel(workspace: IWorkspaceIdentifier | URI, workspaceHome: string): string { +export function getSimpleWorkspaceLabel(workspace: IWorkspaceIdentifier | URI, workspaceHome: URI): string { if (isSingleFolderWorkspaceIdentifier(workspace)) { - return resourceBasename(workspace); + return basename(workspace); } // Workspace: Untitled - if (isParent(workspace.configPath, workspaceHome, !isLinux /* ignore case */)) { + if (isEqualOrParent(workspace.configPath, workspaceHome)) { return localize('untitledWorkspace', "Untitled (Workspace)"); } diff --git a/src/vs/platform/launch/electron-main/launchService.ts b/src/vs/platform/launch/electron-main/launchService.ts index 334549fa4e..7a4ec7b08e 100644 --- a/src/vs/platform/launch/electron-main/launchService.ts +++ b/src/vs/platform/launch/electron-main/launchService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { ILogService } from 'vs/platform/log/common/log'; import { IURLService } from 'vs/platform/url/common/url'; import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; @@ -165,7 +165,7 @@ export class LaunchService implements ILaunchService { // Special case extension development if (!!args.extensionDevelopmentPath) { - this.windowsMainService.openExtensionDevelopmentHostWindow({ context, cli: args, userEnv }); + this.windowsMainService.openExtensionDevelopmentHostWindow(args.extensionDevelopmentPath, { context, cli: args, userEnv }); } // Start without file/folder arguments @@ -279,12 +279,16 @@ export class LaunchService implements ILaunchService { if (window.openedFolderUri) { folderURIs.push(window.openedFolderUri); } else if (window.openedWorkspace) { - const resolvedWorkspace = this.workspacesMainService.resolveWorkspaceSync(window.openedWorkspace.configPath); + // workspace folders can only be shown for local workspaces + const workspaceConfigPath = window.openedWorkspace.configPath; + const resolvedWorkspace = this.workspacesMainService.resolveLocalWorkspaceSync(workspaceConfigPath); if (resolvedWorkspace) { const rootFolders = resolvedWorkspace.folders; rootFolders.forEach(root => { folderURIs.push(root.uri); }); + } else { + //TODO: can we add the workspace file here? } } diff --git a/src/vs/platform/layout/browser/layoutService.ts b/src/vs/platform/layout/browser/layoutService.ts new file mode 100644 index 0000000000..17bc5d5f50 --- /dev/null +++ b/src/vs/platform/layout/browser/layoutService.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const ILayoutService = createDecorator('layoutService'); + +export interface IDimension { + readonly width: number; + readonly height: number; +} + +export interface ILayoutService { + + _serviceBrand: any; + + /** + * The dimensions of the container. + */ + readonly dimension: IDimension; + + /** + * Container of the application. + */ + readonly container: HTMLElement; + + /** + * An event that is emitted when the container is layed out. The + * event carries the dimensions of the container as part of it. + */ + readonly onLayout: Event; + + + /** + * Indicates if the layout has a workbench surrounding the editor + */ + readonly hasWorkbench: boolean; +} diff --git a/src/vs/platform/lifecycle/common/lifecycle.ts b/src/vs/platform/lifecycle/common/lifecycle.ts index 3c4db299df..ac1c8b6c99 100644 --- a/src/vs/platform/lifecycle/common/lifecycle.ts +++ b/src/vs/platform/lifecycle/common/lifecycle.ts @@ -133,7 +133,7 @@ export interface ILifecycleService { /** * A flag indicating in what phase of the lifecycle we currently are. */ - readonly phase: LifecyclePhase; + phase: LifecyclePhase; /** * Fired before shutdown happens. Allows listeners to veto against the diff --git a/src/vs/platform/lifecycle/common/lifecycleService.ts b/src/vs/platform/lifecycle/common/lifecycleService.ts new file mode 100644 index 0000000000..e056eab5cf --- /dev/null +++ b/src/vs/platform/lifecycle/common/lifecycleService.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event, Emitter } from 'vs/base/common/event'; +import { Barrier } from 'vs/base/common/async'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ILifecycleService, BeforeShutdownEvent, WillShutdownEvent, StartupKind, LifecyclePhase, LifecyclePhaseToString } from 'vs/platform/lifecycle/common/lifecycle'; +import { ILogService } from 'vs/platform/log/common/log'; +import { mark } from 'vs/base/common/performance'; + +export abstract class AbstractLifecycleService extends Disposable implements ILifecycleService { + + _serviceBrand: any; + + protected readonly _onBeforeShutdown = this._register(new Emitter()); + get onBeforeShutdown(): Event { return this._onBeforeShutdown.event; } + + protected readonly _onWillShutdown = this._register(new Emitter()); + get onWillShutdown(): Event { return this._onWillShutdown.event; } + + protected readonly _onShutdown = this._register(new Emitter()); + get onShutdown(): Event { return this._onShutdown.event; } + + protected _startupKind: StartupKind; + get startupKind(): StartupKind { return this._startupKind; } + + private _phase: LifecyclePhase = LifecyclePhase.Starting; + get phase(): LifecyclePhase { return this._phase; } + + private phaseWhen = new Map(); + + constructor( + @ILogService protected readonly logService: ILogService + ) { + super(); + } + + set phase(value: LifecyclePhase) { + if (value < this.phase) { + throw new Error('Lifecycle cannot go backwards'); + } + + if (this._phase === value) { + return; + } + + this.logService.trace(`lifecycle: phase changed (value: ${value})`); + + this._phase = value; + mark(`LifecyclePhase/${LifecyclePhaseToString(value)}`); + + const barrier = this.phaseWhen.get(this._phase); + if (barrier) { + barrier.open(); + this.phaseWhen.delete(this._phase); + } + } + + when(phase: LifecyclePhase): Promise { + if (phase <= this._phase) { + return Promise.resolve(); + } + + let barrier = this.phaseWhen.get(phase); + if (!barrier) { + barrier = new Barrier(); + this.phaseWhen.set(phase, barrier); + } + + return barrier.wait(); + } +} diff --git a/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts b/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts index 8b00097b23..b000efc918 100644 --- a/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts +++ b/src/vs/platform/lifecycle/electron-browser/lifecycleService.ts @@ -4,50 +4,30 @@ *--------------------------------------------------------------------------------------------*/ import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { ILifecycleService, BeforeShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, handleVetos, LifecyclePhaseToString, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; +import { ShutdownReason, StartupKind, handleVetos } from 'vs/platform/lifecycle/common/lifecycle'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ipcRenderer as ipc } from 'electron'; -import { Event, Emitter } from 'vs/base/common/event'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { mark } from 'vs/base/common/performance'; -import { Barrier } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { Disposable } from 'vs/base/common/lifecycle'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { AbstractLifecycleService } from 'vs/platform/lifecycle/common/lifecycleService'; -export class LifecycleService extends Disposable implements ILifecycleService { +export class LifecycleService extends AbstractLifecycleService { private static readonly LAST_SHUTDOWN_REASON_KEY = 'lifecyle.lastShutdownReason'; _serviceBrand: any; - private readonly _onBeforeShutdown = this._register(new Emitter()); - get onBeforeShutdown(): Event { return this._onBeforeShutdown.event; } - - private readonly _onWillShutdown = this._register(new Emitter()); - get onWillShutdown(): Event { return this._onWillShutdown.event; } - - private readonly _onShutdown = this._register(new Emitter()); - get onShutdown(): Event { return this._onShutdown.event; } - - private readonly _startupKind: StartupKind; - get startupKind(): StartupKind { return this._startupKind; } - - private _phase: LifecyclePhase = LifecyclePhase.Starting; - get phase(): LifecyclePhase { return this._phase; } - - private phaseWhen = new Map(); - private shutdownReason: ShutdownReason; constructor( @INotificationService private readonly notificationService: INotificationService, @IWindowService private readonly windowService: IWindowService, - @IStorageService private readonly storageService: IStorageService, - @ILogService private readonly logService: ILogService + @IStorageService readonly storageService: IStorageService, + @ILogService readonly logService: ILogService ) { - super(); + super(logService); this._startupKind = this.resolveStartupKind(); @@ -55,7 +35,7 @@ export class LifecycleService extends Disposable implements ILifecycleService { } private resolveStartupKind(): StartupKind { - const lastShutdownReason = this.storageService.getInteger(LifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE); + const lastShutdownReason = this.storageService.getNumber(LifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE); this.storageService.remove(LifecycleService.LAST_SHUTDOWN_REASON_KEY, StorageScope.WORKSPACE); let startupKind: StartupKind; @@ -148,39 +128,4 @@ export class LifecycleService extends Disposable implements ILifecycleService { onUnexpectedError(err); }); } - - set phase(value: LifecyclePhase) { - if (value < this.phase) { - throw new Error('Lifecycle cannot go backwards'); - } - - if (this._phase === value) { - return; - } - - this.logService.trace(`lifecycle: phase changed (value: ${value})`); - - this._phase = value; - mark(`LifecyclePhase/${LifecyclePhaseToString(value)}`); - - const barrier = this.phaseWhen.get(this._phase); - if (barrier) { - barrier.open(); - this.phaseWhen.delete(this._phase); - } - } - - when(phase: LifecyclePhase): Promise { - if (phase <= this._phase) { - return Promise.resolve(); - } - - let barrier = this.phaseWhen.get(phase); - if (!barrier) { - barrier = new Barrier(); - this.phaseWhen.set(phase, barrier); - } - - return barrier.wait(); - } } diff --git a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts b/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts index 0fd782f04e..cbbbb43ba0 100644 --- a/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts +++ b/src/vs/platform/lifecycle/electron-main/lifecycleMain.ts @@ -233,7 +233,7 @@ export class LifecycleService extends Disposable implements ILifecycleService { } }); - this.pendingWillShutdownPromise = Promise.all(joiners).then(undefined, err => this.logService.error(err)); + this.pendingWillShutdownPromise = Promise.all(joiners).then(() => undefined, err => this.logService.error(err)); return this.pendingWillShutdownPromise; } diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 03ee8ec793..87e3dafba9 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -3,39 +3,34 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addClass, addStandardDisposableListener, createStyleSheet, getTotalHeight, removeClass } from 'vs/base/browser/dom'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { IInputOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; +import { createStyleSheet } from 'vs/base/browser/dom'; import { IListMouseEvent, IListTouchEvent, IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IPagedRenderer, PagedList } from 'vs/base/browser/ui/list/listPaging'; import { DefaultStyleController, IListOptions, IMultipleSelectionController, IOpenController, isSelectionRangeChangeEvent, isSelectionSingleChangeEvent, List } from 'vs/base/browser/ui/list/listWidget'; -import { canceled, onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { FuzzyScore } from 'vs/base/common/filters'; -import { KeyCode } from 'vs/base/common/keyCodes'; import { combinedDisposable, Disposable, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { isUndefinedOrNull } from 'vs/base/common/types'; -import { IFilter, ITree, ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; +import { ITree, ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; import { ClickBehavior, DefaultController, DefaultTreestyler, IControllerOptions, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults'; import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; import { localize } from 'vs/nls'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, getMigratedSettingValue } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigurationExtensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Registry } from 'vs/platform/registry/common/platform'; -import { attachInputBoxStyler, attachListStyler, computeStyles, defaultListStyles } from 'vs/platform/theme/common/styler'; +import { attachListStyler, computeStyles, defaultListStyles } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { InputFocusedContextKey } from 'vs/platform/workbench/common/contextkeys'; +import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { ObjectTree, IObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree'; -import { ITreeEvent, ITreeRenderer, IAsyncDataSource, IDataSource } from 'vs/base/browser/ui/tree/tree'; +import { ITreeEvent, ITreeRenderer, IAsyncDataSource, IDataSource, ITreeMouseEvent } from 'vs/base/browser/ui/tree/tree'; import { AsyncDataTree, IAsyncDataTreeOptions } from 'vs/base/browser/ui/tree/asyncDataTree'; import { DataTree, IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree'; import { IKeyboardNavigationEventFilter } from 'vs/base/browser/ui/tree/abstractTree'; +import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; export type ListWidget = List | PagedList | ITree | ObjectTree | DataTree | AsyncDataTree; @@ -107,6 +102,7 @@ export const WorkbenchListMultiSelection = new RawContextKey('listMulti export const WorkbenchListSupportsKeyboardNavigation = new RawContextKey('listSupportsKeyboardNavigation', true); export const WorkbenchListAutomaticKeyboardNavigationKey = 'listAutomaticKeyboardNavigation'; export const WorkbenchListAutomaticKeyboardNavigation = new RawContextKey(WorkbenchListAutomaticKeyboardNavigationKey, true); +export let didBindWorkbenchListAutomaticKeyboardNavigation = false; function createScopedContextKeyService(contextKeyService: IContextKeyService, widget: ListWidget): IContextKeyService { const result = contextKeyService.createScoped(widget.getHTMLElement()); @@ -116,10 +112,15 @@ function createScopedContextKeyService(contextKeyService: IContextKeyService, wi export const multiSelectModifierSettingKey = 'workbench.list.multiSelectModifier'; export const openModeSettingKey = 'workbench.list.openMode'; -export const horizontalScrollingKey = 'workbench.tree.horizontalScrolling'; +export const horizontalScrollingKey = 'workbench.list.horizontalScrolling'; export const keyboardNavigationSettingKey = 'workbench.list.keyboardNavigation'; +export const automaticKeyboardNavigationSettingKey = 'workbench.list.automaticKeyboardNavigation'; const treeIndentKey = 'workbench.tree.indent'; +function getHorizontalScrollingSetting(configurationService: IConfigurationService): boolean { + return getMigratedSettingValue(configurationService, horizontalScrollingKey, 'workbench.tree.horizontalScrolling'); +} + function useAltAsMultipleSelectionModifier(configurationService: IConfigurationService): boolean { return configurationService.getValue(multiSelectModifierSettingKey) === 'alt'; } @@ -128,12 +129,27 @@ function useSingleClickToOpen(configurationService: IConfigurationService): bool return configurationService.getValue(openModeSettingKey) !== 'doubleClick'; } -class MultipleSelectionController implements IMultipleSelectionController { +class MultipleSelectionController extends Disposable implements IMultipleSelectionController { + private useAltAsMultipleSelectionModifier: boolean; - constructor(private configurationService: IConfigurationService) { } + constructor(private configurationService: IConfigurationService) { + super(); + + this.useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(configurationService); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(multiSelectModifierSettingKey)) { + this.useAltAsMultipleSelectionModifier = useAltAsMultipleSelectionModifier(this.configurationService); + } + })); + } isSelectionSingleChangeEvent(event: IListMouseEvent | IListTouchEvent): boolean { - if (useAltAsMultipleSelectionModifier(this.configurationService)) { + if (this.useAltAsMultipleSelectionModifier) { return event.browserEvent.altKey; } @@ -145,15 +161,30 @@ class MultipleSelectionController implements IMultipleSelectionController } } -class WorkbenchOpenController implements IOpenController { +class WorkbenchOpenController extends Disposable implements IOpenController { + private openOnSingleClick: boolean; - constructor(private configurationService: IConfigurationService, private existingOpenController?: IOpenController) { } + constructor(private configurationService: IConfigurationService, private existingOpenController?: IOpenController) { + super(); + + this.openOnSingleClick = useSingleClickToOpen(configurationService); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(openModeSettingKey)) { + this.openOnSingleClick = useSingleClickToOpen(this.configurationService); + } + })); + } shouldOpen(event: UIEvent): boolean { if (event instanceof MouseEvent) { const isLeftButton = event.button === 0; const isDoubleClick = event.detail === 2; - if (isLeftButton && !useSingleClickToOpen(this.configurationService) && !isDoubleClick) { + if (isLeftButton && !this.openOnSingleClick && !isDoubleClick) { return false; } @@ -168,14 +199,19 @@ class WorkbenchOpenController implements IOpenController { } } -function toWorkbenchListOptions(options: IListOptions, configurationService: IConfigurationService, keybindingService: IKeybindingService): IListOptions { +function toWorkbenchListOptions(options: IListOptions, configurationService: IConfigurationService, keybindingService: IKeybindingService): [IListOptions, IDisposable] { + const disposables: IDisposable[] = []; const result = { ...options }; if (options.multipleSelectionSupport !== false && !options.multipleSelectionController) { - result.multipleSelectionController = new MultipleSelectionController(configurationService); + const multipleSelectionController = new MultipleSelectionController(configurationService); + result.multipleSelectionController = multipleSelectionController; + disposables.push(multipleSelectionController); } - result.openController = new WorkbenchOpenController(configurationService, options.openController); + const openController = new WorkbenchOpenController(configurationService, options.openController); + result.openController = openController; + disposables.push(openController); if (options.keyboardNavigationLabelProvider) { const tlp = options.keyboardNavigationLabelProvider; @@ -186,7 +222,7 @@ function toWorkbenchListOptions(options: IListOptions, configurationServic }; } - return result; + return [result, combinedDisposable(disposables)]; } let sharedListStyleSheet: HTMLStyleElement; @@ -198,27 +234,6 @@ function getSharedListStyleSheet(): HTMLStyleElement { return sharedListStyleSheet; } -let sharedTreeStyleSheet: HTMLStyleElement; -function getSharedTreeStyleSheet(): HTMLStyleElement { - if (!sharedTreeStyleSheet) { - sharedTreeStyleSheet = createStyleSheet(); - } - - return sharedTreeStyleSheet; -} - -function handleTreeController(configuration: ITreeConfiguration, instantiationService: IInstantiationService): ITreeConfiguration { - if (!configuration.controller) { - configuration.controller = instantiationService.createInstance(WorkbenchTreeController, {}); - } - - if (!configuration.styler) { - configuration.styler = new DefaultTreestyler(getSharedTreeStyleSheet()); - } - - return configuration; -} - export class WorkbenchList extends List { readonly contextKeyService: IContextKeyService; @@ -241,18 +256,21 @@ export class WorkbenchList extends List { @IConfigurationService configurationService: IConfigurationService, @IKeybindingService keybindingService: IKeybindingService ) { - const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : configurationService.getValue(horizontalScrollingKey); + const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : getHorizontalScrollingSetting(configurationService); + const [workbenchListOptions, workbenchListOptionsDisposable] = toWorkbenchListOptions(options, configurationService, keybindingService); super(container, delegate, renderers, { keyboardSupport: false, styleController: new DefaultStyleController(getSharedListStyleSheet()), ...computeStyles(themeService.getTheme(), defaultListStyles), - ...toWorkbenchListOptions(options, configurationService, keybindingService), + ...workbenchListOptions, horizontalScrolling } as IListOptions ); + this.disposables.push(workbenchListOptionsDisposable); + this.contextKeyService = createScopedContextKeyService(contextKeyService, this); this.configurationService = configurationService; @@ -321,18 +339,19 @@ export class WorkbenchPagedList extends PagedList { @IConfigurationService configurationService: IConfigurationService, @IKeybindingService keybindingService: IKeybindingService ) { - const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : configurationService.getValue(horizontalScrollingKey); + const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : getHorizontalScrollingSetting(configurationService); + const [workbenchListOptions, workbenchListOptionsDisposable] = toWorkbenchListOptions(options, configurationService, keybindingService); super(container, delegate, renderers, { keyboardSupport: false, styleController: new DefaultStyleController(getSharedListStyleSheet()), ...computeStyles(themeService.getTheme(), defaultListStyles), - ...toWorkbenchListOptions(options, configurationService, keybindingService), + ...workbenchListOptions, horizontalScrolling } as IListOptions ); - this.disposables = []; + this.disposables = [workbenchListOptionsDisposable]; this.contextKeyService = createScopedContextKeyService(contextKeyService, this); this.configurationService = configurationService; @@ -370,6 +389,36 @@ export class WorkbenchPagedList extends PagedList { } } +/** + * @deprecated + */ +let sharedTreeStyleSheet: HTMLStyleElement; +function getSharedTreeStyleSheet(): HTMLStyleElement { + if (!sharedTreeStyleSheet) { + sharedTreeStyleSheet = createStyleSheet(); + } + + return sharedTreeStyleSheet; +} + +/** + * @deprecated + */ +function handleTreeController(configuration: ITreeConfiguration, instantiationService: IInstantiationService): ITreeConfiguration { + if (!configuration.controller) { + configuration.controller = instantiationService.createInstance(WorkbenchTreeController, {}); + } + + if (!configuration.styler) { + configuration.styler = new DefaultTreestyler(getSharedTreeStyleSheet()); + } + + return configuration; +} + +/** + * @deprecated + */ export class WorkbenchTree extends Tree { readonly contextKeyService: IContextKeyService; @@ -464,6 +513,9 @@ export class WorkbenchTree extends Tree { } } +/** + * @deprecated + */ function massageControllerOptions(options: IControllerOptions): IControllerOptions { if (typeof options.keyboardSupport !== 'boolean') { options.keyboardSupport = false; @@ -476,6 +528,9 @@ function massageControllerOptions(options: IControllerOptions): IControllerOptio return options; } +/** + * @deprecated + */ export class WorkbenchTreeController extends DefaultController { protected disposables: IDisposable[] = []; @@ -521,6 +576,9 @@ export interface IResourceResultsNavigationOptions { openOnFocus: boolean; } +/** + * @deprecated + */ export class TreeResourceNavigator extends Disposable { private readonly _openResource = new Emitter(); @@ -598,6 +656,7 @@ export interface IOpenEvent { editorOptions: IEditorOptions; sideBySide: boolean; element: T; + browserEvent?: UIEvent; } export interface IResourceResultsNavigationOptions { @@ -608,7 +667,7 @@ export interface SelectionKeyboardEvent extends KeyboardEvent { preserveFocus?: boolean; } -export function getSelectionKeyboardEvent(typeArg: string, preserveFocus?: boolean): SelectionKeyboardEvent { +export function getSelectionKeyboardEvent(typeArg = 'keydown', preserveFocus?: boolean): SelectionKeyboardEvent { const e = new KeyboardEvent(typeArg); (e).preserveFocus = preserveFocus; @@ -634,6 +693,8 @@ export class TreeResourceNavigator2 extends Disposable { } this._register(this.tree.onDidChangeSelection(e => this.onSelection(e))); + this._register(this.tree.onMouseDblClick(e => this.onSelection(e))); + this._register(this.tree.onDidOpen(e => this.onSelection(e))); } private onFocus(e: ITreeEvent): void { @@ -647,27 +708,29 @@ export class TreeResourceNavigator2 extends Disposable { const isMouseEvent = e.browserEvent && e.browserEvent instanceof MouseEvent; if (!isMouseEvent) { - this.open(true, false, false); + this.open(true, false, false, e.browserEvent); } } - private onSelection(e: ITreeEvent): void { + private onSelection(e: ITreeEvent | ITreeMouseEvent, doubleClick = false): void { if (!e.browserEvent || e.browserEvent.type === 'contextmenu') { return; } + const isKeyboardEvent = e.browserEvent instanceof KeyboardEvent; + const isMiddleClick = e.browserEvent instanceof MouseEvent ? e.browserEvent.button === 1 : false; const isDoubleClick = e.browserEvent.detail === 2; const preserveFocus = (e.browserEvent instanceof KeyboardEvent && typeof (e.browserEvent).preserveFocus === 'boolean') ? !!(e.browserEvent).preserveFocus : !isDoubleClick; - if (this.tree.openOnSingleClick || isDoubleClick) { + if (this.tree.openOnSingleClick || isDoubleClick || isKeyboardEvent) { const sideBySide = e.browserEvent instanceof MouseEvent && (e.browserEvent.ctrlKey || e.browserEvent.metaKey || e.browserEvent.altKey); - this.open(preserveFocus, isDoubleClick, sideBySide); + this.open(preserveFocus, isDoubleClick || isMiddleClick, sideBySide, e.browserEvent); } } - private open(preserveFocus: boolean, pinned: boolean, sideBySide: boolean): void { + private open(preserveFocus: boolean, pinned: boolean, sideBySide: boolean, browserEvent?: UIEvent): void { this._onDidOpenResource.fire({ editorOptions: { preserveFocus, @@ -675,246 +738,12 @@ export class TreeResourceNavigator2 extends Disposable { revealIfVisible: true }, sideBySide, - element: this.tree.getSelection()[0] + element: this.tree.getSelection()[0], + browserEvent }); } } -export interface IHighlighter { - getHighlights(tree: ITree, element: any, pattern: string): FuzzyScore; - getHighlightsStorageKey?(element: any): any; -} - -export interface IHighlightingTreeConfiguration extends ITreeConfiguration { - highlighter: IHighlighter; -} - -export interface IHighlightingTreeOptions extends ITreeOptions { - filterOnType?: boolean; -} - -export class HighlightingTreeController extends WorkbenchTreeController { - - constructor( - options: IControllerOptions, - private readonly onType: () => any, - @IConfigurationService configurationService: IConfigurationService, - @IKeybindingService private readonly _keybindingService: IKeybindingService, - ) { - super(options, configurationService); - } - - onKeyDown(tree: ITree, event: IKeyboardEvent) { - let handled = super.onKeyDown(tree, event); - if (handled) { - return true; - } - if (this.upKeyBindingDispatcher.has(event.keyCode)) { - return false; - } - if (this._keybindingService.mightProducePrintableCharacter(event)) { - this.onType(); - return true; - } - return false; - } -} - -class HightlightsFilter implements IFilter { - - static add(config: ITreeConfiguration, options: IHighlightingTreeOptions): ITreeConfiguration { - const myFilter = new HightlightsFilter(); - myFilter.enabled = !!options.filterOnType; - if (!config.filter) { - config.filter = myFilter; - } else { - let otherFilter = config.filter; - config.filter = { - isVisible(tree: ITree, element: any): boolean { - return myFilter.isVisible(tree, element) && otherFilter.isVisible(tree, element); - } - }; - } - return config; - } - - enabled: boolean = true; - - isVisible(tree: ITree, element: any): boolean { - if (!this.enabled) { - return true; - } - let tree2 = (tree as HighlightingWorkbenchTree); - if (!tree2.isHighlighterScoring()) { - return true; - } - if (tree2.getHighlighterScore(element)) { - return true; - } - return false; - } -} - -export class HighlightingWorkbenchTree extends WorkbenchTree { - - protected readonly domNode: HTMLElement; - protected readonly inputContainer: HTMLElement; - protected readonly input: InputBox; - - protected readonly highlighter: IHighlighter; - protected readonly highlights: Map; - - private readonly _onDidStartFilter: Emitter; - readonly onDidStartFiltering: Event; - - constructor( - parent: HTMLElement, - treeConfiguration: IHighlightingTreeConfiguration, - treeOptions: IHighlightingTreeOptions, - listOptions: IInputOptions, - @IContextKeyService contextKeyService: IContextKeyService, - @IContextViewService contextViewService: IContextViewService, - @IListService listService: IListService, - @IThemeService themeService: IThemeService, - @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService - ) { - // build html skeleton - const container = document.createElement('div'); - container.className = 'highlighting-tree'; - const inputContainer = document.createElement('div'); - inputContainer.className = 'input'; - const treeContainer = document.createElement('div'); - treeContainer.className = 'tree'; - container.appendChild(inputContainer); - container.appendChild(treeContainer); - parent.appendChild(container); - - // create tree - treeConfiguration.controller = treeConfiguration.controller || instantiationService.createInstance(HighlightingTreeController, {}, () => this.onTypeInTree()); - super(treeContainer, HightlightsFilter.add(treeConfiguration, treeOptions), treeOptions, contextKeyService, listService, themeService, instantiationService, configurationService); - this.highlighter = treeConfiguration.highlighter; - this.highlights = new Map(); - - this.domNode = container; - addClass(this.domNode, 'inactive'); - - // create input - this.inputContainer = inputContainer; - this.input = new InputBox(inputContainer, contextViewService, listOptions); - this.input.setEnabled(false); - this.input.onDidChange(this.updateHighlights, this, this.disposables); - this.disposables.push(attachInputBoxStyler(this.input, themeService)); - this.disposables.push(this.input); - this.disposables.push(addStandardDisposableListener(this.input.inputElement, 'keydown', event => { - //todo@joh make this command/context-key based - switch (event.keyCode) { - case KeyCode.UpArrow: - case KeyCode.DownArrow: - case KeyCode.Tab: - this.domFocus(); - event.preventDefault(); - break; - case KeyCode.Enter: - this.setSelection(this.getSelection()); - event.preventDefault(); - break; - case KeyCode.Escape: - this.input.value = ''; - this.domFocus(); - event.preventDefault(); - break; - } - })); - - this._onDidStartFilter = new Emitter(); - this.onDidStartFiltering = this._onDidStartFilter.event; - this.disposables.push(this._onDidStartFilter); - } - - setInput(element: any): Promise { - this.input.setEnabled(false); - return super.setInput(element).then(value => { - if (!this.input.inputElement) { - // has been disposed in the meantime -> cancel - return Promise.reject(canceled()); - } - this.input.setEnabled(true); - return value; - }); - } - - layout(height?: number, width?: number): void { - this.input.layout(); - super.layout(typeof height !== 'number' || isNaN(height) ? height : height - getTotalHeight(this.inputContainer), width); - } - - private onTypeInTree(): void { - removeClass(this.domNode, 'inactive'); - this.input.focus(); - this.layout(); - this._onDidStartFilter.fire(this); - } - - private lastSelection: any[]; - - private updateHighlights(pattern: string): void { - - // remember old selection - let defaultSelection: any[] = []; - if (!this.lastSelection && pattern) { - this.lastSelection = this.getSelection(); - } else if (this.lastSelection && !pattern) { - defaultSelection = this.lastSelection; - this.lastSelection = []; - } - - let topElement: any; - if (pattern) { - let nav = this.getNavigator(undefined, false); - let topScore: FuzzyScore | undefined; - while (nav.next()) { - let element = nav.current(); - let score = this.highlighter.getHighlights(this, element, pattern); - this.highlights.set(this._getHighlightsStorageKey(element), score); - element.foo = 1; - if (!topScore || score && topScore[0] < score[0]) { - topScore = score; - topElement = element; - } - } - } else { - // no pattern, clear highlights - this.highlights.clear(); - } - - this.refresh().then(() => { - if (topElement) { - this.reveal(topElement, 0.5).then(_ => { - this.setSelection([topElement], this); - this.setFocus(topElement, this); - }); - } else { - this.setSelection(defaultSelection, this); - } - }, onUnexpectedError); - } - - isHighlighterScoring(): boolean { - return this.highlights.size > 0; - } - - getHighlighterScore(element: any): FuzzyScore | undefined { - return this.highlights.get(this._getHighlightsStorageKey(element)); - } - - private _getHighlightsStorageKey(element: any): any { - return typeof this.highlighter.getHighlightsStorageKey === 'function' - ? this.highlighter.getHighlightsStorageKey(element) - : element; - } -} - function createKeyboardNavigationEventFilter(container: HTMLElement, keybindingService: IKeybindingService): IKeyboardNavigationEventFilter { let inChord = false; @@ -957,23 +786,40 @@ export class WorkbenchObjectTree, TFilterData = void> @IListService listService: IListService, @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, - @IKeybindingService keybindingService: IKeybindingService + @IKeybindingService keybindingService: IKeybindingService, + @IAccessibilityService accessibilityService: IAccessibilityService ) { WorkbenchListSupportsKeyboardNavigation.bindTo(contextKeyService); - WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService); - const automaticKeyboardNavigation = contextKeyService.getContextKeyValue(WorkbenchListAutomaticKeyboardNavigationKey); - const keyboardNavigation = configurationService.getValue(keyboardNavigationSettingKey); - const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : configurationService.getValue(horizontalScrollingKey); + if (!didBindWorkbenchListAutomaticKeyboardNavigation) { + WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService); + didBindWorkbenchListAutomaticKeyboardNavigation = true; + } + + const getAutomaticKeyboardNavigation = () => { + // give priority to the context key value to disable this completely + let automaticKeyboardNavigation = contextKeyService.getContextKeyValue(WorkbenchListAutomaticKeyboardNavigationKey); + + if (automaticKeyboardNavigation) { + automaticKeyboardNavigation = configurationService.getValue(automaticKeyboardNavigationSettingKey); + } + + return automaticKeyboardNavigation; + }; + + const accessibilityOn = accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; + const keyboardNavigation = accessibilityOn ? 'simple' : configurationService.getValue(keyboardNavigationSettingKey); + const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : getHorizontalScrollingSetting(configurationService); const openOnSingleClick = useSingleClickToOpen(configurationService); + const [workbenchListOptions, workbenchListOptionsDisposable] = toWorkbenchListOptions(options, configurationService, keybindingService); super(container, delegate, renderers, { keyboardSupport: false, styleController: new DefaultStyleController(getSharedListStyleSheet()), ...computeStyles(themeService.getTheme(), defaultListStyles), - ...toWorkbenchListOptions(options, configurationService, keybindingService), + ...workbenchListOptions, indent: configurationService.getValue(treeIndentKey), - automaticKeyboardNavigation, + automaticKeyboardNavigation: getAutomaticKeyboardNavigation(), simpleKeyboardNavigation: keyboardNavigation === 'simple', filterOnType: keyboardNavigation === 'filter', horizontalScrolling, @@ -981,6 +827,8 @@ export class WorkbenchObjectTree, TFilterData = void> keyboardNavigationEventFilter: createKeyboardNavigationEventFilter(container, keybindingService) }); + this.disposables.push(workbenchListOptionsDisposable); + this.contextKeyService = createScopedContextKeyService(contextKeyService, this); const listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService); @@ -994,6 +842,14 @@ export class WorkbenchObjectTree, TFilterData = void> const interestingContextKeys = new Set(); interestingContextKeys.add(WorkbenchListAutomaticKeyboardNavigationKey); + const updateKeyboardNavigation = () => { + const accessibilityOn = accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; + const keyboardNavigation = accessibilityOn ? 'simple' : configurationService.getValue(keyboardNavigationSettingKey); + this.updateOptions({ + simpleKeyboardNavigation: keyboardNavigation === 'simple', + filterOnType: keyboardNavigation === 'filter' + }); + }; this.disposables.push( this.contextKeyService, @@ -1025,21 +881,18 @@ export class WorkbenchObjectTree, TFilterData = void> this.updateOptions({ indent }); } if (e.affectsConfiguration(keyboardNavigationSettingKey)) { - const keyboardNavigation = configurationService.getValue(keyboardNavigationSettingKey); - this.updateOptions({ - simpleKeyboardNavigation: keyboardNavigation === 'simple', - filterOnType: keyboardNavigation === 'filter' - }); + updateKeyboardNavigation(); + } + if (e.affectsConfiguration(automaticKeyboardNavigationSettingKey)) { + this.updateOptions({ automaticKeyboardNavigation: getAutomaticKeyboardNavigation() }); } }), this.contextKeyService.onDidChangeContext(e => { if (e.affectsSome(interestingContextKeys)) { - const automaticKeyboardNavigation = this.contextKeyService.getContextKeyValue(WorkbenchListAutomaticKeyboardNavigationKey); - this.updateOptions({ - automaticKeyboardNavigation - }); + this.updateOptions({ automaticKeyboardNavigation: getAutomaticKeyboardNavigation() }); } - }) + }), + accessibilityService.onDidChangeAccessibilitySupport(() => updateKeyboardNavigation()) ); } @@ -1073,23 +926,40 @@ export class WorkbenchDataTree extends DataTree(WorkbenchListAutomaticKeyboardNavigationKey); - const keyboardNavigation = configurationService.getValue(keyboardNavigationSettingKey); - const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : configurationService.getValue(horizontalScrollingKey); + if (!didBindWorkbenchListAutomaticKeyboardNavigation) { + WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService); + didBindWorkbenchListAutomaticKeyboardNavigation = true; + } + + const getAutomaticKeyboardNavigation = () => { + // give priority to the context key value to disable this completely + let automaticKeyboardNavigation = contextKeyService.getContextKeyValue(WorkbenchListAutomaticKeyboardNavigationKey); + + if (automaticKeyboardNavigation) { + automaticKeyboardNavigation = configurationService.getValue(automaticKeyboardNavigationSettingKey); + } + + return automaticKeyboardNavigation; + }; + + const accessibilityOn = accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; + const keyboardNavigation = accessibilityOn ? 'simple' : configurationService.getValue(keyboardNavigationSettingKey); + const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : getHorizontalScrollingSetting(configurationService); const openOnSingleClick = useSingleClickToOpen(configurationService); + const [workbenchListOptions, workbenchListOptionsDisposable] = toWorkbenchListOptions(options, configurationService, keybindingService); super(container, delegate, renderers, dataSource, { keyboardSupport: false, styleController: new DefaultStyleController(getSharedListStyleSheet()), ...computeStyles(themeService.getTheme(), defaultListStyles), - ...toWorkbenchListOptions(options, configurationService, keybindingService), + ...workbenchListOptions, indent: configurationService.getValue(treeIndentKey), - automaticKeyboardNavigation, + automaticKeyboardNavigation: getAutomaticKeyboardNavigation(), simpleKeyboardNavigation: keyboardNavigation === 'simple', filterOnType: keyboardNavigation === 'filter', horizontalScrolling, @@ -1097,6 +967,8 @@ export class WorkbenchDataTree extends DataTree extends DataTree { + const accessibilityOn = accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; + const keyboardNavigation = accessibilityOn ? 'simple' : configurationService.getValue(keyboardNavigationSettingKey); + this.updateOptions({ + simpleKeyboardNavigation: keyboardNavigation === 'simple', + filterOnType: keyboardNavigation === 'filter' + }); + }; this.disposables.push( this.contextKeyService, @@ -1141,21 +1021,18 @@ export class WorkbenchDataTree extends DataTree(keyboardNavigationSettingKey); - this.updateOptions({ - simpleKeyboardNavigation: keyboardNavigation === 'simple', - filterOnType: keyboardNavigation === 'filter' - }); + updateKeyboardNavigation(); + } + if (e.affectsConfiguration(automaticKeyboardNavigationSettingKey)) { + this.updateOptions({ automaticKeyboardNavigation: getAutomaticKeyboardNavigation() }); } }), this.contextKeyService.onDidChangeContext(e => { if (e.affectsSome(interestingContextKeys)) { - const automaticKeyboardNavigation = this.contextKeyService.getContextKeyValue(WorkbenchListAutomaticKeyboardNavigationKey); - this.updateOptions({ - automaticKeyboardNavigation - }); + this.updateOptions({ automaticKeyboardNavigation: getAutomaticKeyboardNavigation() }); } - }) + }), + accessibilityService.onDidChangeAccessibilitySupport(() => updateKeyboardNavigation()) ); } @@ -1184,23 +1061,40 @@ export class WorkbenchAsyncDataTree extends Async @IListService listService: IListService, @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, - @IKeybindingService keybindingService: IKeybindingService + @IKeybindingService keybindingService: IKeybindingService, + @IAccessibilityService accessibilityService: IAccessibilityService ) { WorkbenchListSupportsKeyboardNavigation.bindTo(contextKeyService); - WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService); - const automaticKeyboardNavigation = contextKeyService.getContextKeyValue(WorkbenchListAutomaticKeyboardNavigationKey); - const keyboardNavigation = configurationService.getValue(keyboardNavigationSettingKey); - const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : configurationService.getValue(horizontalScrollingKey); + if (!didBindWorkbenchListAutomaticKeyboardNavigation) { + WorkbenchListAutomaticKeyboardNavigation.bindTo(contextKeyService); + didBindWorkbenchListAutomaticKeyboardNavigation = true; + } + + const getAutomaticKeyboardNavigation = () => { + // give priority to the context key value to disable this completely + let automaticKeyboardNavigation = contextKeyService.getContextKeyValue(WorkbenchListAutomaticKeyboardNavigationKey); + + if (automaticKeyboardNavigation) { + automaticKeyboardNavigation = configurationService.getValue(automaticKeyboardNavigationSettingKey); + } + + return automaticKeyboardNavigation; + }; + + const accessibilityOn = accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; + const keyboardNavigation = accessibilityOn ? 'simple' : configurationService.getValue(keyboardNavigationSettingKey); + const horizontalScrolling = typeof options.horizontalScrolling !== 'undefined' ? options.horizontalScrolling : getHorizontalScrollingSetting(configurationService); const openOnSingleClick = useSingleClickToOpen(configurationService); + const [workbenchListOptions, workbenchListOptionsDisposable] = toWorkbenchListOptions(options, configurationService, keybindingService); super(container, delegate, renderers, dataSource, { keyboardSupport: false, styleController: new DefaultStyleController(getSharedListStyleSheet()), ...computeStyles(themeService.getTheme(), defaultListStyles), - ...toWorkbenchListOptions(options, configurationService, keybindingService), + ...workbenchListOptions, indent: configurationService.getValue(treeIndentKey), - automaticKeyboardNavigation, + automaticKeyboardNavigation: getAutomaticKeyboardNavigation(), simpleKeyboardNavigation: keyboardNavigation === 'simple', filterOnType: keyboardNavigation === 'filter', horizontalScrolling, @@ -1208,6 +1102,8 @@ export class WorkbenchAsyncDataTree extends Async keyboardNavigationEventFilter: createKeyboardNavigationEventFilter(container, keybindingService) }); + this.disposables.push(workbenchListOptionsDisposable); + this.contextKeyService = createScopedContextKeyService(contextKeyService, this); const listSupportsMultiSelect = WorkbenchListSupportsMultiSelectContextKey.bindTo(this.contextKeyService); @@ -1221,6 +1117,14 @@ export class WorkbenchAsyncDataTree extends Async const interestingContextKeys = new Set(); interestingContextKeys.add(WorkbenchListAutomaticKeyboardNavigationKey); + const updateKeyboardNavigation = () => { + const accessibilityOn = accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; + const keyboardNavigation = accessibilityOn ? 'simple' : configurationService.getValue(keyboardNavigationSettingKey); + this.updateOptions({ + simpleKeyboardNavigation: keyboardNavigation === 'simple', + filterOnType: keyboardNavigation === 'filter' + }); + }; this.disposables.push( this.contextKeyService, @@ -1252,21 +1156,18 @@ export class WorkbenchAsyncDataTree extends Async this.updateOptions({ indent }); } if (e.affectsConfiguration(keyboardNavigationSettingKey)) { - const keyboardNavigation = configurationService.getValue(keyboardNavigationSettingKey); - this.updateOptions({ - simpleKeyboardNavigation: keyboardNavigation === 'simple', - filterOnType: keyboardNavigation === 'filter' - }); + updateKeyboardNavigation(); + } + if (e.affectsConfiguration(automaticKeyboardNavigationSettingKey)) { + this.updateOptions({ automaticKeyboardNavigation: getAutomaticKeyboardNavigation() }); } }), this.contextKeyService.onDidChangeContext(e => { if (e.affectsSome(interestingContextKeys)) { - const automaticKeyboardNavigation = this.contextKeyService.getContextKeyValue(WorkbenchListAutomaticKeyboardNavigationKey); - this.updateOptions({ - automaticKeyboardNavigation - }); + this.updateOptions({ automaticKeyboardNavigation: getAutomaticKeyboardNavigation() }); } - }) + }), + accessibilityService.onDidChangeAccessibilitySupport(() => updateKeyboardNavigation()) ); } @@ -1313,11 +1214,17 @@ configurationRegistry.registerConfiguration({ 'default': false, 'description': localize('horizontalScrolling setting', "Controls whether lists and trees support horizontal scrolling in the workbench.") }, + 'workbench.tree.horizontalScrolling': { + 'type': 'boolean', + 'default': false, + 'description': localize('tree horizontalScrolling setting', "Controls whether trees support horizontal scrolling in the workbench."), + 'deprecationMessage': localize('deprecated', "This setting is deprecated, please use '{0}' instead.", horizontalScrollingKey) + }, [treeIndentKey]: { 'type': 'number', 'default': 8, minimum: 0, - maximum: 20, + maximum: 40, 'description': localize('tree indent setting', "Controls tree indentation in pixels.") }, [keyboardNavigationSettingKey]: { @@ -1331,5 +1238,10 @@ configurationRegistry.registerConfiguration({ 'default': 'highlight', 'description': localize('keyboardNavigationSettingKey', "Controls the keyboard navigation style for lists and trees in the workbench. Can be simple, highlight and filter.") }, + [automaticKeyboardNavigationSettingKey]: { + 'type': 'boolean', + 'default': true, + markdownDescription: localize('automatic keyboard navigation setting', "Controls whether keyboard navigation in lists and trees is automatically triggered simply by typing. If set to `false`, keyboard navigation is only triggered when executing the `list.toggleKeyboardNavigation` command, for which you can assign a keyboard shortcut.") + } } }); diff --git a/src/vs/platform/localizations/electron-browser/localizationsService.ts b/src/vs/platform/localizations/electron-browser/localizationsService.ts new file mode 100644 index 0000000000..81c5e66b95 --- /dev/null +++ b/src/vs/platform/localizations/electron-browser/localizationsService.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Event } from 'vs/base/common/event'; +import { ILocalizationsService, LanguageType } from 'vs/platform/localizations/common/localizations'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; + +export class LocalizationsService implements ILocalizationsService { + + _serviceBrand: ServiceIdentifier; + + private channel: IChannel; + + constructor(@ISharedProcessService sharedProcessService: ISharedProcessService) { + this.channel = sharedProcessService.getChannel('localizations'); + } + + get onDidLanguagesChange(): Event { return this.channel.listen('onDidLanguagesChange'); } + + getLanguageIds(type?: LanguageType): Promise { + return this.channel.call('getLanguageIds', type); + } +} diff --git a/src/vs/platform/localizations/node/localizations.ts b/src/vs/platform/localizations/node/localizations.ts index 7c831566b4..1cd5fed37a 100644 --- a/src/vs/platform/localizations/node/localizations.ts +++ b/src/vs/platform/localizations/node/localizations.ts @@ -12,11 +12,11 @@ import { Queue } from 'vs/base/common/async'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ILogService } from 'vs/platform/log/common/log'; import { isValidLocalization, ILocalizationsService, LanguageType } from 'vs/platform/localizations/common/localizations'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { distinct, equals } from 'vs/base/common/arrays'; import { Event, Emitter } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; -import { posix } from 'path'; +import { join } from 'vs/base/common/path'; interface ILanguagePack { hash: string; @@ -51,8 +51,6 @@ export class LocalizationsService extends Disposable implements ILocalizationsSe this._register(extensionManagementService.onDidInstallExtension(({ local }) => this.onDidInstallExtension(local))); this._register(extensionManagementService.onDidUninstallExtension(({ identifier }) => this.onDidUninstallExtension(identifier))); - - this.extensionManagementService.getInstalled().then(installed => this.cache.update(installed)); } getLanguageIds(type: LanguageType): Promise { @@ -69,7 +67,7 @@ export class LocalizationsService extends Disposable implements ILocalizationsSe private onDidInstallExtension(extension: ILocalExtension | undefined): void { if (extension && extension.manifest && extension.manifest.contributes && extension.manifest.contributes.localizations && extension.manifest.contributes.localizations.length) { this.logService.debug('Adding language packs from the extension', extension.identifier.id); - this.update(); + this.update().then(changed => { if (changed) { this._onDidLanguagesChange.fire(); } }); } } @@ -78,19 +76,15 @@ export class LocalizationsService extends Disposable implements ILocalizationsSe .then(languagePacks => { if (Object.keys(languagePacks).some(language => languagePacks[language] && languagePacks[language].extensions.some(e => areSameExtensions(e.extensionIdentifier, identifier)))) { this.logService.debug('Removing language packs from the extension', identifier.id); - this.update(); + this.update().then(changed => { if (changed) { this._onDidLanguagesChange.fire(); } }); } }); } - private update(): void { - Promise.all([this.cache.getLanguagePacks(), this.extensionManagementService.getInstalled()]) + update(): Promise { + return Promise.all([this.cache.getLanguagePacks(), this.extensionManagementService.getInstalled()]) .then(([current, installed]) => this.cache.update(installed) - .then(updated => { - if (!equals(Object.keys(current), Object.keys(updated))) { - this._onDidLanguagesChange.fire(); - } - })); + .then(updated => !equals(Object.keys(current), Object.keys(updated)))); } } @@ -99,19 +93,20 @@ class LanguagePacksCache extends Disposable { private languagePacks: { [language: string]: ILanguagePack } = {}; private languagePacksFilePath: string; private languagePacksFileLimiter: Queue; + private initializedCache: boolean; constructor( @IEnvironmentService environmentService: IEnvironmentService, @ILogService private readonly logService: ILogService ) { super(); - this.languagePacksFilePath = posix.join(environmentService.userDataPath, 'languagepacks.json'); + this.languagePacksFilePath = join(environmentService.userDataPath, 'languagepacks.json'); this.languagePacksFileLimiter = new Queue(); } getLanguagePacks(): Promise<{ [language: string]: ILanguagePack }> { // if queue is not empty, fetch from disk - if (this.languagePacksFileLimiter.size) { + if (this.languagePacksFileLimiter.size || !this.initializedCache) { return this.withLanguagePacks() .then(() => this.languagePacks); } @@ -151,7 +146,7 @@ class LanguagePacksCache extends Disposable { languagePack.extensions.push({ extensionIdentifier, version: extension.manifest.version }); } for (const translation of localizationContribution.translations) { - languagePack.translations[translation.id] = posix.join(extension.location.fsPath, translation.path); + languagePack.translations[translation.id] = join(extension.location.fsPath, translation.path); } } } @@ -181,6 +176,7 @@ class LanguagePacksCache extends Disposable { } } this.languagePacks = languagePacks; + this.initializedCache = true; const raw = JSON.stringify(this.languagePacks); this.logService.debug('Writing language packs', raw); return pfs.writeFile(this.languagePacksFilePath, raw); diff --git a/src/vs/platform/localizations/node/localizationsIpc.ts b/src/vs/platform/localizations/node/localizationsIpc.ts index 4653aa093b..4707a5d2d1 100644 --- a/src/vs/platform/localizations/node/localizationsIpc.ts +++ b/src/vs/platform/localizations/node/localizationsIpc.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; -import { ILocalizationsService, LanguageType } from 'vs/platform/localizations/common/localizations'; +import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; export class LocalizationsChannel implements IServerChannel { @@ -31,16 +31,3 @@ export class LocalizationsChannel implements IServerChannel { throw new Error(`Call not found: ${command}`); } } - -export class LocalizationsChannelClient implements ILocalizationsService { - - _serviceBrand: any; - - constructor(private channel: IChannel) { } - - get onDidLanguagesChange(): Event { return this.channel.listen('onDidLanguagesChange'); } - - getLanguageIds(type?: LanguageType): Promise { - return this.channel.call('getLanguageIds', type); - } -} \ No newline at end of file diff --git a/src/vs/platform/log/node/logIpc.ts b/src/vs/platform/log/node/logIpc.ts index 27fe940bc3..1b998407bd 100644 --- a/src/vs/platform/log/node/logIpc.ts +++ b/src/vs/platform/log/node/logIpc.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { LogLevel, ILogService, DelegatedLogService } from 'vs/platform/log/common/log'; import { Event } from 'vs/base/common/event'; @@ -25,7 +25,7 @@ export class LogLevelSetterChannel implements IServerChannel { call(_, command: string, arg?: any): Promise { switch (command) { - case 'setLevel': this.service.setLevel(arg); + case 'setLevel': this.service.setLevel(arg); return Promise.resolve(); } throw new Error(`Call not found: ${command}`); diff --git a/src/vs/platform/log/node/spdlogService.ts b/src/vs/platform/log/node/spdlogService.ts index f7846f40f6..7b5bfd2acf 100644 --- a/src/vs/platform/log/node/spdlogService.ts +++ b/src/vs/platform/log/node/spdlogService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { ILogService, LogLevel, NullLogService, AbstractLogService } from 'vs/platform/log/common/log'; import * as spdlog from 'spdlog'; diff --git a/src/vs/platform/markers/common/markerService.ts b/src/vs/platform/markers/common/markerService.ts index 63b0fcc21b..0bbbd4e289 100644 --- a/src/vs/platform/markers/common/markerService.ts +++ b/src/vs/platform/markers/common/markerService.ts @@ -201,7 +201,7 @@ export class MarkerService implements IMarkerService { return { resource, owner, - code: code || undefined, + code, severity, message, source, diff --git a/src/vs/platform/menubar/common/menubar.ts b/src/vs/platform/menubar/common/menubar.ts index 3d5392fd3d..0cf64720dd 100644 --- a/src/vs/platform/menubar/common/menubar.ts +++ b/src/vs/platform/menubar/common/menubar.ts @@ -25,7 +25,7 @@ export interface IMenubarMenu { export interface IMenubarKeybinding { label: string; - userSettingsLabel: string; + userSettingsLabel?: string; isNative?: boolean; // Assumed true if missing } diff --git a/src/vs/platform/menubar/electron-browser/menubarService.ts b/src/vs/platform/menubar/electron-browser/menubarService.ts new file mode 100644 index 0000000000..0743b3e213 --- /dev/null +++ b/src/vs/platform/menubar/electron-browser/menubarService.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IMenubarService, IMenubarData } from 'vs/platform/menubar/common/menubar'; +import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; + +export class MenubarService implements IMenubarService { + + _serviceBrand: ServiceIdentifier; + + private channel: IChannel; + + constructor(@IMainProcessService mainProcessService: IMainProcessService) { + this.channel = mainProcessService.getChannel('menubar'); + } + + updateMenubar(windowId: number, menuData: IMenubarData): Promise { + return this.channel.call('updateMenubar', [windowId, menuData]); + } +} diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 9c7ba30c18..8fa030b330 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -11,7 +11,7 @@ import { OpenContext, IRunActionInWindowRequest, getTitleBarStyle, IRunKeybindin import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUpdateService, StateType } from 'vs/platform/update/common/update'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { mnemonicMenuLabel as baseMnemonicLabel } from 'vs/base/common/labels'; @@ -368,7 +368,7 @@ export class Menubar { if ( this.windowsMainService.getWindowCount() === 0 || // allow to quit when no more windows are open !!this.windowsMainService.getFocusedWindow() || // allow to quit when window has focus (fix for https://github.com/Microsoft/vscode/issues/39191) - this.windowsMainService.getLastActiveWindow().isMinimized() // allow to quit when window has no focus but is minimized (https://github.com/Microsoft/vscode/issues/63000) + this.windowsMainService.getLastActiveWindow()!.isMinimized() // allow to quit when window has no focus but is minimized (https://github.com/Microsoft/vscode/issues/63000) ) { this.windowsMainService.quit(); } @@ -433,7 +433,7 @@ export class Menubar { this.setMenu(submenu, item.submenu.items); menu.append(submenuItem); } else if (isMenubarMenuItemUriAction(item)) { - menu.append(this.createOpenRecentMenuItem(item.uri, item.label, item.id, item.id === 'openRecentFile')); + menu.append(this.createOpenRecentMenuItem(item.uri, item.label, item.id)); } else if (isMenubarMenuItemAction(item)) { if (item.id === 'workbench.action.showAboutDialog') { this.insertCheckForUpdatesItems(menu); @@ -471,8 +471,9 @@ export class Menubar { } } - private createOpenRecentMenuItem(uri: URI, label: string, commandId: string, isFile: boolean): Electron.MenuItem { + private createOpenRecentMenuItem(uri: URI, label: string, commandId: string): Electron.MenuItem { const revivedUri = URI.revive(uri); + const typeHint = commandId === 'openRecentFile' || commandId === 'openRecentWorkspace' ? 'file' : 'folder'; return new MenuItem(this.likeAction(commandId, { label, @@ -481,9 +482,9 @@ export class Menubar { const success = this.windowsMainService.open({ context: OpenContext.MENU, cli: this.environmentService.args, - urisToOpen: [revivedUri], + urisToOpen: [{ uri: revivedUri, typeHint }], forceNewWindow: openInNewWindow, - forceOpenWorkspaceAsFile: isFile + forceOpenWorkspaceAsFile: commandId === 'openRecentFile' }).length > 0; if (!success) { @@ -711,7 +712,7 @@ export class Menubar { let activeWindow = this.windowsMainService.getFocusedWindow(); if (!activeWindow) { const lastActiveWindow = this.windowsMainService.getLastActiveWindow(); - if (lastActiveWindow.isMinimized()) { + if (lastActiveWindow && lastActiveWindow.isMinimized()) { activeWindow = lastActiveWindow; } } diff --git a/src/vs/platform/menubar/node/menubarIpc.ts b/src/vs/platform/menubar/node/menubarIpc.ts index 19072fbb29..339124c0a6 100644 --- a/src/vs/platform/menubar/node/menubarIpc.ts +++ b/src/vs/platform/menubar/node/menubarIpc.ts @@ -2,8 +2,9 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; -import { IMenubarService, IMenubarData } from 'vs/platform/menubar/common/menubar'; + +import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IMenubarService } from 'vs/platform/menubar/common/menubar'; import { Event } from 'vs/base/common/event'; export class MenubarChannel implements IServerChannel { @@ -21,15 +22,4 @@ export class MenubarChannel implements IServerChannel { throw new Error(`Call not found: ${command}`); } -} - -export class MenubarChannelClient implements IMenubarService { - - _serviceBrand: any; - - constructor(private channel: IChannel) { } - - updateMenubar(windowId: number, menuData: IMenubarData): Promise { - return this.channel.call('updateMenubar', [windowId, menuData]); - } -} +} \ No newline at end of file diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts new file mode 100644 index 0000000000..5f47886895 --- /dev/null +++ b/src/vs/platform/product/common/product.ts @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IProductService = createDecorator('productService'); + +export interface IProductService { + _serviceBrand: any; + + version?: string; + commit?: string; + + enableTelemetry: boolean; +} diff --git a/src/vs/platform/node/package.ts b/src/vs/platform/product/node/package.ts similarity index 93% rename from src/vs/platform/node/package.ts rename to src/vs/platform/product/node/package.ts index 640dcc389c..51154fcfa1 100644 --- a/src/vs/platform/node/package.ts +++ b/src/vs/platform/product/node/package.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; export interface IPackageConfiguration { diff --git a/src/vs/platform/node/product.ts b/src/vs/platform/product/node/product.ts similarity index 98% rename from src/vs/platform/node/product.ts rename to src/vs/platform/product/node/product.ts index fb1e85109c..e95410290c 100644 --- a/src/vs/platform/node/product.ts +++ b/src/vs/platform/product/node/product.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; export interface IProductConfiguration { diff --git a/src/vs/platform/product/node/productService.ts b/src/vs/platform/product/node/productService.ts new file mode 100644 index 0000000000..45deb8e564 --- /dev/null +++ b/src/vs/platform/product/node/productService.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IProductService } from 'vs/platform/product/common/product'; +import product from 'vs/platform/product/node/product'; +import pkg from 'vs/platform/product/node/package'; + +export class ProductService implements IProductService { + + _serviceBrand: any; + + get version(): string | undefined { return pkg.version; } + + get commit(): string | undefined { return product.commit; } + + get enableTelemetry(): boolean { return product.enableTelemetry; } +} diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 511c41825b..e13a0febf0 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -52,6 +52,16 @@ export interface IPickOptions { */ matchOnDetail?: boolean; + /** + * an optional flag to filter the picks based on label. Defaults to true. + */ + matchOnLabel?: boolean; + + /** + * an option flag to control whether focus is always automatically brought to a list item. Defaults to true. + */ + autoFocusOnList?: boolean; + /** * an optional flag to not close the picker on focus lost */ @@ -114,7 +124,7 @@ export interface IInputOptions { /** * an optional function that is used to validate user input. */ - validateInput?: (input: string) => Promise; + validateInput?: (input: string) => Promise; } export interface IQuickInput { @@ -150,7 +160,7 @@ export interface IQuickPick extends IQuickInput { readonly onDidChangeValue: Event; - readonly onDidAccept: Event; + readonly onDidAccept: Event; buttons: ReadonlyArray; @@ -166,6 +176,10 @@ export interface IQuickPick extends IQuickInput { matchOnDetail: boolean; + matchOnLabel: boolean; + + autoFocusOnList: boolean; + quickNavigate: IQuickNavigateConfiguration | undefined; activeItems: ReadonlyArray; @@ -177,13 +191,17 @@ export interface IQuickPick extends IQuickInput { readonly onDidChangeSelection: Event; readonly keyMods: IKeyMods; + + valueSelection: Readonly<[number, number]> | undefined; + + validationMessage: string | undefined; } export interface IInputBox extends IQuickInput { value: string; - valueSelection: Readonly<[number, number]>; + valueSelection: Readonly<[number, number]> | undefined; placeholder: string | undefined; @@ -191,7 +209,7 @@ export interface IInputBox extends IQuickInput { readonly onDidChangeValue: Event; - readonly onDidAccept: Event; + readonly onDidAccept: Event; buttons: ReadonlyArray; @@ -203,7 +221,9 @@ export interface IInputBox extends IQuickInput { } export interface IQuickInputButton { + /** iconPath or iconClass required */ iconPath?: { dark: URI; light?: URI; }; + /** iconPath or iconClass required */ iconClass?: string; tooltip?: string; } diff --git a/src/vs/platform/remote/common/remoteAgentEnvironment.ts b/src/vs/platform/remote/common/remoteAgentEnvironment.ts new file mode 100644 index 0000000000..cd08fca8b8 --- /dev/null +++ b/src/vs/platform/remote/common/remoteAgentEnvironment.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { OperatingSystem } from 'vs/base/common/platform'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; + +export interface IRemoteAgentEnvironment { + pid: number; + appRoot: URI; + appSettingsHome: URI; + logsPath: URI; + extensionsPath: URI; + extensionHostLogsPath: URI; + globalStorageHome: URI; + userHome: URI; + extensions: IExtensionDescription[]; + os: OperatingSystem; + syncExtensions: boolean; +} + +export interface RemoteAgentConnectionContext { + remoteAuthority: string; + clientId: string; +} diff --git a/src/vs/platform/remote/common/remoteAuthorityResolver.ts b/src/vs/platform/remote/common/remoteAuthorityResolver.ts index d49cae4ffb..d298b1708a 100644 --- a/src/vs/platform/remote/common/remoteAuthorityResolver.ts +++ b/src/vs/platform/remote/common/remoteAuthorityResolver.ts @@ -11,7 +11,6 @@ export interface ResolvedAuthority { readonly authority: string; readonly host: string; readonly port: number; - readonly syncExtensions: boolean; readonly debugListenPort?: number; readonly debugConnectPort?: number; } diff --git a/src/vs/platform/remote/common/remoteHosts.ts b/src/vs/platform/remote/common/remoteHosts.ts index 097087de90..83960bf381 100644 --- a/src/vs/platform/remote/common/remoteHosts.ts +++ b/src/vs/platform/remote/common/remoteHosts.ts @@ -7,6 +7,6 @@ import { URI } from 'vs/base/common/uri'; export const REMOTE_HOST_SCHEME = 'vscode-remote'; -export function getRemoteAuthority(uri: URI) { +export function getRemoteAuthority(uri: URI): string | undefined { return uri.scheme === REMOTE_HOST_SCHEME ? uri.authority : undefined; } \ No newline at end of file diff --git a/src/vs/platform/remote/node/remoteAgentConnection.ts b/src/vs/platform/remote/node/remoteAgentConnection.ts index 0b9d838cd9..f817c260d3 100644 --- a/src/vs/platform/remote/node/remoteAgentConnection.ts +++ b/src/vs/platform/remote/node/remoteAgentConnection.ts @@ -4,12 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Client, BufferedProtocol } from 'vs/base/parts/ipc/node/ipc.net'; -import { IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; - -export interface RemoteAgentConnectionContext { - remoteAuthority: string; - clientId: string; -} +import { RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; export function connectRemoteAgentManagement(remoteAuthority: string, host: string, port: number, clientId: string, isBuilt: boolean): Promise> { throw new Error(`Not implemented`); @@ -20,10 +15,14 @@ export interface IExtensionHostConnectionResult { debugPort?: number; } -export interface IRemoteExtensionHostDebugParams extends IExtensionHostDebugParams { +export interface IRemoteExtensionHostStartParams { + language: string; + debugId?: string; + break: boolean; + port: number | null; updatePort?: boolean; } -export function connectRemoteAgentExtensionHost(host: string, port: number, debugArguments: IRemoteExtensionHostDebugParams, isBuilt: boolean): Promise { +export function connectRemoteAgentExtensionHost(host: string, port: number, startArguments: IRemoteExtensionHostStartParams, isBuilt: boolean): Promise { throw new Error(`Not implemented`); } diff --git a/src/vs/platform/remote/node/remoteAgentFileSystemChannel.ts b/src/vs/platform/remote/node/remoteAgentFileSystemChannel.ts index 1a71493561..a175c2ec64 100644 --- a/src/vs/platform/remote/node/remoteAgentFileSystemChannel.ts +++ b/src/vs/platform/remote/node/remoteAgentFileSystemChannel.ts @@ -7,7 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; -import { IChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { FileChangeType, FileDeleteOptions, FileOverwriteOptions, FileSystemProviderCapabilities, FileType, FileWriteOptions, IFileChange, IFileSystemProvider, IStat, IWatchOptions } from 'vs/platform/files/common/files'; export const REMOTE_FILE_SYSTEM_CHANNEL_NAME = 'remotefilesystem'; diff --git a/src/vs/platform/request/electron-browser/requestService.ts b/src/vs/platform/request/electron-browser/requestService.ts index c7149e9a68..7fa19aee34 100644 --- a/src/vs/platform/request/electron-browser/requestService.ts +++ b/src/vs/platform/request/electron-browser/requestService.ts @@ -6,7 +6,7 @@ import { IRequestOptions, IRequestContext, IRequestFunction } from 'vs/base/node/request'; import { Readable } from 'stream'; import { RequestService as NodeRequestService } from 'vs/platform/request/node/requestService'; -import { CancellationToken } from 'vscode'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { canceled } from 'vs/base/common/errors'; /** diff --git a/src/vs/platform/request/node/request.ts b/src/vs/platform/request/node/request.ts index 8cf64a13b6..07c19a3449 100644 --- a/src/vs/platform/request/node/request.ts +++ b/src/vs/platform/request/node/request.ts @@ -10,7 +10,7 @@ import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/co import { Registry } from 'vs/platform/registry/common/platform'; import { CancellationToken } from 'vs/base/common/cancellation'; -export const IRequestService = createDecorator('requestService2'); +export const IRequestService = createDecorator('requestService'); export interface IRequestService { _serviceBrand: any; @@ -58,6 +58,11 @@ Registry.as(Extensions.Configuration) ], default: 'override', description: localize('proxySupport', "Use the proxy support for extensions.") + }, + 'http.systemCertificates': { + type: 'boolean', + default: true, + description: localize('systemCertificates', "Controls whether CA certificates should be loaded from the OS.") } } }); diff --git a/src/vs/platform/state/node/stateService.ts b/src/vs/platform/state/node/stateService.ts index d99f484d73..4a6e7f3974 100644 --- a/src/vs/platform/state/node/stateService.ts +++ b/src/vs/platform/state/node/stateService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as fs from 'fs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { writeFileAndFlushSync } from 'vs/base/node/extfs'; diff --git a/src/vs/platform/state/test/node/state.test.ts b/src/vs/platform/state/test/node/state.test.ts index d6c60c4f09..14f5827175 100644 --- a/src/vs/platform/state/test/node/state.test.ts +++ b/src/vs/platform/state/test/node/state.test.ts @@ -5,9 +5,9 @@ import * as assert from 'assert'; import * as os from 'os'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as extfs from 'vs/base/node/extfs'; -import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices'; +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { FileStorage } from 'vs/platform/state/node/stateService'; suite('StateService', () => { diff --git a/src/vs/platform/statusbar/common/statusbar.ts b/src/vs/platform/statusbar/common/statusbar.ts index bad6ef21aa..f860fee04c 100644 --- a/src/vs/platform/statusbar/common/statusbar.ts +++ b/src/vs/platform/statusbar/common/statusbar.ts @@ -36,6 +36,11 @@ export interface IStatusbarEntry { */ readonly color?: string | ThemeColor; + /** + * An optional background color to use for the entry + */ + readonly backgroundColor?: string | ThemeColor; + /** * An optional id of a command that is known to the workbench to execute on click */ diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index 2588a6be91..989aa37aab 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -67,8 +67,8 @@ export interface IStorageService { * The scope argument allows to define the scope of the storage * operation to either the current workspace only or all workspaces. */ - getInteger(key: string, scope: StorageScope, fallbackValue: number): number; - getInteger(key: string, scope: StorageScope, fallbackValue?: number): number | undefined; + getNumber(key: string, scope: StorageScope, fallbackValue: number): number; + getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined; /** * Store a value under the given key to storage. The value will be converted to a string. @@ -108,7 +108,7 @@ export interface IWorkspaceStorageChangeEvent { export class InMemoryStorageService extends Disposable implements IStorageService { _serviceBrand = undefined; - private _onDidChangeStorage: Emitter = this._register(new Emitter()); + private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); get onDidChangeStorage(): Event { return this._onDidChangeStorage.event; } readonly onWillSaveState = Event.None; @@ -142,8 +142,8 @@ export class InMemoryStorageService extends Disposable implements IStorageServic return value === 'true'; } - getInteger(key: string, scope: StorageScope, fallbackValue: number): number; - getInteger(key: string, scope: StorageScope, fallbackValue?: number): number | undefined { + getNumber(key: string, scope: StorageScope, fallbackValue: number): number; + getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined { const value = this.getCache(scope).get(key); if (isUndefinedOrNull(value)) { diff --git a/src/vs/platform/storage/node/storageIpc.ts b/src/vs/platform/storage/node/storageIpc.ts index b04b86d96f..fb7f15e818 100644 --- a/src/vs/platform/storage/node/storageIpc.ts +++ b/src/vs/platform/storage/node/storageIpc.ts @@ -3,12 +3,16 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event, Emitter } from 'vs/base/common/event'; import { StorageMainService, IStorageChangeEvent } from 'vs/platform/storage/node/storageMainService'; import { IUpdateRequest, IStorageDatabase, IStorageItemsChangeEvent } from 'vs/base/node/storage'; import { mapToSerializable, serializableToMap, values } from 'vs/base/common/map'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { ILogService } from 'vs/platform/log/common/log'; +import { generateUuid } from 'vs/base/common/uuid'; +import { instanceStorageKey, firstSessionDateStorageKey, lastSessionDateStorageKey, currentSessionDateStorageKey } from 'vs/platform/telemetry/node/workbenchCommonProperties'; type Key = string; type Value = string; @@ -27,13 +31,51 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC private static STORAGE_CHANGE_DEBOUNCE_TIME = 100; - private _onDidChangeItems: Emitter = this._register(new Emitter()); + private readonly _onDidChangeItems: Emitter = this._register(new Emitter()); get onDidChangeItems(): Event { return this._onDidChangeItems.event; } - constructor(private storageMainService: StorageMainService) { + private whenReady: Promise; + + constructor( + private logService: ILogService, + private storageMainService: StorageMainService + ) { super(); - this.registerListeners(); + this.whenReady = this.init(); + } + + private init(): Promise { + return this.storageMainService.initialize().then(undefined, error => { + onUnexpectedError(error); + this.logService.error(error); + }).then(() => { + + // Apply global telemetry values as part of the initialization + // These are global across all windows and thereby should be + // written from the main process once. + this.initTelemetry(); + + // Setup storage change listeners + this.registerListeners(); + }); + } + + private initTelemetry(): void { + const instanceId = this.storageMainService.get(instanceStorageKey, undefined); + if (instanceId === undefined) { + this.storageMainService.store(instanceStorageKey, generateUuid()); + } + + const firstSessionDate = this.storageMainService.get(firstSessionDateStorageKey, undefined); + if (firstSessionDate === undefined) { + this.storageMainService.store(firstSessionDateStorageKey, new Date().toUTCString()); + } + + const lastSessionDate = this.storageMainService.get(currentSessionDateStorageKey, undefined); // previous session date was the "current" one at that time + const currentSessionDate = new Date().toUTCString(); // current session date is "now" + this.storageMainService.store(lastSessionDateStorageKey, typeof lastSessionDate === 'undefined' ? null : lastSessionDate); + this.storageMainService.store(currentSessionDateStorageKey, currentSessionDate); } private registerListeners(): void { @@ -73,26 +115,26 @@ export class GlobalStorageDatabaseChannel extends Disposable implements IServerC call(_, command: string, arg?: any): Promise { switch (command) { case 'getItems': { - return Promise.resolve(mapToSerializable(this.storageMainService.items)); + return this.whenReady.then(() => mapToSerializable(this.storageMainService.items)); } case 'updateItems': { - const items = arg as ISerializableUpdateRequest; - if (items.insert) { - for (const [key, value] of items.insert) { - this.storageMainService.store(key, value); + return this.whenReady.then(() => { + const items = arg as ISerializableUpdateRequest; + if (items.insert) { + for (const [key, value] of items.insert) { + this.storageMainService.store(key, value); + } } - } - if (items.delete) { - items.delete.forEach(key => this.storageMainService.remove(key)); - } - - return Promise.resolve(); // do not wait for modifications to complete + if (items.delete) { + items.delete.forEach(key => this.storageMainService.remove(key)); + } + }); } case 'checkIntegrity': { - return this.storageMainService.checkIntegrity(arg); + return this.whenReady.then(() => this.storageMainService.checkIntegrity(arg)); } } @@ -104,7 +146,7 @@ export class GlobalStorageDatabaseChannelClient extends Disposable implements IS _serviceBrand: any; - private _onDidChangeItemsExternal: Emitter = this._register(new Emitter()); + private readonly _onDidChangeItemsExternal: Emitter = this._register(new Emitter()); get onDidChangeItemsExternal(): Event { return this._onDidChangeItemsExternal.event; } private onDidChangeItemsOnMainListener: IDisposable; diff --git a/src/vs/platform/storage/node/storageMainService.ts b/src/vs/platform/storage/node/storageMainService.ts index 08e6d3d458..fa8c473219 100644 --- a/src/vs/platform/storage/node/storageMainService.ts +++ b/src/vs/platform/storage/node/storageMainService.ts @@ -9,9 +9,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IStorage, Storage, SQLiteStorageDatabase, ISQLiteStorageDatabaseLoggingOptions, InMemoryStorageDatabase } from 'vs/base/node/storage'; -import { join } from 'path'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { mark, getDuration } from 'vs/base/common/performance'; +import { join } from 'vs/base/common/path'; import { exists, readdir } from 'vs/base/node/pfs'; import { Database } from 'vscode-sqlite3'; import { endsWith, startsWith } from 'vs/base/common/strings'; @@ -54,8 +52,8 @@ export interface IStorageMainService { * the provided defaultValue if the element is null or undefined. The element * will be converted to a number using parseInt with a base of 10. */ - getInteger(key: string, fallbackValue: number): number; - getInteger(key: string, fallbackValue?: number): number | undefined; + getNumber(key: string, fallbackValue: number): number; + getNumber(key: string, fallbackValue?: number): number | undefined; /** * Store a string value under the given key to storage. The value will @@ -79,20 +77,21 @@ export class StorageMainService extends Disposable implements IStorageMainServic private static STORAGE_NAME = 'state.vscdb'; - private _onDidChangeStorage: Emitter = this._register(new Emitter()); + private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); get onDidChangeStorage(): Event { return this._onDidChangeStorage.event; } - private _onWillSaveState: Emitter = this._register(new Emitter()); + private readonly _onWillSaveState: Emitter = this._register(new Emitter()); get onWillSaveState(): Event { return this._onWillSaveState.event; } get items(): Map { return this.storage.items; } private storage: IStorage; + private initializePromise: Promise; + constructor( @ILogService private readonly logService: ILogService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ITelemetryService private readonly telemetryService: ITelemetryService + @IEnvironmentService private readonly environmentService: IEnvironmentService ) { super(); @@ -101,7 +100,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic } private get storagePath(): string { - if (!!this.environmentService.extensionTestsPath) { + if (!!this.environmentService.extensionTestsLocationURI) { return SQLiteStorageDatabase.IN_MEMORY_PATH; // no storage during extension tests! } @@ -109,31 +108,21 @@ export class StorageMainService extends Disposable implements IStorageMainServic } private createLogginOptions(): ISQLiteStorageDatabaseLoggingOptions { - const loggedStorageErrors = new Set(); - return { logTrace: (this.logService.getLevel() === LogLevel.Trace) ? msg => this.logService.trace(msg) : undefined, - logError: error => { - this.logService.error(error); - - const errorStr = `${error}`; - if (!loggedStorageErrors.has(errorStr)) { - loggedStorageErrors.add(errorStr); - - /* __GDPR__ - "sqliteMainStorageError" : { - "storageError": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('sqliteMainStorageError', { - 'storageError': errorStr - }); - } - } + logError: error => this.logService.error(error) } as ISQLiteStorageDatabaseLoggingOptions; } initialize(): Promise { + if (!this.initializePromise) { + this.initializePromise = this.doInitialize(); + } + + return this.initializePromise; + } + + private doInitialize(): Promise { const useInMemoryStorage = this.storagePath === SQLiteStorageDatabase.IN_MEMORY_PATH; let globalStorageExists: Promise; @@ -151,14 +140,7 @@ export class StorageMainService extends Disposable implements IStorageMainServic this._register(this.storage.onDidChangeStorage(key => this._onDidChangeStorage.fire({ key }))); - mark('main:willInitGlobalStorage'); return this.storage.init().then(() => { - mark('main:didInitGlobalStorage'); - }, error => { - mark('main:didInitGlobalStorage'); - - return Promise.reject(error); - }).then(() => { // Migrate storage if this is the first start and we are not using in-memory let migrationPromise: Promise; @@ -240,6 +222,10 @@ export class StorageMainService extends Disposable implements IStorageMainServic 'update/updateNotificationTime' ].forEach(key => supportedKeys.set(key.toLowerCase(), key)); + // https://github.com/Microsoft/vscode/issues/68468 + const wellKnownPublishers = ['Microsoft', 'GitHub']; + const wellKnownExtensions = ['ms-vscode.Go', 'WallabyJs.quokka-vscode', 'Telerik.nativescript', 'Shan.code-settings-sync', 'ritwickdey.LiveServer', 'PKief.material-icon-theme', 'PeterJausovec.vscode-docker', 'ms-vscode.PowerShell', 'LaurentTreguier.vscode-simple-icons', 'KnisterPeter.vscode-github', 'DotJoshJohnson.xml', 'Dart-Code.dart-code', 'alefragnani.Bookmarks']; + // Support extension storage as well (always the ID of the extension) extensions.forEach(extension => { let extensionId: string; @@ -250,13 +236,29 @@ export class StorageMainService extends Disposable implements IStorageMainServic } if (extensionId) { + for (let i = 0; i < wellKnownPublishers.length; i++) { + const publisher = wellKnownPublishers[i]; + if (startsWith(extensionId, `${publisher.toLowerCase()}.`)) { + extensionId = `${publisher}${extensionId.substr(publisher.length)}`; + break; + } + } + + for (let j = 0; j < wellKnownExtensions.length; j++) { + const wellKnownExtension = wellKnownExtensions[j]; + if (extensionId === wellKnownExtension.toLowerCase()) { + extensionId = wellKnownExtension; + break; + } + } + supportedKeys.set(extensionId.toLowerCase(), extensionId); } }); return import('vscode-sqlite3').then(sqlite3 => { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { const handleSuffixKey = (row, key: string, suffix: string) => { if (endsWith(key, suffix.toLowerCase())) { const value: string = row.value.toString('utf16le'); @@ -360,10 +362,10 @@ export class StorageMainService extends Disposable implements IStorageMainServic return this.storage.getBoolean(key, fallbackValue); } - getInteger(key: string, fallbackValue: number): number; - getInteger(key: string, fallbackValue?: number): number | undefined; - getInteger(key: string, fallbackValue?: number): number | undefined { - return this.storage.getInteger(key, fallbackValue); + getNumber(key: string, fallbackValue: number): number; + getNumber(key: string, fallbackValue?: number): number | undefined; + getNumber(key: string, fallbackValue?: number): number | undefined { + return this.storage.getNumber(key, fallbackValue); } store(key: string, value: any): Promise { @@ -375,21 +377,15 @@ export class StorageMainService extends Disposable implements IStorageMainServic } close(): Promise { - this.logService.trace('StorageMainService#close() - begin'); // Signal as event so that clients can still store data this._onWillSaveState.fire(); // Do it - mark('main:willCloseGlobalStorage'); - return this.storage.close().then(() => { - mark('main:didCloseGlobalStorage'); - - this.logService.trace(`StorageMainService#close() - finished in ${getDuration('main:willCloseGlobalStorage', 'main:didCloseGlobalStorage')}ms`); - }); + return this.storage.close(); } checkIntegrity(full: boolean): Promise { return this.storage.checkIntegrity(full); } -} \ No newline at end of file +} diff --git a/src/vs/platform/storage/node/storageService.ts b/src/vs/platform/storage/node/storageService.ts index f8857f62b7..4e53040019 100644 --- a/src/vs/platform/storage/node/storageService.ts +++ b/src/vs/platform/storage/node/storageService.ts @@ -12,7 +12,7 @@ import { Action } from 'vs/base/common/actions'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { localize } from 'vs/nls'; import { mark, getDuration } from 'vs/base/common/performance'; -import { join } from 'path'; +import { join } from 'vs/base/common/path'; import { copy, exists, mkdirp, writeFile } from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceInitializationPayload, isWorkspaceIdentifier, isSingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; @@ -24,39 +24,20 @@ export class StorageService extends Disposable implements IStorageService { private static WORKSPACE_STORAGE_NAME = 'state.vscdb'; private static WORKSPACE_META_NAME = 'workspace.json'; - private _onDidChangeStorage: Emitter = this._register(new Emitter()); + private readonly _onDidChangeStorage: Emitter = this._register(new Emitter()); get onDidChangeStorage(): Event { return this._onDidChangeStorage.event; } - private _onWillSaveState: Emitter = this._register(new Emitter()); + private readonly _onWillSaveState: Emitter = this._register(new Emitter()); get onWillSaveState(): Event { return this._onWillSaveState.event; } - private _hasErrors = false; - get hasErrors(): boolean { return this._hasErrors; } - - private bufferedWorkspaceStorageErrors?: Array = []; - private _onWorkspaceStorageError: Emitter = this._register(new Emitter()); - get onWorkspaceStorageError(): Event { - if (Array.isArray(this.bufferedWorkspaceStorageErrors)) { - // todo@ben cleanup after a while - if (this.bufferedWorkspaceStorageErrors.length > 0) { - const bufferedStorageErrors = this.bufferedWorkspaceStorageErrors; - setTimeout(() => { - this._onWorkspaceStorageError.fire(`[startup errors] ${bufferedStorageErrors.join('\n')}`); - }, 0); - } - - this.bufferedWorkspaceStorageErrors = undefined; - } - - return this._onWorkspaceStorageError.event; - } - private globalStorage: IStorage; private workspaceStoragePath: string; private workspaceStorage: IStorage; private workspaceStorageListener: IDisposable; + private initializePromise: Promise; + constructor( globalStorageDatabase: IStorageDatabase, @ILogService private readonly logService: ILogService, @@ -74,6 +55,14 @@ export class StorageService extends Disposable implements IStorageService { } initialize(payload: IWorkspaceInitializationPayload): Promise { + if (!this.initializePromise) { + this.initializePromise = this.doInitialize(payload); + } + + return this.initializePromise; + } + + private doInitialize(payload: IWorkspaceInitializationPayload): Promise { return Promise.all([ this.initializeGlobalStorage(), this.initializeWorkspaceStorage(payload) @@ -81,22 +70,14 @@ export class StorageService extends Disposable implements IStorageService { } private initializeGlobalStorage(): Promise { - mark('willInitGlobalStorage'); - - return this.globalStorage.init().then(() => { - mark('didInitGlobalStorage'); - }, error => { - mark('didInitGlobalStorage'); - - return Promise.reject(error); - }); + return this.globalStorage.init(); } private initializeWorkspaceStorage(payload: IWorkspaceInitializationPayload): Promise { // Prepare workspace storage folder for DB return this.prepareWorkspaceStorageFolder(payload).then(result => { - const useInMemoryStorage = !!this.environmentService.extensionTestsPath; // no storage during extension tests! + const useInMemoryStorage = !!this.environmentService.extensionTestsLocationURI; // no storage during extension tests! // Create workspace storage and initalize mark('willInitWorkspaceStorage'); @@ -120,17 +101,7 @@ export class StorageService extends Disposable implements IStorageService { // Logger for workspace storage const workspaceLoggingOptions: ISQLiteStorageDatabaseLoggingOptions = { logTrace: (this.logService.getLevel() === LogLevel.Trace) ? msg => this.logService.trace(msg) : undefined, - logError: error => { - this.logService.error(error); - - this._hasErrors = true; - - if (Array.isArray(this.bufferedWorkspaceStorageErrors)) { - this.bufferedWorkspaceStorageErrors.push(error); - } else { - this._onWorkspaceStorageError.fire(error); - } - } + logError: error => this.logService.error(error) }; // Dispose old (if any) @@ -152,7 +123,7 @@ export class StorageService extends Disposable implements IStorageService { private prepareWorkspaceStorageFolder(payload: IWorkspaceInitializationPayload): Promise<{ path: string, wasCreated: boolean }> { const workspaceStorageFolderPath = this.getWorkspaceStorageFolderPath(payload); - return exists(workspaceStorageFolderPath).then(exists => { + return exists(workspaceStorageFolderPath).then<{ path: string, wasCreated: boolean }>(exists => { if (exists) { return { path: workspaceStorageFolderPath, wasCreated: false }; } @@ -199,10 +170,10 @@ export class StorageService extends Disposable implements IStorageService { return this.getStorage(scope).getBoolean(key, fallbackValue); } - getInteger(key: string, scope: StorageScope, fallbackValue: number): number; - getInteger(key: string, scope: StorageScope): number | undefined; - getInteger(key: string, scope: StorageScope, fallbackValue?: number): number | undefined { - return this.getStorage(scope).getInteger(key, fallbackValue); + getNumber(key: string, scope: StorageScope, fallbackValue: number): number; + getNumber(key: string, scope: StorageScope): number | undefined; + getNumber(key: string, scope: StorageScope, fallbackValue?: number): number | undefined { + return this.getStorage(scope).getNumber(key, fallbackValue); } store(key: string, value: string | boolean | number, scope: StorageScope): void { @@ -219,14 +190,10 @@ export class StorageService extends Disposable implements IStorageService { this._onWillSaveState.fire({ reason: WillSaveStateReason.SHUTDOWN }); // Do it - mark('willCloseGlobalStorage'); - mark('willCloseWorkspaceStorage'); return Promise.all([ - this.globalStorage.close().then(() => mark('didCloseGlobalStorage')), - this.workspaceStorage.close().then(() => mark('didCloseWorkspaceStorage')) - ]).then(() => { - this.logService.trace(`[storage] closing took ${getDuration('willCloseGlobalStorage', 'didCloseGlobalStorage')}ms global / ${getDuration('willCloseWorkspaceStorage', 'didCloseWorkspaceStorage')}ms workspace`); - }); + this.globalStorage.close(), + this.workspaceStorage.close() + ]).then(() => undefined); } private getStorage(scope: StorageScope): IStorage { @@ -270,7 +237,7 @@ export class StorageService extends Disposable implements IStorageService { workspaceItemsParsed.set(key, safeParse(value)); }); - console.group(`Storage: Global (integrity: ${result[2]}, load: ${getDuration('main:willInitGlobalStorage', 'main:didInitGlobalStorage')}, path: ${this.environmentService.globalStorageHome})`); + console.group(`Storage: Global (integrity: ${result[2]}, path: ${this.environmentService.globalStorageHome})`); let globalValues: { key: string, value: string }[] = []; globalItems.forEach((value, key) => { globalValues.push({ key, value }); diff --git a/src/vs/platform/storage/test/node/storageService.test.ts b/src/vs/platform/storage/test/node/storageService.test.ts index 4ed7124dfc..86fd8d5d54 100644 --- a/src/vs/platform/storage/test/node/storageService.test.ts +++ b/src/vs/platform/storage/test/node/storageService.test.ts @@ -4,11 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { strictEqual, ok, equal } from 'assert'; -import { StorageScope } from 'vs/platform/storage/common/storage'; -import { TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { StorageScope, InMemoryStorageService } from 'vs/platform/storage/common/storage'; import { StorageService } from 'vs/platform/storage/node/storageService'; import { generateUuid } from 'vs/base/common/uuid'; -import { join } from 'path'; +import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; import { mkdirp, del } from 'vs/base/node/pfs'; import { NullLogService } from 'vs/platform/log/common/log'; @@ -27,7 +26,7 @@ suite('StorageService', () => { }); function removeData(scope: StorageScope): void { - const storage = new TestStorageService(); + const storage = new InMemoryStorageService(); storage.store('Monaco.IDE.Core.Storage.Test.remove', 'foobar', scope); strictEqual('foobar', storage.get('Monaco.IDE.Core.Storage.Test.remove', scope, (undefined)!)); @@ -45,12 +44,12 @@ suite('StorageService', () => { }); function storeData(scope: StorageScope): void { - const storage = new TestStorageService(); + const storage = new InMemoryStorageService(); strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, 'foobar'), 'foobar'); strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, ''), ''); - strictEqual(storage.getInteger('Monaco.IDE.Core.Storage.Test.getInteger', scope, 5), 5); - strictEqual(storage.getInteger('Monaco.IDE.Core.Storage.Test.getInteger', scope, 0), 0); + strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, 5), 5); + strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, 0), 0); strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, true), true); strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, false), false); @@ -60,11 +59,11 @@ suite('StorageService', () => { storage.store('Monaco.IDE.Core.Storage.Test.get', '', scope); strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.get', scope, (undefined)!), ''); - storage.store('Monaco.IDE.Core.Storage.Test.getInteger', 5, scope); - strictEqual(storage.getInteger('Monaco.IDE.Core.Storage.Test.getInteger', scope, (undefined)!), 5); + storage.store('Monaco.IDE.Core.Storage.Test.getNumber', 5, scope); + strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, (undefined)!), 5); - storage.store('Monaco.IDE.Core.Storage.Test.getInteger', 0, scope); - strictEqual(storage.getInteger('Monaco.IDE.Core.Storage.Test.getInteger', scope, (undefined)!), 0); + storage.store('Monaco.IDE.Core.Storage.Test.getNumber', 0, scope); + strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumber', scope, (undefined)!), 0); storage.store('Monaco.IDE.Core.Storage.Test.getBoolean', true, scope); strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, (undefined)!), true); @@ -73,7 +72,7 @@ suite('StorageService', () => { strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBoolean', scope, (undefined)!), false); strictEqual(storage.get('Monaco.IDE.Core.Storage.Test.getDefault', scope, 'getDefault'), 'getDefault'); - strictEqual(storage.getInteger('Monaco.IDE.Core.Storage.Test.getIntegerDefault', scope, 5), 5); + strictEqual(storage.getNumber('Monaco.IDE.Core.Storage.Test.getNumberDefault', scope, 5), 5); strictEqual(storage.getBoolean('Monaco.IDE.Core.Storage.Test.getBooleanDefault', scope, true), true); } @@ -112,7 +111,7 @@ suite('StorageService', () => { await storage.migrate({ id: String(Date.now() + 100) }); equal(storage.get('bar', StorageScope.WORKSPACE), 'foo'); - equal(storage.getInteger('barNumber', StorageScope.WORKSPACE), 55); + equal(storage.getNumber('barNumber', StorageScope.WORKSPACE), 55); equal(storage.getBoolean('barBoolean', StorageScope.GLOBAL), true); await storage.close(); diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index 8d5439cc4a..d30232b33d 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -4,9 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable } from 'vs/base/common/lifecycle'; -import { guessMimeTypes } from 'vs/base/common/mime'; -import * as paths from 'vs/base/common/paths'; -import { URI } from 'vs/base/common/uri'; import { IConfigurationService, ConfigurationTarget, ConfigurationTargetToString } from 'vs/platform/configuration/common/configuration'; import { IKeybindingService, KeybindingSource } from 'vs/platform/keybinding/common/keybinding'; import { ITelemetryService, ITelemetryInfo, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; @@ -77,11 +74,6 @@ export interface URIDescriptor { path?: string; } -export function telemetryURIDescriptor(uri: URI, hashPath: (path: string) => string): URIDescriptor { - const fsPath = uri && uri.fsPath; - return fsPath ? { mimeType: guessMimeTypes(fsPath).join(', '), scheme: uri.scheme, ext: paths.extname(fsPath), path: hashPath(fsPath) } : {}; -} - /** * Only add settings that cannot contain any personal/private information of users (PII). */ @@ -95,6 +87,7 @@ const configurationValueWhitelist = [ 'editor.rulers', 'editor.wordSeparators', 'editor.tabSize', + 'editor.indentSize', 'editor.insertSpaces', 'editor.detectIndentation', 'editor.roundedSelection', diff --git a/src/vs/platform/telemetry/electron-browser/telemetryService.ts b/src/vs/platform/telemetry/electron-browser/telemetryService.ts new file mode 100644 index 0000000000..92e577aca4 --- /dev/null +++ b/src/vs/platform/telemetry/electron-browser/telemetryService.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITelemetryService, ITelemetryInfo, ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IProductService } from 'vs/platform/product/common/product'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/workbenchCommonProperties'; +import { TelemetryService as BaseTelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; + +export class TelemetryService extends Disposable implements ITelemetryService { + + _serviceBrand: any; + + private impl: ITelemetryService; + + constructor( + @IEnvironmentService environmentService: IEnvironmentService, + @IProductService productService: IProductService, + @ISharedProcessService sharedProcessService: ISharedProcessService, + @ILogService logService: ILogService, + @IStorageService storageService: IStorageService, + @IConfigurationService configurationService: IConfigurationService, + @IWindowService windowService: IWindowService + ) { + super(); + + if (!environmentService.isExtensionDevelopment && !environmentService.args['disable-telemetry'] && !!productService.enableTelemetry) { + const channel = sharedProcessService.getChannel('telemetryAppender'); + const config: ITelemetryServiceConfig = { + appender: combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(logService)), + commonProperties: resolveWorkbenchCommonProperties(storageService, productService.commit, productService.version, windowService.getConfiguration().machineId, environmentService.installSourcePath), + piiPaths: [environmentService.appRoot, environmentService.extensionsPath] + }; + + this.impl = this._register(new BaseTelemetryService(config, configurationService)); + } else { + this.impl = NullTelemetryService; + } + } + + get isOptedIn(): boolean { + return this.impl.isOptedIn; + } + + publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise { + return this.impl.publicLog(eventName, data, anonymizeFilePaths); + } + + getTelemetryInfo(): Promise { + return this.impl.getTelemetryInfo(); + } +} diff --git a/src/vs/platform/telemetry/node/commonProperties.ts b/src/vs/platform/telemetry/node/commonProperties.ts index 0bc5ef4d42..6600924428 100644 --- a/src/vs/platform/telemetry/node/commonProperties.ts +++ b/src/vs/platform/telemetry/node/commonProperties.ts @@ -9,9 +9,9 @@ import * as uuid from 'vs/base/common/uuid'; import { readFile } from 'vs/base/node/pfs'; // {{SQL CARBON EDIT}} -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; -export function resolveCommonProperties(commit: string | undefined, version: string, machineId: string | undefined, installSourcePath: string): Promise<{ [name: string]: string | undefined; }> { +export function resolveCommonProperties(commit: string | undefined, version: string | undefined, machineId: string | undefined, installSourcePath: string): Promise<{ [name: string]: string | undefined; }> { const result: { [name: string]: string | undefined; } = Object.create(null); // {{SQL CARBON EDIT}} diff --git a/src/vs/platform/telemetry/node/telemetryIpc.ts b/src/vs/platform/telemetry/node/telemetryIpc.ts index 2d1d64edbc..897924f8a8 100644 --- a/src/vs/platform/telemetry/node/telemetryIpc.ts +++ b/src/vs/platform/telemetry/node/telemetryIpc.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { ITelemetryAppender } from 'vs/platform/telemetry/common/telemetryUtils'; import { Event } from 'vs/base/common/event'; diff --git a/src/vs/platform/telemetry/node/telemetryNodeUtils.ts b/src/vs/platform/telemetry/node/telemetryNodeUtils.ts index 30b1b2b8e5..1a5a82124d 100644 --- a/src/vs/platform/telemetry/node/telemetryNodeUtils.ts +++ b/src/vs/platform/telemetry/node/telemetryNodeUtils.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; diff --git a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts index 9c4f7d8f12..6f786fc268 100644 --- a/src/vs/platform/telemetry/node/workbenchCommonProperties.ts +++ b/src/vs/platform/telemetry/node/workbenchCommonProperties.ts @@ -6,16 +6,18 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; +export const instanceStorageKey = 'telemetry.instanceId'; +export const currentSessionDateStorageKey = 'telemetry.currentSessionDate'; +export const firstSessionDateStorageKey = 'telemetry.firstSessionDate'; export const lastSessionDateStorageKey = 'telemetry.lastSessionDate'; // {{ SQL CARBON EDIT }} -import product from 'vs/platform/node/product'; -import * as Utils from 'sql/common/telemetryUtilities'; +import product from 'vs/platform/product/node/product'; -export function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string, version: string, machineId: string, installSourcePath: string): Promise<{ [name: string]: string | undefined }> { +export function resolveWorkbenchCommonProperties(storageService: IStorageService, commit: string | undefined, version: string | undefined, machineId: string, installSourcePath: string): Promise<{ [name: string]: string | undefined }> { return resolveCommonProperties(commit, version, machineId, installSourcePath).then(result => { - const instanceId = storageService.get('telemetry.instanceId', StorageScope.GLOBAL)!; - const firstSessionDate = storageService.get('telemetry.firstSessionDate', StorageScope.GLOBAL)!; + const instanceId = storageService.get(instanceStorageKey, StorageScope.GLOBAL)!; + const firstSessionDate = storageService.get(firstSessionDateStorageKey, StorageScope.GLOBAL)!; const lastSessionDate = storageService.get(lastSessionDateStorageKey, StorageScope.GLOBAL)!; // __GDPR__COMMON__ "common.version.shell" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } diff --git a/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts b/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts index 166efec107..85abe4f142 100644 --- a/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts +++ b/src/vs/platform/telemetry/test/electron-browser/commonProperties.test.ts @@ -3,11 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as os from 'os'; import * as fs from 'fs'; import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/workbenchCommonProperties'; -import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices'; +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { IStorageService, StorageScope, InMemoryStorageService } from 'vs/platform/storage/common/storage'; import { del } from 'vs/base/node/extfs'; import { mkdirp } from 'vs/base/node/pfs'; diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index 267dbfffbc..d9607f5700 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -27,7 +27,7 @@ export interface ColorContribution { export interface ColorFunction { - (theme: ITheme): Color | null; + (theme: ITheme): Color | undefined; } export interface ColorDefaults { @@ -71,7 +71,7 @@ export interface IColorRegistry { /** * Gets the default color of the given id */ - resolveDefaultColor(id: ColorIdentifier, theme: ITheme): Color | null; + resolveDefaultColor(id: ColorIdentifier, theme: ITheme): Color | undefined; /** * JSON schema for an object to assign color values to one of the color contributions. @@ -131,13 +131,13 @@ class ColorRegistry implements IColorRegistry { return Object.keys(this.colorsById).map(id => this.colorsById[id]); } - public resolveDefaultColor(id: ColorIdentifier, theme: ITheme): Color | null { - let colorDesc = this.colorsById[id]; + public resolveDefaultColor(id: ColorIdentifier, theme: ITheme): Color | undefined { + const colorDesc = this.colorsById[id]; if (colorDesc && colorDesc.defaults) { - let colorValue = colorDesc.defaults[theme.type]; + const colorValue = colorDesc.defaults[theme.type]; return resolveColorValue(colorValue, theme); } - return null; + return undefined; } public getColorSchema(): IJSONSchema { @@ -229,7 +229,7 @@ export const listActiveSelectionBackground = registerColor('list.activeSelection export const listActiveSelectionForeground = registerColor('list.activeSelectionForeground', { dark: Color.white, light: Color.white, hc: null }, nls.localize('listActiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listInactiveSelectionBackground = registerColor('list.inactiveSelectionBackground', { dark: '#37373D', light: '#E4E6F1', hc: null }, nls.localize('listInactiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); export const listInactiveSelectionForeground = registerColor('list.inactiveSelectionForeground', { dark: null, light: null, hc: null }, nls.localize('listInactiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); -export const listInactiveFocusBackground = registerColor('list.inactiveFocusBackground', { dark: '#313135', light: '#d8dae6', hc: null }, nls.localize('listInactiveFocusBackground', "List/Tree background color for the focused item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); +export const listInactiveFocusBackground = registerColor('list.inactiveFocusBackground', { dark: null, light: null, hc: null }, nls.localize('listInactiveFocusBackground', "List/Tree background color for the focused item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); export const listHoverBackground = registerColor('list.hoverBackground', { dark: '#2A2D2E', light: '#F0F0F0', hc: null }, nls.localize('listHoverBackground', "List/Tree background when hovering over items using the mouse.")); export const listHoverForeground = registerColor('list.hoverForeground', { dark: null, light: null, hc: null }, nls.localize('listHoverForeground', "List/Tree foreground when hovering over items using the mouse.")); export const listDropBackground = registerColor('list.dropBackground', { dark: listFocusBackground, light: listFocusBackground, hc: null }, nls.localize('listDropBackground', "List/Tree drag and drop background when moving items around using the mouse.")); @@ -259,7 +259,7 @@ export const scrollbarSliderActiveBackground = registerColor('scrollbarSlider.ac export const progressBarBackground = registerColor('progressBar.background', { dark: Color.fromHex('#0E70C0'), light: Color.fromHex('#0E70C0'), hc: contrastBorder }, nls.localize('progressBarBackground', "Background color of the progress bar that can show for long running operations.")); export const menuBorder = registerColor('menu.border', { dark: null, light: null, hc: contrastBorder }, nls.localize('menuBorder', "Border color of menus.")); -export const menuForeground = registerColor('menu.foreground', { dark: selectForeground, light: selectForeground, hc: selectForeground }, nls.localize('menuForeground', "Foreground color of menu items.")); +export const menuForeground = registerColor('menu.foreground', { dark: selectForeground, light: foreground, hc: selectForeground }, nls.localize('menuForeground', "Foreground color of menu items.")); export const menuBackground = registerColor('menu.background', { dark: selectBackground, light: selectBackground, hc: selectBackground }, nls.localize('menuBackground', "Background color of menu items.")); export const menuSelectionForeground = registerColor('menu.selectionForeground', { dark: listActiveSelectionForeground, light: listActiveSelectionForeground, hc: listActiveSelectionForeground }, nls.localize('menuSelectionForeground', "Foreground color of the selected menu item in menus.")); export const menuSelectionBackground = registerColor('menu.selectionBackground', { dark: listActiveSelectionBackground, light: listActiveSelectionBackground, hc: listActiveSelectionBackground }, nls.localize('menuSelectionBackground', "Background color of the selected menu item in menus.")); @@ -312,6 +312,7 @@ export const editorFindRangeHighlightBorder = registerColor('editor.findRangeHig export const editorHoverHighlight = registerColor('editor.hoverHighlightBackground', { light: '#ADD6FF26', dark: '#264f7840', hc: '#ADD6FF26' }, nls.localize('hoverHighlight', 'Highlight below the word for which a hover is shown. The color must not be opaque so as not to hide underlying decorations.'), true); export const editorHoverBackground = registerColor('editorHoverWidget.background', { light: editorWidgetBackground, dark: editorWidgetBackground, hc: editorWidgetBackground }, nls.localize('hoverBackground', 'Background color of the editor hover.')); export const editorHoverBorder = registerColor('editorHoverWidget.border', { light: editorWidgetBorder, dark: editorWidgetBorder, hc: editorWidgetBorder }, nls.localize('hoverBorder', 'Border color of the editor hover.')); +export const editorHoverStatusBarBackground = registerColor('editorHoverWidget.statusBarBackground', { dark: lighten(editorHoverBackground, 0.2), light: darken(editorHoverBackground, 0.05), hc: editorWidgetBackground }, nls.localize('statusBarBackground', "Background color of the editor hover status bar.")); /** * Editor link colors @@ -387,7 +388,7 @@ export function darken(colorValue: ColorValue, factor: number): ColorFunction { if (color) { return color.darken(factor); } - return null; + return undefined; }; } @@ -397,7 +398,7 @@ export function lighten(colorValue: ColorValue, factor: number): ColorFunction { if (color) { return color.lighten(factor); } - return null; + return undefined; }; } @@ -407,7 +408,7 @@ export function transparent(colorValue: ColorValue, factor: number): ColorFuncti if (color) { return color.transparent(factor); } - return null; + return undefined; }; } @@ -419,7 +420,7 @@ export function oneOf(...colorValues: ColorValue[]): ColorFunction { return color; } } - return null; + return undefined; }; } @@ -436,7 +437,7 @@ function lessProminent(colorValue: ColorValue, backgroundColorValue: ColorValue, } return from.transparent(factor * transparency); } - return null; + return undefined; }; } @@ -445,9 +446,9 @@ function lessProminent(colorValue: ColorValue, backgroundColorValue: ColorValue, /** * @param colorValue Resolve a color value in the context of a theme */ -function resolveColorValue(colorValue: ColorValue | null, theme: ITheme): Color | null { +function resolveColorValue(colorValue: ColorValue | null, theme: ITheme): Color | undefined { if (colorValue === null) { - return null; + return undefined; } else if (typeof colorValue === 'string') { if (colorValue[0] === '#') { return Color.fromHex(colorValue); @@ -458,7 +459,7 @@ function resolveColorValue(colorValue: ColorValue | null, theme: ITheme): Color } else if (typeof colorValue === 'function') { return colorValue(theme); } - return null; + return undefined; } export const workbenchColorsSchemaId = 'vscode://schemas/workbench-colors'; diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index c590b687c6..4c6a7331ba 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -9,7 +9,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; -export type styleFn = (colors: { [name: string]: Color | null }) => void; +export type styleFn = (colors: { [name: string]: Color | undefined }) => void; export interface IStyleOverrides { [color: string]: ColorIdentifier | undefined; @@ -24,7 +24,7 @@ export interface IColorMapping { } export interface IComputedStyles { - [color: string]: Color | null; + [color: string]: Color | undefined; } export function computeStyles(theme: ITheme, styleMap: IColorMapping): IComputedStyles { @@ -327,10 +327,5 @@ export const defaultMenuStyles = { }; export function attachMenuStyler(widget: IThemable, themeService, style?: IMenuStyleOverrides): IDisposable { - const styles = { ...defaultMenuStyles, ...style }; - const fallback: IMenuStyleOverrides = { - foregroundColor: !!styles.foregroundColor && !!themeService && !!themeService.getTheme().getColor(styles.foregroundColor) ? styles.foregroundColor : foreground - }; - - return attachStyler(themeService, { ...styles, ...fallback }, widget); + return attachStyler(themeService, { ...defaultMenuStyles, ...style }, widget); } diff --git a/src/vs/platform/theme/common/themeService.ts b/src/vs/platform/theme/common/themeService.ts index 27e75ade45..19dbac4bd2 100644 --- a/src/vs/platform/theme/common/themeService.ts +++ b/src/vs/platform/theme/common/themeService.ts @@ -52,7 +52,7 @@ export interface ITheme { * @param color the id of the color * @param useDefault specifies if the default color should be used. If not set, the default is used. */ - getColor(color: ColorIdentifier, useDefault?: boolean): Color | null; + getColor(color: ColorIdentifier, useDefault?: boolean): Color | undefined; /** * Returns whether the theme defines a value for the color. If not, that means the diff --git a/src/vs/platform/theme/test/common/testThemeService.ts b/src/vs/platform/theme/test/common/testThemeService.ts index 11f5d675ef..40e87b7b29 100644 --- a/src/vs/platform/theme/test/common/testThemeService.ts +++ b/src/vs/platform/theme/test/common/testThemeService.ts @@ -12,12 +12,12 @@ export class TestTheme implements ITheme { constructor(private colors: { [id: string]: string; } = {}, public type = DARK) { } - getColor(color: string, useDefault?: boolean): Color | null { + getColor(color: string, useDefault?: boolean): Color | undefined { let value = this.colors[color]; if (value) { return Color.fromHex(value); } - return null; + return undefined; } defines(color: string): boolean { diff --git a/src/vs/platform/update/electron-browser/updateService.ts b/src/vs/platform/update/electron-browser/updateService.ts new file mode 100644 index 0000000000..9340751c72 --- /dev/null +++ b/src/vs/platform/update/electron-browser/updateService.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IUpdateService, State } from 'vs/platform/update/common/update'; +import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; + +export class UpdateService implements IUpdateService { + + _serviceBrand: ServiceIdentifier; + + private _onStateChange = new Emitter(); + get onStateChange(): Event { return this._onStateChange.event; } + + private _state: State = State.Uninitialized; + get state(): State { return this._state; } + + private channel: IChannel; + + constructor(@IMainProcessService mainProcessService: IMainProcessService) { + this.channel = mainProcessService.getChannel('update'); + + // always set this._state as the state changes + this.onStateChange(state => this._state = state); + + this.channel.call('_getInitialState').then(state => { + // fire initial state + this._onStateChange.fire(state); + + // fire subsequent states as they come in from remote + + this.channel.listen('onStateChange')(state => this._onStateChange.fire(state)); + }); + } + + checkForUpdates(context: any): Promise { + return this.channel.call('checkForUpdates', context); + } + + downloadUpdate(): Promise { + return this.channel.call('downloadUpdate'); + } + + applyUpdate(): Promise { + return this.channel.call('applyUpdate'); + } + + quitAndInstall(): Promise { + return this.channel.call('quitAndInstall'); + } + + isLatestVersion(): Promise { + return this.channel.call('isLatestVersion'); + } +} diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 3231dd4983..53a1109204 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -5,9 +5,9 @@ import { Event, Emitter } from 'vs/base/common/event'; import { timeout } from 'vs/base/common/async'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, getMigratedSettingValue } from 'vs/platform/configuration/common/configuration'; import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; @@ -56,8 +56,8 @@ export abstract class AbstractUpdateService implements IUpdateService { return; } - const updateChannel = this.configurationService.getValue('update.channel'); - const quality = this.getProductQuality(updateChannel); + const updateMode = getMigratedSettingValue(this.configurationService, 'update.mode', 'update.channel'); + const quality = this.getProductQuality(updateMode); if (!quality) { this.logService.info('update#ctor - updates are disabled by user preference'); @@ -72,7 +72,7 @@ export abstract class AbstractUpdateService implements IUpdateService { this.setState(State.Idle(this.getUpdateType())); - if (updateChannel === 'manual') { + if (updateMode === 'manual') { this.logService.info('update#ctor - manual checks only; automatic updates are disabled by user preference'); return; } @@ -81,8 +81,8 @@ export abstract class AbstractUpdateService implements IUpdateService { this.scheduleCheckForUpdates(30 * 1000).then(undefined, err => this.logService.error(err)); } - private getProductQuality(updateChannel: string): string | undefined { - return updateChannel === 'none' ? undefined : product.quality; + private getProductQuality(updateMode: string): string | undefined { + return updateMode === 'none' ? undefined : product.quality; } private scheduleCheckForUpdates(delay = 60 * 60 * 1000): Promise { diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index 94829c7990..65b4867df1 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { IRequestService } from 'vs/platform/request/node/request'; @@ -15,9 +15,6 @@ import { createUpdateURL, AbstractUpdateService } from 'vs/platform/update/elect import { asJson } from 'vs/base/node/request'; import { shell } from 'electron'; import { CancellationToken } from 'vs/base/common/cancellation'; -import * as path from 'path'; -import { spawn } from 'child_process'; -import { realpath } from 'fs'; export class LinuxUpdateService extends AbstractUpdateService { @@ -45,61 +42,36 @@ export class LinuxUpdateService extends AbstractUpdateService { this.setState(State.CheckingForUpdates(context)); - if (process.env.SNAP && process.env.SNAP_REVISION) { - this.checkForSnapUpdate(); - } else { - this.requestService.request({ url: this.url }, CancellationToken.None) - .then(asJson) - .then(update => { - if (!update || !update.url || !update.version || !update.productVersion) { - /* __GDPR__ - "update:notAvailable" : { - "explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - this.telemetryService.publicLog('update:notAvailable', { explicit: !!context }); - - this.setState(State.Idle(UpdateType.Archive)); - } else { - this.setState(State.AvailableForDownload(update)); - } - }) - .then(undefined, err => { - this.logService.error(err); - + this.requestService.request({ url: this.url }, CancellationToken.None) + .then(asJson) + .then(update => { + if (!update || !update.url || !update.version || !update.productVersion) { /* __GDPR__ - "update:notAvailable" : { - "explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } + "update:notAvailable" : { + "explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + } */ this.telemetryService.publicLog('update:notAvailable', { explicit: !!context }); - // only show message when explicitly checking for updates - const message: string | undefined = !!context ? (err.message || err) : undefined; - this.setState(State.Idle(UpdateType.Archive, message)); - }); - } - } + this.setState(State.Idle(UpdateType.Archive)); + } else { + this.setState(State.AvailableForDownload(update)); + } + }) + .then(undefined, err => { + this.logService.error(err); - private checkForSnapUpdate(): void { - // If the application was installed as a snap, updates happen in the - // background automatically, we just need to check to see if an update - // has already happened. - realpath(`${path.dirname(process.env.SNAP!)}/current`, (err, resolvedCurrentSnapPath) => { - if (err) { - this.logService.error('update#checkForSnapUpdate(): Could not get realpath of application.'); - return; - } + /* __GDPR__ + "update:notAvailable" : { + "explicit" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + } + */ + this.telemetryService.publicLog('update:notAvailable', { explicit: !!context }); - const currentRevision = path.basename(resolvedCurrentSnapPath); - - if (process.env.SNAP_REVISION !== currentRevision) { - // TODO@joao: snap - this.setState(State.Ready({ version: '', productVersion: '' })); - } else { - this.setState(State.Idle(UpdateType.Archive)); - } - }); + // only show message when explicitly checking for updates + const message: string | undefined = !!context ? (err.message || err) : undefined; + this.setState(State.Idle(UpdateType.Archive, message)); + }); } protected async doDownloadUpdate(state: AvailableForDownload): Promise { @@ -113,23 +85,4 @@ export class LinuxUpdateService extends AbstractUpdateService { this.setState(State.Idle(UpdateType.Archive)); } - - protected doQuitAndInstall(): void { - this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()'); - - const snap = process.env.SNAP; - - // TODO@joao what to do? - if (!snap) { - return; - } - - // Allow 3 seconds for VS Code to close - spawn('sleep 3 && $SNAP_NAME', { - shell: true, - detached: true, - stdio: 'ignore', - }); - - } } diff --git a/src/vs/platform/update/electron-main/updateService.snap.ts b/src/vs/platform/update/electron-main/updateService.snap.ts index 9e67e45786..7035931b5e 100644 --- a/src/vs/platform/update/electron-main/updateService.snap.ts +++ b/src/vs/platform/update/electron-main/updateService.snap.ts @@ -9,7 +9,7 @@ import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycle import { IUpdateService, State, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILogService } from 'vs/platform/log/common/log'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { realpath, watch } from 'fs'; import { spawn } from 'child_process'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -190,7 +190,7 @@ export class SnapUpdateService extends AbstractUpdateService2 { this.logService.trace('update#quitAndInstall(): running raw#quitAndInstall()'); // Allow 3 seconds for VS Code to close - spawn('sleep 3 && $SNAP_NAME', { + spawn('sleep 3 && ' + path.basename(process.argv[0]), { shell: true, detached: true, stdio: 'ignore', diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 6cd52f9472..b29ef5a86d 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { memoize } from 'vs/base/common/decorators'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { IRequestService } from 'vs/platform/request/node/request'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { State, IUpdate, StateType, AvailableForDownload, UpdateType } from 'vs/platform/update/common/update'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; diff --git a/src/vs/platform/update/node/update.config.contribution.ts b/src/vs/platform/update/node/update.config.contribution.ts index d1a0dd7998..6905c4407e 100644 --- a/src/vs/platform/update/node/update.config.contribution.ts +++ b/src/vs/platform/update/node/update.config.contribution.ts @@ -14,12 +14,12 @@ configurationRegistry.registerConfiguration({ title: localize('updateConfigurationTitle', "Update"), type: 'object', properties: { - 'update.channel': { + 'update.mode': { type: 'string', enum: ['none', 'manual', 'default'], default: 'default', scope: ConfigurationScope.APPLICATION, - description: localize('updateChannel', "Configure whether you receive automatic updates from an update channel. Requires a restart after change. The updates are fetched from a Microsoft online service."), + description: localize('updateMode', "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service."), tags: ['usesOnlineServices'], enumDescriptions: [ localize('none', "Disable updates."), @@ -27,6 +27,13 @@ configurationRegistry.registerConfiguration({ localize('default', "Enable automatic update checks. Code will check for updates automatically and periodically.") ] }, + 'update.channel': { + type: 'string', + default: 'default', + scope: ConfigurationScope.APPLICATION, + description: localize('updateMode', "Configure whether you receive automatic updates. Requires a restart after change. The updates are fetched from a Microsoft online service."), + deprecationMessage: localize('deprecated', "This setting is deprecated, please use '{0}' instead.", 'update.mode') + }, 'update.enableWindowsBackgroundUpdates': { type: 'boolean', default: true, diff --git a/src/vs/platform/update/node/updateIpc.ts b/src/vs/platform/update/node/updateIpc.ts index f1841c73c9..9683c8bccc 100644 --- a/src/vs/platform/update/node/updateIpc.ts +++ b/src/vs/platform/update/node/updateIpc.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; -import { Event, Emitter } from 'vs/base/common/event'; -import { IUpdateService, State } from 'vs/platform/update/common/update'; +import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Event } from 'vs/base/common/event'; +import { IUpdateService } from 'vs/platform/update/common/update'; export class UpdateChannel implements IServerChannel { @@ -31,49 +31,4 @@ export class UpdateChannel implements IServerChannel { throw new Error(`Call not found: ${command}`); } -} - -export class UpdateChannelClient implements IUpdateService { - - _serviceBrand: any; - - private _onStateChange = new Emitter(); - get onStateChange(): Event { return this._onStateChange.event; } - - private _state: State = State.Uninitialized; - get state(): State { return this._state; } - - constructor(private channel: IChannel) { - // always set this._state as the state changes - this.onStateChange(state => this._state = state); - - channel.call('_getInitialState').then(state => { - // fire initial state - this._onStateChange.fire(state); - - // fire subsequent states as they come in from remote - - this.channel.listen('onStateChange')(state => this._onStateChange.fire(state)); - }); - } - - checkForUpdates(context: any): Promise { - return this.channel.call('checkForUpdates', context); - } - - downloadUpdate(): Promise { - return this.channel.call('downloadUpdate'); - } - - applyUpdate(): Promise { - return this.channel.call('applyUpdate'); - } - - quitAndInstall(): Promise { - return this.channel.call('quitAndInstall'); - } - - isLatestVersion(): Promise { - return this.channel.call('isLatestVersion'); - } -} +} \ No newline at end of file diff --git a/src/vs/platform/url/common/urlService.ts b/src/vs/platform/url/common/urlService.ts index 994aa1d22b..39c8cff654 100644 --- a/src/vs/platform/url/common/urlService.ts +++ b/src/vs/platform/url/common/urlService.ts @@ -8,10 +8,11 @@ import { URI } from 'vs/base/common/uri'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { first } from 'vs/base/common/async'; import { values } from 'vs/base/common/map'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; export class URLService implements IURLService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private handlers = new Set(); @@ -25,18 +26,3 @@ export class URLService implements IURLService { return toDisposable(() => this.handlers.delete(handler)); } } - -export class RelayURLService extends URLService implements IURLHandler { - - constructor(private urlService: IURLService) { - super(); - } - - open(uri: URI): Promise { - return this.urlService.open(uri); - } - - handleURL(uri: URI): Promise { - return super.open(uri); - } -} diff --git a/src/vs/platform/url/electron-browser/urlService.ts b/src/vs/platform/url/electron-browser/urlService.ts new file mode 100644 index 0000000000..00093099b0 --- /dev/null +++ b/src/vs/platform/url/electron-browser/urlService.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; +import { URI } from 'vs/base/common/uri'; +import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { URLServiceChannelClient, URLHandlerChannel } from 'vs/platform/url/node/urlIpc'; +import { URLService } from 'vs/platform/url/common/urlService'; + +export class RelayURLService extends URLService implements IURLHandler { + private urlService: IURLService; + + constructor(@IMainProcessService mainProcessService: IMainProcessService) { + super(); + + this.urlService = new URLServiceChannelClient(mainProcessService.getChannel('url')); + + mainProcessService.registerChannel('urlHandler', new URLHandlerChannel(this)); + } + + open(uri: URI): Promise { + return this.urlService.open(uri); + } + + handleURL(uri: URI): Promise { + return super.open(uri); + } +} diff --git a/src/vs/platform/url/electron-main/electronUrlListener.ts b/src/vs/platform/url/electron-main/electronUrlListener.ts index 78d07b873c..5ea97de746 100644 --- a/src/vs/platform/url/electron-main/electronUrlListener.ts +++ b/src/vs/platform/url/electron-main/electronUrlListener.ts @@ -5,7 +5,7 @@ import { Event } from 'vs/base/common/event'; import { IURLService } from 'vs/platform/url/common/url'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { app } from 'electron'; import { URI } from 'vs/base/common/uri'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; diff --git a/src/vs/platform/url/node/urlIpc.ts b/src/vs/platform/url/node/urlIpc.ts index 47629cd725..67a91cf863 100644 --- a/src/vs/platform/url/node/urlIpc.ts +++ b/src/vs/platform/url/node/urlIpc.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { URI } from 'vs/base/common/uri'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; diff --git a/src/vs/platform/widget/common/contextScopedWidget.ts b/src/vs/platform/widget/common/contextScopedWidget.ts deleted file mode 100644 index 730c7af429..0000000000 --- a/src/vs/platform/widget/common/contextScopedWidget.ts +++ /dev/null @@ -1,24 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IContextKeyService, RawContextKey, IContextKeyServiceTarget } from 'vs/platform/contextkey/common/contextkey'; - -export function bindContextScopedWidget(contextKeyService: IContextKeyService, widget: IContextScopedWidget, contextKey: string): void { - new RawContextKey(contextKey, widget).bindTo(contextKeyService); -} - -export function createWidgetScopedContextKeyService(contextKeyService: IContextKeyService, widget: IContextScopedWidget): IContextKeyService { - return contextKeyService.createScoped(widget.target); -} - -export function getContextScopedWidget(contextKeyService: IContextKeyService, contextKey: string): T | undefined { - return contextKeyService.getContext(document.activeElement).getValue(contextKey); -} - -export interface IContextScopedWidget { - - readonly target: IContextKeyServiceTarget; - -} \ No newline at end of file diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 6eb31213f5..c579f98660 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -6,10 +6,10 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; -import { IProcessEnvironment, isMacintosh } from 'vs/base/common/platform'; +import { IProcessEnvironment, isMacintosh, isLinux } from 'vs/base/common/platform'; import { ParsedArgs, IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IRecentlyOpened } from 'vs/platform/history/common/history'; +import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { ExportData } from 'vs/base/common/performance'; import { LogLevel } from 'vs/platform/log/common/log'; @@ -31,7 +31,7 @@ export interface INativeOpenDialogOptions { export interface IEnterWorkspaceResult { workspace: IWorkspaceIdentifier; - backupPath: string; + backupPath?: string; } export interface CrashReporterStartOptions { @@ -117,8 +117,8 @@ export interface IWindowsService { enterWorkspace(windowId: number, path: URI): Promise; toggleFullScreen(windowId: number): Promise; setRepresentedFilename(windowId: number, fileName: string): Promise; - addRecentlyOpened(files: URI[]): Promise; - removeFromRecentlyOpened(paths: Array): Promise; + addRecentlyOpened(recents: IRecent[]): Promise; + removeFromRecentlyOpened(paths: URI[]): Promise; clearRecentlyOpened(): Promise; getRecentlyOpened(windowId: number): Promise; focusWindow(windowId: number): Promise; @@ -149,13 +149,13 @@ export interface IWindowsService { toggleSharedProcess(): Promise; // Global methods - openWindow(windowId: number, paths: URI[], options?: IOpenSettings): Promise; + openWindow(windowId: number, uris: IURIToOpen[], options?: IOpenSettings): Promise; openNewWindow(options?: INewWindowOptions): Promise; showWindow(windowId: number): Promise; getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]>; getWindowCount(): Promise; log(severity: string, ...messages: string[]): Promise; - showItemInFolder(path: string): Promise; + showItemInFolder(path: URI): Promise; getActiveWindowId(): Promise; // This needs to be handled from browser process to prevent @@ -185,6 +185,14 @@ export interface IOpenSettings { args?: ParsedArgs; } +export type URIType = 'file' | 'folder'; + +export interface IURIToOpen { + uri: URI; + typeHint?: URIType; + label?: string; +} + export interface IWindowService { _serviceBrand: any; @@ -211,7 +219,7 @@ export interface IWindowService { getRecentlyOpened(): Promise; focusWindow(): Promise; closeWindow(): Promise; - openWindow(paths: URI[], options?: IOpenSettings): Promise; + openWindow(uris: IURIToOpen[], options?: IOpenSettings): Promise; isFocused(): Promise; setDocumentEdited(flag: boolean): Promise; isMaximized(): Promise; @@ -270,14 +278,14 @@ export function getTitleBarStyle(configurationService: IConfigurationService, en } const style = configuration.titleBarStyle; - if (style === 'native') { - return 'native'; + if (style === 'native' || style === 'custom') { + return style; } } // {{SQL CARBON EDIT}} - Always use native toolbar return 'native'; - // return 'custom'; // default to custom on all OS + // return isLinux ? 'native' : 'custom'; // default to custom on all macOS and Windows } export const enum OpenContext { @@ -375,7 +383,7 @@ export interface IWindowConfiguration extends ParsedArgs { isInitialStartup?: boolean; userEnv: IProcessEnvironment; - nodeCachedDataDir: string; + nodeCachedDataDir?: string; backupPath?: string; @@ -390,7 +398,7 @@ export interface IWindowConfiguration extends ParsedArgs { highContrast?: boolean; frameless?: boolean; accessibilitySupport?: boolean; - partsSplashData?: string; + partsSplashPath?: string; perfStartTime?: number; perfAppReady?: number; diff --git a/src/vs/platform/windows/electron-browser/windowService.ts b/src/vs/platform/windows/electron-browser/windowService.ts index b8308aad41..cbc093540f 100644 --- a/src/vs/platform/windows/electron-browser/windowService.ts +++ b/src/vs/platform/windows/electron-browser/windowService.ts @@ -4,12 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { IWindowService, IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IWindowConfiguration, IDevToolsOptions, IOpenSettings } from 'vs/platform/windows/common/windows'; +import { IWindowService, IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IWindowConfiguration, IDevToolsOptions, IOpenSettings, IURIToOpen } from 'vs/platform/windows/common/windows'; import { IRecentlyOpened } from 'vs/platform/history/common/history'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; import { Disposable } from 'vs/base/common/lifecycle'; +import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; +import { ILabelService } from 'vs/platform/label/common/label'; export class WindowService extends Disposable implements IWindowService { @@ -18,20 +20,24 @@ export class WindowService extends Disposable implements IWindowService { _serviceBrand: any; + private windowId: number; + private _hasFocus: boolean; get hasFocus(): boolean { return this._hasFocus; } constructor( - private windowId: number, private configuration: IWindowConfiguration, - @IWindowsService private readonly windowsService: IWindowsService + @IWindowsService private readonly windowsService: IWindowsService, + @ILabelService private readonly labelService: ILabelService ) { super(); - const onThisWindowFocus = Event.map(Event.filter(windowsService.onWindowFocus, id => id === windowId), _ => true); - const onThisWindowBlur = Event.map(Event.filter(windowsService.onWindowBlur, id => id === windowId), _ => false); - const onThisWindowMaximize = Event.map(Event.filter(windowsService.onWindowMaximize, id => id === windowId), _ => true); - const onThisWindowUnmaximize = Event.map(Event.filter(windowsService.onWindowUnmaximize, id => id === windowId), _ => false); + this.windowId = configuration.windowId; + + const onThisWindowFocus = Event.map(Event.filter(windowsService.onWindowFocus, id => id === this.windowId), _ => true); + const onThisWindowBlur = Event.map(Event.filter(windowsService.onWindowBlur, id => id === this.windowId), _ => false); + const onThisWindowMaximize = Event.map(Event.filter(windowsService.onWindowMaximize, id => id === this.windowId), _ => true); + const onThisWindowUnmaximize = Event.map(Event.filter(windowsService.onWindowUnmaximize, id => id === this.windowId), _ => false); this.onDidChangeFocus = Event.any(onThisWindowFocus, onThisWindowBlur); this.onDidChangeMaximize = Event.any(onThisWindowMaximize, onThisWindowUnmaximize); @@ -92,8 +98,11 @@ export class WindowService extends Disposable implements IWindowService { return this.windowsService.enterWorkspace(this.windowId, path); } - openWindow(paths: URI[], options?: IOpenSettings): Promise { - return this.windowsService.openWindow(this.windowId, paths, options); + openWindow(uris: IURIToOpen[], options?: IOpenSettings): Promise { + if (!!this.configuration.remoteAuthority) { + uris.forEach(u => u.label = u.label || this.getRecentLabel(u, !!(options && options.forceOpenWorkspaceAsFile))); + } + return this.windowsService.openWindow(this.windowId, uris, options); } closeWindow(): Promise { @@ -167,4 +176,15 @@ export class WindowService extends Disposable implements IWindowService { resolveProxy(url: string): Promise { return this.windowsService.resolveProxy(this.windowId, url); } + + private getRecentLabel(u: IURIToOpen, forceOpenWorkspaceAsFile: boolean): string { + if (u.typeHint === 'folder') { + return this.labelService.getWorkspaceLabel(u.uri, { verbose: true }); + } else if (!forceOpenWorkspaceAsFile && hasWorkspaceFileExtension(u.uri.path)) { + return this.labelService.getWorkspaceLabel({ id: '', configPath: u.uri }, { verbose: true }); + } else { + return this.labelService.getUriLabel(u.uri); + } + } } + diff --git a/src/vs/platform/windows/electron-browser/windowsService.ts b/src/vs/platform/windows/electron-browser/windowsService.ts new file mode 100644 index 0000000000..7f5d5bf50b --- /dev/null +++ b/src/vs/platform/windows/electron-browser/windowsService.ts @@ -0,0 +1,250 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, CrashReporterStartOptions, IMessageBoxResult, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, IDevToolsOptions, INewWindowOptions, IURIToOpen } from 'vs/platform/windows/common/windows'; +import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IRecentlyOpened, IRecent, isRecentWorkspace } from 'vs/platform/history/common/history'; +import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; +import { URI } from 'vs/base/common/uri'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; + +export class WindowsService implements IWindowsService { + + _serviceBrand: any; + + private channel: IChannel; + + constructor(@IMainProcessService mainProcessService: IMainProcessService) { + this.channel = mainProcessService.getChannel('windows'); + } + + get onWindowOpen(): Event { return this.channel.listen('onWindowOpen'); } + get onWindowFocus(): Event { return this.channel.listen('onWindowFocus'); } + get onWindowBlur(): Event { return this.channel.listen('onWindowBlur'); } + get onWindowMaximize(): Event { return this.channel.listen('onWindowMaximize'); } + get onWindowUnmaximize(): Event { return this.channel.listen('onWindowUnmaximize'); } + get onRecentlyOpenedChange(): Event { return this.channel.listen('onRecentlyOpenedChange'); } + + pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise { + return this.channel.call('pickFileFolderAndOpen', options); + } + + pickFileAndOpen(options: INativeOpenDialogOptions): Promise { + return this.channel.call('pickFileAndOpen', options); + } + + pickFolderAndOpen(options: INativeOpenDialogOptions): Promise { + return this.channel.call('pickFolderAndOpen', options); + } + + pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise { + return this.channel.call('pickWorkspaceAndOpen', options); + } + + showMessageBox(windowId: number, options: MessageBoxOptions): Promise { + return this.channel.call('showMessageBox', [windowId, options]); + } + + showSaveDialog(windowId: number, options: SaveDialogOptions): Promise { + return this.channel.call('showSaveDialog', [windowId, options]); + } + + showOpenDialog(windowId: number, options: OpenDialogOptions): Promise { + return this.channel.call('showOpenDialog', [windowId, options]); + } + + reloadWindow(windowId: number, args?: ParsedArgs): Promise { + return this.channel.call('reloadWindow', [windowId, args]); + } + + openDevTools(windowId: number, options?: IDevToolsOptions): Promise { + return this.channel.call('openDevTools', [windowId, options]); + } + + toggleDevTools(windowId: number): Promise { + return this.channel.call('toggleDevTools', windowId); + } + + closeWorkspace(windowId: number): Promise { + return this.channel.call('closeWorkspace', windowId); + } + + enterWorkspace(windowId: number, path: URI): Promise { + return this.channel.call('enterWorkspace', [windowId, path]).then((result: IEnterWorkspaceResult) => { + result.workspace = reviveWorkspaceIdentifier(result.workspace); + return result; + }); + } + + toggleFullScreen(windowId: number): Promise { + return this.channel.call('toggleFullScreen', windowId); + } + + setRepresentedFilename(windowId: number, fileName: string): Promise { + return this.channel.call('setRepresentedFilename', [windowId, fileName]); + } + + addRecentlyOpened(recent: IRecent[]): Promise { + return this.channel.call('addRecentlyOpened', recent); + } + + removeFromRecentlyOpened(paths: Array): Promise { + return this.channel.call('removeFromRecentlyOpened', paths); + } + + clearRecentlyOpened(): Promise { + return this.channel.call('clearRecentlyOpened'); + } + + getRecentlyOpened(windowId: number): Promise { + return this.channel.call('getRecentlyOpened', windowId) + .then((recentlyOpened: IRecentlyOpened) => { + recentlyOpened.workspaces.forEach(recent => isRecentWorkspace(recent) ? recent.workspace = reviveWorkspaceIdentifier(recent.workspace) : recent.folderUri = URI.revive(recent.folderUri)); + recentlyOpened.files.forEach(recent => recent.fileUri = URI.revive(recent.fileUri)); + return recentlyOpened; + }); + } + + newWindowTab(): Promise { + return this.channel.call('newWindowTab'); + } + + showPreviousWindowTab(): Promise { + return this.channel.call('showPreviousWindowTab'); + } + + showNextWindowTab(): Promise { + return this.channel.call('showNextWindowTab'); + } + + moveWindowTabToNewWindow(): Promise { + return this.channel.call('moveWindowTabToNewWindow'); + } + + mergeAllWindowTabs(): Promise { + return this.channel.call('mergeAllWindowTabs'); + } + + toggleWindowTabsBar(): Promise { + return this.channel.call('toggleWindowTabsBar'); + } + + focusWindow(windowId: number): Promise { + return this.channel.call('focusWindow', windowId); + } + + closeWindow(windowId: number): Promise { + return this.channel.call('closeWindow', windowId); + } + + isFocused(windowId: number): Promise { + return this.channel.call('isFocused', windowId); + } + + isMaximized(windowId: number): Promise { + return this.channel.call('isMaximized', windowId); + } + + maximizeWindow(windowId: number): Promise { + return this.channel.call('maximizeWindow', windowId); + } + + unmaximizeWindow(windowId: number): Promise { + return this.channel.call('unmaximizeWindow', windowId); + } + + minimizeWindow(windowId: number): Promise { + return this.channel.call('minimizeWindow', windowId); + } + + onWindowTitleDoubleClick(windowId: number): Promise { + return this.channel.call('onWindowTitleDoubleClick', windowId); + } + + setDocumentEdited(windowId: number, flag: boolean): Promise { + return this.channel.call('setDocumentEdited', [windowId, flag]); + } + + quit(): Promise { + return this.channel.call('quit'); + } + + relaunch(options: { addArgs?: string[], removeArgs?: string[] }): Promise { + return this.channel.call('relaunch', [options]); + } + + whenSharedProcessReady(): Promise { + return this.channel.call('whenSharedProcessReady'); + } + + toggleSharedProcess(): Promise { + return this.channel.call('toggleSharedProcess'); + } + + openWindow(windowId: number, uris: IURIToOpen[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean, args?: ParsedArgs }): Promise { + return this.channel.call('openWindow', [windowId, uris, options]); + } + + openNewWindow(options?: INewWindowOptions): Promise { + return this.channel.call('openNewWindow', options); + } + + showWindow(windowId: number): Promise { + return this.channel.call('showWindow', windowId); + } + + getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { + return this.channel.call<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]>('getWindows').then(result => { + for (const win of result) { + if (win.folderUri) { + win.folderUri = URI.revive(win.folderUri); + } + if (win.workspace) { + win.workspace = reviveWorkspaceIdentifier(win.workspace); + } + } + return result; + }); + } + + getWindowCount(): Promise { + return this.channel.call('getWindowCount'); + } + + log(severity: string, ...messages: string[]): Promise { + return this.channel.call('log', [severity, messages]); + } + + showItemInFolder(path: URI): Promise { + return this.channel.call('showItemInFolder', path); + } + + getActiveWindowId(): Promise { + return this.channel.call('getActiveWindowId'); + } + + openExternal(url: string): Promise { + return this.channel.call('openExternal', url); + } + + startCrashReporter(config: CrashReporterStartOptions): Promise { + return this.channel.call('startCrashReporter', config); + } + + updateTouchBar(windowId: number, items: ISerializableCommandAction[][]): Promise { + return this.channel.call('updateTouchBar', [windowId, items]); + } + + openAboutDialog(): Promise { + return this.channel.call('openAboutDialog'); + } + + resolveProxy(windowId: number, url: string): Promise { + return Promise.resolve(this.channel.call('resolveProxy', [windowId, url])); + } +} diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index f55ac51a62..36375262ee 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OpenContext, IWindowConfiguration, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions } from 'vs/platform/windows/common/windows'; +import { OpenContext, IWindowConfiguration, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, INewWindowOptions, IURIToOpen } from 'vs/platform/windows/common/windows'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import { Event } from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -33,11 +33,11 @@ export interface ICodeWindow { readonly win: Electron.BrowserWindow; readonly config: IWindowConfiguration; - readonly openedFolderUri: URI; - readonly openedWorkspace: IWorkspaceIdentifier; - readonly backupPath: string; + readonly openedFolderUri?: URI; + readonly openedWorkspace?: IWorkspaceIdentifier; + readonly backupPath?: string; - readonly remoteAuthority: string; + readonly remoteAuthority?: string; readonly isExtensionDevelopmentHost: boolean; readonly isExtensionTestHost: boolean; @@ -94,10 +94,10 @@ export interface IWindowsMainService { // methods ready(initialUserEnv: IProcessEnvironment): void; reload(win: ICodeWindow, cli?: ParsedArgs): void; - enterWorkspace(win: ICodeWindow, path: URI): Promise; + enterWorkspace(win: ICodeWindow, path: URI): Promise; closeWorkspace(win: ICodeWindow): void; open(openConfig: IOpenConfiguration): ICodeWindow[]; - openExtensionDevelopmentHostWindow(openConfig: IOpenConfiguration): void; + openExtensionDevelopmentHostWindow(extensionDevelopmentPath: string, openConfig: IOpenConfiguration): void; pickFileFolderAndOpen(options: INativeOpenDialogOptions): void; pickFolderAndOpen(options: INativeOpenDialogOptions): void; pickFileAndOpen(options: INativeOpenDialogOptions): void; @@ -106,14 +106,14 @@ export interface IWindowsMainService { showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): Promise; showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): Promise; focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow; - getLastActiveWindow(): ICodeWindow; + getLastActiveWindow(): ICodeWindow | undefined; waitForWindowCloseOrLoad(windowId: number): Promise; openNewWindow(context: OpenContext, options?: INewWindowOptions): ICodeWindow[]; openNewTabbedWindow(context: OpenContext): ICodeWindow[]; sendToFocused(channel: string, ...args: any[]): void; sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void; - getFocusedWindow(): ICodeWindow; - getWindowById(windowId: number): ICodeWindow; + getFocusedWindow(): ICodeWindow | undefined; + getWindowById(windowId: number): ICodeWindow | undefined; getWindows(): ICodeWindow[]; getWindowCount(): number; quit(): void; @@ -124,7 +124,7 @@ export interface IOpenConfiguration { readonly contextWindowId?: number; readonly cli: ParsedArgs; readonly userEnv?: IProcessEnvironment; - readonly urisToOpen?: URI[]; + readonly urisToOpen?: IURIToOpen[]; readonly preferNewWindow?: boolean; readonly forceNewWindow?: boolean; readonly forceNewTabbedWindow?: boolean; diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 167aec3a49..7735f0c09a 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -8,15 +8,15 @@ import * as os from 'os'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { assign } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; -import product from 'vs/platform/node/product'; -import { IWindowsService, OpenContext, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IDevToolsOptions, INewWindowOptions, IOpenSettings } from 'vs/platform/windows/common/windows'; +import product from 'vs/platform/product/node/product'; +import { IWindowsService, OpenContext, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IDevToolsOptions, INewWindowOptions, IOpenSettings, IURIToOpen } from 'vs/platform/windows/common/windows'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { shell, crashReporter, app, Menu, clipboard } from 'electron'; import { Event } from 'vs/base/common/event'; import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { IWindowsMainService, ISharedProcess, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; -import { IHistoryMainService, IRecentlyOpened } from 'vs/platform/history/common/history'; +import { IHistoryMainService, IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { Schemas } from 'vs/base/common/network'; @@ -37,7 +37,7 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable readonly onWindowMaximize: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-maximize', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); readonly onWindowUnmaximize: Event = Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-unmaximize', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)); readonly onWindowFocus: Event = Event.any( - Event.map(Event.filter(Event.map(this.windowsMainService.onWindowsCountChanged, () => this.windowsMainService.getLastActiveWindow()), w => !!w), w => w.id), + Event.map(Event.filter(Event.map(this.windowsMainService.onWindowsCountChanged, () => this.windowsMainService.getLastActiveWindow()), w => !!w), w => w!.id), Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-focus', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)) ); @@ -156,13 +156,12 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable return this.withWindow(windowId, codeWindow => codeWindow.setRepresentedFilename(fileName)); } - async addRecentlyOpened(files: URI[]): Promise { + async addRecentlyOpened(recents: IRecent[]): Promise { this.logService.trace('windowsService#addRecentlyOpened'); - - this.historyService.addRecentlyOpened(undefined, files); + this.historyService.addRecentlyOpened(recents); } - async removeFromRecentlyOpened(paths: Array): Promise { + async removeFromRecentlyOpened(paths: URI[]): Promise { this.logService.trace('windowsService#removeFromRecentlyOpened'); this.historyService.removeFromRecentlyOpened(paths); @@ -177,7 +176,7 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable async getRecentlyOpened(windowId: number): Promise { this.logService.trace('windowsService#getRecentlyOpened', windowId); - return this.withWindow(windowId, codeWindow => this.historyService.getRecentlyOpened(codeWindow.config.workspace || codeWindow.config.folderUri, codeWindow.config.filesToOpen), () => this.historyService.getRecentlyOpened())!; + return this.withWindow(windowId, codeWindow => this.historyService.getRecentlyOpened(codeWindow.config.workspace, codeWindow.config.folderUri, codeWindow.config.filesToOpen), () => this.historyService.getRecentlyOpened())!; } async newWindowTab(): Promise { @@ -274,16 +273,16 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable }); } - async openWindow(windowId: number, paths: URI[], options?: IOpenSettings): Promise { + async openWindow(windowId: number, urisToOpen: IURIToOpen[], options?: IOpenSettings): Promise { this.logService.trace('windowsService#openWindow'); - if (!paths || !paths.length) { + if (!urisToOpen || !urisToOpen.length) { return undefined; } this.windowsMainService.open({ context: OpenContext.API, contextWindowId: windowId, - urisToOpen: paths, + urisToOpen: urisToOpen, cli: options && options.args ? { ...this.environmentService.args, ...options.args } : this.environmentService.args, forceNewWindow: options && options.forceNewWindow, forceReuseWindow: options && options.forceReuseWindow, @@ -324,10 +323,12 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable console[severity].apply(console, ...messages); } - async showItemInFolder(path: string): Promise { + async showItemInFolder(path: URI): Promise { this.logService.trace('windowsService#showItemInFolder'); - shell.showItemInFolder(path); + if (path.scheme === Schemas.file) { + shell.showItemInFolder(path.fsPath); + } } async getActiveWindowId(): Promise { @@ -421,14 +422,14 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable // Catch file URLs if (uri.authority === Schemas.file && !!uri.path) { - this.openFileForURI(URI.file(uri.fsPath)); + this.openFileForURI({ uri: URI.file(uri.fsPath) }); // using fsPath on a non-file URI... return true; } return false; } - private openFileForURI(uri: URI): void { + private openFileForURI(uri: IURIToOpen): void { const cli = assign(Object.create(null), this.environmentService.args, { goto: true }); const urisToOpen = [uri]; diff --git a/src/vs/platform/windows/node/windowsIpc.ts b/src/vs/platform/windows/node/windowsIpc.ts index 75e74d1f67..152951159f 100644 --- a/src/vs/platform/windows/node/windowsIpc.ts +++ b/src/vs/platform/windows/node/windowsIpc.ts @@ -4,13 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; -import { IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, CrashReporterStartOptions, IMessageBoxResult, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions, IDevToolsOptions, INewWindowOptions } from 'vs/platform/windows/common/windows'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IRecentlyOpened } from 'vs/platform/history/common/history'; -import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; +import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IWindowsService, IURIToOpen } from 'vs/platform/windows/common/windows'; +import { reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; -import { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { IRecent, isRecentFile, isRecentFolder } from 'vs/platform/history/common/history'; export class WindowsChannel implements IServerChannel { @@ -59,14 +57,17 @@ export class WindowsChannel implements IServerChannel { case 'enterWorkspace': return this.service.enterWorkspace(arg[0], URI.revive(arg[1])); case 'toggleFullScreen': return this.service.toggleFullScreen(arg); case 'setRepresentedFilename': return this.service.setRepresentedFilename(arg[0], arg[1]); - case 'addRecentlyOpened': return this.service.addRecentlyOpened(arg.map(URI.revive)); - case 'removeFromRecentlyOpened': { - let paths: Array = arg; - if (Array.isArray(paths)) { - paths = paths.map(path => isWorkspaceIdentifier(path) || typeof path === 'string' ? path : URI.revive(path)); + case 'addRecentlyOpened': return this.service.addRecentlyOpened(arg.map((recent: IRecent) => { + if (isRecentFile(recent)) { + recent.fileUri = URI.revive(recent.fileUri); + } else if (isRecentFolder(recent)) { + recent.folderUri = URI.revive(recent.folderUri); + } else { + recent.workspace = reviveWorkspaceIdentifier(recent.workspace); } - return this.service.removeFromRecentlyOpened(paths); - } + return recent; + })); + case 'removeFromRecentlyOpened': return this.service.removeFromRecentlyOpened(arg.map(URI.revive)); case 'clearRecentlyOpened': return this.service.clearRecentlyOpened(); case 'newWindowTab': return this.service.newWindowTab(); case 'showPreviousWindowTab': return this.service.showPreviousWindowTab(); @@ -85,7 +86,7 @@ export class WindowsChannel implements IServerChannel { case 'minimizeWindow': return this.service.minimizeWindow(arg); case 'onWindowTitleDoubleClick': return this.service.onWindowTitleDoubleClick(arg); case 'setDocumentEdited': return this.service.setDocumentEdited(arg[0], arg[1]); - case 'openWindow': return this.service.openWindow(arg[0], arg[1] ? (arg[1]).map(r => URI.revive(r)) : arg[1], arg[2]); + case 'openWindow': return this.service.openWindow(arg[0], arg[1] ? (arg[1]).map(r => { r.uri = URI.revive(r.uri); return r; }) : arg[1], arg[2]); case 'openNewWindow': return this.service.openNewWindow(arg); case 'showWindow': return this.service.showWindow(arg); case 'getWindows': return this.service.getWindows(); @@ -95,7 +96,7 @@ export class WindowsChannel implements IServerChannel { case 'toggleSharedProcess': return this.service.toggleSharedProcess(); case 'quit': return this.service.quit(); case 'log': return this.service.log(arg[0], arg[1]); - case 'showItemInFolder': return this.service.showItemInFolder(arg); + case 'showItemInFolder': return this.service.showItemInFolder(URI.revive(arg)); case 'getActiveWindowId': return this.service.getActiveWindowId(); case 'openExternal': return this.service.openExternal(arg); case 'startCrashReporter': return this.service.startCrashReporter(arg); @@ -105,223 +106,4 @@ export class WindowsChannel implements IServerChannel { throw new Error(`Call not found: ${command}`); } -} - -export class WindowsChannelClient implements IWindowsService { - - _serviceBrand: any; - - constructor(private channel: IChannel) { } - - get onWindowOpen(): Event { return this.channel.listen('onWindowOpen'); } - get onWindowFocus(): Event { return this.channel.listen('onWindowFocus'); } - get onWindowBlur(): Event { return this.channel.listen('onWindowBlur'); } - get onWindowMaximize(): Event { return this.channel.listen('onWindowMaximize'); } - get onWindowUnmaximize(): Event { return this.channel.listen('onWindowUnmaximize'); } - get onRecentlyOpenedChange(): Event { return this.channel.listen('onRecentlyOpenedChange'); } - - pickFileFolderAndOpen(options: INativeOpenDialogOptions): Promise { - return this.channel.call('pickFileFolderAndOpen', options); - } - - pickFileAndOpen(options: INativeOpenDialogOptions): Promise { - return this.channel.call('pickFileAndOpen', options); - } - - pickFolderAndOpen(options: INativeOpenDialogOptions): Promise { - return this.channel.call('pickFolderAndOpen', options); - } - - pickWorkspaceAndOpen(options: INativeOpenDialogOptions): Promise { - return this.channel.call('pickWorkspaceAndOpen', options); - } - - showMessageBox(windowId: number, options: MessageBoxOptions): Promise { - return this.channel.call('showMessageBox', [windowId, options]); - } - - showSaveDialog(windowId: number, options: SaveDialogOptions): Promise { - return this.channel.call('showSaveDialog', [windowId, options]); - } - - showOpenDialog(windowId: number, options: OpenDialogOptions): Promise { - return this.channel.call('showOpenDialog', [windowId, options]); - } - - reloadWindow(windowId: number, args?: ParsedArgs): Promise { - return this.channel.call('reloadWindow', [windowId, args]); - } - - openDevTools(windowId: number, options?: IDevToolsOptions): Promise { - return this.channel.call('openDevTools', [windowId, options]); - } - - toggleDevTools(windowId: number): Promise { - return this.channel.call('toggleDevTools', windowId); - } - - closeWorkspace(windowId: number): Promise { - return this.channel.call('closeWorkspace', windowId); - } - - enterWorkspace(windowId: number, path: URI): Promise { - return this.channel.call('enterWorkspace', [windowId, path]); - } - - toggleFullScreen(windowId: number): Promise { - return this.channel.call('toggleFullScreen', windowId); - } - - setRepresentedFilename(windowId: number, fileName: string): Promise { - return this.channel.call('setRepresentedFilename', [windowId, fileName]); - } - - addRecentlyOpened(files: URI[]): Promise { - return this.channel.call('addRecentlyOpened', files); - } - - removeFromRecentlyOpened(paths: Array): Promise { - return this.channel.call('removeFromRecentlyOpened', paths); - } - - clearRecentlyOpened(): Promise { - return this.channel.call('clearRecentlyOpened'); - } - - getRecentlyOpened(windowId: number): Promise { - return this.channel.call('getRecentlyOpened', windowId) - .then((recentlyOpened: IRecentlyOpened) => { - recentlyOpened.workspaces = recentlyOpened.workspaces.map(workspace => isWorkspaceIdentifier(workspace) ? workspace : URI.revive(workspace)); - recentlyOpened.files = recentlyOpened.files.map(URI.revive); - return recentlyOpened; - }); - } - - newWindowTab(): Promise { - return this.channel.call('newWindowTab'); - } - - showPreviousWindowTab(): Promise { - return this.channel.call('showPreviousWindowTab'); - } - - showNextWindowTab(): Promise { - return this.channel.call('showNextWindowTab'); - } - - moveWindowTabToNewWindow(): Promise { - return this.channel.call('moveWindowTabToNewWindow'); - } - - mergeAllWindowTabs(): Promise { - return this.channel.call('mergeAllWindowTabs'); - } - - toggleWindowTabsBar(): Promise { - return this.channel.call('toggleWindowTabsBar'); - } - - focusWindow(windowId: number): Promise { - return this.channel.call('focusWindow', windowId); - } - - closeWindow(windowId: number): Promise { - return this.channel.call('closeWindow', windowId); - } - - isFocused(windowId: number): Promise { - return this.channel.call('isFocused', windowId); - } - - isMaximized(windowId: number): Promise { - return this.channel.call('isMaximized', windowId); - } - - maximizeWindow(windowId: number): Promise { - return this.channel.call('maximizeWindow', windowId); - } - - unmaximizeWindow(windowId: number): Promise { - return this.channel.call('unmaximizeWindow', windowId); - } - - minimizeWindow(windowId: number): Promise { - return this.channel.call('minimizeWindow', windowId); - } - - onWindowTitleDoubleClick(windowId: number): Promise { - return this.channel.call('onWindowTitleDoubleClick', windowId); - } - - setDocumentEdited(windowId: number, flag: boolean): Promise { - return this.channel.call('setDocumentEdited', [windowId, flag]); - } - - quit(): Promise { - return this.channel.call('quit'); - } - - relaunch(options: { addArgs?: string[], removeArgs?: string[] }): Promise { - return this.channel.call('relaunch', [options]); - } - - whenSharedProcessReady(): Promise { - return this.channel.call('whenSharedProcessReady'); - } - - toggleSharedProcess(): Promise { - return this.channel.call('toggleSharedProcess'); - } - - openWindow(windowId: number, paths: URI[], options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean, args?: ParsedArgs }): Promise { - return this.channel.call('openWindow', [windowId, paths, options]); - } - - openNewWindow(options?: INewWindowOptions): Promise { - return this.channel.call('openNewWindow', options); - } - - showWindow(windowId: number): Promise { - return this.channel.call('showWindow', windowId); - } - - getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { - return this.channel.call<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]>('getWindows').then(result => { result.forEach(win => win.folderUri = win.folderUri ? URI.revive(win.folderUri) : win.folderUri); return result; }); - } - - getWindowCount(): Promise { - return this.channel.call('getWindowCount'); - } - - log(severity: string, ...messages: string[]): Promise { - return this.channel.call('log', [severity, messages]); - } - - showItemInFolder(path: string): Promise { - return this.channel.call('showItemInFolder', path); - } - - getActiveWindowId(): Promise { - return this.channel.call('getActiveWindowId'); - } - - openExternal(url: string): Promise { - return this.channel.call('openExternal', url); - } - - startCrashReporter(config: CrashReporterStartOptions): Promise { - return this.channel.call('startCrashReporter', config); - } - - updateTouchBar(windowId: number, items: ISerializableCommandAction[][]): Promise { - return this.channel.call('updateTouchBar', [windowId, items]); - } - - openAboutDialog(): Promise { - return this.channel.call('openAboutDialog'); - } - - resolveProxy(windowId: number, url: string): Promise { - return Promise.resolve(this.channel.call('resolveProxy', [windowId, url])); - } -} +} \ No newline at end of file diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index ebb97472e3..bd51d8445d 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import * as paths from 'vs/base/common/paths'; +import { isAbsolute } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { TernarySearchTree } from 'vs/base/common/map'; @@ -183,12 +183,12 @@ export class Workspace implements IWorkspace { this._configuration = configuration; } - getFolder(resource: URI): IWorkspaceFolder | null | undefined { + getFolder(resource: URI): IWorkspaceFolder | null { if (!resource) { return null; } - return this._foldersMap.findSubstr(resource.toString()); + return this._foldersMap.findSubstr(resource.toString()) || null; } private updateFoldersMap(): void { @@ -257,7 +257,7 @@ function parseWorkspaceFolders(configuredFolders: IStoredWorkspaceFolder[], rela function toUri(path: string, relativeTo: URI | undefined): URI | null { if (path) { - if (paths.isAbsolute(path)) { + if (isAbsolute(path)) { return URI.file(path); } if (relativeTo) { diff --git a/src/vs/platform/workspaces/common/workspaces.ts b/src/vs/platform/workspaces/common/workspaces.ts index d9409520b3..20219108ee 100644 --- a/src/vs/platform/workspaces/common/workspaces.ts +++ b/src/vs/platform/workspaces/common/workspaces.ts @@ -7,7 +7,15 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { localize } from 'vs/nls'; import { Event } from 'vs/base/common/event'; import { IWorkspaceFolder, IWorkspace } from 'vs/platform/workspace/common/workspace'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; +import { extname } from 'vs/base/common/path'; +import { dirname, resolvePath, isEqualAuthority, isEqualOrParent, relativePath } from 'vs/base/common/resources'; +import * as jsonEdit from 'vs/base/common/jsonEdit'; +import * as json from 'vs/base/common/json'; +import { Schemas } from 'vs/base/common/network'; +import { normalizeDriveLetter } from 'vs/base/common/labels'; +import { toSlashes } from 'vs/base/common/extpath'; export const IWorkspacesMainService = createDecorator('workspacesMainService'); export const IWorkspacesService = createDecorator('workspacesService'); @@ -23,7 +31,11 @@ export type ISingleFolderWorkspaceIdentifier = URI; export interface IWorkspaceIdentifier { id: string; - configPath: string; + configPath: URI; +} + +export function reviveWorkspaceIdentifier(workspace: { id: string, configPath: UriComponents; }): IWorkspaceIdentifier { + return { id: workspace.id, configPath: URI.revive(workspace.configPath) }; } export function isStoredWorkspaceFolder(thing: any): thing is IStoredWorkspaceFolder { @@ -58,6 +70,7 @@ export type IStoredWorkspaceFolder = IRawFileWorkspaceFolder | IRawUriWorkspaceF export interface IResolvedWorkspace extends IWorkspaceIdentifier { folders: IWorkspaceFolder[]; + remoteAuthority?: string; } export interface IStoredWorkspace { @@ -74,32 +87,35 @@ export interface IWorkspaceFolderCreationData { name?: string; } +export interface IUntitledWorkspaceInfo { + workspace: IWorkspaceIdentifier; + remoteAuthority?: string; +} + export interface IWorkspacesMainService extends IWorkspacesService { _serviceBrand: any; onUntitledWorkspaceDeleted: Event; - saveWorkspaceAs(workspace: IWorkspaceIdentifier, target: string): Promise; - createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[]): IWorkspaceIdentifier; - resolveWorkspaceSync(path: string): IResolvedWorkspace | null; + resolveLocalWorkspaceSync(path: URI): IResolvedWorkspace | null; isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean; deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void; - getUntitledWorkspacesSync(): IWorkspaceIdentifier[]; - - getWorkspaceId(workspacePath: string): string; - - getWorkspaceIdentifier(workspacePath: URI): IWorkspaceIdentifier; + getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[]; } export interface IWorkspacesService { _serviceBrand: any; - createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[]): Promise; + createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise; + + deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise; + + getWorkspaceIdentifier(workspacePath: URI): Promise; } export function isSingleFolderWorkspaceIdentifier(obj: any): obj is ISingleFolderWorkspaceIdentifier { @@ -109,13 +125,13 @@ export function isSingleFolderWorkspaceIdentifier(obj: any): obj is ISingleFolde export function isWorkspaceIdentifier(obj: any): obj is IWorkspaceIdentifier { const workspaceIdentifier = obj as IWorkspaceIdentifier; - return workspaceIdentifier && typeof workspaceIdentifier.id === 'string' && typeof workspaceIdentifier.configPath === 'string'; + return workspaceIdentifier && typeof workspaceIdentifier.id === 'string' && workspaceIdentifier.configPath instanceof URI; } export function toWorkspaceIdentifier(workspace: IWorkspace): IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | undefined { if (workspace.configuration) { return { - configPath: workspace.configuration.fsPath, + configPath: workspace.configuration, id: workspace.id }; } @@ -136,3 +152,120 @@ export type IWorkspaceInitializationPayload = IMultiFolderWorkspaceInitializatio export function isSingleFolderWorkspaceInitializationPayload(obj: any): obj is ISingleFolderWorkspaceInitializationPayload { return isSingleFolderWorkspaceIdentifier((obj.folder as ISingleFolderWorkspaceIdentifier)); } + +const WORKSPACE_SUFFIX = '.' + WORKSPACE_EXTENSION; + +export function hasWorkspaceFileExtension(path: string) { + return extname(path) === WORKSPACE_SUFFIX; +} + +const SLASH = '/'; + +/** + * Given a folder URI and the workspace config folder, computes the IStoredWorkspaceFolder using +* a relative or absolute path or a uri. + * Undefined is returned if the folderURI and the targetConfigFolderURI don't have the same schema or authority + * + * @param folderURI a workspace folder + * @param folderName a workspace name + * @param targetConfigFolderURI the folder where the workspace is living in + * @param useSlashForPath if set, use forward slashes for file paths on windows + */ +export function getStoredWorkspaceFolder(folderURI: URI, folderName: string | undefined, targetConfigFolderURI: URI, useSlashForPath = !isWindows): IStoredWorkspaceFolder { + + if (folderURI.scheme !== targetConfigFolderURI.scheme) { + return { name: folderName, uri: folderURI.toString(true) }; + } + + let folderPath: string | undefined; + if (isEqualOrParent(folderURI, targetConfigFolderURI)) { + // use relative path + folderPath = relativePath(targetConfigFolderURI, folderURI) || '.'; // always uses forward slashes + if (isWindows && folderURI.scheme === Schemas.file && !useSlashForPath) { + // Windows gets special treatment: + // - use backslahes unless slash is used by other existing folders + folderPath = folderPath.replace(/\//g, '\\'); + } + } else { + // use absolute path + if (folderURI.scheme === Schemas.file) { + folderPath = folderURI.fsPath; + if (isWindows) { + // Windows gets special treatment: + // - normalize all paths to get nice casing of drive letters + // - use backslahes unless slash is used by other existing folders + folderPath = normalizeDriveLetter(folderPath); + if (useSlashForPath) { + folderPath = toSlashes(folderPath); + } + } + } else { + if (!isEqualAuthority(folderURI.authority, targetConfigFolderURI.authority)) { + return { name: folderName, uri: folderURI.toString(true) }; + } + folderPath = folderURI.path; + } + } + return { name: folderName, path: folderPath }; +} + +/** + * Rewrites the content of a workspace file to be saved at a new location. + * Throws an exception if file is not a valid workspace file + */ +export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string, configPathURI: URI, targetConfigPathURI: URI) { + let storedWorkspace = doParseStoredWorkspace(configPathURI, rawWorkspaceContents); + + const sourceConfigFolder = dirname(configPathURI); + const targetConfigFolder = dirname(targetConfigPathURI); + + const rewrittenFolders: IStoredWorkspaceFolder[] = []; + const slashForPath = useSlashForPath(storedWorkspace.folders); + + // Rewrite absolute paths to relative paths if the target workspace folder + // is a parent of the location of the workspace file itself. Otherwise keep + // using absolute paths. + for (const folder of storedWorkspace.folders) { + let folderURI = isRawFileWorkspaceFolder(folder) ? resolvePath(sourceConfigFolder, folder.path) : URI.parse(folder.uri); + rewrittenFolders.push(getStoredWorkspaceFolder(folderURI, folder.name, targetConfigFolder, slashForPath)); + } + + // Preserve as much of the existing workspace as possible by using jsonEdit + // and only changing the folders portion. + let newRawWorkspaceContents = rawWorkspaceContents; + const edits = jsonEdit.setProperty(rawWorkspaceContents, ['folders'], rewrittenFolders, { insertSpaces: false, tabSize: 4, eol: (isLinux || isMacintosh) ? '\n' : '\r\n' }); + edits.forEach(edit => { + newRawWorkspaceContents = jsonEdit.applyEdit(rawWorkspaceContents, edit); + }); + return newRawWorkspaceContents; +} + +function doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace { + + // Parse workspace file + let storedWorkspace: IStoredWorkspace = json.parse(contents); // use fault tolerant parser + + // Filter out folders which do not have a path or uri set + if (Array.isArray(storedWorkspace.folders)) { + storedWorkspace.folders = storedWorkspace.folders.filter(folder => isStoredWorkspaceFolder(folder)); + } + + // Validate + if (!Array.isArray(storedWorkspace.folders)) { + throw new Error(`${path} looks like an invalid workspace file.`); + } + + return storedWorkspace; +} + +export function useSlashForPath(storedFolders: IStoredWorkspaceFolder[]): boolean { + if (isWindows) { + for (const folder of storedFolders) { + if (isRawFileWorkspaceFolder(folder) && folder.path.indexOf(SLASH) >= 0) { + return true; + } + } + return false; + } + return true; +} \ No newline at end of file diff --git a/src/vs/platform/workspaces/electron-browser/workspacesService.ts b/src/vs/platform/workspaces/electron-browser/workspacesService.ts new file mode 100644 index 0000000000..45b4d77972 --- /dev/null +++ b/src/vs/platform/workspaces/electron-browser/workspacesService.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IWorkspacesService, IWorkspaceIdentifier, IWorkspaceFolderCreationData, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; + +export class WorkspacesService implements IWorkspacesService { + + _serviceBrand: ServiceIdentifier; + + private channel: IChannel; + + constructor(@IMainProcessService mainProcessService: IMainProcessService) { + this.channel = mainProcessService.getChannel('workspaces'); + } + + createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { + return this.channel.call('createUntitledWorkspace', [folders, remoteAuthority]).then(reviveWorkspaceIdentifier); + } + + deleteUntitledWorkspace(workspaceIdentifier: IWorkspaceIdentifier): Promise { + return this.channel.call('deleteUntitledWorkspace', workspaceIdentifier); + } + + getWorkspaceIdentifier(configPath: URI): Promise { + return this.channel.call('getWorkspaceIdentifier', configPath); + } +} diff --git a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts index 5b1215d11c..edaf0186c0 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesMainService.ts @@ -3,36 +3,33 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IWorkspacesMainService, IWorkspaceIdentifier, WORKSPACE_EXTENSION, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; -import { isParent } from 'vs/platform/files/common/files'; +import { IWorkspacesMainService, IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { join, dirname, extname } from 'path'; -import { mkdirp, writeFile, readFile } from 'vs/base/node/pfs'; +import { join, dirname } from 'vs/base/common/path'; +import { mkdirp, writeFile } from 'vs/base/node/pfs'; import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs'; import { isLinux } from 'vs/base/common/platform'; import { delSync, readdirSync, writeFileAndFlushSync } from 'vs/base/node/extfs'; import { Event, Emitter } from 'vs/base/common/event'; import { ILogService } from 'vs/platform/log/common/log'; -import { isEqual } from 'vs/base/common/paths'; -import { coalesce } from 'vs/base/common/arrays'; import { createHash } from 'crypto'; import * as json from 'vs/base/common/json'; -import { massageFolderPathForWorkspace, rewriteWorkspaceFileForNewLocation } from 'vs/platform/workspaces/node/workspaces'; import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; -import { fsPath, dirname as resourcesDirname } from 'vs/base/common/resources'; +import { originalFSPath, dirname as resourcesDirname, isEqualOrParent, joinPath } from 'vs/base/common/resources'; export interface IStoredWorkspace { folders: IStoredWorkspaceFolder[]; + remoteAuthority?: string; } export class WorkspacesMainService extends Disposable implements IWorkspacesMainService { _serviceBrand: any; - private workspacesHome: string; + private readonly untitledWorkspacesHome: URI; // local URI that contains all untitled workspaces private readonly _onUntitledWorkspaceDeleted = this._register(new Emitter()); get onUntitledWorkspaceDeleted(): Event { return this._onUntitledWorkspaceDeleted.event; } @@ -43,36 +40,40 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain ) { super(); - this.workspacesHome = environmentService.workspacesHome; + this.untitledWorkspacesHome = environmentService.untitledWorkspacesHome; } - resolveWorkspaceSync(path: string): IResolvedWorkspace | null { - if (!this.isWorkspacePath(path)) { + resolveLocalWorkspaceSync(uri: URI): IResolvedWorkspace | null { + if (!this.isWorkspacePath(uri)) { return null; // does not look like a valid workspace config file } + if (uri.scheme !== Schemas.file) { + return null; + } let contents: string; try { - contents = readFileSync(path, 'utf8'); + contents = readFileSync(uri.fsPath, 'utf8'); } catch (error) { return null; // invalid workspace } - return this.doResolveWorkspace(URI.file(path), contents); + return this.doResolveWorkspace(uri, contents); } - private isWorkspacePath(path: string): boolean { - return this.isInsideWorkspacesHome(path) || extname(path) === `.${WORKSPACE_EXTENSION}`; + private isWorkspacePath(uri: URI): boolean { + return this.isInsideWorkspacesHome(uri) || hasWorkspaceFileExtension(uri.path); } private doResolveWorkspace(path: URI, contents: string): IResolvedWorkspace | null { try { const workspace = this.doParseStoredWorkspace(path, contents); - const workspaceIdentifier = this.getWorkspaceIdentifier(path); + const workspaceIdentifier = getWorkspaceIdentifier(path); return { id: workspaceIdentifier.id, configPath: workspaceIdentifier.configPath, - folders: toWorkspaceFolders(workspace.folders, resourcesDirname(path)!) + folders: toWorkspaceFolders(workspace.folders, resourcesDirname(path)), + remoteAuthority: workspace.remoteAuthority }; } catch (error) { this.logService.warn(error.toString()); @@ -99,135 +100,87 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain return storedWorkspace; } - private isInsideWorkspacesHome(path: string): boolean { - return isParent(path, this.environmentService.workspacesHome, !isLinux /* ignore case */); + private isInsideWorkspacesHome(path: URI): boolean { + return isEqualOrParent(path, this.environmentService.untitledWorkspacesHome); } - createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[]): Promise { - const { workspace, configParent, storedWorkspace } = this.newUntitledWorkspace(folders); + createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): Promise { + const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority); + const configPath = workspace.configPath.fsPath; - return mkdirp(configParent).then(() => { - return writeFile(workspace.configPath, JSON.stringify(storedWorkspace, null, '\t')).then(() => workspace); + return mkdirp(dirname(configPath)).then(() => { + return writeFile(configPath, JSON.stringify(storedWorkspace, null, '\t')).then(() => workspace); }); } - createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[]): IWorkspaceIdentifier { - const { workspace, configParent, storedWorkspace } = this.newUntitledWorkspace(folders); + createUntitledWorkspaceSync(folders?: IWorkspaceFolderCreationData[], remoteAuthority?: string): IWorkspaceIdentifier { + const { workspace, storedWorkspace } = this.newUntitledWorkspace(folders, remoteAuthority); + const configPath = workspace.configPath.fsPath; - if (!existsSync(this.workspacesHome)) { - mkdirSync(this.workspacesHome); + const configPathDir = dirname(configPath); + if (!existsSync(configPathDir)) { + const configPathDirDir = dirname(configPathDir); + if (!existsSync(configPathDirDir)) { + mkdirSync(configPathDirDir); + } + mkdirSync(configPathDir); } - mkdirSync(configParent); - - writeFileAndFlushSync(workspace.configPath, JSON.stringify(storedWorkspace, null, '\t')); + writeFileAndFlushSync(configPath, JSON.stringify(storedWorkspace, null, '\t')); return workspace; } - private newUntitledWorkspace(folders: IWorkspaceFolderCreationData[] = []): { workspace: IWorkspaceIdentifier, configParent: string, storedWorkspace: IStoredWorkspace } { + private newUntitledWorkspace(folders: IWorkspaceFolderCreationData[] = [], remoteAuthority?: string): { workspace: IWorkspaceIdentifier, storedWorkspace: IStoredWorkspace } { const randomId = (Date.now() + Math.round(Math.random() * 1000)).toString(); - const untitledWorkspaceConfigFolder = join(this.workspacesHome, randomId); - const untitledWorkspaceConfigPath = join(untitledWorkspaceConfigFolder, UNTITLED_WORKSPACE_NAME); + const untitledWorkspaceConfigFolder = joinPath(this.untitledWorkspacesHome, randomId); + const untitledWorkspaceConfigPath = joinPath(untitledWorkspaceConfigFolder, UNTITLED_WORKSPACE_NAME); - const storedWorkspace: IStoredWorkspace = { - folders: folders.map(folder => { - const folderResource = folder.uri; - let storedWorkspace: IStoredWorkspaceFolder; + const storedWorkspaceFolder: IStoredWorkspaceFolder[] = []; - // File URI - if (folderResource.scheme === Schemas.file) { - storedWorkspace = { path: massageFolderPathForWorkspace(fsPath(folderResource), URI.file(untitledWorkspaceConfigFolder), []) }; - } - - // Any URI - else { - storedWorkspace = { uri: folderResource.toString(true) }; - } - - if (folder.name) { - storedWorkspace.name = folder.name; - } - - return storedWorkspace; - }) - }; + for (const folder of folders) { + storedWorkspaceFolder.push(getStoredWorkspaceFolder(folder.uri, folder.name, untitledWorkspaceConfigFolder)); + } return { - workspace: { - id: this.getWorkspaceId(untitledWorkspaceConfigPath), - configPath: untitledWorkspaceConfigPath - }, - configParent: untitledWorkspaceConfigFolder, - storedWorkspace + workspace: getWorkspaceIdentifier(untitledWorkspaceConfigPath), + storedWorkspace: { folders: storedWorkspaceFolder, remoteAuthority } }; } - getWorkspaceId(workspaceConfigPath: string): string { - if (!isLinux) { - workspaceConfigPath = workspaceConfigPath.toLowerCase(); // sanitize for platform file system - } - - return createHash('md5').update(workspaceConfigPath).digest('hex'); - } - - getWorkspaceIdentifier(workspacePath: URI): IWorkspaceIdentifier { - if (workspacePath.scheme === Schemas.file) { - const configPath = fsPath(workspacePath); - return { - configPath, - id: this.getWorkspaceId(configPath) - }; - } - throw new Error('Not yet supported'); - /*return { - configPath: workspacePath - id: this.getWorkspaceId(workspacePath.toString()); - };*/ + getWorkspaceIdentifier(configPath: URI): Promise { + return Promise.resolve(getWorkspaceIdentifier(configPath)); } isUntitledWorkspace(workspace: IWorkspaceIdentifier): boolean { return this.isInsideWorkspacesHome(workspace.configPath); } - saveWorkspaceAs(workspace: IWorkspaceIdentifier, targetConfigPath: string): Promise { - - // Return early if target is same as source - if (isEqual(workspace.configPath, targetConfigPath, !isLinux)) { - return Promise.resolve(workspace); - } - - // Read the contents of the workspace file and resolve it - return readFile(workspace.configPath).then(raw => { - const targetConfigPathURI = URI.file(targetConfigPath); - const newRawWorkspaceContents = rewriteWorkspaceFileForNewLocation(raw.toString(), URI.file(workspace.configPath), targetConfigPathURI); - - return writeFile(targetConfigPath, newRawWorkspaceContents).then(() => { - return this.getWorkspaceIdentifier(targetConfigPathURI); - }); - }); - } - deleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void { if (!this.isUntitledWorkspace(workspace)) { return; // only supported for untitled workspaces } // Delete from disk - this.doDeleteUntitledWorkspaceSync(workspace.configPath); + this.doDeleteUntitledWorkspaceSync(workspace); // Event this._onUntitledWorkspaceDeleted.fire(workspace); } - private doDeleteUntitledWorkspaceSync(configPath: string): void { - try { + deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise { + this.deleteUntitledWorkspaceSync(workspace); + return Promise.resolve(); + } + private doDeleteUntitledWorkspaceSync(workspace: IWorkspaceIdentifier): void { + const configPath = originalFSPath(workspace.configPath); + try { // Delete Workspace delSync(dirname(configPath)); // Mark Workspace Storage to be deleted - const workspaceStoragePath = join(this.environmentService.workspaceStorageHome, this.getWorkspaceId(configPath)); + const workspaceStoragePath = join(this.environmentService.workspaceStorageHome, workspace.id); if (existsSync(workspaceStoragePath)) { writeFileSync(join(workspaceStoragePath, 'obsolete'), ''); } @@ -236,27 +189,40 @@ export class WorkspacesMainService extends Disposable implements IWorkspacesMain } } - getUntitledWorkspacesSync(): IWorkspaceIdentifier[] { - let untitledWorkspacePaths: string[] = []; + getUntitledWorkspacesSync(): IUntitledWorkspaceInfo[] { + let untitledWorkspaces: IUntitledWorkspaceInfo[] = []; try { - untitledWorkspacePaths = readdirSync(this.workspacesHome).map(folder => join(this.workspacesHome, folder, UNTITLED_WORKSPACE_NAME)); + const untitledWorkspacePaths = readdirSync(this.untitledWorkspacesHome.fsPath).map(folder => joinPath(this.untitledWorkspacesHome, folder, UNTITLED_WORKSPACE_NAME)); + for (const untitledWorkspacePath of untitledWorkspacePaths) { + const workspace = getWorkspaceIdentifier(untitledWorkspacePath); + const resolvedWorkspace = this.resolveLocalWorkspaceSync(untitledWorkspacePath); + if (!resolvedWorkspace) { + this.doDeleteUntitledWorkspaceSync(workspace); + } else { + untitledWorkspaces.push({ workspace, remoteAuthority: resolvedWorkspace.remoteAuthority }); + } + } } catch (error) { if (error && error.code !== 'ENOENT') { - this.logService.warn(`Unable to read folders in ${this.workspacesHome} (${error}).`); + this.logService.warn(`Unable to read folders in ${this.untitledWorkspacesHome} (${error}).`); } } - - const untitledWorkspaces: IWorkspaceIdentifier[] = coalesce(untitledWorkspacePaths.map(untitledWorkspacePath => { - const workspace = this.resolveWorkspaceSync(untitledWorkspacePath); - if (!workspace) { - this.doDeleteUntitledWorkspaceSync(untitledWorkspacePath); - - return null; // invalid workspace - } - - return { id: workspace.id, configPath: untitledWorkspacePath }; - })); - return untitledWorkspaces; } } + +function getWorkspaceId(configPath: URI): string { + let workspaceConfigPath = configPath.scheme === Schemas.file ? originalFSPath(configPath) : configPath.toString(); + if (!isLinux) { + workspaceConfigPath = workspaceConfigPath.toLowerCase(); // sanitize for platform file system + } + + return createHash('md5').update(workspaceConfigPath).digest('hex'); +} + +export function getWorkspaceIdentifier(configPath: URI): IWorkspaceIdentifier { + return { + configPath, + id: getWorkspaceId(configPath) + }; +} diff --git a/src/vs/platform/workspaces/node/workspaces.ts b/src/vs/platform/workspaces/node/workspaces.ts deleted file mode 100644 index f6cd844b82..0000000000 --- a/src/vs/platform/workspaces/node/workspaces.ts +++ /dev/null @@ -1,127 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IStoredWorkspaceFolder, isRawFileWorkspaceFolder, IStoredWorkspace, isStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; -import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; -import { isAbsolute, relative, posix, resolve } from 'path'; -import { normalize, isEqualOrParent } from 'vs/base/common/paths'; -import { normalizeDriveLetter } from 'vs/base/common/labels'; -import { URI } from 'vs/base/common/uri'; -import { fsPath, dirname } from 'vs/base/common/resources'; -import { Schemas } from 'vs/base/common/network'; -import * as jsonEdit from 'vs/base/common/jsonEdit'; -import * as json from 'vs/base/common/json'; - -const SLASH = '/'; - -/** - * Given the absolute path to a folder, massage it in a way that it fits - * into an existing set of workspace folders of a workspace. - * - * @param absoluteFolderPath the absolute path of a workspace folder - * @param targetConfigFolder the folder where the workspace is living in - * @param existingFolders a set of existing folders of the workspace - */ -export function massageFolderPathForWorkspace(absoluteFolderPath: string, targetConfigFolderURI: URI, existingFolders: IStoredWorkspaceFolder[]): string { - - if (targetConfigFolderURI.scheme === Schemas.file) { - const targetFolderPath = fsPath(targetConfigFolderURI); - // Convert path to relative path if the target config folder - // is a parent of the path. - if (isEqualOrParent(absoluteFolderPath, targetFolderPath, !isLinux)) { - absoluteFolderPath = relative(targetFolderPath, absoluteFolderPath) || '.'; - } - - // Windows gets special treatment: - // - normalize all paths to get nice casing of drive letters - // - convert to slashes if we want to use slashes for paths - if (isWindows) { - if (isAbsolute(absoluteFolderPath)) { - if (shouldUseSlashForPath(existingFolders)) { - absoluteFolderPath = normalize(absoluteFolderPath, false /* do not use OS path separator */); - } - - absoluteFolderPath = normalizeDriveLetter(absoluteFolderPath); - } else if (shouldUseSlashForPath(existingFolders)) { - absoluteFolderPath = absoluteFolderPath.replace(/[\\]/g, SLASH); - } - } - } else { - if (isEqualOrParent(absoluteFolderPath, targetConfigFolderURI.path)) { - absoluteFolderPath = posix.relative(absoluteFolderPath, targetConfigFolderURI.path) || '.'; - } - } - - return absoluteFolderPath; -} - -/** - * Rewrites the content of a workspace file to be saved at a new location. - * Throws an exception if file is not a valid workspace file - */ -export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string, configPathURI: URI, targetConfigPathURI: URI) { - let storedWorkspace = doParseStoredWorkspace(configPathURI, rawWorkspaceContents); - - const sourceConfigFolder = dirname(configPathURI)!; - const targetConfigFolder = dirname(targetConfigPathURI)!; - - // Rewrite absolute paths to relative paths if the target workspace folder - // is a parent of the location of the workspace file itself. Otherwise keep - // using absolute paths. - for (const folder of storedWorkspace.folders) { - if (isRawFileWorkspaceFolder(folder)) { - if (sourceConfigFolder.scheme === Schemas.file) { - if (!isAbsolute(folder.path)) { - folder.path = resolve(fsPath(sourceConfigFolder), folder.path); // relative paths get resolved against the workspace location - } - folder.path = massageFolderPathForWorkspace(folder.path, targetConfigFolder, storedWorkspace.folders); - } - } - } - - // Preserve as much of the existing workspace as possible by using jsonEdit - // and only changing the folders portion. - let newRawWorkspaceContents = rawWorkspaceContents; - const edits = jsonEdit.setProperty(rawWorkspaceContents, ['folders'], storedWorkspace.folders, { insertSpaces: false, tabSize: 4, eol: (isLinux || isMacintosh) ? '\n' : '\r\n' }); - edits.forEach(edit => { - newRawWorkspaceContents = jsonEdit.applyEdit(rawWorkspaceContents, edit); - }); - return newRawWorkspaceContents; -} - -function doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace { - - // Parse workspace file - let storedWorkspace: IStoredWorkspace = json.parse(contents); // use fault tolerant parser - - // Filter out folders which do not have a path or uri set - if (Array.isArray(storedWorkspace.folders)) { - storedWorkspace.folders = storedWorkspace.folders.filter(folder => isStoredWorkspaceFolder(folder)); - } - - // Validate - if (!Array.isArray(storedWorkspace.folders)) { - throw new Error(`${path} looks like an invalid workspace file.`); - } - - return storedWorkspace; -} - -function shouldUseSlashForPath(storedFolders: IStoredWorkspaceFolder[]): boolean { - - // Determine which path separator to use: - // - macOS/Linux: slash - // - Windows: use slash if already used in that file - let useSlashesForPath = !isWindows; - if (isWindows) { - storedFolders.forEach(folder => { - if (isRawFileWorkspaceFolder(folder) && !useSlashesForPath && folder.path.indexOf(SLASH) >= 0) { - useSlashesForPath = true; - } - }); - } - - return useSlashesForPath; -} diff --git a/src/vs/platform/workspaces/node/workspacesIpc.ts b/src/vs/platform/workspaces/node/workspacesIpc.ts index be044176bb..c9fb8848b9 100644 --- a/src/vs/platform/workspaces/node/workspacesIpc.ts +++ b/src/vs/platform/workspaces/node/workspacesIpc.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; -import { IWorkspacesService, IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces'; +import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesMainService } from 'vs/platform/workspaces/common/workspaces'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; @@ -19,7 +19,8 @@ export class WorkspacesChannel implements IServerChannel { call(_, command: string, arg?: any): Promise { switch (command) { case 'createUntitledWorkspace': { - const rawFolders: IWorkspaceFolderCreationData[] = arg; + const rawFolders: IWorkspaceFolderCreationData[] = arg[0]; + const remoteAuthority: string = arg[1]; let folders: IWorkspaceFolderCreationData[] | undefined = undefined; if (Array.isArray(rawFolders)) { folders = rawFolders.map(rawFolder => { @@ -30,21 +31,17 @@ export class WorkspacesChannel implements IServerChannel { }); } - return this.service.createUntitledWorkspace(folders); + return this.service.createUntitledWorkspace(folders, remoteAuthority); + } + case 'deleteUntitledWorkspace': { + const w: IWorkspaceIdentifier = arg; + return this.service.deleteUntitledWorkspace({ id: w.id, configPath: URI.revive(w.configPath) }); + } + case 'getWorkspaceIdentifier': { + return this.service.getWorkspaceIdentifier(URI.revive(arg)); } } throw new Error(`Call not found: ${command}`); } } - -export class WorkspacesChannelClient implements IWorkspacesService { - - _serviceBrand: any; - - constructor(private channel: IChannel) { } - - createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[]): Promise { - return this.channel.call('createUntitledWorkspace', folders); - } -} diff --git a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts index 8c8e1ec8c8..9c7b830f05 100644 --- a/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts +++ b/src/vs/platform/workspaces/test/electron-main/workspacesMainService.test.ts @@ -6,26 +6,25 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as os from 'os'; -import * as path from 'path'; -import * as extfs from 'vs/base/node/extfs'; +import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; import { parseArgs } from 'vs/platform/environment/node/argv'; import { WorkspacesMainService, IStoredWorkspace } from 'vs/platform/workspaces/electron-main/workspacesMainService'; -import { WORKSPACE_EXTENSION, IWorkspaceIdentifier, IRawFileWorkspaceFolder, IWorkspaceFolderCreationData, IRawUriWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; +import { WORKSPACE_EXTENSION, IWorkspaceIdentifier, IRawFileWorkspaceFolder, IWorkspaceFolderCreationData, IRawUriWorkspaceFolder, rewriteWorkspaceFileForNewLocation } from 'vs/platform/workspaces/common/workspaces'; import { NullLogService } from 'vs/platform/log/common/log'; import { URI } from 'vs/base/common/uri'; -import { getRandomTestPath } from 'vs/workbench/test/workbenchTestServices'; +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { isWindows } from 'vs/base/common/platform'; import { normalizeDriveLetter } from 'vs/base/common/labels'; suite('WorkspacesMainService', () => { const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'workspacesservice'); - const workspacesHome = path.join(parentDir, 'Workspaces'); + const untitledWorkspacesHomePath = path.join(parentDir, 'Workspaces'); class TestEnvironmentService extends EnvironmentService { - get workspacesHome(): string { - return workspacesHome; + get untitledWorkspacesHome(): URI { + return URI.file(untitledWorkspacesHomePath); } } @@ -56,13 +55,13 @@ suite('WorkspacesMainService', () => { service = new TestWorkspacesMainService(environmentService, logService); // Delete any existing backups completely and then re-create it. - return pfs.del(workspacesHome, os.tmpdir()).then(() => { - return pfs.mkdirp(workspacesHome); + return pfs.del(untitledWorkspacesHomePath, os.tmpdir()).then(() => { + return pfs.mkdirp(untitledWorkspacesHomePath); }); }); teardown(() => { - return pfs.del(workspacesHome, os.tmpdir()); + return pfs.del(untitledWorkspacesHomePath, os.tmpdir()); }); function assertPathEquals(p1: string, p2): void { @@ -74,13 +73,17 @@ suite('WorkspacesMainService', () => { assert.equal(p1, p2); } + function assertEqualURI(u1: URI, u2: URI): void { + assert.equal(u1.toString(), u2.toString()); + } + test('createWorkspace (folders)', () => { return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { assert.ok(workspace); - assert.ok(fs.existsSync(workspace.configPath)); + assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); - const ws = JSON.parse(fs.readFileSync(workspace.configPath).toString()) as IStoredWorkspace; + const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; assert.equal(ws.folders.length, 2); // assertPathEquals((ws.folders[0]).path, process.cwd()); assertPathEquals((ws.folders[1]).path, os.tmpdir()); @@ -93,10 +96,10 @@ suite('WorkspacesMainService', () => { test('createWorkspace (folders with name)', () => { return createWorkspace([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']).then(workspace => { assert.ok(workspace); - assert.ok(fs.existsSync(workspace.configPath)); + assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); - const ws = JSON.parse(fs.readFileSync(workspace.configPath).toString()) as IStoredWorkspace; + const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; assert.equal(ws.folders.length, 2); // assertPathEquals((ws.folders[0]).path, process.cwd()); assertPathEquals((ws.folders[1]).path, os.tmpdir()); @@ -107,28 +110,33 @@ suite('WorkspacesMainService', () => { }); test('createUntitledWorkspace (folders as other resource URIs)', () => { - return service.createUntitledWorkspace([{ uri: URI.from({ scheme: 'myScheme', path: process.cwd() }) }, { uri: URI.from({ scheme: 'myScheme', path: os.tmpdir() }) }]).then(workspace => { + const folder1URI = URI.parse('myscheme://server/work/p/f1'); + const folder2URI = URI.parse('myscheme://server/work/o/f3'); + + return service.createUntitledWorkspace([{ uri: folder1URI }, { uri: folder2URI }], 'server').then(workspace => { assert.ok(workspace); - assert.ok(fs.existsSync(workspace.configPath)); + assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); - const ws = JSON.parse(fs.readFileSync(workspace.configPath).toString()) as IStoredWorkspace; + const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; assert.equal(ws.folders.length, 2); - assert.equal((ws.folders[0]).uri, URI.from({ scheme: 'myScheme', path: process.cwd() }).toString(true)); - assert.equal((ws.folders[1]).uri, URI.from({ scheme: 'myScheme', path: os.tmpdir() }).toString(true)); + assert.equal((ws.folders[0]).uri, folder1URI.toString(true)); + assert.equal((ws.folders[1]).uri, folder2URI.toString(true)); assert.ok(!(ws.folders[0]).name); assert.ok(!(ws.folders[1]).name); + + assert.equal(ws.remoteAuthority, 'server'); }); }); test('createWorkspaceSync (folders)', () => { const workspace = createWorkspaceSync([process.cwd(), os.tmpdir()]); assert.ok(workspace); - assert.ok(fs.existsSync(workspace.configPath)); + assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); - const ws = JSON.parse(fs.readFileSync(workspace.configPath).toString()) as IStoredWorkspace; + const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; assert.equal(ws.folders.length, 2); assertPathEquals((ws.folders[0]).path, process.cwd()); assertPathEquals((ws.folders[1]).path, os.tmpdir()); @@ -140,10 +148,10 @@ suite('WorkspacesMainService', () => { test('createWorkspaceSync (folders with names)', () => { const workspace = createWorkspaceSync([process.cwd(), os.tmpdir()], ['currentworkingdirectory', 'tempdir']); assert.ok(workspace); - assert.ok(fs.existsSync(workspace.configPath)); + assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); - const ws = JSON.parse(fs.readFileSync(workspace.configPath).toString()) as IStoredWorkspace; + const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; assert.equal(ws.folders.length, 2); assertPathEquals((ws.folders[0]).path, process.cwd()); assertPathEquals((ws.folders[1]).path, os.tmpdir()); @@ -153,15 +161,18 @@ suite('WorkspacesMainService', () => { }); test('createUntitledWorkspaceSync (folders as other resource URIs)', () => { - const workspace = service.createUntitledWorkspaceSync([{ uri: URI.from({ scheme: 'myScheme', path: process.cwd() }) }, { uri: URI.from({ scheme: 'myScheme', path: os.tmpdir() }) }]); + const folder1URI = URI.parse('myscheme://server/work/p/f1'); + const folder2URI = URI.parse('myscheme://server/work/o/f3'); + + const workspace = service.createUntitledWorkspaceSync([{ uri: folder1URI }, { uri: folder2URI }]); assert.ok(workspace); - assert.ok(fs.existsSync(workspace.configPath)); + assert.ok(fs.existsSync(workspace.configPath.fsPath)); assert.ok(service.isUntitledWorkspace(workspace)); - const ws = JSON.parse(fs.readFileSync(workspace.configPath).toString()) as IStoredWorkspace; + const ws = JSON.parse(fs.readFileSync(workspace.configPath.fsPath).toString()) as IStoredWorkspace; assert.equal(ws.folders.length, 2); - assert.equal((ws.folders[0]).uri, URI.from({ scheme: 'myScheme', path: process.cwd() }).toString(true)); - assert.equal((ws.folders[1]).uri, URI.from({ scheme: 'myScheme', path: os.tmpdir() }).toString(true)); + assert.equal((ws.folders[0]).uri, folder1URI.toString(true)); + assert.equal((ws.folders[1]).uri, folder2URI.toString(true)); assert.ok(!(ws.folders[0]).name); assert.ok(!(ws.folders[1]).name); @@ -169,195 +180,202 @@ suite('WorkspacesMainService', () => { test('resolveWorkspaceSync', () => { return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - assert.ok(service.resolveWorkspaceSync(workspace.configPath)); + assert.ok(service.resolveLocalWorkspaceSync(workspace.configPath)); // make it a valid workspace path - const newPath = path.join(path.dirname(workspace.configPath), `workspace.${WORKSPACE_EXTENSION}`); - fs.renameSync(workspace.configPath, newPath); - workspace.configPath = newPath; + const newPath = path.join(path.dirname(workspace.configPath.fsPath), `workspace.${WORKSPACE_EXTENSION}`); + fs.renameSync(workspace.configPath.fsPath, newPath); + workspace.configPath = URI.file(newPath); - const resolved = service.resolveWorkspaceSync(workspace.configPath); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); assert.equal(2, resolved!.folders.length); - assert.equal(resolved!.configPath, workspace.configPath); + assertEqualURI(resolved!.configPath, workspace.configPath); assert.ok(resolved!.id); - fs.writeFileSync(workspace.configPath, JSON.stringify({ something: 'something' })); // invalid workspace - const resolvedInvalid = service.resolveWorkspaceSync(workspace.configPath); + fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ something: 'something' })); // invalid workspace + const resolvedInvalid = service.resolveLocalWorkspaceSync(workspace.configPath); assert.ok(!resolvedInvalid); }); }); test('resolveWorkspaceSync (support relative paths)', () => { return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - fs.writeFileSync(workspace.configPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib' }] })); + fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib' }] })); - const resolved = service.resolveWorkspaceSync(workspace.configPath); - assert.equal(resolved!.folders[0].uri.fsPath, URI.file(path.join(path.dirname(workspace.configPath), 'ticino-playground', 'lib')).fsPath); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); + assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); }); }); test('resolveWorkspaceSync (support relative paths #2)', () => { return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - fs.writeFileSync(workspace.configPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib/../other' }] })); + fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: './ticino-playground/lib/../other' }] })); - const resolved = service.resolveWorkspaceSync(workspace.configPath); - assert.equal(resolved!.folders[0].uri.fsPath, URI.file(path.join(path.dirname(workspace.configPath), 'ticino-playground', 'other')).fsPath); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); + assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'other'))); }); }); test('resolveWorkspaceSync (support relative paths #3)', () => { return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - fs.writeFileSync(workspace.configPath, JSON.stringify({ folders: [{ path: 'ticino-playground/lib' }] })); + fs.writeFileSync(workspace.configPath.fsPath, JSON.stringify({ folders: [{ path: 'ticino-playground/lib' }] })); - const resolved = service.resolveWorkspaceSync(workspace.configPath); - assert.equal(resolved!.folders[0].uri.fsPath, URI.file(path.join(path.dirname(workspace.configPath), 'ticino-playground', 'lib')).fsPath); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); + assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); }); }); test('resolveWorkspaceSync (support invalid JSON via fault tolerant parsing)', () => { return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - fs.writeFileSync(workspace.configPath, '{ "folders": [ { "path": "./ticino-playground/lib" } , ] }'); // trailing comma + fs.writeFileSync(workspace.configPath.fsPath, '{ "folders": [ { "path": "./ticino-playground/lib" } , ] }'); // trailing comma - const resolved = service.resolveWorkspaceSync(workspace.configPath); - assert.equal(resolved!.folders[0].uri.fsPath, URI.file(path.join(path.dirname(workspace.configPath), 'ticino-playground', 'lib')).fsPath); + const resolved = service.resolveLocalWorkspaceSync(workspace.configPath); + assertEqualURI(resolved!.folders[0].uri, URI.file(path.join(path.dirname(workspace.configPath.fsPath), 'ticino-playground', 'lib'))); }); }); - test('saveWorkspace (untitled)', () => { - return createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]).then(workspace => { - const workspaceConfigPath = path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`); + test('rewriteWorkspaceFileForNewLocation', () => { + const folder1 = process.cwd(); // absolute path because outside of tmpDir + const tmpDir = os.tmpdir(); + const tmpInsideDir = path.join(os.tmpdir(), 'inside'); - return service.saveWorkspaceAs(workspace, workspaceConfigPath).then(savedWorkspace => { - assert.ok(savedWorkspace.id); - assert.notEqual(savedWorkspace.id, workspace.id); - assert.equal(savedWorkspace.configPath, workspaceConfigPath); + return createWorkspace([folder1, tmpInsideDir, path.join(tmpInsideDir, 'somefolder')]).then(workspace => { + const origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); - const ws = JSON.parse(fs.readFileSync(savedWorkspace.configPath).toString()) as IStoredWorkspace; - assert.equal(ws.folders.length, 3); - assertPathEquals((ws.folders[0]).path, process.cwd()); // absolute - assertPathEquals((ws.folders[1]).path, '.'); // relative - assertPathEquals((ws.folders[2]).path, path.relative(path.dirname(workspaceConfigPath), path.join(os.tmpdir(), 'somefolder'))); // relative + let origConfigPath = workspace.configPath; + let workspaceConfigPath = URI.file(path.join(tmpDir, 'inside', 'myworkspace1.code-workspace')); + let newContent = rewriteWorkspaceFileForNewLocation(origContent, origConfigPath, workspaceConfigPath); - extfs.delSync(workspaceConfigPath); - }); + let ws = JSON.parse(newContent) as IStoredWorkspace; + assert.equal(ws.folders.length, 3); + assertPathEquals((ws.folders[0]).path, folder1); // absolute path because outside of tmpdir + assertPathEquals((ws.folders[1]).path, '.'); + assertPathEquals((ws.folders[2]).path, 'somefolder'); + + origConfigPath = workspaceConfigPath; + workspaceConfigPath = URI.file(path.join(tmpDir, 'myworkspace2.code-workspace')); + newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); + + ws = JSON.parse(newContent) as IStoredWorkspace; + assert.equal(ws.folders.length, 3); + assertPathEquals((ws.folders[0]).path, folder1); + assertPathEquals((ws.folders[1]).path, 'inside'); + assertPathEquals((ws.folders[2]).path, isWindows ? 'inside\\somefolder' : 'inside/somefolder'); + + origConfigPath = workspaceConfigPath; + workspaceConfigPath = URI.file(path.join(tmpDir, 'other', 'myworkspace2.code-workspace')); + newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); + + ws = JSON.parse(newContent) as IStoredWorkspace; + assert.equal(ws.folders.length, 3); + assertPathEquals((ws.folders[0]).path, folder1); + assertPathEquals((ws.folders[1]).path, tmpInsideDir); + assertPathEquals((ws.folders[2]).path, path.join(tmpInsideDir, 'somefolder')); + + origConfigPath = workspaceConfigPath; + workspaceConfigPath = URI.parse('foo://foo/bar/myworkspace2.code-workspace'); + newContent = rewriteWorkspaceFileForNewLocation(newContent, origConfigPath, workspaceConfigPath); + + ws = JSON.parse(newContent) as IStoredWorkspace; + assert.equal(ws.folders.length, 3); + assert.equal((ws.folders[0]).uri, URI.file(folder1).toString(true)); + assert.equal((ws.folders[1]).uri, URI.file(tmpInsideDir).toString(true)); + assert.equal((ws.folders[2]).uri, URI.file(path.join(tmpInsideDir, 'somefolder')).toString(true)); + + service.deleteUntitledWorkspaceSync(workspace); }); }); - test('saveWorkspace (saved workspace)', () => { + test('rewriteWorkspaceFileForNewLocation (preserves comments)', () => { return createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]).then(workspace => { - const workspaceConfigPath = path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`); - const newWorkspaceConfigPath = path.join(os.tmpdir(), `mySavedWorkspace.${Date.now()}.${WORKSPACE_EXTENSION}`); + const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); - return service.saveWorkspaceAs(workspace, workspaceConfigPath).then(savedWorkspace => { - return service.saveWorkspaceAs(savedWorkspace, newWorkspaceConfigPath).then(newSavedWorkspace => { - assert.ok(newSavedWorkspace.id); - assert.notEqual(newSavedWorkspace.id, workspace.id); - assertPathEquals(newSavedWorkspace.configPath, newWorkspaceConfigPath); + let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); + origContent = `// this is a comment\n${origContent}`; - const ws = JSON.parse(fs.readFileSync(newSavedWorkspace.configPath).toString()) as IStoredWorkspace; - assert.equal(ws.folders.length, 3); - assertPathEquals((ws.folders[0]).path, process.cwd()); // absolute path because outside of tmpdir - assertPathEquals((ws.folders[1]).path, '.'); // relative path because inside of tmpdir - assertPathEquals((ws.folders[2]).path, path.relative(path.dirname(workspaceConfigPath), path.join(os.tmpdir(), 'somefolder'))); // relative + let newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); - extfs.delSync(workspaceConfigPath); - extfs.delSync(newWorkspaceConfigPath); - }); - }); + assert.equal(0, newContent.indexOf('// this is a comment')); + + service.deleteUntitledWorkspaceSync(workspace); }); }); - test('saveWorkspace (saved workspace, preserves comments)', () => { + test('rewriteWorkspaceFileForNewLocation (preserves forward slashes)', () => { return createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]).then(workspace => { - const workspaceConfigPath = path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`); - const newWorkspaceConfigPath = path.join(os.tmpdir(), `mySavedWorkspace.${Date.now()}.${WORKSPACE_EXTENSION}`); + const workspaceConfigPath = URI.file(path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); + let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); + origContent = origContent.replace(/[\\]/g, '/'); // convert backslash to slash - return service.saveWorkspaceAs(workspace, workspaceConfigPath).then(savedWorkspace => { - const contents = fs.readFileSync(savedWorkspace.configPath).toString(); - fs.writeFileSync(savedWorkspace.configPath, `// this is a comment\n${contents}`); + const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); - return service.saveWorkspaceAs(savedWorkspace, newWorkspaceConfigPath).then(newSavedWorkspace => { - assert.ok(newSavedWorkspace.id); - assert.notEqual(newSavedWorkspace.id, workspace.id); - assertPathEquals(newSavedWorkspace.configPath, newWorkspaceConfigPath); + const ws = JSON.parse(newContent) as IStoredWorkspace; + assert.ok(ws.folders.every(f => (f).path.indexOf('\\') < 0)); - const savedContents = fs.readFileSync(newSavedWorkspace.configPath).toString(); - assert.equal(0, savedContents.indexOf('// this is a comment')); - - extfs.delSync(workspaceConfigPath); - extfs.delSync(newWorkspaceConfigPath); - }); - }); + service.deleteUntitledWorkspaceSync(workspace); }); }); - test('saveWorkspace (saved workspace, preserves forward slashes)', () => { - return createWorkspace([process.cwd(), os.tmpdir(), path.join(os.tmpdir(), 'somefolder')]).then(workspace => { - const workspaceConfigPath = path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`); - const newWorkspaceConfigPath = path.join(os.tmpdir(), `mySavedWorkspace.${Date.now()}.${WORKSPACE_EXTENSION}`); + test('rewriteWorkspaceFileForNewLocation (unc paths)', () => { + if (!isWindows) { + return Promise.resolve(); + } - return service.saveWorkspaceAs(workspace, workspaceConfigPath).then(savedWorkspace => { - const contents = fs.readFileSync(savedWorkspace.configPath).toString(); - fs.writeFileSync(savedWorkspace.configPath, contents.replace(/[\\]/g, '/')); // convert backslash to slash + const workspaceLocation = path.join(os.tmpdir(), 'wsloc'); + const folder1Location = 'x:\\foo'; + const folder2Location = '\\\\server\\share2\\some\\path'; + const folder3Location = path.join(os.tmpdir(), 'wsloc', 'inner', 'more'); - return service.saveWorkspaceAs(savedWorkspace, newWorkspaceConfigPath).then(newSavedWorkspace => { - assert.ok(newSavedWorkspace.id); - assert.notEqual(newSavedWorkspace.id, workspace.id); - assertPathEquals(newSavedWorkspace.configPath, newWorkspaceConfigPath); + return createWorkspace([folder1Location, folder2Location, folder3Location]).then(workspace => { + const workspaceConfigPath = URI.file(path.join(workspaceLocation, `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`)); + let origContent = fs.readFileSync(workspace.configPath.fsPath).toString(); - const ws = JSON.parse(fs.readFileSync(newSavedWorkspace.configPath).toString()) as IStoredWorkspace; - assert.ok(ws.folders.every(f => (f).path.indexOf('\\') < 0)); + const newContent = rewriteWorkspaceFileForNewLocation(origContent, workspace.configPath, workspaceConfigPath); - extfs.delSync(workspaceConfigPath); - extfs.delSync(newWorkspaceConfigPath); - }); - }); + const ws = JSON.parse(newContent) as IStoredWorkspace; + assertPathEquals((ws.folders[0]).path, folder1Location); + assertPathEquals((ws.folders[1]).path, folder2Location); + assertPathEquals((ws.folders[2]).path, 'inner\\more'); + + service.deleteUntitledWorkspaceSync(workspace); }); }); test('deleteUntitledWorkspaceSync (untitled)', () => { return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - assert.ok(fs.existsSync(workspace.configPath)); + assert.ok(fs.existsSync(workspace.configPath.fsPath)); service.deleteUntitledWorkspaceSync(workspace); - assert.ok(!fs.existsSync(workspace.configPath)); + assert.ok(!fs.existsSync(workspace.configPath.fsPath)); }); }); test('deleteUntitledWorkspaceSync (saved)', () => { return createWorkspace([process.cwd(), os.tmpdir()]).then(workspace => { - const workspaceConfigPath = path.join(os.tmpdir(), `myworkspace.${Date.now()}.${WORKSPACE_EXTENSION}`); - - return service.saveWorkspaceAs(workspace, workspaceConfigPath).then(savedWorkspace => { - assert.ok(fs.existsSync(savedWorkspace.configPath)); - - service.deleteUntitledWorkspaceSync(savedWorkspace); - - assert.ok(fs.existsSync(savedWorkspace.configPath)); - }); + service.deleteUntitledWorkspaceSync(workspace); }); }); test('getUntitledWorkspaceSync', () => { let untitled = service.getUntitledWorkspacesSync(); - assert.equal(0, untitled.length); + assert.equal(untitled.length, 0); return createWorkspace([process.cwd(), os.tmpdir()]).then(untitledOne => { - assert.ok(fs.existsSync(untitledOne.configPath)); + assert.ok(fs.existsSync(untitledOne.configPath.fsPath)); untitled = service.getUntitledWorkspacesSync(); assert.equal(1, untitled.length); - assert.equal(untitledOne.id, untitled[0].id); + assert.equal(untitledOne.id, untitled[0].workspace.id); return createWorkspace([os.tmpdir(), process.cwd()]).then(untitledTwo => { - assert.ok(fs.existsSync(untitledTwo.configPath)); + assert.ok(fs.existsSync(untitledTwo.configPath.fsPath)); untitled = service.getUntitledWorkspacesSync(); if (untitled.length === 1) { - assert.fail('Unexpected workspaces count, contents:\n' + fs.readFileSync(untitledTwo.configPath, 'utf8')); + assert.fail('Unexpected workspaces count, contents:\n' + fs.readFileSync(untitledTwo.configPath.fsPath, 'utf8')); } assert.equal(2, untitled.length); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 1898387678..19184c2175 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -1235,11 +1235,16 @@ declare module 'vscode' { * Create an URI from a string, e.g. `http://www.msft.com/some/path`, * `file:///usr/home`, or `scheme:with/path`. * + * *Note* that for a while uris without a `scheme` were accepted. That is not correct + * as all uris should have a scheme. To avoid breakage of existing code the optional + * `strict`-argument has been added. We *strongly* advise to use it, e.g. `Uri.parse('my:uri', true)` + * * @see [Uri.toString](#Uri.toString) * @param value The string value of an Uri. + * @param strict Throw an error when `value` is empty or when no `scheme` can be parsed. * @return A new Uri instance. */ - static parse(value: string): Uri; + static parse(value: string, strict?: boolean): Uri; /** * Create an URI from a file system path. The [scheme](#Uri.scheme) @@ -2014,12 +2019,20 @@ declare module 'vscode' { */ static readonly SourceOrganizeImports: CodeActionKind; + /** + * Base kind for auto-fix source actions: `source.fixAll`. + * + * Fix all actions automatically fix errors that have a clear fix that do not require user input. + * They should not suppress errors or perform unsafe fixes such as generating new types or classes. + */ + static readonly SourceFixAll: CodeActionKind; + private constructor(value: string); /** * String value of the kind, e.g. `"refactor.extract.function"`. */ - readonly value?: string; + readonly value: string; /** * Create a new kind by appending a more specific selector to the current kind. @@ -2102,6 +2115,15 @@ declare module 'vscode' { */ kind?: CodeActionKind; + /** + * Marks this as a preferred action. Preferred actions are used by the `auto fix` command and can be targeted + * by keybindings. + * + * A quick fix should be marked preferred if it properly addresses the underlying error. + * A refactoring should be marked preferred if it is the most reasonable choice of actions to take. + */ + isPreferred?: boolean; + /** * Creates a new code action. * @@ -2655,7 +2677,7 @@ declare module 'vscode' { * [location](#SymbolInformation.location)-objects, without a `range` defined. The editor will then call * `resolveWorkspaceSymbol` for selected symbols only, e.g. when opening a workspace symbol. * - * @param query A non-empty query string. + * @param query A query string, can be the empty string in which case all symbols should be returned. * @param token A cancellation token. * @return An array of document highlights or a thenable that resolves to such. The lack of a result can be * signaled by returning `undefined`, `null`, or an empty array. @@ -3184,6 +3206,14 @@ declare module 'vscode' { * typing a trigger character, a cursor move, or document content changes. */ readonly isRetrigger: boolean; + + /** + * The currently active [`SignatureHelp`](#SignatureHelp). + * + * The `activeSignatureHelp` has its [`SignatureHelp.activeSignature`] field updated based on + * the user arrowing through available signatures. + */ + readonly activeSignatureHelp?: SignatureHelp; } /** @@ -3676,7 +3706,7 @@ declare module 'vscode' { } /** - * A line based folding range. To be valid, start and end line must a zero or larger and smaller than the number of lines in the document. + * A line based folding range. To be valid, start and end line must be bigger than zero and smaller than the number of lines in the document. * Invalid ranges will be ignored. */ export class FoldingRange { @@ -4487,7 +4517,7 @@ declare module 'vscode' { * The priority of this item. Higher value means the item should * be shown more to the left. */ - readonly priority: number; + readonly priority?: number; /** * The text to show for the entry. You can embed icons in the text by leveraging the syntax: diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index cf2ab8bff1..10e8e89058 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -16,49 +16,129 @@ declare module 'vscode' { - //#region Joh - selection range provider + //#region Alex - resolvers - export class SelectionRangeKind { + export class ResolvedAuthority { + readonly host: string; + readonly port: number; + debugListenPort?: number; + debugConnectPort?: number; - /** - * Empty Kind. - */ - static readonly Empty: SelectionRangeKind; - - /** - * The statement kind, its value is `statement`, possible extensions can be - * `statement.if` etc - */ - static readonly Statement: SelectionRangeKind; - - /** - * The declaration kind, its value is `declaration`, possible extensions can be - * `declaration.function`, `declaration.class` etc. - */ - static readonly Declaration: SelectionRangeKind; - - readonly value: string; - - private constructor(value: string); - - append(value: string): SelectionRangeKind; + constructor(host: string, port: number); } - export class SelectionRange { - kind: SelectionRangeKind; + export interface RemoteAuthorityResolver { + resolve(authority: string): ResolvedAuthority | Thenable; + } + + export interface ResourceLabelFormatter { + scheme: string; + authority?: string; + formatting: ResourceLabelFormatting; + } + + export interface ResourceLabelFormatting { + label: string; // myLabel:/${path} + separator: '/' | '\\' | ''; + tildify?: boolean; + normalizeDriveLetter?: boolean; + workspaceSuffix?: string; + authorityPrefix?: string; + } + + export namespace workspace { + export function registerRemoteAuthorityResolver(authorityPrefix: string, resolver: RemoteAuthorityResolver): Disposable; + export function registerResourceLabelFormatter(formatter: ResourceLabelFormatter): Disposable; + } + + //#endregion + + + // #region Joh - code insets + + /** + */ + export class CodeInset { range: Range; - constructor(range: Range, kind: SelectionRangeKind); + height?: number; + constructor(range: Range, height?: number); + } + + export interface CodeInsetProvider { + onDidChangeCodeInsets?: Event; + provideCodeInsets(document: TextDocument, token: CancellationToken): ProviderResult; + resolveCodeInset(codeInset: CodeInset, webview: Webview, token: CancellationToken): ProviderResult; + } + + export namespace languages { + + /** + * Register a code inset provider. + * + */ + export function registerCodeInsetProvider(selector: DocumentSelector, provider: CodeInsetProvider): Disposable; + } + + //#endregion + + + //#region Joh - selection range provider + + /** + * A selection range represents a part of a selection hierarchy. A selection range + * may have a parent selection range that contains it. + */ + export class SelectionRange { + + /** + * The [range](#Range) of this selection range. + */ + range: Range; + + /** + * The parent selection range containing this range. + */ + parent?: SelectionRange; + + /** + * Creates a new selection range. + * + * @param range The range of the selection range. + * @param parent The parent of the selection range. + */ + constructor(range: Range, parent?: SelectionRange); } export interface SelectionRangeProvider { /** - * Provide selection ranges starting at a given position. The first range must [contain](#Range.contains) - * position and subsequent ranges must contain the previous range. + * Provide selection ranges for the given positions. + * + * Selection ranges should be computed individually and independend for each postion. The editor will merge + * and deduplicate ranges but providers must return hierarchies of selection ranges so that a range + * is [contained](#Range.contains) by its parent. + * + * @param document The document in which the command was invoked. + * @param positions The positions at which the command was invoked. + * @param token A cancellation token. + * @return Selection ranges or a thenable that resolves to such. The lack of a result can be + * signaled by returning `undefined` or `null`. */ - provideSelectionRanges(document: TextDocument, position: Position, token: CancellationToken): ProviderResult; + provideSelectionRanges(document: TextDocument, positions: Position[], token: CancellationToken): ProviderResult; } export namespace languages { + + /** + * Register a selection range provider. + * + * Multiple providers can be registered for a language. In that case providers are asked in + * parallel and the results are merged. A failing provider (rejected promise or exception) will + * not cause a failure of the whole operation. + * + * @param selector A selector that defines the documents this provider is applicable to. + * @param provider A selection range provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ export function registerSelectionRangeProvider(selector: DocumentSelector, provider: SelectionRangeProvider): Disposable; } @@ -246,11 +326,6 @@ declare module 'vscode' { session?: CancellationToken; } - /** - * Options that apply to requesting the file index. - */ - export interface FileIndexOptions extends SearchOptions { } - /** * A preview of the text result. */ @@ -310,26 +385,6 @@ declare module 'vscode' { export type TextSearchResult = TextSearchMatch | TextSearchContext; - /** - * A FileIndexProvider provides a list of files in the given folder. VS Code will filter that list for searching with quickopen or from other extensions. - * - * A FileIndexProvider is the simpler of two ways to implement file search in VS Code. Use a FileIndexProvider if you are able to provide a listing of all files - * in a folder, and want VS Code to filter them according to the user's search query. - * - * The FileIndexProvider will be invoked once when quickopen is opened, and VS Code will filter the returned list. It will also be invoked when - * `workspace.findFiles` is called. - * - * If a [`FileSearchProvider`](#FileSearchProvider) is registered for the scheme, that provider will be used instead. - */ - export interface FileIndexProvider { - /** - * Provide the set of files in the folder. - * @param options A set of options to consider while searching. - * @param token A cancellation token. - */ - provideFileIndex(options: FileIndexOptions, token: CancellationToken): ProviderResult; - } - /** * A FileSearchProvider provides search results for files in the given folder that match a query string. It can be invoked by quickopen or other extensions. * @@ -338,8 +393,6 @@ declare module 'vscode' { * * The FileSearchProvider will be invoked on every keypress in quickopen. When `workspace.findFiles` is called, it will be invoked with an empty query string, * and in that case, every file in the folder should be returned. - * - * @see [FileIndexProvider](#FileIndexProvider) */ export interface FileSearchProvider { /** @@ -435,17 +488,6 @@ declare module 'vscode' { */ export function registerSearchProvider(): Disposable; - /** - * Register a file index provider. - * - * Only one provider can be registered per scheme. - * - * @param scheme The provider will be invoked for workspace folders that have this file scheme. - * @param provider The provider. - * @return A [disposable](#Disposable) that unregisters this provider when being disposed. - */ - export function registerFileIndexProvider(scheme: string, provider: FileIndexProvider): Disposable; - /** * Register a search provider. * @@ -554,6 +596,22 @@ declare module 'vscode' { //#region André: debug + export namespace debug { + + /** + * Start debugging by using either a named launch or named compound configuration, + * or by directly passing a [DebugConfiguration](#DebugConfiguration). + * The named configurations are looked up in '.vscode/launch.json' found in the given folder. + * Before debugging starts, all unsaved files are saved and the launch configurations are brought up-to-date. + * Folder specific variables used in the configuration (e.g. '${workspaceFolder}') are resolved against the given folder. + * @param folder The [workspace folder](#WorkspaceFolder) for looking up named configurations and resolving variables or `undefined` for a non-folder setup. + * @param nameOrConfiguration Either the name of a debug or compound configuration or a [DebugConfiguration](#DebugConfiguration) object. + * @param parent If specified the newly created debug session is registered as a "child" session of a "parent" debug session. + * @return A thenable that resolves when debugging could be successfully started. + */ + export function startDebugging(folder: WorkspaceFolder | undefined, nameOrConfiguration: string | DebugConfiguration, parentSession?: DebugSession): Thenable; + } + // deprecated export interface DebugConfigurationProvider { @@ -713,7 +771,7 @@ declare module 'vscode' { /** * A collection of comments representing a conversation at a particular range in a document. */ - interface CommentThread { + export interface CommentThread { /** * A unique identifier of the comment thread. */ @@ -730,21 +788,49 @@ declare module 'vscode' { */ range: Range; + /** + * The human-readable label describing the [Comment Thread](#CommentThread) + */ + label?: string; + /** * The ordered comments of the thread. */ comments: Comment[]; /** - * Whether the thread should be collapsed or expanded when opening the document. Defaults to Collapsed. + * Optional accept input command + * + * `acceptInputCommand` is the default action rendered on Comment Widget, which is always placed rightmost. + * This command will be invoked when users the user accepts the value in the comment editor. + * This command will disabled when the comment editor is empty. + */ + acceptInputCommand?: Command; + + /** + * Optional additonal commands. + * + * `additionalCommands` are the secondary actions rendered on Comment Widget. + */ + additionalCommands?: Command[]; + + /** + * Whether the thread should be collapsed or expanded when opening the document. + * Defaults to Collapsed. */ collapsibleState?: CommentThreadCollapsibleState; + + /** + * Dispose this comment thread. + * Once disposed, the comment thread will be removed from visible text editors and Comments Panel. + */ + dispose?(): void; } /** * A comment is displayed within the editor or the Comments Panel, depending on how it is provided. */ - interface Comment { + export interface Comment { /** * The id of the comment */ @@ -755,6 +841,12 @@ declare module 'vscode' { */ body: MarkdownString; + /** + * Optional label describing the [Comment](#Comment) + * Label will be rendered next to userName if exists. + */ + label?: string; + /** * The display name of the user who created the comment */ @@ -765,7 +857,6 @@ declare module 'vscode' { */ userIconPath?: Uri; - /** * @deprecated Use userIconPath instead. The avatar src of the user who created the comment */ @@ -776,6 +867,8 @@ declare module 'vscode' { * * This will be treated as false if the comment is provided by a `WorkspaceCommentProvider`, or * if it is provided by a `DocumentCommentProvider` and no `editComment` method is given. + * + * DEPRECATED, use editCommand */ canEdit?: boolean; @@ -784,18 +877,46 @@ declare module 'vscode' { * * This will be treated as false if the comment is provided by a `WorkspaceCommentProvider`, or * if it is provided by a `DocumentCommentProvider` and no `deleteComment` method is given. + * + * DEPRECATED, use deleteCommand */ canDelete?: boolean; /** + * @deprecated * The command to be executed if the comment is selected in the Comments Panel */ command?: Command; + /** + * The command to be executed if the comment is selected in the Comments Panel + */ + selectCommand?: Command; + + /** + * The command to be executed when users try to save the edits to the comment + */ + editCommand?: Command; + + /** + * The command to be executed when users try to delete the comment + */ + deleteCommand?: Command; + + /** + * Deprecated + */ isDraft?: boolean; + + /** + * Proposed Comment Reaction + */ commentReactions?: CommentReaction[]; } + /** + * Deprecated + */ export interface CommentThreadChangedEvent { /** * Added comment threads. @@ -818,11 +939,19 @@ declare module 'vscode' { readonly inDraftMode: boolean; } + /** + * Comment Reactions + */ interface CommentReaction { readonly label?: string; + readonly iconPath?: string | Uri; + count?: number; readonly hasReacted?: boolean; } + /** + * DEPRECATED + */ interface DocumentCommentProvider { /** * Provide the commenting ranges and comment threads for the given document. The comments are displayed within the editor. @@ -867,6 +996,9 @@ declare module 'vscode' { onDidChangeCommentThreads: Event; } + /** + * DEPRECATED + */ interface WorkspaceCommentProvider { /** * Provide all comments for the workspace. Comments are shown within the comments panel. Selecting a comment @@ -880,21 +1012,135 @@ declare module 'vscode' { onDidChangeCommentThreads: Event; } + /** + * The comment input box in Comment Widget. + */ + export interface CommentInputBox { + /** + * Setter and getter for the contents of the comment input box. + */ + value: string; + } + + export interface CommentReactionProvider { + availableReactions: CommentReaction[]; + toggleReaction?(document: TextDocument, comment: Comment, reaction: CommentReaction): Promise; + } + + export interface CommentingRangeProvider { + /** + * Provide a list of ranges which allow new comment threads creation or null for a given document + */ + provideCommentingRanges(document: TextDocument, token: CancellationToken): ProviderResult; + } + + export interface EmptyCommentThreadFactory { + /** + * The method `createEmptyCommentThread` is called when users attempt to create new comment thread from the gutter or command palette. + * Extensions still need to call `createCommentThread` inside this call when appropriate. + */ + createEmptyCommentThread(document: TextDocument, range: Range): ProviderResult; + } + + export interface CommentController { + /** + * The id of this comment controller. + */ + readonly id: string; + + /** + * The human-readable label of this comment controller. + */ + readonly label: string; + + /** + * The active (focused) [comment input box](#CommentInputBox). + */ + readonly inputBox?: CommentInputBox; + + /** + * Create a [CommentThread](#CommentThread) + */ + createCommentThread(id: string, resource: Uri, range: Range, comments: Comment[]): CommentThread; + + /** + * Optional commenting range provider. + * Provide a list [ranges](#Range) which support commenting to any given resource uri. + */ + commentingRangeProvider?: CommentingRangeProvider; + + /** + * Optional new comment thread factory. + */ + emptyCommentThreadFactory?: EmptyCommentThreadFactory; + + /** + * Optional reaction provider + */ + reactionProvider?: CommentReactionProvider; + + /** + * Dispose this comment controller. + */ + dispose(): void; + } + + namespace comment { + export function createCommentController(id: string, label: string): CommentController; + } + namespace workspace { + /** + * DEPRECATED + * Use vscode.comment.createCommentController instead. + */ export function registerDocumentCommentProvider(provider: DocumentCommentProvider): Disposable; + /** + * DEPRECATED + * Use vscode.comment.createCommentController instead and we don't differentiate document comments and workspace comments anymore. + */ export function registerWorkspaceCommentProvider(provider: WorkspaceCommentProvider): Disposable; } + //#endregion //#region Terminal + /** + * An [event](#Event) which fires when a [Terminal](#Terminal)'s dimensions change. + */ + export interface TerminalDimensionsChangeEvent { + /** + * The [terminal](#Terminal) for which the dimensions have changed. + */ + readonly terminal: Terminal; + /** + * The new value for the [terminal's dimensions](#Terminal.dimensions). + */ + readonly dimensions: TerminalDimensions; + } + + namespace window { + /** + * An event which fires when the [dimensions](#Terminal.dimensions) of the terminal change. + */ + export const onDidChangeTerminalDimensions: Event; + } + export interface Terminal { + /** + * The current dimensions of the terminal. This will be `undefined` immediately after the + * terminal is created as the dimensions are not known until shortly after the terminal is + * created. + */ + readonly dimensions: TerminalDimensions | undefined; + /** * Fires when the terminal's pty slave pseudo-device is written to. In other words, this * provides access to the raw data stream from the process running within the terminal, * including VT sequences. */ - onDidWriteData: Event; + readonly onDidWriteData: Event; } /** @@ -1003,7 +1249,7 @@ declare module 'vscode' { readonly onDidAcceptInput: Event; /** - * An event which fires when the [maximum dimensions](#TerminalRenderer.maimumDimensions) of + * An event which fires when the [maximum dimensions](#TerminalRenderer.maximumDimensions) of * the terminal renderer change. */ readonly onDidChangeMaximumDimensions: Event; @@ -1098,29 +1344,47 @@ declare module 'vscode' { } //#endregion - //#region SignatureHelpContext active parameters - mjbvz - export interface SignatureHelpContext { + /** + * Class used to execute an extension callback as a task. + */ + export class CustomExecution { /** - * The currently active [`SignatureHelp`](#SignatureHelp). - * - * The `activeSignatureHelp` has its [`SignatureHelp.activeSignature`] field updated based on - * the user arrowing through available signatures. + * @param callback The callback that will be called when the extension callback task is executed. */ - readonly activeSignatureHelp?: SignatureHelp; - } - //#endregion + constructor(callback: (terminalRenderer: TerminalRenderer, cancellationToken: CancellationToken, thisArg?: any) => Thenable); - //#region CodeAction.isPreferred - mjbvz - export interface CodeAction { /** - * Marks this as a preferred action. Preferred actions are used by the `auto fix` command. - * - * A quick fix should be marked preferred if it properly addresses the underlying error. - * A refactoring should be marked preferred if it is the most reasonable choice of actions to take. + * The callback used to execute the task. + * @param terminalRenderer Used by the task to render output and receive input. + * @param cancellationToken Cancellation used to signal a cancel request to the executing task. + * @returns The callback should return '0' for success and a non-zero value for failure. */ - isPreferred?: boolean; + callback: (terminalRenderer: TerminalRenderer, cancellationToken: CancellationToken, thisArg?: any) => Thenable; + } + + /** + * A task to execute + */ + export class Task2 extends Task { + /** + * Creates a new task. + * + * @param definition The task definition as defined in the taskDefinitions extension point. + * @param scope Specifies the task's scope. It is either a global or a workspace task or a task for a specific workspace folder. + * @param name The task's name. Is presented in the user interface. + * @param source The task's source (e.g. 'gulp', 'npm', ...). Is presented in the user interface. + * @param execution The process or shell execution. + * @param problemMatchers the names of problem matchers to use, like '$tsc' + * or '$eslint'. Problem matchers can be contributed by an extension using + * the `problemMatchers` extension point. + */ + constructor(taskDefinition: TaskDefinition, scope: WorkspaceFolder | TaskScope.Global | TaskScope.Workspace, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution, problemMatchers?: string | string[]); + + /** + * The task's execution engine + */ + execution2?: ProcessExecution | ShellExecution | CustomExecution; } - //#endregion //#region Tasks export interface TaskPresentationOptions { @@ -1130,16 +1394,4 @@ declare module 'vscode' { group?: string; } //#endregion - - //#region Autofix - mjbvz - export namespace CodeActionKind { - /** - * Base kind for auto-fix source actions: `source.fixAll`. - * - * Fix all actions automatically fix errors that have a clear fix that do not require user input. - * They should not suppress errors or perform unsafe fixes such as generating new types or classes. - */ - export const SourceFixAll: CodeActionKind; - } - //#endregion } diff --git a/src/vs/workbench/api/browser/viewsExtensionPoint.ts b/src/vs/workbench/api/browser/viewsExtensionPoint.ts index b24e3d7777..770e0d33d0 100644 --- a/src/vs/workbench/api/browser/viewsExtensionPoint.ts +++ b/src/vs/workbench/api/browser/viewsExtensionPoint.ts @@ -8,7 +8,7 @@ import { forEach } from 'vs/base/common/collections'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import * as resources from 'vs/base/common/resources'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ViewContainer, ViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor } from 'vs/workbench/common/views'; +import { ViewContainer, IViewsRegistry, ITreeViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, TEST_VIEW_CONTAINER_ID, IViewDescriptor } from 'vs/workbench/common/views'; import { CustomTreeViewPanel, CustomTreeView } from 'vs/workbench/browser/parts/views/customView'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { coalesce, } from 'vs/base/common/arrays'; @@ -16,16 +16,16 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWo import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { VIEWLET_ID as EXPLORER } from 'vs/workbench/parts/files/common/files'; -import { VIEWLET_ID as SCM } from 'vs/workbench/parts/scm/common/scm'; -import { VIEWLET_ID as DEBUG } from 'vs/workbench/parts/debug/common/debug'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { VIEWLET_ID as EXPLORER } from 'vs/workbench/contrib/files/common/files'; +import { VIEWLET_ID as SCM } from 'vs/workbench/contrib/scm/common/scm'; +import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from 'vs/workbench/browser/viewlet'; -import { IExtensionDescription, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -33,7 +33,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { createCSSRule } from 'vs/base/browser/dom'; @@ -145,27 +145,27 @@ type ViewContainerExtensionPointType = { [loc: string]: IUserFriendlyViewsContai // {{SQL CARBON EDIT}} - Export viewsContainersExtensionPoint export const viewsContainersExtensionPoint: IExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'viewsContainers', - jsonSchema: viewsContainersContribution, - isDynamic: true + jsonSchema: viewsContainersContribution }); type ViewExtensionPointType = { [loc: string]: IUserFriendlyViewDescriptor[] }; const viewsExtensionPoint: IExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'views', deps: [viewsContainersExtensionPoint], - jsonSchema: viewsContribution, - isDynamic: true + jsonSchema: viewsContribution }); const TEST_VIEW_CONTAINER_ORDER = 6; class ViewsExtensionHandler implements IWorkbenchContribution { private viewContainersRegistry: IViewContainersRegistry; + private viewsRegistry: IViewsRegistry; constructor( @IInstantiationService private readonly instantiationService: IInstantiationService ) { this.viewContainersRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); + this.viewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); this.handleAndRegisterCustomViewContainers(); this.handleAndRegisterCustomViews(); } @@ -205,9 +205,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution { for (const viewContainer of viewContainersRegistry.all) { if (viewContainer.extensionId && removedExtensions.has(ExtensionIdentifier.toKey(viewContainer.extensionId))) { // move only those views that do not belong to the removed extension - const views = ViewsRegistry.getViews(viewContainer).filter((view: ICustomViewDescriptor) => !removedExtensions.has(ExtensionIdentifier.toKey(view.extensionId))); + const views = this.viewsRegistry.getViews(viewContainer).filter((view: ICustomViewDescriptor) => !removedExtensions.has(ExtensionIdentifier.toKey(view.extensionId))); if (views.length) { - ViewsRegistry.moveViews(views, this.getDefaultViewContainer()); + this.viewsRegistry.moveViews(views, this.getDefaultViewContainer()); } this.deregisterCustomViewContainer(viewContainer); } @@ -262,11 +262,11 @@ class ViewsExtensionHandler implements IWorkbenchContribution { const viewsToMove: IViewDescriptor[] = []; for (const existingViewContainer of existingViewContainers) { if (viewContainer !== existingViewContainer) { - viewsToMove.push(...ViewsRegistry.getViews(existingViewContainer).filter((view: ICustomViewDescriptor) => view.originalContainerId === descriptor.id)); + viewsToMove.push(...this.viewsRegistry.getViews(existingViewContainer).filter((view: ICustomViewDescriptor) => view.originalContainerId === descriptor.id)); } } if (viewsToMove.length) { - ViewsRegistry.moveViews(viewsToMove, viewContainer); + this.viewsRegistry.moveViews(viewsToMove, viewContainer); } } }); @@ -278,13 +278,13 @@ class ViewsExtensionHandler implements IWorkbenchContribution { if (!viewContainer) { - viewContainer = this.viewContainersRegistry.registerViewContainer(id, extensionId); + viewContainer = this.viewContainersRegistry.registerViewContainer(id, true, extensionId); // Register as viewlet class CustomViewlet extends ViewContainerViewlet { constructor( @IConfigurationService configurationService: IConfigurationService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IStorageService storageService: IStorageService, @@ -294,7 +294,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService ) { - super(id, `${id}.state`, true, configurationService, partService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + super(id, `${id}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); } } const viewletDescriptor = new ViewletDescriptor( @@ -314,9 +314,9 @@ class ViewsExtensionHandler implements IWorkbenchContribution { id: string, label: string, @IViewletService viewletService: IViewletService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IPartService partService: IPartService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(id, label, id, viewletService, editorGroupService, partService); + super(id, label, id, viewletService, editorGroupService, layoutService); } } const registry = Registry.as(ActionExtensions.WorkbenchActions); @@ -359,12 +359,12 @@ class ViewsExtensionHandler implements IWorkbenchContribution { return; } - let container = this.getViewContainer(entry.key); - if (!container) { + const viewContainer = this.getViewContainer(entry.key); + if (!viewContainer) { collector.warn(localize('ViewContainerDoesnotExist', "View container '{0}' does not exist and all views registered to it will be added to 'Explorer'.", entry.key)); - container = this.getDefaultViewContainer(); } - const registeredViews = ViewsRegistry.getViews(container); + const container = viewContainer || this.getDefaultViewContainer(); + const registeredViews = this.viewsRegistry.getViews(container); const viewIds: string[] = []; const viewDescriptors = coalesce(entry.value.map((item, index) => { // validate @@ -380,7 +380,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { const viewDescriptor = { id: item.id, name: item.name, - ctor: CustomTreeViewPanel, + ctorDescriptor: { ctor: CustomTreeViewPanel }, when: ContextKeyExpr.deserialize(item.when), canToggleVisibility: true, collapsed: this.showCollapsed(container), @@ -393,21 +393,21 @@ class ViewsExtensionHandler implements IWorkbenchContribution { viewIds.push(viewDescriptor.id); return viewDescriptor; })); - ViewsRegistry.registerViews(viewDescriptors, container); + this.viewsRegistry.registerViews(viewDescriptors, container); }); } } private getDefaultViewContainer(): ViewContainer { - return this.viewContainersRegistry.get(EXPLORER); + return this.viewContainersRegistry.get(EXPLORER)!; } private removeViews(extensions: IExtensionPointUser[]): void { const removedExtensions: Set = extensions.reduce((result, e) => { result.add(ExtensionIdentifier.toKey(e.description.identifier)); return result; }, new Set()); for (const viewContainer of this.viewContainersRegistry.all) { - const removedViews = ViewsRegistry.getViews(viewContainer).filter((v: ICustomViewDescriptor) => v.extensionId && removedExtensions.has(ExtensionIdentifier.toKey(v.extensionId))); + const removedViews = this.viewsRegistry.getViews(viewContainer).filter((v: ICustomViewDescriptor) => v.extensionId && removedExtensions.has(ExtensionIdentifier.toKey(v.extensionId))); if (removedViews.length) { - ViewsRegistry.deregisterViews(removedViews, viewContainer); + this.viewsRegistry.deregisterViews(removedViews, viewContainer); } } } @@ -436,7 +436,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution { return true; } - private getViewContainer(value: string): ViewContainer { + private getViewContainer(value: string): ViewContainer | undefined { switch (value) { case 'explorer': return this.viewContainersRegistry.get(EXPLORER); case 'debug': return this.viewContainersRegistry.get(DEBUG); diff --git a/src/vs/workbench/api/common/configurationExtensionPoint.ts b/src/vs/workbench/api/common/configurationExtensionPoint.ts index bffbc44b9a..6138239057 100644 --- a/src/vs/workbench/api/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/api/common/configurationExtensionPoint.ts @@ -91,8 +91,7 @@ const defaultConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint { if (removed.length) { @@ -134,8 +133,7 @@ const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint = new Map(); diff --git a/src/vs/workbench/services/jsonschemas/common/jsonValidationExtensionPoint.ts b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts similarity index 98% rename from src/vs/workbench/services/jsonschemas/common/jsonValidationExtensionPoint.ts rename to src/vs/workbench/api/common/jsonValidationExtensionPoint.ts index 30363ef36d..18d0f74733 100644 --- a/src/vs/workbench/services/jsonschemas/common/jsonValidationExtensionPoint.ts +++ b/src/vs/workbench/api/common/jsonValidationExtensionPoint.ts @@ -15,7 +15,7 @@ interface IJSONValidationExtensionPoint { const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'jsonValidation', - isDynamic: true, + defaultExtensionKind: 'workspace', jsonSchema: { description: nls.localize('contributes.jsonValidation', 'Contributes json schema configuration.'), type: 'array', diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 7cb0473bb6..60f8a871f6 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -34,6 +34,7 @@ namespace schema { case 'explorer/context': return MenuId.ExplorerContext; case 'editor/title/context': return MenuId.EditorTitleContext; case 'debug/callstack/context': return MenuId.DebugCallStackContext; + case 'debug/toolbar': return MenuId.DebugToolbar; case 'scm/title': return MenuId.SCMTitle; case 'scm/sourceControl': return MenuId.SCMSourceControl; case 'scm/resourceGroup/context': return MenuId.SCMResourceGroupContext; @@ -139,6 +140,11 @@ namespace schema { type: 'array', items: menuItem }, + 'debug/toolbar': { + description: localize('menus.debugToolbar', "The debug toolbar menu"), + type: 'array', + items: menuItem + }, 'scm/title': { description: localize('menus.scmTitle', "The Source Control title menu"), type: 'array', @@ -286,8 +292,7 @@ let _commandRegistrations: IDisposable[] = []; ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'commands', - jsonSchema: schema.commandsContribution, - isDynamic: true + jsonSchema: schema.commandsContribution }).setHandler(extensions => { function handleCommand(userFriendlyCommand: schema.IUserFriendlyCommand, extension: IExtensionPointUser, disposables: IDisposable[]) { @@ -336,8 +341,7 @@ let _menuRegistrations: IDisposable[] = []; ExtensionsRegistry.registerExtensionPoint<{ [loc: string]: schema.IUserFriendlyMenuItem[] }>({ extensionPoint: 'menus', - jsonSchema: schema.menusContribtion, - isDynamic: true + jsonSchema: schema.menusContribtion }).setHandler(extensions => { // remove all previous menu registrations diff --git a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts index f47f6ce270..14ed9e027c 100644 --- a/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/electron-browser/extensionHost.contribution.ts @@ -9,9 +9,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; // --- other interested parties -import { JSONValidationExtensionPoint } from 'vs/workbench/services/jsonschemas/common/jsonValidationExtensionPoint'; +import { JSONValidationExtensionPoint } from 'vs/workbench/api/common/jsonValidationExtensionPoint'; import { ColorExtensionPoint } from 'vs/workbench/services/themes/common/colorExtensionPoint'; -import { LanguageConfigurationFileHandler } from 'vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint'; +import { LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint'; // --- mainThread participants import 'vs/workbench/api/node/apiCommands'; diff --git a/src/vs/workbench/api/electron-browser/mainThreadClipboard.ts b/src/vs/workbench/api/electron-browser/mainThreadClipboard.ts index 27d60cc55e..0c22e5d76a 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadClipboard.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadClipboard.ts @@ -20,6 +20,6 @@ export class MainThreadCommands implements MainThreadClipboardShape { $writeText(value: string): Promise { clipboard.writeText(value); - return undefined; + return Promise.resolve(); } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadCommands.ts b/src/vs/workbench/api/electron-browser/mainThreadCommands.ts index 37d28f5338..665889a35b 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadCommands.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadCommands.ts @@ -64,13 +64,14 @@ export class MainThreadCommands implements MainThreadCommandsShape { } $unregisterCommand(id: string): void { - if (this._disposables.has(id)) { - this._disposables.get(id).dispose(); + const command = this._disposables.get(id); + if (command) { + command.dispose(); this._disposables.delete(id); } } - $executeCommand(id: string, args: any[]): Promise { + $executeCommand(id: string, args: any[]): Promise { for (let i = 0; i < args.length; i++) { args[i] = revive(args[i], 0); } @@ -88,7 +89,7 @@ function _generateMarkdown(description: string | ICommandHandlerDescription): st if (typeof description === 'string') { return description; } else { - let parts = [description.description]; + const parts = [description.description]; parts.push('\n\n'); if (description.args) { for (let arg of description.args) { diff --git a/src/vs/workbench/api/electron-browser/mainThreadComments.ts b/src/vs/workbench/api/electron-browser/mainThreadComments.ts index 581529c441..382e5d5f14 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadComments.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadComments.ts @@ -11,26 +11,28 @@ import { keys } from 'vs/base/common/map'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ExtHostCommentsShape, ExtHostContext, IExtHostContext, MainContext, MainThreadCommentsShape, CommentProviderFeatures } from '../node/extHost.protocol'; -import { ICommentService } from 'vs/workbench/parts/comments/electron-browser/commentService'; -import { COMMENTS_PANEL_ID, CommentsPanel, COMMENTS_PANEL_TITLE } from 'vs/workbench/parts/comments/electron-browser/commentsPanel'; +import { ICommentService, ICommentInfo } from 'vs/workbench/contrib/comments/electron-browser/commentService'; +import { COMMENTS_PANEL_ID, CommentsPanel, COMMENTS_PANEL_TITLE } from 'vs/workbench/contrib/comments/electron-browser/commentsPanel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { URI } from 'vs/base/common/uri'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { generateUuid } from 'vs/base/common/uuid'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ICommentsConfiguration } from 'vs/workbench/parts/comments/electron-browser/comments.contribution'; +import { ICommentsConfiguration } from 'vs/workbench/contrib/comments/electron-browser/comments.contribution'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel'; +import { IRange } from 'vs/editor/common/core/range'; +import { Emitter, Event } from 'vs/base/common/event'; export class MainThreadDocumentCommentProvider implements modes.DocumentCommentProvider { - private _proxy: ExtHostCommentsShape; - private _handle: number; - private _features: CommentProviderFeatures; - get startDraftLabel(): string { return this._features.startDraftLabel; } - get deleteDraftLabel(): string { return this._features.deleteDraftLabel; } - get finishDraftLabel(): string { return this._features.finishDraftLabel; } - get reactionGroup(): modes.CommentReaction[] { return this._features.reactionGroup; } + private readonly _proxy: ExtHostCommentsShape; + private readonly _handle: number; + private readonly _features: CommentProviderFeatures; + get startDraftLabel(): string | undefined { return this._features.startDraftLabel; } + get deleteDraftLabel(): string | undefined { return this._features.deleteDraftLabel; } + get finishDraftLabel(): string | undefined { return this._features.finishDraftLabel; } + get reactionGroup(): modes.CommentReaction[] | undefined { return this._features.reactionGroup; } constructor(proxy: ExtHostCommentsShape, handle: number, features: CommentProviderFeatures) { this._proxy = proxy; @@ -75,16 +77,330 @@ export class MainThreadDocumentCommentProvider implements modes.DocumentCommentP } - onDidChangeCommentThreads = null; + // onDidChangeCommentThreads = null; +} + +export class MainThreadCommentThread implements modes.CommentThread2 { + private _input?: modes.CommentInput; + get input(): modes.CommentInput | undefined { + return this._input; + } + + set input(value: modes.CommentInput | undefined) { + this._input = value; + this._onDidChangeInput.fire(value); + } + + private _onDidChangeInput = new Emitter(); + get onDidChangeInput(): Event { return this._onDidChangeInput.event; } + + private _label: string; + + get label(): string { + return this._label; + } + + set label(label: string) { + this._label = label; + this._onDidChangeLabel.fire(this._label); + } + + private _onDidChangeLabel = new Emitter(); + get onDidChangeLabel(): Event { return this._onDidChangeLabel.event; } + + + public get comments(): modes.Comment[] { + return this._comments; + } + + public set comments(newComments: modes.Comment[]) { + this._comments = newComments; + this._onDidChangeComments.fire(this._comments); + } + + private _onDidChangeComments = new Emitter(); + get onDidChangeComments(): Event { return this._onDidChangeComments.event; } + + set acceptInputCommand(newCommand: modes.Command) { + this._acceptInputCommand = newCommand; + this._onDidChangeAcceptInputCommand.fire(this._acceptInputCommand); + } + + get acceptInputCommand(): modes.Command { + return this._acceptInputCommand!; + } + + private _onDidChangeAcceptInputCommand = new Emitter(); + get onDidChangeAcceptInputCommand(): Event { return this._onDidChangeAcceptInputCommand.event; } + + set additionalCommands(newCommands: modes.Command[]) { + this._additionalCommands = newCommands; + this._onDidChangeAdditionalCommands.fire(this._additionalCommands); + } + + get additionalCommands(): modes.Command[] { + return this._additionalCommands; + } + + private _onDidChangeAdditionalCommands = new Emitter(); + get onDidChangeAdditionalCommands(): Event { return this._onDidChangeAdditionalCommands.event; } + + set range(range: IRange) { + this._range = range; + this._onDidChangeRange.fire(this._range); + } + + get range(): IRange { + return this._range; + } + + private _onDidChangeRange = new Emitter(); + public onDidChangeRange = this._onDidChangeRange.event; + + get collapsibleState() { + return this._collapsibleState; + } + + set collapsibleState(newState: modes.CommentThreadCollapsibleState) { + this._collapsibleState = newState; + this._onDidChangeCollasibleState.fire(this._collapsibleState); + } + + private _onDidChangeCollasibleState = new Emitter(); + public onDidChangeCollasibleState = this._onDidChangeCollasibleState.event; + + constructor( + public commentThreadHandle: number, + public controller: MainThreadCommentController, + public extensionId: string, + public threadId: string, + public resource: string, + private _range: IRange, + private _comments: modes.Comment[], + private _acceptInputCommand: modes.Command | undefined, + private _additionalCommands: modes.Command[], + private _collapsibleState: modes.CommentThreadCollapsibleState + ) { + + } + + dispose() { } + + toJSON(): any { + return { + $mid: 7, + commentControlHandle: this.controller.handle, + commentThreadHandle: this.commentThreadHandle, + }; + } +} + +export class MainThreadCommentController { + get handle(): number { + return this._handle; + } + + get id(): string { + return this._id; + } + + get proxy(): ExtHostCommentsShape { + return this._proxy; + } + + get label(): string { + return this._label; + } + + private _reactions: modes.CommentReaction[] | undefined; + + get reactions() { + return this._reactions; + } + + set reactions(reactions: modes.CommentReaction[] | undefined) { + this._reactions = reactions; + } + + private readonly _threads: Map = new Map(); + public activeCommentThread?: MainThreadCommentThread; + + + constructor( + private readonly _proxy: ExtHostCommentsShape, + private readonly _commentService: ICommentService, + private readonly _handle: number, + private readonly _uniqueId: string, + private readonly _id: string, + private readonly _label: string, + private _features: CommentProviderFeatures + ) { } + + updateFeatures(features: CommentProviderFeatures) { + this._features = features; + } + + createCommentThread(commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, comments: modes.Comment[], acceptInputCommand: modes.Command | undefined, additionalCommands: modes.Command[], collapseState: modes.CommentThreadCollapsibleState): modes.CommentThread2 { + let thread = new MainThreadCommentThread( + commentThreadHandle, + this, + '', + threadId, + URI.revive(resource).toString(), + range, + comments, + acceptInputCommand, + additionalCommands, + collapseState + ); + + this._threads.set(commentThreadHandle, thread); + this._commentService.updateComments(this._uniqueId, { + added: [thread], + removed: [], + changed: [], + draftMode: modes.DraftMode.NotSupported + }); + + return thread; + } + + deleteCommentThread(commentThreadHandle: number) { + let thread = this.getKnownThread(commentThreadHandle); + this._threads.delete(commentThreadHandle); + + this._commentService.updateComments(this._uniqueId, { + added: [], + removed: [thread], + changed: [], + draftMode: modes.DraftMode.NotSupported + }); + + thread.dispose(); + } + + updateComments(commentThreadHandle: number, comments: modes.Comment[]) { + let thread = this.getKnownThread(commentThreadHandle); + thread.comments = comments; + + this._commentService.updateComments(this._uniqueId, { + added: [], + removed: [], + changed: [thread], + draftMode: modes.DraftMode.NotSupported + }); + } + + updateAcceptInputCommand(commentThreadHandle: number, acceptInputCommand: modes.Command) { + let thread = this.getKnownThread(commentThreadHandle); + thread.acceptInputCommand = acceptInputCommand; + } + + updateAdditionalCommands(commentThreadHandle: number, additionalCommands: modes.Command[]) { + let thread = this.getKnownThread(commentThreadHandle); + thread.additionalCommands = additionalCommands; + } + + updateCollapsibleState(commentThreadHandle: number, collapseState: modes.CommentThreadCollapsibleState) { + let thread = this.getKnownThread(commentThreadHandle); + thread.collapsibleState = collapseState; + } + + updateCommentThreadRange(commentThreadHandle: number, range: IRange) { + let thread = this.getKnownThread(commentThreadHandle); + thread.range = range; + } + + updateCommentThreadLabel(commentThreadHandle: number, label: string) { + let thread = this.getKnownThread(commentThreadHandle); + thread.label = label; + } + + updateInput(input: string) { + let thread = this.activeCommentThread; + + if (thread && thread.input) { + let commentInput = thread.input; + commentInput.value = input; + thread.input = commentInput; + } + } + + private getKnownThread(commentThreadHandle: number) { + const thread = this._threads.get(commentThreadHandle); + if (!thread) { + throw new Error('unknown thread'); + } + return thread; + } + + + async getDocumentComments(resource: URI, token) { + let ret: modes.CommentThread2[] = []; + for (let thread of keys(this._threads)) { + const commentThread = this._threads.get(thread)!; + if (commentThread.resource === resource.toString()) { + ret.push(commentThread); + } + } + + let commentingRanges = await this._proxy.$provideCommentingRanges(this.handle, resource, token); + + return { + owner: this._uniqueId, + threads: ret, + commentingRanges: commentingRanges ? + { + resource: resource, ranges: commentingRanges, newCommentThreadCallback: (uri: UriComponents, range: IRange) => { + this._proxy.$createNewCommentWidgetCallback(this.handle, uri, range, token); + } + } : [], + draftMode: modes.DraftMode.NotSupported + }; + } + + async getCommentingRanges(resource: URI, token): Promise { + let commentingRanges = await this._proxy.$provideCommentingRanges(this.handle, resource, token); + return commentingRanges || []; + } + + getReactionGroup(): modes.CommentReaction[] | undefined { + return this._features.reactionGroup; + } + + async toggleReaction(uri, thread: modes.CommentThread2, comment: modes.Comment, reaction: modes.CommentReaction, token): Promise { + return this._proxy.$toggleReaction(this._handle, thread.commentThreadHandle, uri, comment, reaction); + } + + getAllComments(): MainThreadCommentThread[] { + let ret: MainThreadCommentThread[] = []; + for (let thread of keys(this._threads)) { + ret.push(this._threads.get(thread)!); + } + + return ret; + } + + toJSON(): any { + return { + $mid: 6, + handle: this.handle + }; + } } @extHostNamedCustomer(MainContext.MainThreadComments) export class MainThreadComments extends Disposable implements MainThreadCommentsShape { private _disposables: IDisposable[]; - private _proxy: ExtHostCommentsShape; + private _activeCommentThreadDisposables: IDisposable[]; + private readonly _proxy: ExtHostCommentsShape; private _documentProviders = new Map(); private _workspaceProviders = new Map(); private _handlers = new Map(); + private _commentControllers = new Map(); + + private _activeCommentThread?: MainThreadCommentThread; + private _input?: modes.CommentInput; private _openPanelListener: IDisposable | null; constructor( @@ -97,7 +413,149 @@ export class MainThreadComments extends Disposable implements MainThreadComments ) { super(); this._disposables = []; + this._activeCommentThreadDisposables = []; this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostComments); + this._disposables.push(this._commentService.onDidChangeActiveCommentThread(async thread => { + let controller = (thread as MainThreadCommentThread).controller; + + if (!controller) { + return; + } + + this._activeCommentThreadDisposables = dispose(this._activeCommentThreadDisposables); + this._activeCommentThread = thread as MainThreadCommentThread; + controller.activeCommentThread = this._activeCommentThread; + + this._activeCommentThreadDisposables.push(this._activeCommentThread.onDidChangeInput(input => { // todo, dispose + this._input = input; + this._proxy.$onCommentWidgetInputChange(controller.handle, this._input ? this._input.value : undefined); + })); + + await this._proxy.$onCommentWidgetInputChange(controller.handle, this._input ? this._input.value : undefined); + })); + } + + $registerCommentController(handle: number, id: string, label: string): void { + const providerId = generateUuid(); + this._handlers.set(handle, providerId); + + const provider = new MainThreadCommentController(this._proxy, this._commentService, handle, providerId, id, label, {}); + this._commentService.registerCommentController(providerId, provider); + this._commentControllers.set(handle, provider); + + const commentsPanelAlreadyConstructed = this._panelService.getPanels().some(panel => panel.id === COMMENTS_PANEL_ID); + if (!commentsPanelAlreadyConstructed) { + this.registerPanel(commentsPanelAlreadyConstructed); + this.registerOpenPanelListener(commentsPanelAlreadyConstructed); + } + this._commentService.setWorkspaceComments(String(handle), []); + } + + $unregisterCommentController(handle: number): void { + const providerId = this._handlers.get(handle); + this._commentService.unregisterCommentController(providerId); + this._handlers.delete(handle); + this._commentControllers.delete(handle); + } + + $updateCommentControllerFeatures(handle: number, features: CommentProviderFeatures): void { + let provider = this._commentControllers.get(handle); + + if (!provider) { + return undefined; + } + + provider.updateFeatures(features); + } + + $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, comments: modes.Comment[], acceptInputCommand: modes.Command | undefined, additionalCommands: modes.Command[], collapseState: modes.CommentThreadCollapsibleState): modes.CommentThread2 | undefined { + let provider = this._commentControllers.get(handle); + + if (!provider) { + return undefined; + } + + return provider.createCommentThread(commentThreadHandle, threadId, resource, range, comments, acceptInputCommand, additionalCommands, collapseState); + } + + $deleteCommentThread(handle: number, commentThreadHandle: number) { + let provider = this._commentControllers.get(handle); + + if (!provider) { + return; + } + + return provider.deleteCommentThread(commentThreadHandle); + } + + $updateComments(handle: number, commentThreadHandle: number, comments: modes.Comment[]) { + let provider = this._commentControllers.get(handle); + + if (!provider) { + return; + } + + provider.updateComments(commentThreadHandle, comments); + } + + $setInputValue(handle: number, input: string) { + let provider = this._commentControllers.get(handle); + + if (!provider) { + return; + } + + provider.updateInput(input); + } + + $updateCommentThreadAcceptInputCommand(handle: number, commentThreadHandle: number, acceptInputCommand: modes.Command) { + let provider = this._commentControllers.get(handle); + + if (!provider) { + return; + } + + provider.updateAcceptInputCommand(commentThreadHandle, acceptInputCommand); + } + + $updateCommentThreadAdditionalCommands(handle: number, commentThreadHandle: number, additionalCommands: modes.Command[]) { + let provider = this._commentControllers.get(handle); + + if (!provider) { + return; + } + + provider.updateAdditionalCommands(commentThreadHandle, additionalCommands); + } + + $updateCommentThreadCollapsibleState(handle: number, commentThreadHandle: number, collapseState: modes.CommentThreadCollapsibleState): void { + let provider = this._commentControllers.get(handle); + + if (!provider) { + return; + } + + provider.updateCollapsibleState(commentThreadHandle, collapseState); + } + + $updateCommentThreadRange(handle: number, commentThreadHandle: number, range: any): void { + let provider = this._commentControllers.get(handle); + + if (!provider) { + return; + } + + provider.updateCommentThreadRange(commentThreadHandle, range); + } + + $updateCommentThreadLabel(handle: number, commentThreadHandle: number, label: string): void { + let provider = this._commentControllers.get(handle); + + if (!provider) { + return; + } + + provider.updateCommentThreadLabel(commentThreadHandle, label); } $registerDocumentCommentProvider(handle: number, features: CommentProviderFeatures): void { @@ -110,6 +568,18 @@ export class MainThreadComments extends Disposable implements MainThreadComments this._commentService.registerDataProvider(providerId, handler); } + private registerPanel(commentsPanelAlreadyConstructed: boolean) { + if (!commentsPanelAlreadyConstructed) { + Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( + CommentsPanel, + COMMENTS_PANEL_ID, + COMMENTS_PANEL_TITLE, + 'commentsPanel', + 10 + )); + } + } + /** * If the comments panel has never been opened, the constructor for it has not yet run so it has * no listeners for comment threads being set or updated. Listen for the panel opening for the @@ -122,20 +592,37 @@ export class MainThreadComments extends Disposable implements MainThreadComments keys(this._workspaceProviders).forEach(handle => { this._proxy.$provideWorkspaceComments(handle).then(commentThreads => { if (commentThreads) { - const providerId = this._handlers.get(handle); + const providerId = this.getHandler(handle); this._commentService.setWorkspaceComments(providerId, commentThreads); } - }); }); - this._openPanelListener.dispose(); - this._openPanelListener = null; + keys(this._commentControllers).forEach(handle => { + let threads = this._commentControllers.get(handle)!.getAllComments(); + + if (threads.length) { + const providerId = this.getHandler(handle); + this._commentService.setWorkspaceComments(providerId, threads); + } + }); + + if (this._openPanelListener) { + this._openPanelListener.dispose(); + this._openPanelListener = null; + } } }); } } + private getHandler(handle: number) { + if (!this._handlers.has(handle)) { + throw new Error('Unknown handler'); + } + return this._handlers.get(handle)!; + } + $registerWorkspaceCommentProvider(handle: number, extensionId: ExtensionIdentifier): void { this._workspaceProviders.set(handle, undefined); @@ -143,13 +630,9 @@ export class MainThreadComments extends Disposable implements MainThreadComments this._handlers.set(handle, providerId); const commentsPanelAlreadyConstructed = this._panelService.getPanels().some(panel => panel.id === COMMENTS_PANEL_ID); - Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( - CommentsPanel, - COMMENTS_PANEL_ID, - COMMENTS_PANEL_TITLE, - 'commentsPanel', - 10 - )); + if (!commentsPanelAlreadyConstructed) { + this.registerPanel(commentsPanelAlreadyConstructed); + } const openPanel = this._configurationService.getValue('comments').openPanel; @@ -187,7 +670,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments $unregisterDocumentCommentProvider(handle: number): void { this._documentProviders.delete(handle); - const handlerId = this._handlers.get(handle); + const handlerId = this.getHandler(handle); this._commentService.unregisterDataProvider(handlerId); this._handlers.delete(handle); } @@ -203,14 +686,14 @@ export class MainThreadComments extends Disposable implements MainThreadComments } } - const handlerId = this._handlers.get(handle); + const handlerId = this.getHandler(handle); this._commentService.removeWorkspaceComments(handlerId); this._handlers.delete(handle); } $onDidCommentThreadsChange(handle: number, event: modes.CommentThreadChangedEvent) { // notify comment service - const providerId = this._handlers.get(handle); + const providerId = this.getHandler(handle); this._commentService.updateComments(providerId, event); } @@ -234,13 +717,16 @@ export class MainThreadComments extends Disposable implements MainThreadComments async provideWorkspaceComments(): Promise { const result: modes.CommentThread[] = []; for (const handle of keys(this._workspaceProviders)) { - result.push(...await this._proxy.$provideWorkspaceComments(handle)); + const result = await this._proxy.$provideWorkspaceComments(handle); + if (Array.isArray(result)) { + result.push(...result); + } } return result; } - async provideDocumentComments(resource: URI): Promise { - const result: modes.CommentInfo[] = []; + async provideDocumentComments(resource: URI): Promise> { + const result: Array = []; for (const handle of keys(this._documentProviders)) { result.push(await this._proxy.$provideDocumentComments(handle, resource)); } @@ -249,6 +735,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments dispose(): void { this._disposables = dispose(this._disposables); + this._activeCommentThreadDisposables = dispose(this._activeCommentThreadDisposables); this._workspaceProviders.forEach(value => dispose(value)); this._workspaceProviders.clear(); this._documentProviders.forEach(value => dispose(value)); diff --git a/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts b/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts index 7441601250..c4c316af12 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts @@ -8,10 +8,9 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope, getScopes } from 'vs/platform/configuration/common/configurationRegistry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { MainThreadConfigurationShape, MainContext, ExtHostContext, IExtHostContext, IWorkspaceConfigurationChangeEventData, IConfigurationInitData } from '../node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; -import { ConfigurationTarget, IConfigurationChangeEvent, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationChangeEvent, IConfigurationModel, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @extHostNamedCustomer(MainContext.MainThreadConfiguration) @@ -22,7 +21,7 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { constructor( extHostContext: IExtHostContext, @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, - @IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IEnvironmentService private readonly _environmentService: IEnvironmentService, ) { const proxy = extHostContext.getProxy(ExtHostContext.ExtHostConfiguration); @@ -34,7 +33,7 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { } private _getConfigurationData(): IConfigurationInitData { - const configurationData: IConfigurationInitData = { ...this.configurationService.getConfigurationData(), configurationScopes: {} }; + const configurationData: IConfigurationInitData = { ...(this.configurationService.getConfigurationData()!), configurationScopes: {} }; // Send configurations scopes only in development mode. if (!this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment) { configurationData.configurationScopes = getScopes(); @@ -46,22 +45,22 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { this._configurationListener.dispose(); } - $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any, resourceUriComponenets: UriComponents): Promise { + $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, resourceUriComponenets: UriComponents | undefined): Promise { const resource = resourceUriComponenets ? URI.revive(resourceUriComponenets) : null; return this.writeConfiguration(target, key, value, resource); } - $removeConfigurationOption(target: ConfigurationTarget, key: string, resourceUriComponenets: UriComponents): Promise { + $removeConfigurationOption(target: ConfigurationTarget | null, key: string, resourceUriComponenets: UriComponents | undefined): Promise { const resource = resourceUriComponenets ? URI.revive(resourceUriComponenets) : null; return this.writeConfiguration(target, key, undefined, resource); } - private writeConfiguration(target: ConfigurationTarget, key: string, value: any, resource: URI): Promise { + private writeConfiguration(target: ConfigurationTarget | null, key: string, value: any, resource: URI | null): Promise { target = target !== null && target !== undefined ? target : this.deriveConfigurationTarget(key, resource); return this.configurationService.updateValue(key, value, { resource }, target, true); } - private deriveConfigurationTarget(key: string, resource: URI): ConfigurationTarget { + private deriveConfigurationTarget(key: string, resource: URI | null): ConfigurationTarget { if (resource && this._workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); if (configurationProperties[key] && configurationProperties[key].scope === ConfigurationScope.RESOURCE) { diff --git a/src/vs/workbench/api/electron-browser/mainThreadConsole.ts b/src/vs/workbench/api/electron-browser/mainThreadConsole.ts index a14dd0ebf2..0758acdb35 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadConsole.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadConsole.ts @@ -9,7 +9,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IRemoteConsoleLog, log, parse } from 'vs/base/node/console'; import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/electron-browser/extensionHost'; import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { IBroadcastService } from 'vs/platform/broadcast/electron-browser/broadcastService'; +import { IBroadcastService } from 'vs/workbench/services/broadcast/electron-browser/broadcastService'; import { EXTENSION_LOG_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost'; @extHostNamedCustomer(MainContext.MainThreadConsole) diff --git a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts index d2a42b25c6..f32d2846b5 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts @@ -8,29 +8,29 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { URI as uri } from 'vs/base/common/uri'; -import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, ITerminalSettings, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDebugAdapterTrackerFactory } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, IConfig, IDebugConfigurationProvider, IBreakpoint, IFunctionBreakpoint, IBreakpointData, ITerminalSettings, IDebugAdapter, IDebugAdapterDescriptorFactory, IDebugSession, IDebugAdapterFactory, IDebugAdapterTrackerFactory } from 'vs/workbench/contrib/debug/common/debug'; import { ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext, IExtHostContext, IBreakpointsDeltaDto, ISourceMultiBreakpointDto, ISourceBreakpointDto, IFunctionBreakpointDto, IDebugSessionDto } from 'vs/workbench/api/node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import severity from 'vs/base/common/severity'; -import { AbstractDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter'; +import { AbstractDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { convertToVSCPaths, convertToDAPaths } from 'vs/workbench/parts/debug/common/debugUtils'; +import { convertToVSCPaths, convertToDAPaths } from 'vs/workbench/contrib/debug/common/debugUtils'; @extHostNamedCustomer(MainContext.MainThreadDebugService) export class MainThreadDebugService implements MainThreadDebugServiceShape, IDebugAdapterFactory { - private _proxy: ExtHostDebugServiceShape; + private readonly _proxy: ExtHostDebugServiceShape; private _toDispose: IDisposable[]; private _breakpointEventsActive: boolean; - private _debugAdapters: Map; + private readonly _debugAdapters: Map; private _debugAdaptersHandleCounter = 1; - private _debugConfigurationProviders: Map; - private _debugAdapterDescriptorFactories: Map; - private _debugAdapterTrackerFactories: Map; - private _sessions: Set; + private readonly _debugConfigurationProviders: Map; + private readonly _debugAdapterDescriptorFactories: Map; + private readonly _debugAdapterTrackerFactories: Map; + private readonly _sessions: Set; constructor( extHostContext: IExtHostContext, @@ -73,7 +73,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb return da; } - substituteVariables(folder: IWorkspaceFolder, config: IConfig): Promise { + substituteVariables(folder: IWorkspaceFolder | undefined, config: IConfig): Promise { return Promise.resolve(this._proxy.$substituteVariables(folder ? folder.uri : undefined, config)); } @@ -144,13 +144,13 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb this.debugService.addFunctionBreakpoint(dto.functionName, dto.id); } } - return undefined; + return Promise.resolve(); } public $unregisterBreakpoints(breakpointIds: string[], functionBreakpointIds: string[]): Promise { breakpointIds.forEach(id => this.debugService.removeBreakpoints(id)); functionBreakpointIds.forEach(id => this.debugService.removeFunctionBreakpoints(id)); - return undefined; + return Promise.resolve(); } @@ -161,18 +161,18 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb }; if (hasProvide) { provider.provideDebugConfigurations = (folder) => { - return Promise.resolve(this._proxy.$provideDebugConfigurations(handle, folder)); + return this._proxy.$provideDebugConfigurations(handle, folder); }; } if (hasResolve) { provider.resolveDebugConfiguration = (folder, config) => { - return Promise.resolve(this._proxy.$resolveDebugConfiguration(handle, folder, config)); + return this._proxy.$resolveDebugConfiguration(handle, folder, config); }; } if (hasProvideDebugAdapter) { console.info('DebugConfigurationProvider.debugAdapterExecutable is deprecated and will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.'); provider.debugAdapterExecutable = (folder) => { - return Promise.resolve(this._proxy.$legacyDebugAdapterExecutable(handle, folder)); + return this._proxy.$legacyDebugAdapterExecutable(handle, folder); }; } this._debugConfigurationProviders.set(handle, provider); @@ -229,10 +229,17 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb } } - public $startDebugging(_folderUri: uri | undefined, nameOrConfiguration: string | IConfig): Promise { + private getSession(sessionId: DebugSessionUUID | undefined): IDebugSession | undefined { + if (sessionId) { + return this.debugService.getModel().getSessions(true).filter(s => s.getId() === sessionId).pop(); + } + return undefined; + } + + public $startDebugging(_folderUri: uri | undefined, nameOrConfiguration: string | IConfig, parentSessionID: DebugSessionUUID | undefined): Promise { const folderUri = _folderUri ? uri.revive(_folderUri) : undefined; const launch = this.debugService.getConfigurationManager().getLaunch(folderUri); - return this.debugService.startDebugging(launch, nameOrConfiguration).then(success => { + return this.debugService.startDebugging(launch, nameOrConfiguration, false, this.getSession(parentSessionID)).then(success => { return success; }, err => { return Promise.reject(new Error(err && err.message ? err.message : 'cannot start debugging')); @@ -262,26 +269,44 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb } public $acceptDAMessage(handle: number, message: DebugProtocol.ProtocolMessage) { - this._debugAdapters.get(handle).acceptMessage(convertToVSCPaths(message, false)); + this.getDebugAdapter(handle).acceptMessage(convertToVSCPaths(message, false)); } + public $acceptDAError(handle: number, name: string, message: string, stack: string) { - this._debugAdapters.get(handle).fireError(handle, new Error(`${name}: ${message}\n${stack}`)); + this.getDebugAdapter(handle).fireError(handle, new Error(`${name}: ${message}\n${stack}`)); } public $acceptDAExit(handle: number, code: number, signal: string) { - this._debugAdapters.get(handle).fireExit(handle, code, signal); + this.getDebugAdapter(handle).fireExit(handle, code, signal); + } + + private getDebugAdapter(handle: number): ExtensionHostDebugAdapter { + const adapter = this._debugAdapters.get(handle); + if (!adapter) { + throw new Error('Invalid debug adapter'); + } + return adapter; } // dto helpers - getSessionDto(session: IDebugSession): IDebugSessionDto { + public $sessionCached(sessionID: string) { + // remember that the EH has cached the session and we do not have to send it again + this._sessions.add(sessionID); + } + + + getSessionDto(session: undefined): undefined; + getSessionDto(session: IDebugSession): IDebugSessionDto; + getSessionDto(session: IDebugSession | undefined): IDebugSessionDto | undefined; + getSessionDto(session: IDebugSession | undefined): IDebugSessionDto | undefined { if (session) { const sessionID = session.getId(); if (this._sessions.has(sessionID)) { return sessionID; } else { - this._sessions.add(sessionID); + // this._sessions.add(sessionID); // #69534: see $sessionCached above return { id: sessionID, type: session.configuration.type, @@ -333,7 +358,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape, IDeb /* class ExtensionHostDebugAdapter extends AbstractDebugAdapter { - constructor(private _ds: MainThreadDebugService, private _handle: number, private _proxy: ExtHostDebugServiceShape, private _session: IDebugSession) { + constructor(private readonly _ds: MainThreadDebugService, private _handle: number, private _proxy: ExtHostDebugServiceShape, private _session: IDebugSession) { super(); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts b/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts index 9dc505cf4c..7bd0c0864d 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts @@ -21,7 +21,7 @@ class DecorationRequestsQueue { private _timer: any; constructor( - private _proxy: ExtHostDecorationsShape + private readonly _proxy: ExtHostDecorationsShape ) { // } @@ -109,13 +109,17 @@ export class MainThreadDecorations implements MainThreadDecorationsShape { } $onDidChange(handle: number, resources: UriComponents[]): void { - const [emitter] = this._provider.get(handle); - emitter.fire(resources && resources.map(URI.revive)); + const provider = this._provider.get(handle); + if (provider) { + const [emitter] = provider; + emitter.fire(resources && resources.map(URI.revive)); + } } $unregisterDecorationProvider(handle: number): void { - if (this._provider.has(handle)) { - dispose(this._provider.get(handle)); + const provider = this._provider.get(handle); + if (provider) { + dispose(provider); this._provider.delete(handle); } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadDiagnostics.ts b/src/vs/workbench/api/electron-browser/mainThreadDiagnostics.ts index e5c05ad753..8adafe4ad0 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDiagnostics.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDiagnostics.ts @@ -22,7 +22,7 @@ export class MainThreadDiagnostics implements MainThreadDiagnosticsShape { } dispose(): void { - this._activeOwners.forEach(owner => this._markerService.changeAll(owner, undefined)); + this._activeOwners.forEach(owner => this._markerService.changeAll(owner, [])); } $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void { @@ -43,7 +43,7 @@ export class MainThreadDiagnostics implements MainThreadDiagnosticsShape { } $clear(owner: string): void { - this._markerService.changeAll(owner, undefined); + this._markerService.changeAll(owner, []); this._activeOwners.delete(owner); } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadDialogs.ts b/src/vs/workbench/api/electron-browser/mainThreadDialogs.ts index 0e2563b588..7e8ea278c9 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDialogs.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDialogs.ts @@ -23,11 +23,11 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { // } - $showOpenDialog(options: MainThreadDialogOpenOptions): Promise { + $showOpenDialog(options: MainThreadDialogOpenOptions): Promise { return Promise.resolve(this._fileDialogService.showOpenDialog(MainThreadDialogs._convertOpenOptions(options))); } - $showSaveDialog(options: MainThreadDialogSaveOptions): Promise { + $showSaveDialog(options: MainThreadDialogSaveOptions): Promise { return Promise.resolve(this._fileDialogService.showSaveDialog(MainThreadDialogs._convertSaveOptions(options))); } @@ -41,7 +41,7 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { }; if (options.filters) { result.filters = []; - forEach(options.filters, entry => result.filters.push({ name: entry.key, extensions: entry.value })); + forEach(options.filters, entry => result.filters!.push({ name: entry.key, extensions: entry.value })); } return result; } @@ -53,7 +53,7 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { }; if (options.filters) { result.filters = []; - forEach(options.filters, entry => result.filters.push({ name: entry.key, extensions: entry.value })); + forEach(options.filters, entry => result.filters!.push({ name: entry.key, extensions: entry.value })); } return result; } diff --git a/src/vs/workbench/api/electron-browser/mainThreadDocumentContentProviders.ts b/src/vs/workbench/api/electron-browser/mainThreadDocumentContentProviders.ts index 7e8b0ef7ed..3497c6fa17 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDocumentContentProviders.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDocumentContentProviders.ts @@ -41,7 +41,7 @@ export class MainThreadDocumentContentProviders implements MainThreadDocumentCon $registerTextContentProvider(handle: number, scheme: string): void { const registration = this._textModelResolverService.registerTextModelContentProvider(scheme, { - provideTextContent: (uri: URI): Promise => { + provideTextContent: (uri: URI): Promise => { return this._proxy.$provideTextDocumentContent(handle, uri).then(value => { if (typeof value === 'string') { const firstLineText = value.substr(0, 1 + value.search(/\r?\n/)); @@ -70,8 +70,9 @@ export class MainThreadDocumentContentProviders implements MainThreadDocumentCon } // cancel and dispose an existing update - if (this._pendingUpdate.has(model.id)) { - this._pendingUpdate.get(model.id).cancel(); + const pending = this._pendingUpdate.get(model.id); + if (pending) { + pending.cancel(); } // create and keep update token @@ -86,7 +87,7 @@ export class MainThreadDocumentContentProviders implements MainThreadDocumentCon // ignore this return; } - if (edits.length > 0) { + if (edits && edits.length > 0) { // use the evil-edit as these models show in readonly-editor only model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts b/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts index 3fde65f3a1..89dc00ace3 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts @@ -24,8 +24,8 @@ export class BoundModelReferenceCollection { private _length = 0; constructor( - private _maxAge: number = 1000 * 60 * 3, - private _maxLength: number = 1024 * 1024 * 80 + private readonly _maxAge: number = 1000 * 60 * 3, + private readonly _maxLength: number = 1024 * 1024 * 80 ) { // } @@ -35,11 +35,11 @@ export class BoundModelReferenceCollection { } add(ref: IReference): void { - let length = ref.object.textEditorModel.getValueLength(); + const length = ref.object.textEditorModel.getValueLength(); let handle: any; let entry: { length: number, dispose(): void }; const dispose = () => { - let idx = this._data.indexOf(entry); + const idx = this._data.indexOf(entry); if (idx >= 0) { this._length -= length; ref.dispose(); @@ -64,16 +64,16 @@ export class BoundModelReferenceCollection { export class MainThreadDocuments implements MainThreadDocumentsShape { - private _modelService: IModelService; - private _textModelResolverService: ITextModelService; - private _textFileService: ITextFileService; - private _fileService: IFileService; - private _untitledEditorService: IUntitledEditorService; + private readonly _modelService: IModelService; + private readonly _textModelResolverService: ITextModelService; + private readonly _textFileService: ITextFileService; + private readonly _fileService: IFileService; + private readonly _untitledEditorService: IUntitledEditorService; private _toDispose: IDisposable[]; private _modelToDisposeMap: { [modelUrl: string]: IDisposable; }; - private _proxy: ExtHostDocumentsShape; - private _modelIsSynced: { [modelId: string]: boolean; }; + private readonly _proxy: ExtHostDocumentsShape; + private readonly _modelIsSynced: { [modelId: string]: boolean; }; private _modelReferenceCollection = new BoundModelReferenceCollection(); constructor( @@ -130,16 +130,16 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { private _shouldHandleFileEvent(e: TextFileModelChangeEvent): boolean { const model = this._modelService.getModel(e.resource); - return model && shouldSynchronizeModel(model); + return !!model && shouldSynchronizeModel(model); } private _onModelAdded(model: ITextModel): void { // Same filter as in mainThreadEditorsTracker if (!shouldSynchronizeModel(model)) { // don't synchronize too large models - return null; + return; } - let modelUrl = model.uri; + const modelUrl = model.uri; this._modelIsSynced[modelUrl.toString()] = true; this._modelToDisposeMap[modelUrl.toString()] = model.onDidChangeContent((e) => { this._proxy.$acceptModelChanged(modelUrl, e, this._textFileService.isDirty(modelUrl)); @@ -148,7 +148,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { private _onModelModeChanged(event: { model: ITextModel; oldModeId: string; }): void { let { model, oldModeId } = event; - let modelUrl = model.uri; + const modelUrl = model.uri; if (!this._modelIsSynced[modelUrl.toString()]) { return; } @@ -156,7 +156,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { } private _onModelRemoved(modelUrl: URI): void { - let strModelUrl = modelUrl.toString(); + const strModelUrl = modelUrl.toString(); if (!this._modelIsSynced[strModelUrl]) { return; } @@ -214,7 +214,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { } private _handleUntitledScheme(uri: URI): Promise { - let asFileUri = uri.with({ scheme: Schemas.file }); + const asFileUri = uri.with({ scheme: Schemas.file }); return this._fileService.resolveFile(asFileUri).then(stats => { // don't create a new file ontop of an existing file return Promise.reject(new Error('file already exists on disk')); diff --git a/src/vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors.ts index b5da6e6e6c..c5d250c284 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors.ts @@ -7,7 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; -import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, isCodeEditor, isDiffEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IEditor } from 'vs/editor/common/editorCommon'; @@ -25,7 +25,7 @@ import { EditorViewColumn, editorGroupToViewColumn } from 'vs/workbench/api/shar import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; import { IEditor as IWorkbenchEditor } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; @@ -70,7 +70,7 @@ class TextEditorSnapshot { readonly id: string; constructor( - readonly editor: ICodeEditor, + readonly editor: IActiveCodeEditor, ) { this.id = `${editor.getId()},${editor.getModel().id}`; } @@ -85,8 +85,8 @@ class DocumentAndEditorStateDelta { readonly addedDocuments: ITextModel[], readonly removedEditors: TextEditorSnapshot[], readonly addedEditors: TextEditorSnapshot[], - readonly oldActiveEditor: string, - readonly newActiveEditor: string, + readonly oldActiveEditor: string | null | undefined, + readonly newActiveEditor: string | null | undefined, ) { this.isEmpty = this.removedDocuments.length === 0 && this.addedDocuments.length === 0 @@ -131,7 +131,7 @@ class DocumentAndEditorState { constructor( readonly documents: Set, readonly textEditors: Map, - readonly activeEditor: string, + readonly activeEditor: string | null | undefined, ) { // } @@ -230,14 +230,14 @@ class MainThreadDocumentAndEditorStateComputer { // editor: only take those that have a not too large model const editors = new Map(); - let activeEditor: string | null = null; + let activeEditor: string | null = null; // Strict null work. This doesn't like being undefined! for (const editor of this._codeEditorService.listCodeEditors()) { if (editor.isSimpleWidget) { continue; } const model = editor.getModel(); - if (model && shouldSynchronizeModel(model) + if (editor.hasModel() && model && shouldSynchronizeModel(model) && !model.isDisposed() // model disposed && Boolean(this._modelService.getModel(model.uri)) // model disposing, the flag didn't flip yet but the model service already removed it ) { @@ -257,7 +257,7 @@ class MainThreadDocumentAndEditorStateComputer { // to match output panels or the active workbench editor with // one of editor we have just computed if (!activeEditor) { - let candidate: IEditor; + let candidate: IEditor | undefined; if (this._activeEditorOrder === ActiveEditorOrder.Editor) { candidate = this._getActiveEditorFromEditorPart() || this._getActiveEditorFromPanel(); } else { @@ -282,8 +282,8 @@ class MainThreadDocumentAndEditorStateComputer { } } - private _getActiveEditorFromPanel(): IEditor { - let panel = this._panelService.getActivePanel(); + private _getActiveEditorFromPanel(): IEditor | undefined { + const panel = this._panelService.getActivePanel(); if (panel instanceof BaseTextEditor && isCodeEditor(panel.getControl())) { return panel.getControl(); } else { @@ -291,7 +291,7 @@ class MainThreadDocumentAndEditorStateComputer { } } - private _getActiveEditorFromEditorPart(): IEditor { + private _getActiveEditorFromEditorPart(): IEditor | undefined { let result = this._editorService.activeTextEditorWidget; if (isDiffEditor(result)) { result = result.getModifiedEditor(); @@ -304,8 +304,8 @@ class MainThreadDocumentAndEditorStateComputer { export class MainThreadDocumentsAndEditors { private _toDispose: IDisposable[]; - private _proxy: ExtHostDocumentsAndEditorsShape; - private _stateComputer: MainThreadDocumentAndEditorStateComputer; + private readonly _proxy: ExtHostDocumentsAndEditorsShape; + private readonly _stateComputer: MainThreadDocumentAndEditorStateComputer; private _textEditors = <{ [id: string]: MainThreadTextEditor }>Object.create(null); private _onTextEditorAdd = new Emitter(); @@ -362,8 +362,8 @@ export class MainThreadDocumentsAndEditors { private _onDelta(delta: DocumentAndEditorStateDelta): void { let removedDocuments: URI[]; - let removedEditors: string[] = []; - let addedEditors: MainThreadTextEditor[] = []; + const removedEditors: string[] = []; + const addedEditors: MainThreadTextEditor[] = []; // removed models removedDocuments = delta.removedDocuments.map(m => m.uri); @@ -387,7 +387,7 @@ export class MainThreadDocumentsAndEditors { } } - let extHostDelta: IDocumentsAndEditorsDelta = Object.create(null); + const extHostDelta: IDocumentsAndEditorsDelta = Object.create(null); let empty = true; if (delta.newActiveEditor !== undefined) { empty = false; @@ -444,8 +444,8 @@ export class MainThreadDocumentsAndEditors { }; } - private _findEditorPosition(editor: MainThreadTextEditor): EditorViewColumn { - for (let workbenchEditor of this._editorService.visibleControls) { + private _findEditorPosition(editor: MainThreadTextEditor): EditorViewColumn | undefined { + for (const workbenchEditor of this._editorService.visibleControls) { if (editor.matches(workbenchEditor)) { return editorGroupToViewColumn(this._editorGroupService, workbenchEditor.group); } @@ -453,8 +453,8 @@ export class MainThreadDocumentsAndEditors { return undefined; } - findTextEditorIdFor(editor: IWorkbenchEditor): string { - for (let id in this._textEditors) { + findTextEditorIdFor(editor: IWorkbenchEditor): string | undefined { + for (const id in this._textEditors) { if (this._textEditors[id].matches(editor)) { return id; } diff --git a/src/vs/workbench/api/electron-browser/mainThreadEditor.ts b/src/vs/workbench/api/electron-browser/mainThreadEditor.ts index a0111cefe3..535e5beaca 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadEditor.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadEditor.ts @@ -16,6 +16,7 @@ import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2 import { IApplyEditsOptions, IEditorPropertiesChangeData, IResolvedTextEditorConfiguration, ITextEditorConfigurationUpdate, IUndoStopOptions, TextEditorRevealType } from 'vs/workbench/api/node/extHost.protocol'; import { EndOfLine, TextEditorLineNumbersStyle } from 'vs/workbench/api/node/extHostTypes'; import { IEditor } from 'vs/workbench/common/editor'; +import { withNullAsUndefined } from 'vs/base/common/types'; export interface IFocusTracker { onGainedFocus(): void; @@ -24,14 +25,14 @@ export interface IFocusTracker { export class MainThreadTextEditorProperties { - public static readFromEditor(previousProperties: MainThreadTextEditorProperties, model: ITextModel, codeEditor: ICodeEditor): MainThreadTextEditorProperties { + public static readFromEditor(previousProperties: MainThreadTextEditorProperties | null, model: ITextModel, codeEditor: ICodeEditor | null): MainThreadTextEditorProperties { const selections = MainThreadTextEditorProperties._readSelectionsFromCodeEditor(previousProperties, codeEditor); const options = MainThreadTextEditorProperties._readOptionsFromCodeEditor(previousProperties, model, codeEditor); const visibleRanges = MainThreadTextEditorProperties._readVisibleRangesFromCodeEditor(previousProperties, codeEditor); return new MainThreadTextEditorProperties(selections, options, visibleRanges); } - private static _readSelectionsFromCodeEditor(previousProperties: MainThreadTextEditorProperties, codeEditor: ICodeEditor): Selection[] { + private static _readSelectionsFromCodeEditor(previousProperties: MainThreadTextEditorProperties | null, codeEditor: ICodeEditor | null): Selection[] { let result: Selection[] | null = null; if (codeEditor) { result = codeEditor.getSelections(); @@ -45,10 +46,14 @@ export class MainThreadTextEditorProperties { return result; } - private static _readOptionsFromCodeEditor(previousProperties: MainThreadTextEditorProperties, model: ITextModel, codeEditor: ICodeEditor): IResolvedTextEditorConfiguration { + private static _readOptionsFromCodeEditor(previousProperties: MainThreadTextEditorProperties | null, model: ITextModel, codeEditor: ICodeEditor | null): IResolvedTextEditorConfiguration { if (model.isDisposed()) { - // shutdown time - return previousProperties.options; + if (previousProperties) { + // shutdown time + return previousProperties.options; + } else { + throw new Error('No valid properties'); + } } let cursorStyle: TextEditorCursorStyle; @@ -80,12 +85,13 @@ export class MainThreadTextEditorProperties { return { insertSpaces: modelOptions.insertSpaces, tabSize: modelOptions.tabSize, + indentSize: modelOptions.indentSize, cursorStyle: cursorStyle, lineNumbers: lineNumbers }; } - private static _readVisibleRangesFromCodeEditor(previousProperties: MainThreadTextEditorProperties, codeEditor: ICodeEditor): Range[] { + private static _readVisibleRangesFromCodeEditor(previousProperties: MainThreadTextEditorProperties | null, codeEditor: ICodeEditor | null): Range[] { if (codeEditor) { return codeEditor.getVisibleRanges(); } @@ -99,8 +105,8 @@ export class MainThreadTextEditorProperties { ) { } - public generateDelta(oldProps: MainThreadTextEditorProperties, selectionChangeSource: string): IEditorPropertiesChangeData { - let delta: IEditorPropertiesChangeData = { + public generateDelta(oldProps: MainThreadTextEditorProperties | null, selectionChangeSource: string | null): IEditorPropertiesChangeData | null { + const delta: IEditorPropertiesChangeData = { options: null, selections: null, visibleRanges: null @@ -109,7 +115,7 @@ export class MainThreadTextEditorProperties { if (!oldProps || !MainThreadTextEditorProperties._selectionsEqual(oldProps.selections, this.selections)) { delta.selections = { selections: this.selections, - source: selectionChangeSource + source: withNullAsUndefined(selectionChangeSource) }; } @@ -162,6 +168,7 @@ export class MainThreadTextEditorProperties { } return ( a.tabSize === b.tabSize + && a.indentSize === b.indentSize && a.insertSpaces === b.insertSpaces && a.cursorStyle === b.cursorStyle && a.lineNumbers === b.lineNumbers @@ -175,12 +182,12 @@ export class MainThreadTextEditorProperties { */ export class MainThreadTextEditor { - private _id: string; + private readonly _id: string; private _model: ITextModel; - private _modelService: IModelService; + private readonly _modelService: IModelService; private _modelListeners: IDisposable[]; - private _codeEditor: ICodeEditor; - private _focusTracker: IFocusTracker; + private _codeEditor: ICodeEditor | null; + private readonly _focusTracker: IFocusTracker; private _codeEditorListeners: IDisposable[]; private _properties: MainThreadTextEditorProperties; @@ -200,7 +207,6 @@ export class MainThreadTextEditor { this._modelService = modelService; this._codeEditorListeners = []; - this._properties = null; this._onPropertiesChanged = new Emitter(); this._modelListeners = []; @@ -213,20 +219,20 @@ export class MainThreadTextEditor { } public dispose(): void { - this._model = null; + this._model = null!; this._modelListeners = dispose(this._modelListeners); this._codeEditor = null; this._codeEditorListeners = dispose(this._codeEditorListeners); } - private _updatePropertiesNow(selectionChangeSource: string): void { + private _updatePropertiesNow(selectionChangeSource: string | null): void { this._setProperties( MainThreadTextEditorProperties.readFromEditor(this._properties, this._model, this._codeEditor), selectionChangeSource ); } - private _setProperties(newProperties: MainThreadTextEditorProperties, selectionChangeSource: string): void { + private _setProperties(newProperties: MainThreadTextEditorProperties, selectionChangeSource: string | null): void { const delta = newProperties.generateDelta(this._properties, selectionChangeSource); this._properties = newProperties; if (delta) { @@ -242,15 +248,15 @@ export class MainThreadTextEditor { return this._model; } - public getCodeEditor(): ICodeEditor { + public getCodeEditor(): ICodeEditor | null { return this._codeEditor; } - public hasCodeEditor(codeEditor: ICodeEditor): boolean { + public hasCodeEditor(codeEditor: ICodeEditor | null): boolean { return (this._codeEditor === codeEditor); } - public setCodeEditor(codeEditor: ICodeEditor): void { + public setCodeEditor(codeEditor: ICodeEditor | null): void { if (this.hasCodeEditor(codeEditor)) { // Nothing to do... return; @@ -318,10 +324,10 @@ export class MainThreadTextEditor { } private _setIndentConfiguration(newConfiguration: ITextEditorConfigurationUpdate): void { + const creationOpts = this._modelService.getCreationOptions(this._model.getLanguageIdentifier().language, this._model.uri, this._model.isForSimpleWidget); + if (newConfiguration.tabSize === 'auto' || newConfiguration.insertSpaces === 'auto') { // one of the options was set to 'auto' => detect indentation - - let creationOpts = this._modelService.getCreationOptions(this._model.getLanguageIdentifier().language, this._model.uri, this._model.isForSimpleWidget); let insertSpaces = creationOpts.insertSpaces; let tabSize = creationOpts.tabSize; @@ -337,13 +343,20 @@ export class MainThreadTextEditor { return; } - let newOpts: ITextModelUpdateOptions = {}; + const newOpts: ITextModelUpdateOptions = {}; if (typeof newConfiguration.insertSpaces !== 'undefined') { newOpts.insertSpaces = newConfiguration.insertSpaces; } if (typeof newConfiguration.tabSize !== 'undefined') { newOpts.tabSize = newConfiguration.tabSize; } + if (typeof newConfiguration.indentSize !== 'undefined') { + if (newConfiguration.indentSize === 'tabSize') { + newOpts.indentSize = newOpts.tabSize || creationOpts.tabSize; + } else { + newOpts.indentSize = newConfiguration.indentSize; + } + } this._model.updateOptions(newOpts); } @@ -355,7 +368,7 @@ export class MainThreadTextEditor { } if (newConfiguration.cursorStyle) { - let newCursorStyle = cursorStyleToString(newConfiguration.cursorStyle); + const newCursorStyle = cursorStyleToString(newConfiguration.cursorStyle); this._codeEditor.updateOptions({ cursorStyle: newCursorStyle }); @@ -390,7 +403,7 @@ export class MainThreadTextEditor { if (!this._codeEditor) { return; } - let ranges: Range[] = []; + const ranges: Range[] = []; for (let i = 0, len = Math.floor(_ranges.length / 4); i < len; i++) { ranges[i] = new Range(_ranges[4 * i], _ranges[4 * i + 1], _ranges[4 * i + 2], _ranges[4 * i + 3]); } @@ -452,7 +465,7 @@ export class MainThreadTextEditor { this._model.pushEOL(EndOfLineSequence.LF); } - let transformedEdits = edits.map((edit): IIdentifiedSingleEditOperation => { + const transformedEdits = edits.map((edit): IIdentifiedSingleEditOperation => { return { range: Range.lift(edit.range), text: edit.text, diff --git a/src/vs/workbench/api/electron-browser/mainThreadEditors.ts b/src/vs/workbench/api/electron-browser/mainThreadEditors.ts index ce232bbe0b..2c6b2b288d 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadEditors.ts @@ -23,20 +23,20 @@ import { MainThreadTextEditor } from 'vs/workbench/api/electron-browser/mainThre import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, IExtHostContext, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, TextEditorRevealType, WorkspaceEditDto, reviveWorkspaceEditDto } from 'vs/workbench/api/node/extHost.protocol'; import { EditorViewColumn, editorGroupToViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/shared/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IURLService } from 'vs/platform/url/common/url'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; export class MainThreadTextEditors implements MainThreadTextEditorsShape { private static INSTANCE_COUNT: number = 0; - private _instanceId: string; - private _proxy: ExtHostEditorsShape; - private _documentsAndEditors: MainThreadDocumentsAndEditors; + private readonly _instanceId: string; + private readonly _proxy: ExtHostEditorsShape; + private readonly _documentsAndEditors: MainThreadDocumentsAndEditors; private _toDispose: IDisposable[]; private _textEditorsListenersMap: { [editorId: string]: IDisposable[]; }; - private _editorPositionData: ITextEditorPositionData; + private _editorPositionData: ITextEditorPositionData | null; private _registeredDecorationTypes: { [decorationType: string]: boolean; }; constructor( @@ -77,8 +77,8 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { } private _onTextEditorAdd(textEditor: MainThreadTextEditor): void { - let id = textEditor.getId(); - let toDispose: IDisposable[] = []; + const id = textEditor.getId(); + const toDispose: IDisposable[] = []; toDispose.push(textEditor.onPropertiesChanged((data) => { this._proxy.$acceptEditorPropertiesChanged(id, data); })); @@ -94,7 +94,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { private _updateActiveAndVisibleTextEditors(): void { // editor columns - let editorPositionData = this._getTextEditorPositionData(); + const editorPositionData = this._getTextEditorPositionData(); if (!objectEquals(this._editorPositionData, editorPositionData)) { this._editorPositionData = editorPositionData; this._proxy.$acceptEditorPositionData(this._editorPositionData); @@ -102,7 +102,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { } private _getTextEditorPositionData(): ITextEditorPositionData { - let result: ITextEditorPositionData = Object.create(null); + const result: ITextEditorPositionData = Object.create(null); for (let workbenchEditor of this._editorService.visibleControls) { const id = this._documentsAndEditors.findTextEditorIdFor(workbenchEditor); if (id) { @@ -114,7 +114,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { // --- from extension host process - $tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): Promise { + $tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): Promise { const uri = URI.revive(resource); const editorOptions: ITextEditorOptions = { @@ -137,28 +137,28 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { } $tryShowEditor(id: string, position?: EditorViewColumn): Promise { - let mainThreadEditor = this._documentsAndEditors.getEditor(id); + const mainThreadEditor = this._documentsAndEditors.getEditor(id); if (mainThreadEditor) { - let model = mainThreadEditor.getModel(); + const model = mainThreadEditor.getModel(); return this._editorService.openEditor({ resource: model.uri, options: { preserveFocus: false } }, viewColumnToEditorGroup(this._editorGroupService, position)).then(() => { return; }); } - return undefined; + return Promise.resolve(); } $tryHideEditor(id: string): Promise { - let mainThreadEditor = this._documentsAndEditors.getEditor(id); + const mainThreadEditor = this._documentsAndEditors.getEditor(id); if (mainThreadEditor) { - let editors = this._editorService.visibleControls; + const editors = this._editorService.visibleControls; for (let editor of editors) { if (mainThreadEditor.matches(editor)) { return editor.group.closeEditor(editor.input).then(() => { return; }); } } } - return undefined; + return Promise.resolve(); } $trySetSelections(id: string, selections: ISelection[]): Promise { @@ -192,7 +192,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { return Promise.reject(disposed(`TextEditor(${id})`)); } this._documentsAndEditors.getEditor(id).revealRange(range, revealType); - return undefined; + return Promise.resolve(); } $trySetOptions(id: string, options: ITextEditorConfigurationUpdate): Promise { @@ -242,12 +242,16 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape { } const codeEditor = editor.getCodeEditor(); + if (!codeEditor) { + return Promise.reject(new Error('No such CodeEditor')); + } + const codeEditorId = codeEditor.getId(); const diffEditors = this._codeEditorService.listDiffEditors(); const [diffEditor] = diffEditors.filter(d => d.getOriginalEditor().getId() === codeEditorId || d.getModifiedEditor().getId() === codeEditorId); if (diffEditor) { - return Promise.resolve(diffEditor.getLineChanges()); + return Promise.resolve(diffEditor.getLineChanges() || []); } const dirtyDiffContribution = codeEditor.getContribution('editor.contrib.dirtydiff'); diff --git a/src/vs/workbench/api/electron-browser/mainThreadExtensionService.ts b/src/vs/workbench/api/electron-browser/mainThreadExtensionService.ts index 06beb62cc6..80e7ca5871 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadExtensionService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadExtensionService.ts @@ -7,29 +7,45 @@ import { SerializedError } from 'vs/base/common/errors'; import Severity from 'vs/base/common/severity'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { IExtHostContext, MainContext, MainThreadExtensionServiceShape } from 'vs/workbench/api/node/extHost.protocol'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService, ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionService } from 'vs/workbench/services/extensions/electron-browser/extensionService'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { localize } from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; @extHostNamedCustomer(MainContext.MainThreadExtensionService) export class MainThreadExtensionService implements MainThreadExtensionServiceShape { private readonly _extensionService: ExtensionService; + private readonly _notificationService: INotificationService; + private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService; + private readonly _windowService: IWindowService; constructor( extHostContext: IExtHostContext, - @IExtensionService extensionService: IExtensionService + @IExtensionService extensionService: IExtensionService, + @INotificationService notificationService: INotificationService, + @IExtensionsWorkbenchService extensionsWorkbenchService: IExtensionsWorkbenchService, + @IWindowService windowService: IWindowService ) { if (extensionService instanceof ExtensionService) { this._extensionService = extensionService; } + this._notificationService = notificationService; + this._extensionsWorkbenchService = extensionsWorkbenchService; + this._windowService = windowService; } public dispose(): void { } - $localShowMessage(severity: Severity, msg: string): void { - this._extensionService._logOrShowMessage(severity, msg); + $activateExtension(extensionId: ExtensionIdentifier, activationEvent: string): Promise { + return this._extensionService._activateById(extensionId, activationEvent); } $onWillActivateExtension(extensionId: ExtensionIdentifier): void { this._extensionService._onWillActivateExtension(extensionId); @@ -46,9 +62,66 @@ export class MainThreadExtensionService implements MainThreadExtensionServiceSha console.error(`[${extensionId}]${error.message}`); console.error(error.stack); } - $onExtensionActivationFailed(extensionId: ExtensionIdentifier): void { + async $onExtensionActivationError(extensionId: ExtensionIdentifier, activationError: ExtensionActivationError): Promise { + if (typeof activationError === 'string') { + this._extensionService._logOrShowMessage(Severity.Error, activationError); + } else { + this._handleMissingDependency(extensionId, activationError.dependency); + } } - $addMessage(extensionId: ExtensionIdentifier, severity: Severity, message: string): void { - this._extensionService._addMessage(extensionId, severity, message); + + private async _handleMissingDependency(extensionId: ExtensionIdentifier, missingDependency: string): Promise { + const extension = await this._extensionService.getExtension(extensionId.value); + if (extension) { + const local = await this._extensionsWorkbenchService.queryLocal(); + const installedDependency = local.filter(i => areSameExtensions(i.identifier, { id: missingDependency }))[0]; + if (installedDependency) { + await this._handleMissingInstalledDependency(extension, installedDependency); + } else { + await this._handleMissingNotInstalledDependency(extension, missingDependency); + } + } } + + private async _handleMissingInstalledDependency(extension: IExtensionDescription, missingInstalledDependency: IExtension): Promise { + const extName = extension.displayName || extension.name; + if (missingInstalledDependency.enablementState === EnablementState.Enabled || missingInstalledDependency.enablementState === EnablementState.WorkspaceEnabled) { + this._notificationService.notify({ + severity: Severity.Error, + message: localize('reload window', "Cannot activate extension '{0}' because it depends on extension '{1}', which is not loaded. Would you like to reload the window to load the extension?", extName, missingInstalledDependency.displayName), + actions: { + primary: [new Action('reload', localize('reload', "Reload Window"), '', true, () => this._windowService.reloadWindow())] + } + }); + } else { + this._notificationService.notify({ + severity: Severity.Error, + message: localize('disabledDep', "Cannot activate extension '{0}' because it depends on extension '{1}', which is disabled. Would you like to enable the extension and reload the window?", extName, missingInstalledDependency.displayName), + actions: { + primary: [new Action('enable', localize('enable dep', "Enable and Reload"), '', true, + () => this._extensionsWorkbenchService.setEnablement([missingInstalledDependency], missingInstalledDependency.enablementState === EnablementState.Disabled ? EnablementState.Enabled : EnablementState.WorkspaceEnabled) + .then(() => this._windowService.reloadWindow(), e => this._notificationService.error(e)))] + } + }); + } + } + + private async _handleMissingNotInstalledDependency(extension: IExtensionDescription, missingDependency: string): Promise { + const extName = extension.displayName || extension.name; + const dependencyExtension = (await this._extensionsWorkbenchService.queryGallery({ names: [missingDependency] })).firstPage[0]; + if (dependencyExtension) { + this._notificationService.notify({ + severity: Severity.Error, + message: localize('uninstalledDep', "Cannot activate extension '{0}' because it depends on extension '{1}', which is not installed. Would you like to install the extension and reload the window?", extName, dependencyExtension.displayName), + actions: { + primary: [new Action('install', localize('install missing dep', "Install and Reload"), '', true, + () => this._extensionsWorkbenchService.install(dependencyExtension) + .then(() => this._windowService.reloadWindow(), e => this._notificationService.error(e)))] + } + }); + } else { + this._notificationService.error(localize('unknownDep', "Cannot activate extension '{0}' because it depends on an unknown extension '{1}'.", extName, missingDependency)); + } + } + } diff --git a/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts index 6f37704b65..de0c7e7987 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts @@ -16,6 +16,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { private readonly _proxy: ExtHostFileSystemShape; private readonly _fileProvider = new Map(); + private readonly _resourceLabelFormatters = new Map(); constructor( extHostContext: IExtHostContext, @@ -39,12 +40,24 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { this._fileProvider.delete(handle); } - $setUriFormatter(formatter: ResourceLabelFormatter): void { - this._labelService.registerFormatter(formatter); + $registerResourceLabelFormatter(handle: number, formatter: ResourceLabelFormatter): void { + // Dynamicily registered formatters should have priority over those contributed via package.json + formatter.priority = true; + const disposable = this._labelService.registerFormatter(formatter); + this._resourceLabelFormatters.set(handle, disposable); + } + + $unregisterResourceLabelFormatter(handle: number): void { + dispose(this._resourceLabelFormatters.get(handle)); + this._resourceLabelFormatters.delete(handle); } $onFileSystemChange(handle: number, changes: IFileChangeDto[]): void { - this._fileProvider.get(handle).$onFileSystemChange(changes); + const fileProvider = this._fileProvider.get(handle); + if (!fileProvider) { + throw new Error('Unknown file provider'); + } + fileProvider.$onFileSystemChange(changes); } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/electron-browser/mainThreadFileSystemEventService.ts index ff75e3678c..0d2aaf3ba9 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadFileSystemEventService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadFileSystemEventService.ts @@ -52,12 +52,12 @@ export class MainThreadFileSystemEventService { // file operation events - (changes the editor makes) fileService.onAfterOperation(e => { if (e.operation === FileOperation.MOVE) { - proxy.$onFileRename(e.resource, e.target.resource); + proxy.$onFileRename(e.resource, e.target!.resource); } }, undefined, this._listener); textfileService.onWillMove(e => { - let promise = proxy.$onWillRename(e.oldResource, e.newResource); + const promise = proxy.$onWillRename(e.oldResource, e.newResource); e.waitUntil(promise); }, undefined, this._listener); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts b/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts index f09617d66c..3859f93dfd 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts @@ -9,8 +9,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; -import { isThenable } from 'vs/base/common/async'; -import { isNullOrUndefined } from 'util'; +import { GCSignal } from 'gc-signals'; export const IHeapService = createDecorator('heapService'); @@ -20,10 +19,9 @@ export interface IHeapService { readonly onGarbageCollection: Event; /** - * Track gc-collection for all new objects that - * have the $ident-value set. + * Track gc-collection for the given object */ - trackRecursive(obj: T | Promise): Promise; + trackObject(obj: ObjectIdentifier | undefined): void; } export class HeapService implements IHeapService { @@ -35,7 +33,10 @@ export class HeapService implements IHeapService { private _activeSignals = new WeakMap(); private _activeIds = new Set(); + private _consumeHandle: any; + private _ctor: { new(id: number): GCSignal }; + private _ctorInit: Promise; constructor() { // @@ -45,80 +46,57 @@ export class HeapService implements IHeapService { clearInterval(this._consumeHandle); } - trackRecursive(obj: T | Promise): Promise { - if (isThenable(obj)) { - return obj.then(result => this.trackRecursive(result)); + trackObject(obj: ObjectIdentifier | undefined | null): void { + if (!obj) { + return; + } + + const ident = obj.$ident; + if (typeof ident !== 'number') { + return; + } + + if (this._activeIds.has(ident)) { + return; + } + + if (this._ctor) { + // track and leave + this._activeIds.add(ident); + this._activeSignals.set(obj, new this._ctor(ident)); + } else { - return this._doTrackRecursive(obj); - } - } + // make sure to load gc-signals, then track and leave + if (!this._ctorInit) { + this._ctorInit = import('gc-signals').then(({ GCSignal, consumeSignals }) => { + this._ctor = GCSignal; + this._consumeHandle = setInterval(() => { + const ids = consumeSignals(); - private _doTrackRecursive(obj: any): Promise { - - if (isNullOrUndefined(obj)) { - return Promise.resolve(obj); - } - - return import('gc-signals').then(({ GCSignal, consumeSignals }) => { - - if (this._consumeHandle === undefined) { - // ensure that there is one consumer of signals - this._consumeHandle = setInterval(() => { - const ids = consumeSignals(); - - if (ids.length > 0) { - // local book-keeping - for (const id of ids) { - this._activeIds.delete(id); + if (ids.length > 0) { + // local book-keeping + for (const id of ids) { + this._activeIds.delete(id); + } + // fire event + this._onGarbageCollection.fire(ids); } - - // fire event - this._onGarbageCollection.fire(ids); - } - - }, 15 * 1000); + }, 15 * 1000); + }); } - const stack = [obj]; - while (stack.length > 0) { - - // remove first element - let obj = stack.shift(); - - if (!obj || typeof obj !== 'object') { - continue; - } - - for (let key in obj) { - if (!Object.prototype.hasOwnProperty.call(obj, key)) { - continue; - } - - const value = obj[key]; - // recurse -> object/array - if (typeof value === 'object') { - stack.push(value); - - } else if (key === ObjectIdentifier.name) { - // track new $ident-objects - - if (typeof value === 'number' && !this._activeIds.has(value)) { - this._activeIds.add(value); - this._activeSignals.set(obj, new GCSignal(value)); - } - } - } - } - - return obj; - }); + this._ctorInit.then(() => { + this._activeIds.add(ident); + this._activeSignals.set(obj, new this._ctor(ident)); + }); + } } } @extHostCustomer export class MainThreadHeapService { - private _toDispose: IDisposable; + private readonly _toDispose: IDisposable; constructor( extHostContext: IExtHostContext, diff --git a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts index d9f89eb06f..ed0a5d9932 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts @@ -7,11 +7,11 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { Emitter } from 'vs/base/common/event'; import { ITextModel, ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; -import * as search from 'vs/workbench/parts/search/common/search'; +import * as search from 'vs/workbench/contrib/search/common/search'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange } from 'vs/editor/common/core/range'; -import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto, ISerializedSignatureHelpProviderMetadata } from '../node/extHost.protocol'; +import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, WorkspaceSymbolDto, CodeActionDto, reviveWorkspaceEditDto, ISerializedDocumentFilter, DefinitionLinkDto, ISerializedSignatureHelpProviderMetadata, CodeInsetDto, LinkDto } from '../node/extHost.protocol'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; import { IHeapService } from './mainThreadHeapService'; @@ -20,14 +20,16 @@ import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostC import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters'; import { URI } from 'vs/base/common/uri'; import { Selection } from 'vs/editor/common/core/selection'; +import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { - private _proxy: ExtHostLanguageFeaturesShape; - private _heapService: IHeapService; - private _modeService: IModeService; - private _registrations: { [handle: number]: IDisposable; } = Object.create(null); + private readonly _proxy: ExtHostLanguageFeaturesShape; + private readonly _heapService: IHeapService; + private readonly _modeService: IModeService; + private readonly _registrations: { [handle: number]: IDisposable; } = Object.create(null); constructor( extHostContext: IExtHostContext, @@ -46,7 +48,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } $unregister(handle: number): void { - let registration = this._registrations[handle]; + const registration = this._registrations[handle]; if (registration) { registration.dispose(); delete this._registrations[handle]; @@ -85,9 +87,10 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto): search.IWorkspaceSymbol; private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto[]): search.IWorkspaceSymbol[]; - private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto | WorkspaceSymbolDto[]): search.IWorkspaceSymbol | search.IWorkspaceSymbol[] { + private static _reviveWorkspaceSymbolDto(data: undefined): undefined; + private static _reviveWorkspaceSymbolDto(data: WorkspaceSymbolDto | WorkspaceSymbolDto[] | undefined): search.IWorkspaceSymbol | search.IWorkspaceSymbol[] | undefined { if (!data) { - return data; + return data; } else if (Array.isArray(data)) { data.forEach(MainThreadLanguageFeatures._reviveWorkspaceSymbolDto); return data; @@ -97,13 +100,20 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } - private static _reviveCodeActionDto(data: CodeActionDto[]): modes.CodeAction[] { + private static _reviveCodeActionDto(data: CodeActionDto[] | undefined): modes.CodeAction[] { if (data) { data.forEach(code => reviveWorkspaceEditDto(code.edit)); } return data; } + private static _reviveLinkDTO(data: LinkDto): modes.ILink { + if (data.url && typeof data.url !== 'string') { + data.url = URI.revive(data.url); + } + return data; + } + //#endregion // --- outline @@ -111,7 +121,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerDocumentSymbolProvider(handle: number, selector: ISerializedDocumentFilter[], displayName: string): void { this._registrations[handle] = modes.DocumentSymbolProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { displayName, - provideDocumentSymbols: (model: ITextModel, token: CancellationToken): Promise => { + provideDocumentSymbols: (model: ITextModel, token: CancellationToken): Promise => { return this._proxy.$provideDocumentSymbols(handle, model.uri, token); } }); @@ -119,14 +129,28 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- code lens - $registerCodeLensSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number): void { + $registerCodeLensSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number | undefined): void { const provider = { provideCodeLenses: (model: ITextModel, token: CancellationToken): modes.ICodeLensSymbol[] | Promise => { - return this._heapService.trackRecursive(this._proxy.$provideCodeLenses(handle, model.uri, token)); + return this._proxy.$provideCodeLenses(handle, model.uri, token).then(dto => { + if (dto) { + dto.forEach(obj => { + this._heapService.trackObject(obj); + this._heapService.trackObject(obj.command); + }); + } + return dto; + }); }, - resolveCodeLens: (model: ITextModel, codeLens: modes.ICodeLensSymbol, token: CancellationToken): modes.ICodeLensSymbol | Promise => { - return this._heapService.trackRecursive(this._proxy.$resolveCodeLens(handle, model.uri, codeLens, token)); + resolveCodeLens: (model: ITextModel, codeLens: modes.ICodeLensSymbol, token: CancellationToken): Promise => { + return this._proxy.$resolveCodeLens(handle, model.uri, codeLens, token).then(obj => { + if (obj) { + this._heapService.trackObject(obj); + this._heapService.trackObject(obj.command); + } + return obj; + }); } }; @@ -146,6 +170,35 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } + // -- code inset + + $registerCodeInsetSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number): void { + + const provider = { + provideCodeInsets: (model: ITextModel, token: CancellationToken): CodeInsetDto[] | Thenable => { + return this._proxy.$provideCodeInsets(handle, model.uri, token).then(dto => { + if (dto) { dto.forEach(obj => this._heapService.trackObject(obj)); } + return dto; + }); + }, + resolveCodeInset: (model: ITextModel, codeInset: CodeInsetDto, token: CancellationToken): CodeInsetDto | Thenable => { + return this._proxy.$resolveCodeInset(handle, model.uri, codeInset, token).then(obj => { + this._heapService.trackObject(obj); + return obj; + }); + } + }; + + if (typeof eventHandle === 'number') { + const emitter = new Emitter(); + this._registrations[eventHandle] = emitter; + provider.onDidChange = emitter.event; + } + + const langSelector = typeConverters.LanguageSelector.from(selector); + this._registrations[handle] = codeInset.CodeInsetProviderRegistry.register(langSelector, provider); + } + // --- declaration $registerDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void { @@ -184,7 +237,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerHoverProvider(handle: number, selector: ISerializedDocumentFilter[]): void { this._registrations[handle] = modes.HoverProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { - provideHover: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => { + provideHover: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => { return this._proxy.$provideHover(handle, model.uri, position, token); } }); @@ -194,7 +247,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerDocumentHighlightProvider(handle: number, selector: ISerializedDocumentFilter[]): void { this._registrations[handle] = modes.DocumentHighlightProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { - provideDocumentHighlights: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => { + provideDocumentHighlights: (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => { return this._proxy.$provideDocumentHighlights(handle, model.uri, position, token); } }); @@ -215,7 +268,12 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerQuickFixSupport(handle: number, selector: ISerializedDocumentFilter[], providedCodeActionKinds?: string[]): void { this._registrations[handle] = modes.CodeActionProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { provideCodeActions: (model: ITextModel, rangeOrSelection: EditorRange | Selection, context: modes.CodeActionContext, token: CancellationToken): Promise => { - return this._heapService.trackRecursive(this._proxy.$provideCodeActions(handle, model.uri, rangeOrSelection, context, token)).then(MainThreadLanguageFeatures._reviveCodeActionDto); + return this._proxy.$provideCodeActions(handle, model.uri, rangeOrSelection, context, token).then(dto => { + if (dto) { + dto.forEach(obj => { this._heapService.trackObject(obj.command); }); + } + return MainThreadLanguageFeatures._reviveCodeActionDto(dto); + }); }, providedCodeActionKinds }); @@ -223,30 +281,29 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- formatting - $registerDocumentFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], displayName: string): void { + $registerDocumentFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: ExtensionIdentifier): void { this._registrations[handle] = modes.DocumentFormattingEditProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { - displayName, - provideDocumentFormattingEdits: (model: ITextModel, options: modes.FormattingOptions, token: CancellationToken): Promise => { + extensionId, + provideDocumentFormattingEdits: (model: ITextModel, options: modes.FormattingOptions, token: CancellationToken): Promise => { return this._proxy.$provideDocumentFormattingEdits(handle, model.uri, options, token); } }); } - $registerRangeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], displayName: string): void { + $registerRangeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: ExtensionIdentifier): void { this._registrations[handle] = modes.DocumentRangeFormattingEditProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { - displayName, - provideDocumentRangeFormattingEdits: (model: ITextModel, range: EditorRange, options: modes.FormattingOptions, token: CancellationToken): Promise => { + extensionId, + provideDocumentRangeFormattingEdits: (model: ITextModel, range: EditorRange, options: modes.FormattingOptions, token: CancellationToken): Promise => { return this._proxy.$provideDocumentRangeFormattingEdits(handle, model.uri, range, options, token); } }); } - $registerOnTypeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], autoFormatTriggerCharacters: string[]): void { + $registerOnTypeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void { this._registrations[handle] = modes.OnTypeFormattingEditProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { - + extensionId, autoFormatTriggerCharacters, - - provideOnTypeFormattingEdits: (model: ITextModel, position: EditorPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Promise => { + provideOnTypeFormattingEdits: (model: ITextModel, position: EditorPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Promise => { return this._proxy.$provideOnTypeFormattingEdits(handle, model.uri, position, ch, options, token); } }); @@ -255,7 +312,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- navigate type $registerNavigateTypeSupport(handle: number): void { - let lastResultId: number; + let lastResultId: number | undefined; this._registrations[handle] = search.WorkspaceSymbolProviderRegistry.register({ provideWorkspaceSymbols: (search: string, token: CancellationToken): Promise => { return this._proxy.$provideWorkspaceSymbols(handle, search, token).then(result => { @@ -266,8 +323,13 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return MainThreadLanguageFeatures._reviveWorkspaceSymbolDto(result.symbols); }); }, - resolveWorkspaceSymbol: (item: search.IWorkspaceSymbol, token: CancellationToken): Promise => { - return this._proxy.$resolveWorkspaceSymbol(handle, item, token).then(i => MainThreadLanguageFeatures._reviveWorkspaceSymbolDto(i)); + resolveWorkspaceSymbol: (item: search.IWorkspaceSymbol, token: CancellationToken): Promise => { + return this._proxy.$resolveWorkspaceSymbol(handle, item, token).then(i => { + if (i) { + return MainThreadLanguageFeatures._reviveWorkspaceSymbolDto(i); + } + return undefined; + }); } }); } @@ -281,7 +343,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return this._proxy.$provideRenameEdits(handle, model.uri, position, newName, token).then(reviveWorkspaceEditDto); }, resolveRenameLocation: supportResolveLocation - ? (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => this._proxy.$resolveRenameLocation(handle, model.uri, position, token) + ? (model: ITextModel, position: EditorPosition, token: CancellationToken): Promise => this._proxy.$resolveRenameLocation(handle, model.uri, position, token) : undefined }); } @@ -291,7 +353,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerSuggestSupport(handle: number, selector: ISerializedDocumentFilter[], triggerCharacters: string[], supportsResolveDetails: boolean): void { this._registrations[handle] = modes.CompletionProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { triggerCharacters, - provideCompletionItems: (model: ITextModel, position: EditorPosition, context: modes.CompletionContext, token: CancellationToken): Promise => { + provideCompletionItems: (model: ITextModel, position: EditorPosition, context: modes.CompletionContext, token: CancellationToken): Promise => { return this._proxy.$provideCompletionItems(handle, model.uri, position, context, token).then(result => { if (!result) { return result; @@ -299,7 +361,11 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return { suggestions: result.suggestions, incomplete: result.incomplete, - dispose: () => this._proxy.$releaseCompletionItems(handle, result._id) + dispose: () => { + if (typeof result._id === 'number') { + this._proxy.$releaseCompletionItems(handle, result._id); + } + } }; }); }, @@ -317,7 +383,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha signatureHelpTriggerCharacters: metadata.triggerCharacters, signatureHelpRetriggerCharacters: metadata.retriggerCharacters, - provideSignatureHelp: (model: ITextModel, position: EditorPosition, token: CancellationToken, context: modes.SignatureHelpContext): Promise => { + provideSignatureHelp: (model: ITextModel, position: EditorPosition, token: CancellationToken, context: modes.SignatureHelpContext): Promise => { return this._proxy.$provideSignatureHelp(handle, model.uri, position, context, token); } }); @@ -328,10 +394,24 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerDocumentLinkProvider(handle: number, selector: ISerializedDocumentFilter[]): void { this._registrations[handle] = modes.LinkProviderRegistry.register(typeConverters.LanguageSelector.from(selector), { provideLinks: (model, token) => { - return this._heapService.trackRecursive(this._proxy.$provideDocumentLinks(handle, model.uri, token)); + return this._proxy.$provideDocumentLinks(handle, model.uri, token).then(dto => { + if (dto) { + dto.forEach(obj => { + MainThreadLanguageFeatures._reviveLinkDTO(obj); + this._heapService.trackObject(obj); + }); + } + return dto; + }); }, resolveLink: (link, token) => { - return this._proxy.$resolveDocumentLink(handle, link, token); + return this._proxy.$resolveDocumentLink(handle, link, token).then(obj => { + if (obj) { + MainThreadLanguageFeatures._reviveLinkDTO(obj); + this._heapService.trackObject(obj); + } + return obj; + }); } }); } @@ -385,8 +465,8 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha $registerSelectionRangeProvider(handle: number, selector: ISerializedDocumentFilter[]): void { this._registrations[handle] = modes.SelectionRangeRegistry.register(typeConverters.LanguageSelector.from(selector), { - provideSelectionRanges: (model, position, token) => { - return this._proxy.$provideSelectionRanges(handle, model.uri, position, token); + provideSelectionRanges: (model, positions, token) => { + return this._proxy.$provideSelectionRanges(handle, model.uri, positions, token); } }); } @@ -394,61 +474,43 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha // --- configuration private static _reviveRegExp(regExp: ISerializedRegExp): RegExp { - if (typeof regExp === 'undefined') { - return undefined; - } - if (regExp === null) { - return null; - } return new RegExp(regExp.pattern, regExp.flags); } private static _reviveIndentationRule(indentationRule: ISerializedIndentationRule): IndentationRule { - if (typeof indentationRule === 'undefined') { - return undefined; - } - if (indentationRule === null) { - return null; - } return { decreaseIndentPattern: MainThreadLanguageFeatures._reviveRegExp(indentationRule.decreaseIndentPattern), increaseIndentPattern: MainThreadLanguageFeatures._reviveRegExp(indentationRule.increaseIndentPattern), - indentNextLinePattern: MainThreadLanguageFeatures._reviveRegExp(indentationRule.indentNextLinePattern), - unIndentedLinePattern: MainThreadLanguageFeatures._reviveRegExp(indentationRule.unIndentedLinePattern), + indentNextLinePattern: indentationRule.indentNextLinePattern ? MainThreadLanguageFeatures._reviveRegExp(indentationRule.indentNextLinePattern) : undefined, + unIndentedLinePattern: indentationRule.unIndentedLinePattern ? MainThreadLanguageFeatures._reviveRegExp(indentationRule.unIndentedLinePattern) : undefined, }; } private static _reviveOnEnterRule(onEnterRule: ISerializedOnEnterRule): OnEnterRule { return { beforeText: MainThreadLanguageFeatures._reviveRegExp(onEnterRule.beforeText), - afterText: MainThreadLanguageFeatures._reviveRegExp(onEnterRule.afterText), - oneLineAboveText: MainThreadLanguageFeatures._reviveRegExp(onEnterRule.oneLineAboveText), + afterText: onEnterRule.afterText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.afterText) : undefined, + oneLineAboveText: onEnterRule.oneLineAboveText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.oneLineAboveText) : undefined, action: onEnterRule.action }; } private static _reviveOnEnterRules(onEnterRules: ISerializedOnEnterRule[]): OnEnterRule[] { - if (typeof onEnterRules === 'undefined') { - return undefined; - } - if (onEnterRules === null) { - return null; - } return onEnterRules.map(MainThreadLanguageFeatures._reviveOnEnterRule); } $setLanguageConfiguration(handle: number, languageId: string, _configuration: ISerializedLanguageConfiguration): void { - let configuration: LanguageConfiguration = { + const configuration: LanguageConfiguration = { comments: _configuration.comments, brackets: _configuration.brackets, - wordPattern: MainThreadLanguageFeatures._reviveRegExp(_configuration.wordPattern), - indentationRules: MainThreadLanguageFeatures._reviveIndentationRule(_configuration.indentationRules), - onEnterRules: MainThreadLanguageFeatures._reviveOnEnterRules(_configuration.onEnterRules), + wordPattern: _configuration.wordPattern ? MainThreadLanguageFeatures._reviveRegExp(_configuration.wordPattern) : undefined, + indentationRules: _configuration.indentationRules ? MainThreadLanguageFeatures._reviveIndentationRule(_configuration.indentationRules) : undefined, + onEnterRules: _configuration.onEnterRules ? MainThreadLanguageFeatures._reviveOnEnterRules(_configuration.onEnterRules) : undefined, - autoClosingPairs: null, - surroundingPairs: null, - __electricCharacterSupport: null + autoClosingPairs: undefined, + surroundingPairs: undefined, + __electricCharacterSupport: undefined }; if (_configuration.__characterPairSupport) { @@ -465,7 +527,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha }; } - let languageIdentifier = this._modeService.getLanguageIdentifier(languageId); + const languageIdentifier = this._modeService.getLanguageIdentifier(languageId); if (languageIdentifier) { this._registrations[handle] = LanguageConfigurationRegistry.register(languageIdentifier, configuration); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadLanguages.ts b/src/vs/workbench/api/electron-browser/mainThreadLanguages.ts index c98bee2224..97093b54e0 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguages.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadLanguages.ts @@ -29,7 +29,7 @@ export class MainThreadLanguages implements MainThreadLanguagesShape { $changeLanguage(resource: UriComponents, languageId: string): Promise { const uri = URI.revive(resource); - let model = this._modelService.getModel(uri); + const model = this._modelService.getModel(uri); if (!model) { return Promise.reject(new Error('Invalid uri')); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadMessageService.ts b/src/vs/workbench/api/electron-browser/mainThreadMessageService.ts index 3db3891f75..8ba915f32d 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadMessageService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadMessageService.ts @@ -8,13 +8,12 @@ import Severity from 'vs/base/common/severity'; import { Action, IAction } from 'vs/base/common/actions'; import { MainThreadMessageServiceShape, MainContext, IExtHostContext, MainThreadMessageOptions } from '../node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Event } from 'vs/base/common/event'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { dispose } from 'vs/base/common/lifecycle'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @extHostNamedCustomer(MainContext.MainThreadMessageService) export class MainThreadMessageService implements MainThreadMessageServiceShape { @@ -32,7 +31,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape { // } - $showMessage(severity: Severity, message: string, options: MainThreadMessageOptions, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise { + $showMessage(severity: Severity, message: string, options: MainThreadMessageOptions, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise { if (options.modal) { return this._showModalMessage(severity, message, commands); } else { @@ -40,17 +39,17 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape { } } - private _showMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[], extension: IExtensionDescription): Promise { + private _showMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[], extension: IExtensionDescription | undefined): Promise { return new Promise(resolve => { - let primaryActions: MessageItemAction[] = []; + const primaryActions: MessageItemAction[] = []; class MessageItemAction extends Action { constructor(id: string, label: string, handle: number) { super(id, label, undefined, true, () => { resolve(handle); - return undefined; + return Promise.resolve(); }); } } @@ -67,7 +66,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape { primaryActions.push(new MessageItemAction('_extension_message_handle_' + command.handle, command.title, command.handle)); }); - let source: string; + let source: string | undefined; if (extension) { source = nls.localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name); } @@ -97,7 +96,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape { }); } - private _showModalMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise { + private _showModalMessage(severity: Severity, message: string, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise { let cancelId: number | undefined = undefined; const buttons = commands.map((command, index) => { diff --git a/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts b/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts index 8cef823350..5c893eb8a4 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Registry } from 'vs/platform/registry/common/platform'; -import { IOutputService, IOutputChannel, OUTPUT_PANEL_ID, Extensions, IOutputChannelRegistry } from 'vs/workbench/parts/output/common/output'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IOutputService, IOutputChannel, OUTPUT_PANEL_ID, Extensions, IOutputChannelRegistry } from 'vs/workbench/contrib/output/common/output'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { MainThreadOutputServiceShape, MainContext, IExtHostContext, ExtHostOutputServiceShape, ExtHostContext } from '../node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; @@ -18,27 +18,27 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut private static _idPool = 1; - private _proxy: ExtHostOutputServiceShape; + private readonly _proxy: ExtHostOutputServiceShape; private readonly _outputService: IOutputService; - private readonly _partService: IPartService; + private readonly _layoutService: IWorkbenchLayoutService; private readonly _panelService: IPanelService; constructor( extHostContext: IExtHostContext, @IOutputService outputService: IOutputService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IPanelService panelService: IPanelService ) { super(); this._outputService = outputService; - this._partService = partService; + this._layoutService = layoutService; this._panelService = panelService; this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostOutputService); const setVisibleChannel = () => { const panel = this._panelService.getActivePanel(); - const visibleChannel: IOutputChannel = panel && panel.getId() === OUTPUT_PANEL_ID ? this._outputService.getActiveChannel() : null; + const visibleChannel: IOutputChannel | null = panel && panel.getId() === OUTPUT_PANEL_ID ? this._outputService.getActiveChannel() : null; this._proxy.$setVisibleChannel(visibleChannel ? visibleChannel.id : null); }; this._register(Event.any(this._outputService.onActiveOutputChannel, this._panelService.onDidPanelOpen, this._panelService.onDidPanelClose)(() => setVisibleChannel())); @@ -47,12 +47,12 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut public $register(label: string, log: boolean, file?: UriComponents): Promise { const id = 'extension-output-#' + (MainThreadOutputService._idPool++); - Registry.as(Extensions.OutputChannels).registerChannel({ id, label, file: file ? URI.revive(file) : null, log }); + Registry.as(Extensions.OutputChannels).registerChannel({ id, label, file: file ? URI.revive(file) : undefined, log }); this._register(toDisposable(() => this.$dispose(id))); return Promise.resolve(id); } - public $append(channelId: string, value: string): Promise { + public $append(channelId: string, value: string): Promise | undefined { const channel = this._getChannel(channelId); if (channel) { channel.append(value); @@ -60,7 +60,7 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut return undefined; } - public $update(channelId: string): Promise { + public $update(channelId: string): Promise | undefined { const channel = this._getChannel(channelId); if (channel) { channel.update(); @@ -68,7 +68,7 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut return undefined; } - public $clear(channelId: string, till: number): Promise { + public $clear(channelId: string, till: number): Promise | undefined { const channel = this._getChannel(channelId); if (channel) { channel.clear(till); @@ -76,7 +76,7 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut return undefined; } - public $reveal(channelId: string, preserveFocus: boolean): Promise { + public $reveal(channelId: string, preserveFocus: boolean): Promise | undefined { const channel = this._getChannel(channelId); if (channel) { this._outputService.showChannel(channel.id, preserveFocus); @@ -84,16 +84,19 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut return undefined; } - public $close(channelId: string): Promise { + public $close(channelId: string): Promise | undefined { const panel = this._panelService.getActivePanel(); - if (panel && panel.getId() === OUTPUT_PANEL_ID && channelId === this._outputService.getActiveChannel().id) { - this._partService.setPanelHidden(true); + if (panel && panel.getId() === OUTPUT_PANEL_ID) { + const activeChannel = this._outputService.getActiveChannel(); + if (activeChannel && channelId === activeChannel.id) { + this._layoutService.setPanelHidden(true); + } } return undefined; } - public $dispose(channelId: string): Promise { + public $dispose(channelId: string): Promise | undefined { const channel = this._getChannel(channelId); if (channel) { channel.dispose(); @@ -101,7 +104,7 @@ export class MainThreadOutputService extends Disposable implements MainThreadOut return undefined; } - private _getChannel(channelId: string): IOutputChannel { + private _getChannel(channelId: string): IOutputChannel | null { return this._outputService.getChannel(channelId); } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadProgress.ts b/src/vs/workbench/api/electron-browser/mainThreadProgress.ts index c0665b16f1..71f87e89c8 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadProgress.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadProgress.ts @@ -10,9 +10,9 @@ import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostC @extHostNamedCustomer(MainContext.MainThreadProgress) export class MainThreadProgress implements MainThreadProgressShape { - private _progressService: IProgressService2; + private readonly _progressService: IProgressService2; private _progress = new Map void, progress: IProgress }>(); - private _proxy: ExtHostProgressShape; + private readonly _proxy: ExtHostProgressShape; constructor( extHostContext: IExtHostContext, @@ -34,14 +34,16 @@ export class MainThreadProgress implements MainThreadProgressShape { } $progressReport(handle: number, message: IProgressStep): void { - if (this._progress.has(handle)) { - this._progress.get(handle).progress.report(message); + const entry = this._progress.get(handle); + if (entry) { + entry.progress.report(message); } } $progressEnd(handle: number): void { - if (this._progress.has(handle)) { - this._progress.get(handle).resolve(); + const entry = this._progress.get(handle); + if (entry) { + entry.resolve(); this._progress.delete(handle); } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts b/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts index 13bfb113bb..21f89269d4 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts @@ -18,9 +18,9 @@ interface QuickInputSession { @extHostNamedCustomer(MainContext.MainThreadQuickOpen) export class MainThreadQuickOpen implements MainThreadQuickOpenShape { - private _proxy: ExtHostQuickOpenShape; - private _quickInputService: IQuickInputService; - private _items: Record = {}; @@ -36,7 +36,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { public dispose(): void { } - $show(instance: number, options: IPickOptions, token: CancellationToken): Promise { + $show(instance: number, options: IPickOptions, token: CancellationToken): Promise { const contents = new Promise((resolve, reject) => { this._items[instance] = { resolve, reject }; }); @@ -72,7 +72,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { this._items[instance].resolve(items); delete this._items[instance]; } - return undefined; + return Promise.resolve(); } $setError(instance: number, error: Error): Promise { @@ -80,12 +80,12 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { this._items[instance].reject(error); delete this._items[instance]; } - return undefined; + return Promise.resolve(); } // ---- input - $input(options: InputBoxOptions, validateInput: boolean, token: CancellationToken): Promise { + $input(options: InputBoxOptions | undefined, validateInput: boolean, token: CancellationToken): Promise { const inputOptions: IInputOptions = Object.create(null); if (options) { @@ -181,13 +181,13 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { .filter(handle => handlesToItems.has(handle)) .map(handle => handlesToItems.get(handle)); } else if (param === 'buttons') { - input[param] = params.buttons.map(button => { + input[param] = params.buttons!.map(button => { if (button.handle === -1) { return this._quickInputService.backButton; } const { iconPath, tooltip, handle } = button; return { - iconPath: { + iconPath: iconPath && { dark: URI.revive(iconPath.dark), light: iconPath.light && URI.revive(iconPath.light) }, diff --git a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts index 61848cd0ac..f08c916a91 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts @@ -7,7 +7,7 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation } from 'vs/workbench/services/scm/common/scm'; +import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation } from 'vs/workbench/contrib/scm/common/scm'; import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, IExtHostContext } from '../node/extHost.protocol'; import { Command } from 'vs/editor/common/modes'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; @@ -21,14 +21,14 @@ class MainThreadSCMResourceGroup implements ISCMResourceGroup { private _onDidSplice = new Emitter>(); readonly onDidSplice = this._onDidSplice.event; - get hideWhenEmpty(): boolean { return this.features.hideWhenEmpty; } + get hideWhenEmpty(): boolean { return !!this.features.hideWhenEmpty; } private _onDidChange = new Emitter(); get onDidChange(): Event { return this._onDidChange.event; } constructor( - private sourceControlHandle: number, - private handle: number, + private readonly sourceControlHandle: number, + private readonly handle: number, public provider: ISCMProvider, public features: SCMGroupFeatures, public label: string, @@ -62,10 +62,10 @@ class MainThreadSCMResourceGroup implements ISCMResourceGroup { class MainThreadSCMResource implements ISCMResource { constructor( - private proxy: ExtHostSCMShape, - private sourceControlHandle: number, - private groupHandle: number, - private handle: number, + private readonly proxy: ExtHostSCMShape, + private readonly sourceControlHandle: number, + private readonly groupHandle: number, + private readonly handle: number, public sourceUri: URI, public resourceGroup: ISCMResourceGroup, public decorations: ISCMResourceDecorations @@ -92,7 +92,7 @@ class MainThreadSCMProvider implements ISCMProvider { get id(): string { return this._id; } readonly groups = new Sequence(); - private _groupsByHandle: { [handle: number]: MainThreadSCMResourceGroup; } = Object.create(null); + private readonly _groupsByHandle: { [handle: number]: MainThreadSCMResourceGroup; } = Object.create(null); // get groups(): ISequence { // return { @@ -129,11 +129,11 @@ class MainThreadSCMProvider implements ISCMProvider { get onDidChange(): Event { return this._onDidChange.event; } constructor( - private proxy: ExtHostSCMShape, - private _handle: number, - private _contextValue: string, - private _label: string, - private _rootUri: URI | undefined, + private readonly proxy: ExtHostSCMShape, + private readonly _handle: number, + private readonly _contextValue: string, + private readonly _label: string, + private readonly _rootUri: URI | undefined, @ISCMService scmService: ISCMService ) { } @@ -142,11 +142,11 @@ class MainThreadSCMProvider implements ISCMProvider { this._onDidChange.fire(); if (typeof features.commitTemplate !== 'undefined') { - this._onDidChangeCommitTemplate.fire(this.commitTemplate); + this._onDidChangeCommitTemplate.fire(this.commitTemplate!); } if (typeof features.statusBarCommands !== 'undefined') { - this._onDidChangeStatusBarCommands.fire(this.statusBarCommands); + this._onDidChangeStatusBarCommands.fire(this.statusBarCommands!); } } @@ -202,14 +202,14 @@ class MainThreadSCMProvider implements ISCMProvider { const icon = icons[0]; const iconDark = icons[1] || icon; const decorations = { - icon: icon && URI.parse(icon), - iconDark: iconDark && URI.parse(iconDark), + icon: icon ? URI.parse(icon) : undefined, + iconDark: iconDark ? URI.parse(iconDark) : undefined, tooltip, strikeThrough, faded, source, letter, - color: color && color.id + color: color ? color.id : undefined }; return new MainThreadSCMResource( @@ -241,7 +241,7 @@ class MainThreadSCMProvider implements ISCMProvider { this.groups.splice(this.groups.elements.indexOf(group), 1); } - async getOriginalResource(uri: URI): Promise { + async getOriginalResource(uri: URI): Promise { if (!this.features.hasQuickDiffProvider) { return null; } @@ -265,7 +265,7 @@ class MainThreadSCMProvider implements ISCMProvider { @extHostNamedCustomer(MainContext.MainThreadSCM) export class MainThreadSCM implements MainThreadSCMShape { - private _proxy: ExtHostSCMShape; + private readonly _proxy: ExtHostSCMShape; private _repositories: { [handle: number]: ISCMRepository; } = Object.create(null); private _inputDisposables: { [handle: number]: IDisposable; } = Object.create(null); private _disposables: IDisposable[] = []; diff --git a/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts index 57f995267a..ce70c31b3a 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts @@ -7,7 +7,7 @@ import { isNonEmptyArray } from 'vs/base/common/arrays'; import { IdleValue, sequence } from 'vs/base/common/async'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import * as strings from 'vs/base/common/strings'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; @@ -16,14 +16,14 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import { IIdentifiedSingleEditOperation, ISingleEditOperation, ITextModel } from 'vs/editor/common/model'; -import { CodeAction } from 'vs/editor/common/modes'; +import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; +import { CodeAction, TextEdit } from 'vs/editor/common/modes'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { shouldSynchronizeModel } from 'vs/editor/common/services/modelService'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; -import { getDocumentFormattingEdits, NoProviderError } from 'vs/editor/contrib/format/format'; +import { getDocumentFormattingEdits, FormatMode } from 'vs/editor/contrib/format/format'; import { FormattingEdit } from 'vs/editor/contrib/format/formattingEdit'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { localize } from 'vs/nls'; @@ -34,8 +34,10 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ISaveParticipant, ITextFileEditorModel, SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +// {{SQL CARBON EDIT}} +import { ISaveParticipant, SaveReason, IResolvedTextFileEditorModel, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../node/extHost.protocol'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; // {{SQL CARBON EDIT}} import { INotebookService } from 'sql/workbench/services/notebook/common/notebookService'; @@ -78,7 +80,7 @@ class TrimWhitespaceParticipant implements ISaveParticipantParticipant { // Nothing } - async participate(model: ITextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise { if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO); } @@ -88,7 +90,7 @@ class TrimWhitespaceParticipant implements ISaveParticipantParticipant { let prevSelection: Selection[] = []; let cursors: Position[] = []; - let editor = findEditor(model, this.codeEditorService); + const editor = findEditor(model, this.codeEditorService); if (editor) { // Find `prevSelection` in any case do ensure a good undo stack when pushing the edit // Collect active cursors in `cursors` only if `isAutoSaved` to avoid having the cursors jump @@ -113,12 +115,12 @@ class TrimWhitespaceParticipant implements ISaveParticipantParticipant { } } -function findEditor(model: ITextModel, codeEditorService: ICodeEditorService): ICodeEditor { - let candidate: ICodeEditor | null = null; +function findEditor(model: ITextModel, codeEditorService: ICodeEditorService): IActiveCodeEditor | null { + let candidate: IActiveCodeEditor | null = null; if (model.isAttachedToEditor()) { for (const editor of codeEditorService.listCodeEditors()) { - if (editor.getModel() === model) { + if (editor.hasModel() && editor.getModel() === model) { if (editor.hasTextFocus()) { return editor; // favour focused editor if there are multiple } @@ -140,7 +142,7 @@ export class FinalNewLineParticipant implements ISaveParticipantParticipant { // Nothing } - async participate(model: ITextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise { if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { this.doInsertFinalNewLine(model.textEditorModel); } @@ -178,7 +180,7 @@ export class TrimFinalNewLinesParticipant implements ISaveParticipantParticipant // Nothing } - async participate(model: ITextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise { if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) { this.doTrimFinalNewLines(model.textEditorModel, env.reason === SaveReason.AUTO); } @@ -243,12 +245,13 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { constructor( @ICodeEditorService private readonly _editorService: ICodeEditorService, @IEditorWorkerService private readonly _editorWorkerService: IEditorWorkerService, - @IConfigurationService private readonly _configurationService: IConfigurationService + @IConfigurationService private readonly _configurationService: IConfigurationService, + @ITelemetryService private readonly _telemetryService: ITelemetryService, ) { // Nothing } - async participate(editorModel: ITextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise { const model = editorModel.textEditorModel; if (env.reason === SaveReason.AUTO @@ -257,26 +260,19 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { } const versionNow = model.getVersionId(); - const { tabSize, insertSpaces } = model.getOptions(); const timeout = this._configurationService.getValue('editor.formatOnSaveTimeout', { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() }); - return new Promise((resolve, reject) => { - let source = new CancellationTokenSource(); - let request = getDocumentFormattingEdits(model, { tabSize, insertSpaces }, source.token); + return new Promise((resolve, reject) => { + const source = new CancellationTokenSource(); + const request = getDocumentFormattingEdits(this._telemetryService, this._editorWorkerService, model, model.getFormattingOptions(), FormatMode.Auto, source.token); setTimeout(() => { reject(localize('timeout.formatOnSave', "Aborted format on save after {0}ms", timeout)); source.cancel(); }, timeout); - request.then(edits => this._editorWorkerService.computeMoreMinimalEdits(model.uri, edits)).then(resolve, err => { - if (!NoProviderError.is(err)) { - reject(err); - } else { - resolve(); - } - }); + request.then(resolve, reject); }).then(edits => { if (isNonEmptyArray(edits) && versionNow === model.getVersionId()) { @@ -290,11 +286,11 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { }); } - private _editsWithEditor(editor: ICodeEditor, edits: ISingleEditOperation[]): void { + private _editsWithEditor(editor: ICodeEditor, edits: TextEdit[]): void { FormattingEdit.execute(editor, edits); } - private _editWithModel(model: ITextModel, edits: ISingleEditOperation[]): void { + private _editWithModel(model: ITextModel, edits: TextEdit[]): void { const [{ range }] = edits; const initialSelection = new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn); @@ -305,11 +301,11 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { return [new Selection(range.startLineNumber, range.startColumn, range.endLineNumber, range.endColumn)]; } } - return undefined; + return null; }); } - private static _asIdentEdit({ text, range }: ISingleEditOperation): IIdentifiedSingleEditOperation { + private static _asIdentEdit({ text, range }: TextEdit): IIdentifiedSingleEditOperation { return { text, range: Range.lift(range), @@ -326,7 +322,7 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { @IConfigurationService private readonly _configurationService: IConfigurationService ) { } - async participate(editorModel: ITextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise { if (env.reason === SaveReason.AUTO) { return undefined; } @@ -377,14 +373,14 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { for (const codeActionKind of codeActionsOnSave) { const actionsToRun = await this.getActionsToRun(model, codeActionKind, token); try { - await this.applyCodeActions(actionsToRun); + await this.applyCodeActions(actionsToRun.actions); } catch { // Failure to apply a code action should not block other on save actions } } } - private async applyCodeActions(actionsToRun: CodeAction[]) { + private async applyCodeActions(actionsToRun: ReadonlyArray) { for (const action of actionsToRun) { await applyCodeAction(action, this._bulkEditService, this._commandService); } @@ -400,13 +396,13 @@ class CodeActionOnSaveParticipant implements ISaveParticipant { class ExtHostSaveParticipant implements ISaveParticipantParticipant { - private _proxy: ExtHostDocumentSaveParticipantShape; + private readonly _proxy: ExtHostDocumentSaveParticipantShape; constructor(extHostContext: IExtHostContext) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentSaveParticipant); } - async participate(editorModel: ITextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(editorModel: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise { if (!shouldSynchronizeModel(editorModel.textEditorModel)) { // the model never made it to the extension @@ -455,11 +451,11 @@ export class SaveParticipant implements ISaveParticipant { } dispose(): void { - TextFileEditorModel.setSaveParticipant(undefined); + TextFileEditorModel.setSaveParticipant(null); this._saveParticipants.dispose(); } - async participate(model: ITextFileEditorModel, env: { reason: SaveReason }): Promise { + async participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise { return this._progressService.withProgress({ location: ProgressLocation.Window }, progress => { progress.report({ message: localize('saveParticipants', "Running Save Participants...") }); const promiseFactory = this._saveParticipants.getValue().map(p => () => { diff --git a/src/vs/workbench/api/electron-browser/mainThreadSearch.ts b/src/vs/workbench/api/electron-browser/mainThreadSearch.ts index 42eb29362c..28a5943674 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSearch.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSearch.ts @@ -7,7 +7,7 @@ import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IFileMatch, IRawFileMatch2, ISearchComplete, ISearchCompleteStats, ISearchProgressItem, ISearchResultProvider, ISearchService, QueryType, SearchProviderType, ITextQuery, IFileQuery } from 'vs/platform/search/common/search'; +import { IFileMatch, IRawFileMatch2, ISearchComplete, ISearchCompleteStats, ISearchProgressItem, ISearchResultProvider, ISearchService, QueryType, SearchProviderType, ITextQuery, IFileQuery } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { ExtHostContext, ExtHostSearchShape, IExtHostContext, MainContext, MainThreadSearchShape } from '../node/extHost.protocol'; @@ -40,21 +40,27 @@ export class MainThreadSearch implements MainThreadSearchShape { this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.file, scheme, handle, this._proxy)); } - $registerFileIndexProvider(handle: number, scheme: string): void { - this._searchProvider.set(handle, new RemoteSearchProvider(this._searchService, SearchProviderType.fileIndex, scheme, handle, this._proxy)); - } - $unregisterProvider(handle: number): void { dispose(this._searchProvider.get(handle)); this._searchProvider.delete(handle); } $handleFileMatch(handle: number, session, data: UriComponents[]): void { - this._searchProvider.get(handle).handleFindMatch(session, data); + const provider = this._searchProvider.get(handle); + if (!provider) { + throw new Error('Got result for unknown provider'); + } + + provider.handleFindMatch(session, data); } $handleTextMatch(handle: number, session, data: IRawFileMatch2[]): void { - this._searchProvider.get(handle).handleFindMatch(session, data); + const provider = this._searchProvider.get(handle); + if (!provider) { + throw new Error('Got result for unknown provider'); + } + + provider.handleFindMatch(session, data); } $handleTelemetry(eventName: string, data: any): void { @@ -77,7 +83,8 @@ class SearchOperation { addMatch(match: IFileMatch): void { if (this.matches.has(match.resource.toString())) { // Merge with previous IFileMatches - this.matches.get(match.resource.toString()).results.push(...match.results); + // TODO@rob clean up text/file result types + this.matches.get(match.resource.toString())!.results!.push(...match.results!); } else { this.matches.set(match.resource.toString(), match); } @@ -107,15 +114,15 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable { dispose(this._registrations); } - fileSearch(query: IFileQuery, token: CancellationToken = CancellationToken.None): Promise { - return this.doSearch(query, null, token); + fileSearch(query: IFileQuery, token: CancellationToken = CancellationToken.None): Promise { + return this.doSearch(query, undefined, token); } - textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token: CancellationToken = CancellationToken.None): Promise { + textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token: CancellationToken = CancellationToken.None): Promise { return this.doSearch(query, onProgress, token); } - doSearch(query: ITextQuery | IFileQuery, onProgress?: (p: ISearchProgressItem) => void, token: CancellationToken = CancellationToken.None): Promise { + doSearch(query: ITextQuery | IFileQuery, onProgress?: (p: ISearchProgressItem) => void, token: CancellationToken = CancellationToken.None): Promise { if (isFalsyOrEmpty(query.folderQueries)) { return Promise.resolve(undefined); } @@ -141,12 +148,13 @@ class RemoteSearchProvider implements ISearchResultProvider, IDisposable { } handleFindMatch(session: number, dataOrUri: Array): void { - if (!this._searches.has(session)) { + const searchOp = this._searches.get(session); + + if (!searchOp) { // ignore... return; } - const searchOp = this._searches.get(session); dataOrUri.forEach(result => { if ((result).results) { searchOp.addMatch({ diff --git a/src/vs/workbench/api/electron-browser/mainThreadStatusBar.ts b/src/vs/workbench/api/electron-browser/mainThreadStatusBar.ts index 9a82daa142..e0141b068c 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadStatusBar.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadStatusBar.ts @@ -34,12 +34,12 @@ export class MainThreadStatusBar implements MainThreadStatusBarShape { this.$dispose(id); // Add new - let entry = this._statusbarService.addEntry({ text, tooltip, command, color, extensionId }, alignment, priority); + const entry = this._statusbarService.addEntry({ text, tooltip, command, color, extensionId }, alignment, priority); this._entries[id] = entry; } $dispose(id: number) { - let disposeable = this._entries[id]; + const disposeable = this._entries[id]; if (disposeable) { disposeable.dispose(); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadStorage.ts b/src/vs/workbench/api/electron-browser/mainThreadStorage.ts index 1e510fc4a7..afafb1ea18 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadStorage.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadStorage.ts @@ -11,10 +11,10 @@ import { IDisposable } from 'vs/base/common/lifecycle'; @extHostNamedCustomer(MainContext.MainThreadStorage) export class MainThreadStorage implements MainThreadStorageShape { - private _storageService: IStorageService; - private _proxy: ExtHostStorageShape; - private _storageListener: IDisposable; - private _sharedStorageKeysToWatch: Map = new Map(); + private readonly _storageService: IStorageService; + private readonly _proxy: ExtHostStorageShape; + private readonly _storageListener: IDisposable; + private readonly _sharedStorageKeysToWatch: Map = new Map(); constructor( extHostContext: IExtHostContext, @@ -24,7 +24,7 @@ export class MainThreadStorage implements MainThreadStorageShape { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostStorage); this._storageListener = this._storageService.onDidChangeStorage(e => { - let shared = e.scope === StorageScope.GLOBAL; + const shared = e.scope === StorageScope.GLOBAL; if (shared && this._sharedStorageKeysToWatch.has(e.key)) { try { this._proxy.$acceptValue(shared, e.key, this._getValue(shared, e.key)); @@ -39,7 +39,7 @@ export class MainThreadStorage implements MainThreadStorageShape { this._storageListener.dispose(); } - $getValue(shared: boolean, key: string): Promise { + $getValue(shared: boolean, key: string): Promise { if (shared) { this._sharedStorageKeysToWatch.set(key, true); } @@ -50,8 +50,8 @@ export class MainThreadStorage implements MainThreadStorageShape { } } - private _getValue(shared: boolean, key: string): T { - let jsonValue = this._storageService.get(key, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE); + private _getValue(shared: boolean, key: string): T | undefined { + const jsonValue = this._storageService.get(key, shared ? StorageScope.GLOBAL : StorageScope.WORKSPACE); if (!jsonValue) { return undefined; } @@ -66,6 +66,6 @@ export class MainThreadStorage implements MainThreadStorageShape { } catch (err) { return Promise.reject(err); } - return undefined; + return Promise.resolve(undefined); } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadTask.ts b/src/vs/workbench/api/electron-browser/mainThreadTask.ts index e238ab2bf5..51ad5cfc73 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTask.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTask.ts @@ -18,19 +18,19 @@ import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspac import { ContributedTask, KeyedTaskIdentifier, TaskExecution, Task, TaskEvent, TaskEventKind, PresentationOptions, CommandOptions, CommandConfiguration, RuntimeType, CustomTask, TaskScope, TaskSource, TaskSourceKind, ExtensionTaskSource, RunOptions, TaskSet -} from 'vs/workbench/parts/tasks/common/tasks'; +} from 'vs/workbench/contrib/tasks/common/tasks'; -import { ResolveSet, ResolvedVariables } from 'vs/workbench/parts/tasks/common/taskSystem'; -import { ITaskService, TaskFilter, ITaskProvider } from 'vs/workbench/parts/tasks/common/taskService'; +import { ResolveSet, ResolvedVariables } from 'vs/workbench/contrib/tasks/common/taskSystem'; +import { ITaskService, TaskFilter, ITaskProvider } from 'vs/workbench/contrib/tasks/common/taskService'; -import { TaskDefinition } from 'vs/workbench/parts/tasks/node/tasks'; +import { TaskDefinition } from 'vs/workbench/contrib/tasks/node/tasks'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { ExtHostContext, MainThreadTaskShape, ExtHostTaskShape, MainContext, IExtHostContext } from 'vs/workbench/api/node/extHost.protocol'; import { TaskDefinitionDTO, TaskExecutionDTO, ProcessExecutionOptionsDTO, TaskPresentationOptionsDTO, - ProcessExecutionDTO, ShellExecutionDTO, ShellExecutionOptionsDTO, TaskDTO, TaskSourceDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, + ProcessExecutionDTO, ShellExecutionDTO, ShellExecutionOptionsDTO, CustomExecutionDTO, TaskDTO, TaskSourceDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, RunOptionsDTO } from 'vs/workbench/api/shared/tasks'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; @@ -42,12 +42,6 @@ namespace TaskExecutionDTO { task: TaskDTO.from(value.task) }; } - export function to(value: TaskExecutionDTO, workspace: IWorkspaceContextService, executeOnly: boolean): TaskExecution { - return { - id: value.id, - task: TaskDTO.to(value.task, workspace, executeOnly) - }; - } } namespace TaskProcessStartedDTO { @@ -70,11 +64,11 @@ namespace TaskProcessEndedDTO { namespace TaskDefinitionDTO { export function from(value: KeyedTaskIdentifier): TaskDefinitionDTO { - let result = Objects.assign(Object.create(null), value); + const result = Objects.assign(Object.create(null), value); delete result._key; return result; } - export function to(value: TaskDefinitionDTO, executeOnly: boolean): KeyedTaskIdentifier { + export function to(value: TaskDefinitionDTO, executeOnly: boolean): KeyedTaskIdentifier | undefined { let result = TaskDefinition.createTaskIdentifier(value, console); if (result === undefined && executeOnly) { result = { @@ -87,13 +81,13 @@ namespace TaskDefinitionDTO { } namespace TaskPresentationOptionsDTO { - export function from(value: PresentationOptions): TaskPresentationOptionsDTO { + export function from(value: PresentationOptions | undefined): TaskPresentationOptionsDTO | undefined { if (value === undefined || value === null) { return undefined; } return Objects.assign(Object.create(null), value); } - export function to(value: TaskPresentationOptionsDTO): PresentationOptions { + export function to(value: TaskPresentationOptionsDTO | undefined): PresentationOptions { if (value === undefined || value === null) { return PresentationOptions.defaults; } @@ -102,13 +96,13 @@ namespace TaskPresentationOptionsDTO { } namespace RunOptionsDTO { - export function from(value: RunOptions): RunOptionsDTO { + export function from(value: RunOptions): RunOptionsDTO | undefined { if (value === undefined || value === null) { return undefined; } return Objects.assign(Object.create(null), value); } - export function to(value: RunOptionsDTO): RunOptions { + export function to(value: RunOptionsDTO | undefined): RunOptions { if (value === undefined || value === null) { return RunOptions.defaults; } @@ -117,7 +111,7 @@ namespace RunOptionsDTO { } namespace ProcessExecutionOptionsDTO { - export function from(value: CommandOptions): ProcessExecutionOptionsDTO { + export function from(value: CommandOptions): ProcessExecutionOptionsDTO | undefined { if (value === undefined || value === null) { return undefined; } @@ -126,7 +120,7 @@ namespace ProcessExecutionOptionsDTO { env: value.env }; } - export function to(value: ProcessExecutionOptionsDTO): CommandOptions { + export function to(value: ProcessExecutionOptionsDTO | undefined): CommandOptions { if (value === undefined || value === null) { return CommandOptions.defaults; } @@ -138,14 +132,14 @@ namespace ProcessExecutionOptionsDTO { } namespace ProcessExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO): value is ProcessExecutionDTO { - let candidate = value as ProcessExecutionDTO; + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO): value is ProcessExecutionDTO { + const candidate = value as ProcessExecutionDTO; return candidate && !!candidate.process; } export function from(value: CommandConfiguration): ProcessExecutionDTO { - let process: string = Types.isString(value.name) ? value.name : value.name.value; - let args: string[] = value.args ? value.args.map(value => Types.isString(value) ? value : value.value) : []; - let result: ProcessExecutionDTO = { + const process: string = Types.isString(value.name) ? value.name : value.name!.value; + const args: string[] = value.args ? value.args.map(value => Types.isString(value) ? value : value.value) : []; + const result: ProcessExecutionDTO = { process: process, args: args }; @@ -155,7 +149,7 @@ namespace ProcessExecutionDTO { return result; } export function to(value: ProcessExecutionDTO): CommandConfiguration { - let result: CommandConfiguration = { + const result: CommandConfiguration = { runtime: RuntimeType.Process, name: value.process, args: value.args, @@ -167,11 +161,11 @@ namespace ProcessExecutionDTO { } namespace ShellExecutionOptionsDTO { - export function from(value: CommandOptions): ShellExecutionOptionsDTO { + export function from(value: CommandOptions): ShellExecutionOptionsDTO | undefined { if (value === undefined || value === null) { return undefined; } - let result: ShellExecutionOptionsDTO = { + const result: ShellExecutionOptionsDTO = { cwd: value.cwd || CommandOptions.defaults.cwd, env: value.env }; @@ -182,11 +176,11 @@ namespace ShellExecutionOptionsDTO { } return result; } - export function to(value: ShellExecutionOptionsDTO): CommandOptions { + export function to(value: ShellExecutionOptionsDTO): CommandOptions | undefined { if (value === undefined || value === null) { return undefined; } - let result: CommandOptions = { + const result: CommandOptions = { cwd: value.cwd, env: value.env }; @@ -206,12 +200,12 @@ namespace ShellExecutionOptionsDTO { } namespace ShellExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO): value is ShellExecutionDTO { - let candidate = value as ShellExecutionDTO; + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO): value is ShellExecutionDTO { + const candidate = value as ShellExecutionDTO; return candidate && (!!candidate.commandLine || !!candidate.command); } export function from(value: CommandConfiguration): ShellExecutionDTO { - let result: ShellExecutionDTO = {}; + const result: ShellExecutionDTO = {}; if (value.name && Types.isString(value.name) && (value.args === undefined || value.args === null || value.args.length === 0)) { result.commandLine = value.name; } else { @@ -224,7 +218,7 @@ namespace ShellExecutionDTO { return result; } export function to(value: ShellExecutionDTO): CommandConfiguration { - let result: CommandConfiguration = { + const result: CommandConfiguration = { runtime: RuntimeType.Shell, name: value.commandLine ? value.commandLine : value.command, args: value.args, @@ -237,9 +231,29 @@ namespace ShellExecutionDTO { } } +namespace CustomExecutionDTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO): value is CustomExecutionDTO { + const candidate = value as CustomExecutionDTO; + return candidate && candidate.customExecution === 'customExecution'; + } + + export function from(value: CommandConfiguration): CustomExecutionDTO { + return { + customExecution: 'customExecution' + }; + } + + export function to(value: CustomExecutionDTO): CommandConfiguration { + return { + runtime: RuntimeType.CustomExecution, + presentation: undefined + }; + } +} + namespace TaskSourceDTO { export function from(value: TaskSource): TaskSourceDTO { - let result: TaskSourceDTO = { + const result: TaskSourceDTO = { label: value.label }; if (value.kind === TaskSourceKind.Extension) { @@ -257,7 +271,7 @@ namespace TaskSourceDTO { } export function to(value: TaskSourceDTO, workspace: IWorkspaceContextService): ExtensionTaskSource { let scope: TaskScope; - let workspaceFolder: IWorkspaceFolder; + let workspaceFolder: IWorkspaceFolder | undefined; if ((value.scope === undefined) || ((typeof value.scope === 'number') && (value.scope !== TaskScope.Global))) { if (workspace.getWorkspace().folders.length === 0) { scope = TaskScope.Global; @@ -270,9 +284,9 @@ namespace TaskSourceDTO { scope = value.scope; } else { scope = TaskScope.Folder; - workspaceFolder = workspace.getWorkspaceFolder(URI.revive(value.scope)); + workspaceFolder = Types.withNullAsUndefined(workspace.getWorkspaceFolder(URI.revive(value.scope))); } - let result: ExtensionTaskSource = { + const result: ExtensionTaskSource = { kind: TaskSourceKind.Extension, label: value.label, extension: value.extensionId, @@ -285,17 +299,17 @@ namespace TaskSourceDTO { namespace TaskHandleDTO { export function is(value: any): value is TaskHandleDTO { - let candidate: TaskHandleDTO = value; + const candidate: TaskHandleDTO = value; return candidate && Types.isString(candidate.id) && !!candidate.workspaceFolder; } } namespace TaskDTO { - export function from(task: Task): TaskDTO { + export function from(task: Task): TaskDTO | undefined { if (task === undefined || task === null || (!CustomTask.is(task) && !ContributedTask.is(task))) { return undefined; } - let result: TaskDTO = { + const result: TaskDTO = { _id: task._id, name: task.configurationProperties.name, definition: TaskDefinitionDTO.from(task.getDefinition()), @@ -327,26 +341,32 @@ namespace TaskDTO { return result; } - export function to(task: TaskDTO, workspace: IWorkspaceContextService, executeOnly: boolean): ContributedTask { - if (typeof task.name !== 'string') { + export function to(task: TaskDTO | undefined, workspace: IWorkspaceContextService, executeOnly: boolean): ContributedTask | undefined { + if (!task || (typeof task.name !== 'string')) { return undefined; } - let command: CommandConfiguration; - if (ShellExecutionDTO.is(task.execution)) { - command = ShellExecutionDTO.to(task.execution); - } else if (ProcessExecutionDTO.is(task.execution)) { - command = ProcessExecutionDTO.to(task.execution); + + let command: CommandConfiguration | undefined; + if (task.execution) { + if (ShellExecutionDTO.is(task.execution)) { + command = ShellExecutionDTO.to(task.execution); + } else if (ProcessExecutionDTO.is(task.execution)) { + command = ProcessExecutionDTO.to(task.execution); + } else if (CustomExecutionDTO.is(task.execution)) { + command = CustomExecutionDTO.to(task.execution); + } } + if (!command) { return undefined; } command.presentation = TaskPresentationOptionsDTO.to(task.presentationOptions); - let source = TaskSourceDTO.to(task.source, workspace); + const source = TaskSourceDTO.to(task.source, workspace); - let label = nls.localize('task.label', '{0}: {1}', source.label, task.name); - let definition = TaskDefinitionDTO.to(task.definition, executeOnly); - let id = `${task.source.extensionId}.${definition._key}`; - let result: ContributedTask = new ContributedTask( + const label = nls.localize('task.label', '{0}: {1}', source.label, task.name); + const definition = TaskDefinitionDTO.to(task.definition, executeOnly)!; + const id = `${task.source.extensionId}.${definition._key}`; + const result: ContributedTask = new ContributedTask( id, // uuidMap.getUUID(identifier) source, label, @@ -371,7 +391,7 @@ namespace TaskFilterDTO { export function from(value: TaskFilter): TaskFilterDTO { return value; } - export function to(value: TaskFilterDTO): TaskFilter { + export function to(value: TaskFilterDTO | undefined): TaskFilter | undefined { return value; } } @@ -379,9 +399,9 @@ namespace TaskFilterDTO { @extHostNamedCustomer(MainContext.MainThreadTask) export class MainThreadTask implements MainThreadTaskShape { - private _extHostContext: IExtHostContext; - private _proxy: ExtHostTaskShape; - private _providers: Map; + private readonly _extHostContext: IExtHostContext; + private readonly _proxy: ExtHostTaskShape; + private readonly _providers: Map; constructor( extHostContext: IExtHostContext, @@ -392,13 +412,13 @@ export class MainThreadTask implements MainThreadTaskShape { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTask); this._providers = new Map(); this._taskService.onDidStateChange((event: TaskEvent) => { - let task = event.__task; + const task = event.__task!; if (event.kind === TaskEventKind.Start) { - this._proxy.$onDidStartTask(TaskExecutionDTO.from(task.getTaskExecution())); + this._proxy.$onDidStartTask(TaskExecutionDTO.from(task.getTaskExecution()), event.terminalId!); } else if (event.kind === TaskEventKind.ProcessStarted) { - this._proxy.$onDidStartTaskProcess(TaskProcessStartedDTO.from(task.getTaskExecution(), event.processId)); + this._proxy.$onDidStartTaskProcess(TaskProcessStartedDTO.from(task.getTaskExecution(), event.processId!)); } else if (event.kind === TaskEventKind.ProcessEnded) { - this._proxy.$onDidEndTaskProcess(TaskProcessEndedDTO.from(task.getTaskExecution(), event.exitCode)); + this._proxy.$onDidEndTaskProcess(TaskProcessEndedDTO.from(task.getTaskExecution(), event.exitCode!)); } else if (event.kind === TaskEventKind.End) { this._proxy.$OnDidEndTask(TaskExecutionDTO.from(task.getTaskExecution())); } @@ -412,13 +432,24 @@ export class MainThreadTask implements MainThreadTaskShape { this._providers.clear(); } + $createTaskId(taskDTO: TaskDTO): Promise { + return new Promise((resolve, reject) => { + let task = TaskDTO.to(taskDTO, this._workspaceContextServer, true); + if (task) { + resolve(task._id); + } else { + reject(new Error('Task could not be created from DTO')); + } + }); + } + public $registerTaskProvider(handle: number): Promise { - let provider: ITaskProvider = { + const provider: ITaskProvider = { provideTasks: (validTypes: IStringDictionary) => { return Promise.resolve(this._proxy.$provideTasks(handle, validTypes)).then((value) => { - let tasks: Task[] = []; + const tasks: Task[] = []; for (let dto of value.tasks) { - let task = TaskDTO.to(dto, this._workspaceContextServer, true); + const task = TaskDTO.to(dto, this._workspaceContextServer, true); if (task) { tasks.push(task); } else { @@ -432,21 +463,25 @@ export class MainThreadTask implements MainThreadTaskShape { }); } }; - let disposable = this._taskService.registerTaskProvider(provider); + const disposable = this._taskService.registerTaskProvider(provider); this._providers.set(handle, { disposable, provider }); return Promise.resolve(undefined); } public $unregisterTaskProvider(handle: number): Promise { - this._providers.delete(handle); + const provider = this._providers.get(handle); + if (provider) { + provider.disposable.dispose(); + this._providers.delete(handle); + } return Promise.resolve(undefined); } public $fetchTasks(filter?: TaskFilterDTO): Promise { return this._taskService.tasks(TaskFilterDTO.to(filter)).then((tasks) => { - let result: TaskDTO[] = []; + const result: TaskDTO[] = []; for (let task of tasks) { - let item = TaskDTO.from(task); + const item = TaskDTO.from(task); if (item) { result.push(item); } @@ -458,25 +493,29 @@ export class MainThreadTask implements MainThreadTaskShape { public $executeTask(value: TaskHandleDTO | TaskDTO): Promise { return new Promise((resolve, reject) => { if (TaskHandleDTO.is(value)) { - let workspaceFolder = this._workspaceContextServer.getWorkspaceFolder(URI.revive(value.workspaceFolder)); - this._taskService.getTask(workspaceFolder, value.id, true).then((task: Task) => { - this._taskService.run(task).then(undefined, reason => { - // eat the error, it has already been surfaced to the user and we don't care about it here + const workspaceFolder = this._workspaceContextServer.getWorkspaceFolder(URI.revive(value.workspaceFolder)); + if (workspaceFolder) { + this._taskService.getTask(workspaceFolder, value.id, true).then((task: Task) => { + this._taskService.run(task).then(undefined, reason => { + // eat the error, it has already been surfaced to the user and we don't care about it here + }); + const result: TaskExecutionDTO = { + id: value.id, + task: TaskDTO.from(task) + }; + resolve(result); + }, (_error) => { + reject(new Error('Task not found')); }); - let result: TaskExecutionDTO = { - id: value.id, - task: TaskDTO.from(task) - }; - resolve(result); - }, (_error) => { - reject(new Error('Task not found')); - }); + } else { + reject(new Error('No workspace folder')); + } } else { - let task = TaskDTO.to(value, this._workspaceContextServer, true); + const task = TaskDTO.to(value, this._workspaceContextServer, true)!; this._taskService.run(task).then(undefined, reason => { // eat the error, it has already been surfaced to the user and we don't care about it here }); - let result: TaskExecutionDTO = { + const result: TaskExecutionDTO = { id: task._id, task: TaskDTO.from(task) }; @@ -485,6 +524,24 @@ export class MainThreadTask implements MainThreadTaskShape { }); } + public $customExecutionComplete(id: string, result?: number): Promise { + return new Promise((resolve, reject) => { + this._taskService.getActiveTasks().then((tasks) => { + for (let task of tasks) { + if (id === task._id) { + this._taskService.extensionCallbackTaskComplete(task, result).then((value) => { + resolve(undefined); + }, (error) => { + reject(error); + }); + return; + } + } + reject(new Error('Task to mark as complete not found')); + }); + }); + } + public $terminateTask(id: string): Promise { return new Promise((resolve, reject) => { this._taskService.getActiveTasks().then((tasks) => { @@ -525,17 +582,17 @@ export class MainThreadTask implements MainThreadTaskShape { }, context: this._extHostContext, resolveVariables: (workspaceFolder: IWorkspaceFolder, toResolve: ResolveSet): Promise => { - let vars: string[] = []; + const vars: string[] = []; toResolve.variables.forEach(item => vars.push(item)); return Promise.resolve(this._proxy.$resolveVariables(workspaceFolder.uri, { process: toResolve.process, variables: vars })).then(values => { const partiallyResolvedVars = new Array(); forEach(values.variables, (entry) => { partiallyResolvedVars.push(entry.value); }); - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { this._configurationResolverService.resolveWithInteraction(workspaceFolder, partiallyResolvedVars, 'tasks').then(resolvedVars => { - let result = { - process: undefined as string, + const result: ResolvedVariables = { + process: undefined, variables: new Map() }; for (let i = 0; i < partiallyResolvedVars.length; i++) { diff --git a/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts b/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts index 5846031664..b58d966992 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts @@ -4,9 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, ITerminalDimensions, EXT_HOST_CREATION_DELAY } from 'vs/workbench/contrib/terminal/common/terminal'; import { ExtHostContext, ExtHostTerminalServiceShape, MainThreadTerminalServiceShape, MainContext, IExtHostContext, ShellLaunchConfigDto } from 'vs/workbench/api/node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; +import { UriComponents, URI } from 'vs/base/common/uri'; @extHostNamedCustomer(MainContext.MainThreadTerminalService) export class MainThreadTerminalService implements MainThreadTerminalServiceShape { @@ -28,13 +29,16 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape // Delay this message so the TerminalInstance constructor has a chance to finish and // return the ID normally to the extension host. The ID that is passed here will be used // to register non-extension API terminals in the extension host. - setTimeout(() => this._onTerminalOpened(instance), EXT_HOST_CREATION_DELAY); + setTimeout(() => { + this._onTerminalOpened(instance); + this._onInstanceDimensionsChanged(instance); + }, EXT_HOST_CREATION_DELAY); })); this._toDispose.push(terminalService.onInstanceDisposed(instance => this._onTerminalDisposed(instance))); this._toDispose.push(terminalService.onInstanceProcessIdReady(instance => this._onTerminalProcessIdReady(instance))); this._toDispose.push(terminalService.onInstanceDimensionsChanged(instance => this._onInstanceDimensionsChanged(instance))); this._toDispose.push(terminalService.onInstanceRequestExtHostProcess(request => this._onTerminalRequestExtHostProcess(request))); - this._toDispose.push(terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : undefined))); + this._toDispose.push(terminalService.onActiveInstanceChanged(instance => this._onActiveTerminalChanged(instance ? instance.id : null))); this._toDispose.push(terminalService.onInstanceTitleChanged(instance => this._onTitleChanged(instance.id, instance.title))); // Set initial ext host state @@ -55,12 +59,12 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape // when the extension host process goes down ? } - public $createTerminal(name?: string, shellPath?: string, shellArgs?: string[], cwd?: string, env?: { [key: string]: string }, waitOnExit?: boolean, strictEnv?: boolean): Promise<{ id: number, name: string }> { + public $createTerminal(name?: string, shellPath?: string, shellArgs?: string[], cwd?: string | UriComponents, env?: { [key: string]: string }, waitOnExit?: boolean, strictEnv?: boolean): Promise<{ id: number, name: string }> { const shellLaunchConfig: IShellLaunchConfig = { name, executable: shellPath, args: shellArgs, - cwd, + cwd: typeof cwd === 'string' ? cwd : URI.revive(cwd), waitOnExit, ignoreConfigurationCwd: true, env, @@ -87,7 +91,8 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } public $hide(terminalId: number): void { - if (this.terminalService.getActiveInstance().id === terminalId) { + const instance = this.terminalService.getActiveInstance(); + if (instance && instance.id === terminalId) { this.terminalService.hidePanel(); } } @@ -161,7 +166,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape terminalInstance.addDisposable(this._terminalOnDidWriteDataListeners[terminalId]); } - private _onActiveTerminalChanged(terminalId: number | undefined): void { + private _onActiveTerminalChanged(terminalId: number | null): void { this._proxy.$acceptActiveTerminalChanged(terminalId); } @@ -192,15 +197,14 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape } private _onTerminalProcessIdReady(terminalInstance: ITerminalInstance): void { + if (terminalInstance.processId === undefined) { + return; + } this._proxy.$acceptTerminalProcessId(terminalInstance.id, terminalInstance.processId); } private _onInstanceDimensionsChanged(instance: ITerminalInstance): void { - // Only send the dimensions if the terminal is a renderer only as there is no API to access - // dimensions on a plain Terminal. - if (instance.shellLaunchConfig.isRendererOnly) { - this._proxy.$acceptTerminalRendererDimensions(instance.id, instance.cols, instance.rows); - } + this._proxy.$acceptTerminalDimensions(instance.id, instance.cols, instance.rows); } private _onTerminalRequestExtHostProcess(request: ITerminalProcessExtHostRequest): void { diff --git a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts index 8f16f60b6b..e429704064 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts @@ -5,18 +5,19 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, IExtHostContext } from '../node/extHost.protocol'; -import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeView, ViewsRegistry, ITreeViewDescriptor, IRevealOptions } from 'vs/workbench/common/views'; +import { ITreeViewDataProvider, ITreeItem, IViewsService, ITreeView, IViewsRegistry, ITreeViewDescriptor, IRevealOptions, Extensions } from 'vs/workbench/common/views'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { distinct } from 'vs/base/common/arrays'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { isUndefinedOrNull, isNumber } from 'vs/base/common/types'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { Registry } from 'vs/platform/registry/common/platform'; @extHostNamedCustomer(MainContext.MainThreadTreeViews) export class MainThreadTreeViews extends Disposable implements MainThreadTreeViewsShape { - private _proxy: ExtHostTreeViewsShape; - private _dataProviders: Map = new Map(); + private readonly _proxy: ExtHostTreeViewsShape; + private readonly _dataProviders: Map = new Map(); constructor( extHostContext: IExtHostContext, @@ -45,7 +46,10 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie return this.viewsService.openView(treeViewId, options.focus) .then(() => { const viewer = this.getTreeView(treeViewId); - return this.reveal(viewer, this._dataProviders.get(treeViewId), item, parentChain, options); + if (viewer) { + return this.reveal(viewer, this._dataProviders.get(treeViewId)!, item, parentChain, options); + } + return undefined; }); } @@ -56,7 +60,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie const itemsToRefresh = dataProvider.getItemsToRefresh(itemsToRefreshByHandle); return viewer.refresh(itemsToRefresh.length ? itemsToRefresh : undefined); } - return null; + return Promise.resolve(); } $setMessage(treeViewId: string, message: string | IMarkdownString): void { @@ -66,7 +70,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie } } - private async reveal(treeView: ITreeView, dataProvider: TreeViewDataProvider, item: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise { + private async reveal(treeView: ITreeView, dataProvider: TreeViewDataProvider, itemIn: ITreeItem, parentChain: ITreeItem[], options: IRevealOptions): Promise { options = options ? options : { select: false, focus: false }; const select = isUndefinedOrNull(options.select) ? false : options.select; const focus = isUndefinedOrNull(options.focus) ? false : options.focus; @@ -79,7 +83,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie for (const parent of parentChain) { await treeView.expand(parent); } - item = dataProvider.getItem(item.handle); + const item = dataProvider.getItem(itemIn.handle); if (item) { await treeView.reveal(item); if (select) { @@ -91,13 +95,13 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie let itemsToExpand = [item]; for (; itemsToExpand.length > 0 && expand > 0; expand--) { await treeView.expand(itemsToExpand); - itemsToExpand = itemsToExpand.reduce((result, item) => { - item = dataProvider.getItem(item.handle); + itemsToExpand = itemsToExpand.reduce((result, itemValue) => { + const item = dataProvider.getItem(itemValue.handle); if (item && item.children && item.children.length) { result.push(...item.children); } return result; - }, []); + }, [] as ITreeItem[]); } } } @@ -109,8 +113,8 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie this._register(treeView.onDidChangeVisibility(isVisible => this._proxy.$setVisible(treeViewId, isVisible))); } - private getTreeView(treeViewId: string): ITreeView { - const viewDescriptor: ITreeViewDescriptor = ViewsRegistry.getView(treeViewId); + private getTreeView(treeViewId: string): ITreeView | null { + const viewDescriptor: ITreeViewDescriptor = Registry.as(Extensions.ViewsRegistry).getView(treeViewId); return viewDescriptor ? viewDescriptor.treeView : null; } @@ -132,13 +136,12 @@ export type TreeItemHandle = string; // {{SQL CARBON EDIT}} export class TreeViewDataProvider implements ITreeViewDataProvider { - // {{SQL CARBON EDIT}} - protected itemsMap: Map = new Map(); + private readonly itemsMap: Map = new Map(); // {{SQL CARBON EDIT}} - constructor(protected treeViewId: string, - protected _proxy: ExtHostTreeViewsShape, - protected notificationService: INotificationService + constructor(protected readonly treeViewId: string, + protected readonly _proxy: ExtHostTreeViewsShape, + private readonly notificationService: INotificationService ) { } @@ -178,7 +181,7 @@ export class TreeViewDataProvider implements ITreeViewDataProvider { return itemsToRefresh; } - getItem(treeItemHandle: string): ITreeItem { + getItem(treeItemHandle: string): ITreeItem | undefined { return this.itemsMap.get(treeItemHandle); } @@ -198,7 +201,7 @@ export class TreeViewDataProvider implements ITreeViewDataProvider { } private updateTreeItem(current: ITreeItem, treeItem: ITreeItem): void { - treeItem.children = treeItem.children ? treeItem.children : null; + treeItem.children = treeItem.children ? treeItem.children : undefined; if (current) { const properties = distinct([...Object.keys(current), ...Object.keys(treeItem)]); for (const property of properties) { diff --git a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts index 2f69c644dc..f4cead48e7 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWebview.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWebview.ts @@ -2,84 +2,99 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import * as map from 'vs/base/common/map'; import { URI, UriComponents } from 'vs/base/common/uri'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { localize } from 'vs/nls'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelShowOptions } from 'vs/workbench/api/node/extHost.protocol'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { ExtHostContext, ExtHostWebviewsShape, IExtHostContext, MainContext, MainThreadWebviewsShape, WebviewInsetHandle, WebviewPanelHandle, WebviewPanelShowOptions } from 'vs/workbench/api/node/extHost.protocol'; import { editorGroupToViewColumn, EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/shared/editor'; -import { WebviewEditor } from 'vs/workbench/parts/webview/electron-browser/webviewEditor'; -import { WebviewEditorInput } from 'vs/workbench/parts/webview/electron-browser/webviewEditorInput'; -import { ICreateWebViewShowOptions, IWebviewEditorService, WebviewInputOptions, WebviewReviver } from 'vs/workbench/parts/webview/electron-browser/webviewEditorService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { CodeInsetController } from 'vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution'; +import { WebviewEditor } from 'vs/workbench/contrib/webview/electron-browser/webviewEditor'; +import { WebviewEditorInput } from 'vs/workbench/contrib/webview/electron-browser/webviewEditorInput'; +import { ICreateWebViewShowOptions, IWebviewEditorService, WebviewInputOptions } from 'vs/workbench/contrib/webview/electron-browser/webviewEditorService'; +import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ACTIVE_GROUP, IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import * as vscode from 'vscode'; import { extHostNamedCustomer } from './extHostCustomers'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @extHostNamedCustomer(MainContext.MainThreadWebviews) -export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviver { - - private static readonly viewType = 'mainThreadWebview'; +export class MainThreadWebviews extends Disposable implements MainThreadWebviewsShape { private static readonly standardSupportedLinkSchemes = ['http', 'https', 'mailto']; private static revivalPool = 0; - private _toDispose: IDisposable[] = []; private readonly _proxy: ExtHostWebviewsShape; private readonly _webviews = new Map(); - private readonly _revivers = new Set(); + private readonly _webviewsElements = new Map(); + private readonly _revivers = new Map(); private _activeWebview: WebviewPanelHandle | undefined = undefined; constructor( context: IExtHostContext, @ILifecycleService lifecycleService: ILifecycleService, + @IExtensionService extensionService: IExtensionService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IEditorService private readonly _editorService: IEditorService, @IWebviewEditorService private readonly _webviewService: IWebviewEditorService, @IOpenerService private readonly _openerService: IOpenerService, - @IExtensionService private readonly _extensionService: IExtensionService, - @ITelemetryService private readonly _telemetryService: ITelemetryService + @ITelemetryService private readonly _telemetryService: ITelemetryService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ICodeEditorService private readonly _codeEditorService: ICodeEditorService, + @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, ) { + super(); + this._proxy = context.getProxy(ExtHostContext.ExtHostWebviews); _editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this, this._toDispose); _editorService.onDidVisibleEditorsChange(this.onVisibleEditorsChanged, this, this._toDispose); - this._toDispose.push(_webviewService.registerReviver(MainThreadWebviews.viewType, this)); + // This reviver's only job is to activate webview extensions + // This should trigger the real reviver to be registered from the extension host side. + this._toDispose.push(_webviewService.registerReviver({ + canRevive: (webview) => { + const viewType = webview.state.viewType; + if (viewType) { + extensionService.activateByEvent(`onWebviewPanel:${viewType}`); + } + return false; + }, + reviveWebview: () => { throw new Error('not implemented'); } + })); lifecycleService.onBeforeShutdown(e => { e.veto(this._onBeforeShutdown()); }, this, this._toDispose); } - public dispose(): void { - this._toDispose = dispose(this._toDispose); - } - public $createWebviewPanel( handle: WebviewPanelHandle, viewType: string, title: string, - showOptions: { viewColumn: EditorViewColumn | null, preserveFocus: boolean }, + showOptions: { viewColumn?: EditorViewColumn, preserveFocus?: boolean }, options: WebviewInputOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents ): void { const mainThreadShowOptions: ICreateWebViewShowOptions = Object.create(null); if (showOptions) { - mainThreadShowOptions.preserveFocus = showOptions.preserveFocus; + mainThreadShowOptions.preserveFocus = !!showOptions.preserveFocus; mainThreadShowOptions.group = viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn); } - const webview = this._webviewService.createWebview(MainThreadWebviews.viewType, title, mainThreadShowOptions, reviveWebviewOptions(options), URI.revive(extensionLocation), this.createWebviewEventDelegate(handle)); + const webview = this._webviewService.createWebview(this.getInternalWebviewId(viewType), title, mainThreadShowOptions, reviveWebviewOptions(options), URI.revive(extensionLocation), this.createWebviewEventDelegate(handle)); webview.state = { viewType: viewType, state: undefined @@ -96,6 +111,44 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv this._telemetryService.publicLog('webviews:createWebviewPanel', { extensionId: extensionId.value }); } + $createWebviewCodeInset(handle: WebviewInsetHandle, symbolId: string, options: vscode.WebviewOptions, extensionLocation: UriComponents): void { + // todo@joh main is for the lack of a code-inset service + // which we maybe wanna have... this is how it now works + // 1) create webview element + // 2) find the code inset controller that request it + // 3) let the controller adopt the widget + // 4) continue to forward messages to the webview + const webview = this._instantiationService.createInstance( + WebviewElement, + this._layoutService.getContainer(Parts.EDITOR_PART), + { + extensionLocation: URI.revive(extensionLocation), + enableFindWidget: false, + }, + { + allowScripts: options.enableScripts, + } + ); + + let found = false; + for (const editor of this._codeEditorService.listCodeEditors()) { + const ctrl = CodeInsetController.get(editor); + if (ctrl && ctrl.acceptWebview(symbolId, webview)) { + found = true; + break; + } + } + + if (!found) { + webview.dispose(); + return; + } + // this will leak... the adopted webview will be disposed by the + // code inset controller. we might need a dispose-event here so that + // we can clean up things. + this._webviewsElements.set(handle, webview); + } + public $disposeWebview(handle: WebviewPanelHandle): void { const webview = this.getWebview(handle); webview.dispose(); @@ -111,14 +164,22 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv webview.iconPath = reviveWebviewIcon(value); } - public $setHtml(handle: WebviewPanelHandle, value: string): void { - const webview = this.getWebview(handle); - webview.html = value; + public $setHtml(handle: WebviewPanelHandle | WebviewInsetHandle, value: string): void { + if (typeof handle === 'number') { + this.getWebviewElement(handle).contents = value; + } else { + const webview = this.getWebview(handle); + webview.html = value; + } } - public $setOptions(handle: WebviewPanelHandle, options: vscode.WebviewOptions): void { - const webview = this.getWebview(handle); - webview.setOptions(reviveWebviewOptions(options)); + public $setOptions(handle: WebviewPanelHandle | WebviewInsetHandle, options: vscode.WebviewOptions): void { + if (typeof handle === 'number') { + this.getWebviewElement(handle).options = reviveWebviewOptions(options); + } else { + const webview = this.getWebview(handle); + webview.setOptions(reviveWebviewOptions(options)); + } } public $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void { @@ -128,69 +189,82 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv } const targetGroup = this._editorGroupService.getGroup(viewColumnToEditorGroup(this._editorGroupService, showOptions.viewColumn)); - - this._webviewService.revealWebview(webview, targetGroup || this._editorGroupService.activeGroup, showOptions.preserveFocus); + if (targetGroup) { + this._webviewService.revealWebview(webview, targetGroup || this._editorGroupService.getGroup(webview.group || ACTIVE_GROUP), !!showOptions.preserveFocus); + } } - public $postMessage(handle: WebviewPanelHandle, message: any): Promise { - const webview = this.getWebview(handle); - const editors = this._editorService.visibleControls - .filter(e => e instanceof WebviewEditor) - .map(e => e as WebviewEditor) - .filter(e => e.input.matches(webview)); + public $postMessage(handle: WebviewPanelHandle | WebviewInsetHandle, message: any): Promise { + if (typeof handle === 'number') { + this.getWebviewElement(handle).sendMessage(message); + return Promise.resolve(true); - for (const editor of editors) { - editor.sendMessage(message); + } else { + const webview = this.getWebview(handle); + const editors = this._editorService.visibleControls + .filter(e => e instanceof WebviewEditor) + .map(e => e as WebviewEditor) + .filter(e => e.input!.matches(webview)); + + for (const editor of editors) { + editor.sendMessage(message); + } + + return Promise.resolve(editors.length > 0); } - - return Promise.resolve(editors.length > 0); } public $registerSerializer(viewType: string): void { - this._revivers.add(viewType); - } + if (this._revivers.has(viewType)) { + throw new Error(`Reviver for ${viewType} already registered`); + } - public $unregisterSerializer(viewType: string): void { - this._revivers.delete(viewType); - } + this._revivers.set(viewType, this._webviewService.registerReviver({ + canRevive: (webview) => { + return webview.state && webview.state.viewType === viewType; + }, + reviveWebview: async (webview): Promise => { + const viewType = webview.state.viewType; + const handle = 'revival-' + MainThreadWebviews.revivalPool++; + this._webviews.set(handle, webview); + webview._events = this.createWebviewEventDelegate(handle); + let state = undefined; + if (webview.state.state) { + try { + state = JSON.parse(webview.state.state); + } catch { + // noop + } + } - public reviveWebview(webview: WebviewEditorInput): Promise { - const viewType = webview.state.viewType; - return Promise.resolve(this._extensionService.activateByEvent(`onWebviewPanel:${viewType}`).then(() => { - const handle = 'revival-' + MainThreadWebviews.revivalPool++; - this._webviews.set(handle, webview); - webview._events = this.createWebviewEventDelegate(handle); - - let state = undefined; - if (webview.state.state) { try { - state = JSON.parse(webview.state.state); - } catch { - // noop + await this._proxy.$deserializeWebviewPanel(handle, viewType, webview.getTitle(), state, editorGroupToViewColumn(this._editorGroupService, webview.group || ACTIVE_GROUP), webview.options); + } catch (error) { + onUnexpectedError(error); + webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType); } } - - return this._proxy.$deserializeWebviewPanel(handle, webview.state.viewType, webview.getTitle(), state, editorGroupToViewColumn(this._editorGroupService, webview.group), webview.options) - .then(undefined, error => { - onUnexpectedError(error); - - webview.html = MainThreadWebviews.getDeserializationFailedContents(viewType); - }); })); } - public canRevive(webview: WebviewEditorInput): boolean { - if (webview.isDisposed() || !webview.state) { - return false; + public $unregisterSerializer(viewType: string): void { + const reviver = this._revivers.get(viewType); + if (!reviver) { + throw new Error(`No reviver for ${viewType} registered`); } - return this._revivers.has(webview.state.viewType) || !!webview.reviver; + reviver.dispose(); + this._revivers.delete(viewType); + } + + private getInternalWebviewId(viewType: string): string { + return `mainThreadWebview-${viewType}`; } private _onBeforeShutdown(): boolean { - this._webviews.forEach((view) => { - if (this.canRevive(view)) { - view.state.state = view.webviewState; + this._webviews.forEach((webview) => { + if (!webview.isDisposed() && webview.state && this._revivers.has(webview.state.viewType)) { + webview.state.state = webview.webviewState; } }); return false; // Don't veto shutdown @@ -201,12 +275,9 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv onDidClickLink: uri => this.onDidClickLink(handle, uri), onMessage: message => this._proxy.$onMessage(handle, message), onDispose: () => { - const cleanUp = () => { + this._proxy.$onDidDisposeWebviewPanel(handle).finally(() => { this._webviews.delete(handle); - }; - this._proxy.$onDidDisposeWebviewPanel(handle).then( - cleanUp, - cleanUp); + }); } }; } @@ -216,7 +287,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv let newActiveWebview: { input: WebviewEditorInput, handle: WebviewPanelHandle } | undefined = undefined; if (activeEditor && activeEditor.input instanceof WebviewEditorInput) { for (const handle of map.keys(this._webviews)) { - const input = this._webviews.get(handle); + const input = this._webviews.get(handle)!; if (input.matches(activeEditor.input)) { newActiveWebview = { input, handle }; break; @@ -229,7 +300,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv this._proxy.$onDidChangeWebviewPanelViewState(newActiveWebview.handle, { active: true, visible: true, - position: editorGroupToViewColumn(this._editorGroupService, newActiveWebview.input.group) + position: editorGroupToViewColumn(this._editorGroupService, newActiveWebview.input.group || ACTIVE_GROUP) }); return; } @@ -240,8 +311,8 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv if (oldActiveWebview) { this._proxy.$onDidChangeWebviewPanelViewState(this._activeWebview, { active: false, - visible: this._editorService.visibleControls.some(editor => editor.input && editor.input.matches(oldActiveWebview)), - position: editorGroupToViewColumn(this._editorGroupService, oldActiveWebview.group), + visible: this._editorService.visibleControls.some(editor => !!editor.input && editor.input.matches(oldActiveWebview)), + position: editorGroupToViewColumn(this._editorGroupService, oldActiveWebview.group || ACTIVE_GROUP), }); } } @@ -251,7 +322,7 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv this._proxy.$onDidChangeWebviewPanelViewState(newActiveWebview.handle, { active: true, visible: true, - position: editorGroupToViewColumn(this._editorGroupService, activeEditor.group) + position: editorGroupToViewColumn(this._editorGroupService, activeEditor ? activeEditor.group : ACTIVE_GROUP), }); this._activeWebview = newActiveWebview.handle; } else { @@ -263,9 +334,9 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv this._webviews.forEach((input, handle) => { for (const workbenchEditor of this._editorService.visibleControls) { if (workbenchEditor.input && workbenchEditor.input.matches(input)) { - const editorPosition = editorGroupToViewColumn(this._editorGroupService, workbenchEditor.group); + const editorPosition = editorGroupToViewColumn(this._editorGroupService, workbenchEditor.group!); - input.updateGroup(workbenchEditor.group.id); + input.updateGroup(workbenchEditor.group!.id); this._proxy.$onDidChangeWebviewPanelViewState(handle, { active: handle === this._activeWebview, visible: true, @@ -297,6 +368,14 @@ export class MainThreadWebviews implements MainThreadWebviewsShape, WebviewReviv return webview; } + private getWebviewElement(handle: number): WebviewElement { + const webview = this._webviewsElements.get(handle); + if (!webview) { + throw new Error('Unknown webview handle:' + handle); + } + return webview; + } + private static getDeserializationFailedContents(viewType: string) { return ` diff --git a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts index 1d45da9bf9..3f3772ebf7 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts @@ -3,27 +3,26 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { IFolderQuery, IPatternInfo, ISearchConfiguration, ISearchProgressItem, ISearchService, QueryType, IFileQuery, IFileMatch } from 'vs/platform/search/common/search'; +import { IFileMatch, IPatternInfo, ISearchProgressItem, ISearchService } from 'vs/workbench/services/search/common/search'; import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceContextService, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; -import { QueryBuilder, ITextQueryBuilderOptions } from 'vs/workbench/parts/search/common/queryBuilder'; +import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape } from '../node/extHost.protocol'; -import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; +import { ExtHostContext, ExtHostWorkspaceShape, IExtHostContext, MainContext, MainThreadWorkspaceShape, IWorkspaceData } from '../node/extHost.protocol'; import { TextSearchComplete } from 'vscode'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @extHostNamedCustomer(MainContext.MainThreadWorkspace) export class MainThreadWorkspace implements MainThreadWorkspaceShape { @@ -31,13 +30,13 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { private readonly _toDispose: IDisposable[] = []; private readonly _activeCancelTokens: { [id: number]: CancellationTokenSource } = Object.create(null); private readonly _proxy: ExtHostWorkspaceShape; + private readonly _queryBuilder = this._instantiationService.createInstance(QueryBuilder); constructor( extHostContext: IExtHostContext, @ISearchService private readonly _searchService: ISearchService, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @ITextFileService private readonly _textFileService: ITextFileService, - @IConfigurationService private readonly _configurationService: IConfigurationService, @IWorkspaceEditingService private readonly _workspaceEditingService: IWorkspaceEditingService, @IStatusbarService private readonly _statusbarService: IStatusbarService, @IWindowService private readonly _windowService: IWindowService, @@ -45,6 +44,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { @ILabelService private readonly _labelService: ILabelService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace); + this._contextService.getCompleteWorkspace().then(workspace => this._proxy.$initializeWorkspace(this.getWorkspaceData(workspace))); this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose); this._contextService.onDidChangeWorkbenchState(this._onDidChangeWorkspace, this, this._toDispose); } @@ -102,61 +102,41 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { } private _onDidChangeWorkspace(): void { - const workspace = this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : this._contextService.getWorkspace(); - this._proxy.$acceptWorkspaceData(workspace ? { - configuration: workspace.configuration, + this._proxy.$acceptWorkspaceData(this.getWorkspaceData(this._contextService.getWorkspace())); + } + + private getWorkspaceData(workspace: IWorkspace): IWorkspaceData | null { + if (this._contextService.getWorkbenchState() === WorkbenchState.EMPTY) { + return null; + } + return { + configuration: workspace.configuration || undefined, folders: workspace.folders, id: workspace.id, name: this._labelService.getWorkspaceLabel(workspace) - } : null); + }; } // --- search --- - $startFileSearch(includePattern: string, _includeFolder: UriComponents, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + $startFileSearch(includePattern: string, _includeFolder: UriComponents | undefined, excludePatternOrDisregardExcludes: string | false | undefined, maxResults: number, token: CancellationToken): Promise { const includeFolder = URI.revive(_includeFolder); const workspace = this._contextService.getWorkspace(); if (!workspace.folders.length) { - return undefined; + return Promise.resolve(undefined); } - let folderQueries: IFolderQuery[]; - if (includeFolder) { - folderQueries = [{ folder: includeFolder }]; // if base provided, only search in that folder - } else { - folderQueries = workspace.folders.map(folder => ({ folder: folder.uri })); // absolute pattern: search across all folders - } - - if (!folderQueries) { - return undefined; // invalid query parameters - } - - const ignoreSymlinks = folderQueries.every(folderQuery => { - const folderConfig = this._configurationService.getValue({ resource: folderQuery.folder }); - return !folderConfig.search.followSymlinks; - }); - - // TODO replace wth QueryBuilder - folderQueries.forEach(fq => { - fq.ignoreSymlinks = ignoreSymlinks; - }); - - const query: IFileQuery = { - folderQueries, - type: QueryType.File, - maxResults, - disregardExcludeSettings: excludePatternOrDisregardExcludes === false, - _reason: 'startFileSearch' - }; - if (typeof includePattern === 'string') { - query.includePattern = { [includePattern]: true }; - } - - if (typeof excludePatternOrDisregardExcludes === 'string') { - query.excludePattern = { [excludePatternOrDisregardExcludes]: true }; - } - - this._searchService.extendQuery(query); + const query = this._queryBuilder.file( + includeFolder ? [includeFolder] : workspace.folders.map(f => f.uri), + { + maxResults, + disregardExcludeSettings: (excludePatternOrDisregardExcludes === false) || undefined, + disregardSearchExcludeSettings: true, + disregardIgnoreFiles: true, + includePattern, + excludePattern: typeof excludePatternOrDisregardExcludes === 'string' ? excludePatternOrDisregardExcludes : undefined, + _reason: 'startFileSearch' + }); return this._searchService.fileSearch(query, token).then(result => { return result.results.map(m => m.resource); @@ -172,8 +152,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { const workspace = this._contextService.getWorkspace(); const folders = workspace.folders.map(folder => folder.uri); - const queryBuilder = this._instantiationService.createInstance(QueryBuilder); - const query = queryBuilder.text(pattern, folders, options); + const query = this._queryBuilder.text(pattern, folders, options); query._reason = 'startTextSearch'; const onProgress = (p: ISearchProgressItem) => { @@ -203,6 +182,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { const query = queryBuilder.file(folders, { _reason: 'checkExists', includePattern: includes.join(', '), + expandPatterns: true, exists: true }); @@ -227,7 +207,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { }); } - $resolveProxy(url: string): Promise { + $resolveProxy(url: string): Promise { return this._windowService.resolveProxy(url); } } @@ -241,7 +221,7 @@ CommandsRegistry.registerCommand('_workbench.enterWorkspace', async function (ac const runningExtensions = await extensionService.getExtensions(); // If requested extension to disable is running, then reload window with given workspace if (disableExtensions && runningExtensions.some(runningExtension => disableExtensions.some(id => ExtensionIdentifier.equals(runningExtension.identifier, id)))) { - return windowService.openWindow([URI.file(workspace.fsPath)], { args: { _: [], 'disable-extension': disableExtensions } }); + return windowService.openWindow([{ uri: workspace, typeHint: 'file' }], { args: { _: [], 'disable-extension': disableExtensions } }); } } diff --git a/src/vs/workbench/api/node/apiCommands.ts b/src/vs/workbench/api/node/apiCommands.ts index f67de9fc84..721fa7af77 100644 --- a/src/vs/workbench/api/node/apiCommands.ts +++ b/src/vs/workbench/api/node/apiCommands.ts @@ -3,21 +3,16 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { tmpdir } from 'os'; -import { posix } from 'path'; import * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; -import { isMalformedFileUri } from 'vs/base/common/resources'; import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters'; import { CommandsRegistry, ICommandService, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { EditorViewColumn } from 'vs/workbench/api/shared/editor'; -import { EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService'; +import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowsService, IOpenSettings } from 'vs/platform/windows/common/windows'; import { IDownloadService } from 'vs/platform/download/common/download'; -import { generateUuid } from 'vs/base/common/uuid'; // ----------------------------------------------------------------- // The following commands are registered on both sides separately. @@ -27,7 +22,7 @@ import { generateUuid } from 'vs/base/common/uuid'; // ----------------------------------------------------------------- export interface ICommandsExecutor { - executeCommand(id: string, ...args: any[]): Promise; + executeCommand(id: string, ...args: any[]): Promise; } function adjustHandler(handler: (executor: ICommandsExecutor, ...args: any[]) => any): ICommandHandler { @@ -36,36 +31,42 @@ function adjustHandler(handler: (executor: ICommandsExecutor, ...args: any[]) => }; } -export class PreviewHTMLAPICommand { - public static ID = 'vscode.previewHtml'; - public static execute(executor: ICommandsExecutor, uri: URI, position?: vscode.ViewColumn, label?: string, options?: any): Promise { - return executor.executeCommand('_workbench.previewHtml', - uri, - typeof position === 'number' && typeConverters.ViewColumn.from(position), - label, - options - ); - } +interface IOpenFolderAPICommandOptions { + forceNewWindow?: boolean; + noRecentEntry?: boolean; + recentEntryLabel?: string; } -CommandsRegistry.registerCommand(PreviewHTMLAPICommand.ID, adjustHandler(PreviewHTMLAPICommand.execute)); export class OpenFolderAPICommand { public static ID = 'vscode.openFolder'; - public static execute(executor: ICommandsExecutor, uri?: URI, forceNewWindow?: boolean): Promise { + public static execute(executor: ICommandsExecutor, uri?: URI, forceNewWindow?: boolean): Promise; + public static execute(executor: ICommandsExecutor, uri?: URI, options?: IOpenFolderAPICommandOptions): Promise; + public static execute(executor: ICommandsExecutor, uri?: URI, arg: boolean | IOpenFolderAPICommandOptions = {}): Promise { + if (typeof arg === 'boolean') { + arg = { forceNewWindow: arg }; + } if (!uri) { - return executor.executeCommand('_files.pickFolderAndOpen', forceNewWindow); + return executor.executeCommand('_files.pickFolderAndOpen', arg.forceNewWindow); } - let correctedUri = isMalformedFileUri(uri); - if (correctedUri) { - // workaround for #55916 and #55891, will be removed in 1.28 - console.warn(`'vscode.openFolder' command invoked with an invalid URI (file:// scheme missing): '${uri}'. Converted to a 'file://' URI: ${correctedUri}`); - uri = correctedUri; + const options: IOpenSettings = { forceNewWindow: arg.forceNewWindow }; + if (arg.noRecentEntry) { + options.args = { _: [], 'skip-add-to-recently-opened': true }; } - - return executor.executeCommand('_files.windowOpen', { folderURIs: [uri], forceNewWindow }); + uri = URI.revive(uri); + return executor.executeCommand('_files.windowOpen', [{ uri, label: arg.recentEntryLabel }], options); } } -CommandsRegistry.registerCommand(OpenFolderAPICommand.ID, adjustHandler(OpenFolderAPICommand.execute)); +CommandsRegistry.registerCommand({ + id: OpenFolderAPICommand.ID, + handler: adjustHandler(OpenFolderAPICommand.execute), + description: { + description: 'Open a folder or workspace in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder/workspace unless the newWindow parameter is set to true.', + args: [ + { name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', constraint: (value: any) => value === undefined || value instanceof URI }, + { name: 'options', description: '(optional) Options. Object with the following properties: `forceNewWindow `: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. `noRecentEntry`: Wheter the opened URI will appear in the \'Open Recent\' list. Defaults to true. `recentEntryLabel`: The label used for \'Open Recent\' list. Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.', constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean' } + ] + } +}); export class DiffAPICommand { public static ID = 'vscode.diff'; @@ -84,8 +85,8 @@ CommandsRegistry.registerCommand(DiffAPICommand.ID, adjustHandler(DiffAPICommand export class OpenAPICommand { public static ID = 'vscode.open'; public static execute(executor: ICommandsExecutor, resource: URI, columnOrOptions?: vscode.ViewColumn | vscode.TextDocumentShowOptions, label?: string): Promise { - let options: ITextEditorOptions; - let position: EditorViewColumn; + let options: ITextEditorOptions | undefined; + let position: EditorViewColumn | undefined; if (columnOrOptions) { if (typeof columnOrOptions === 'number') { @@ -106,15 +107,19 @@ export class OpenAPICommand { } CommandsRegistry.registerCommand(OpenAPICommand.ID, adjustHandler(OpenAPICommand.execute)); -CommandsRegistry.registerCommand('_workbench.removeFromRecentlyOpened', function (accessor: ServicesAccessor, path: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI | string) { +CommandsRegistry.registerCommand('_workbench.removeFromRecentlyOpened', function (accessor: ServicesAccessor, uri: URI) { const windowsService = accessor.get(IWindowsService); - - return windowsService.removeFromRecentlyOpened([path]).then(() => undefined); + return windowsService.removeFromRecentlyOpened([uri]).then(() => undefined); }); export class RemoveFromRecentlyOpenedAPICommand { public static ID = 'vscode.removeFromRecentlyOpened'; - public static execute(executor: ICommandsExecutor, path: string): Promise { + public static execute(executor: ICommandsExecutor, path: string | URI): Promise { + if (typeof path === 'string') { + path = path.match(/^[^:/?#]+:\/\//) ? URI.parse(path) : URI.file(path); + } else { + path = URI.revive(path); // called from extension host + } return executor.executeCommand('_workbench.removeFromRecentlyOpened', path); } } @@ -126,11 +131,33 @@ export class SetEditorLayoutAPICommand { return executor.executeCommand('layoutEditorGroups', layout); } } -CommandsRegistry.registerCommand(SetEditorLayoutAPICommand.ID, adjustHandler(SetEditorLayoutAPICommand.execute)); +CommandsRegistry.registerCommand({ + id: SetEditorLayoutAPICommand.ID, + handler: adjustHandler(SetEditorLayoutAPICommand.execute), + description: { + description: 'Set Editor Layout', + args: [{ + name: 'args', + schema: { + 'type': 'object', + 'required': ['groups'], + 'properties': { + 'orientation': { + 'type': 'number', + 'default': 0, + 'enum': [0, 1] + }, + 'groups': { + '$ref': '#/definitions/editorGroupsSchema', // defined in keybindingService.ts ... + 'default': [{}, {}], + } + } + } + }] + } +}); CommandsRegistry.registerCommand('_workbench.downloadResource', function (accessor: ServicesAccessor, resource: URI) { const downloadService = accessor.get(IDownloadService); - const location = posix.join(tmpdir(), generateUuid()); - - return downloadService.download(resource, location).then(() => URI.file(location)); -}); \ No newline at end of file + return downloadService.download(resource).then(location => URI.file(location)); +}); diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index e4dbb9e8d7..dd8dd22353 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -8,7 +8,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; import { TernarySearchTree } from 'vs/base/common/map'; -import * as paths from 'vs/base/common/paths'; +import * as path from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; @@ -17,8 +17,8 @@ import { OverviewRulerLane } from 'vs/editor/common/model'; import * as languageConfiguration from 'vs/editor/common/modes/languageConfiguration'; import { score } from 'vs/editor/common/modes/languageSelector'; import * as files from 'vs/platform/files/common/files'; -import pkg from 'vs/platform/node/package'; -import product from 'vs/platform/node/product'; +import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/node/product'; import { ExtHostContext, IInitData, IMainContext, MainContext } from 'vs/workbench/api/node/extHost.protocol'; import { ExtHostApiCommands } from 'vs/workbench/api/node/extHostApiCommands'; import { ExtHostClipboard } from 'vs/workbench/api/node/extHostClipboard'; @@ -60,11 +60,13 @@ import { ExtHostUrls } from 'vs/workbench/api/node/extHostUrls'; import { ExtHostWebviews } from 'vs/workbench/api/node/extHostWebview'; import { ExtHostWindow } from 'vs/workbench/api/node/extHostWindow'; import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; -import { IExtensionDescription, throwProposedApiError, checkProposedApiEnabled, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { throwProposedApiError, checkProposedApiEnabled, nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry'; import * as vscode from 'vscode'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { originalFSPath } from 'vs/base/common/resources'; +import { CLIServer } from 'vs/workbench/api/node/extHostCLIServer'; export interface IExtensionApiFactory { (extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode; @@ -91,7 +93,7 @@ export function createApiFactory( extHostStorage: ExtHostStorage ): IExtensionApiFactory { - let schemeTransformer: ISchemeTransformer | null = null; + const schemeTransformer: ISchemeTransformer | null = null; // Addressable instances rpcProtocol.set(ExtHostContext.ExtHostLogService, extHostLogService); @@ -113,18 +115,22 @@ export function createApiFactory( const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol, extHostLanguageFeatures)); const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService(rpcProtocol, extHostDocumentsAndEditors)); const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands)); - const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol, extHostConfiguration, extHostLogService, extHostCommands)); + const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol, extHostConfiguration, extHostLogService)); // {{SQL CARBON EDIT}} // const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(rpcProtocol, extHostWorkspace, extensionService, extHostDocumentsAndEditors, extHostConfiguration, extHostTerminalService, extHostCommands)); const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, extHostLogService)); + const extHostComment = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands, extHostDocuments)); const extHostSearch = rpcProtocol.set(ExtHostContext.ExtHostSearch, new ExtHostSearch(rpcProtocol, schemeTransformer, extHostLogService)); - const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace, extHostDocumentsAndEditors, extHostConfiguration)); + const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace, extHostDocumentsAndEditors, extHostConfiguration, extHostTerminalService)); const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol)); rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService); const extHostProgress = rpcProtocol.set(ExtHostContext.ExtHostProgress, new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress))); - const exthostCommentProviders = rpcProtocol.set(ExtHostContext.ExtHostComments, new ExtHostComments(rpcProtocol, extHostCommands.converter, extHostDocuments)); const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, new ExtHostOutputService(initData.logsLocation, rpcProtocol)); rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage); + if (initData.remoteAuthority) { + const cliServer = new CLIServer(extHostCommands); + process.env['VSCODE_IPC_HOOK_CLI'] = cliServer.ipcHandlePath; + } // Check that no named customers are missing const expected: ProxyIdentifier[] = Object.keys(ExtHostContext).map((key) => (ExtHostContext)[key]); @@ -176,23 +182,6 @@ export function createApiFactory( }; })(); - // Warn when trying to use the vscode.previewHtml command as it does not work properly in all scenarios and - // has security concerns. - const checkCommand = (() => { - let done = !extension.isUnderDevelopment; - const informOnce = () => { - if (!done) { - done = true; - console.warn(`Extension '${extension.identifier.value}' uses the 'vscode.previewHtml' command which is deprecated and will be removed. Please update your extension to use the Webview API: https://go.microsoft.com/fwlink/?linkid=2039309`); - } - }; - return (commandId: string) => { - if (commandId === 'vscode.previewHtml') { - informOnce(); - } - return commandId; - }; - })(); // namespace: commands const commands: typeof vscode.commands = { @@ -201,7 +190,7 @@ export function createApiFactory( }, registerTextEditorCommand(id: string, callback: (textEditor: vscode.TextEditor, edit: vscode.TextEditorEdit, ...args: any[]) => void, thisArg?: any): vscode.Disposable { return extHostCommands.registerCommand(true, id, (...args: any[]): any => { - let activeTextEditor = extHostEditors.getActiveTextEditor(); + const activeTextEditor = extHostEditors.getActiveTextEditor(); if (!activeTextEditor) { console.warn('Cannot execute ' + id + ' because there is no active text editor.'); return undefined; @@ -221,8 +210,8 @@ export function createApiFactory( }); }, registerDiffInformationCommand: proposedApiFunction(extension, (id: string, callback: (diff: vscode.LineChange[], ...args: any[]) => any, thisArg?: any): vscode.Disposable => { - return extHostCommands.registerCommand(true, id, async (...args: any[]) => { - let activeTextEditor = extHostEditors.getActiveTextEditor(); + return extHostCommands.registerCommand(true, id, async (...args: any[]): Promise => { + const activeTextEditor = extHostEditors.getActiveTextEditor(); if (!activeTextEditor) { console.warn('Cannot execute ' + id + ' because there is no active text editor.'); return undefined; @@ -233,7 +222,7 @@ export function createApiFactory( }); }), executeCommand(id: string, ...args: any[]): Thenable { - return extHostCommands.executeCommand(checkCommand(id), ...args); + return extHostCommands.executeCommand(id, ...args); }, getCommands(filterInternal: boolean = false): Thenable { return extHostCommands.getCommands(filterInternal); @@ -244,9 +233,9 @@ export function createApiFactory( const env: typeof vscode.env = Object.freeze({ get machineId() { return initData.telemetryInfo.machineId; }, get sessionId() { return initData.telemetryInfo.sessionId; }, - get language() { return platform.language; }, + get language() { return platform.language!; }, get appName() { return product.nameLong; }, - get appRoot() { return initData.environment.appRoot.fsPath; }, + get appRoot() { return initData.environment.appRoot!.fsPath; }, get logLevel() { checkProposedApiEnabled(extension); return typeConverters.LogLevel.to(extHostLogService.getLevel()); @@ -265,8 +254,8 @@ export function createApiFactory( // namespace: extensions const extensions: typeof vscode.extensions = { - getExtension(extensionId: string): Extension { - let desc = extensionRegistry.getExtensionDescription(extensionId); + getExtension(extensionId: string): Extension | undefined { + const desc = extensionRegistry.getExtensionDescription(extensionId); if (desc) { return new Extension(extensionService, desc); } @@ -306,6 +295,10 @@ export function createApiFactory( registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable { return extHostLanguageFeatures.registerCodeLensProvider(extension, checkSelector(selector), provider); }, + registerCodeInsetProvider(selector: vscode.DocumentSelector, provider: vscode.CodeInsetProvider): vscode.Disposable { + checkProposedApiEnabled(extension); + return extHostLanguageFeatures.registerCodeInsetProvider(extension, checkSelector(selector), provider); + }, registerDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable { return extHostLanguageFeatures.registerDefinitionProvider(extension, checkSelector(selector), provider); }, @@ -426,6 +419,9 @@ export function createApiFactory( onDidChangeActiveTerminal(listener, thisArg?, disposables?) { return extHostTerminalService.onDidChangeActiveTerminal(listener, thisArg, disposables); }, + onDidChangeTerminalDimensions(listener, thisArg?, disposables?) { + return extHostTerminalService.onDidChangeTerminalDimensions(listener, thisArg, disposables); + }, get state() { return extHostWindow.state; }, @@ -442,7 +438,7 @@ export function createApiFactory( return extHostMessageService.showMessage(extension, Severity.Error, message, first, rest); }, showQuickPick(items: any, options: vscode.QuickPickOptions, token?: vscode.CancellationToken): any { - return extHostQuickOpen.showQuickPick(items, extension.enableProposedApi, options, token); + return extHostQuickOpen.showQuickPick(items, !!extension.enableProposedApi, options, token); }, showWorkspaceFolderPick(options: vscode.WorkspaceFolderPickOptions) { return extHostQuickOpen.showWorkspaceFolderPick(options); @@ -473,17 +469,17 @@ export function createApiFactory( return extHostOutputService.createOutputChannel(name); }, createWebviewPanel(viewType: string, title: string, showOptions: vscode.ViewColumn | { viewColumn: vscode.ViewColumn, preserveFocus?: boolean }, options: vscode.WebviewPanelOptions & vscode.WebviewOptions): vscode.WebviewPanel { - return extHostWebviews.createWebview(extension, viewType, title, showOptions, options); + return extHostWebviews.createWebviewPanel(extension, viewType, title, showOptions, options); }, - createTerminal(nameOrOptions: vscode.TerminalOptions | string, shellPath?: string, shellArgs?: string[]): vscode.Terminal { + createTerminal(nameOrOptions?: vscode.TerminalOptions | string, shellPath?: string, shellArgs?: string[]): vscode.Terminal { if (typeof nameOrOptions === 'object') { return extHostTerminalService.createTerminalFromOptions(nameOrOptions); } return extHostTerminalService.createTerminal(nameOrOptions, shellPath, shellArgs); }, - createTerminalRenderer: proposedApiFunction(extension, (name: string) => { + createTerminalRenderer(name: string): vscode.TerminalRenderer { return extHostTerminalService.createTerminalRenderer(name); - }), + }, registerTreeDataProvider(viewId: string, treeDataProvider: vscode.TreeDataProvider): vscode.Disposable { return extHostTreeViews.registerTreeDataProvider(viewId, treeDataProvider, extension); }, @@ -500,7 +496,7 @@ export function createApiFactory( return extHostUrls.registerUriHandler(extension.identifier, handler); }, createQuickPick(): vscode.QuickPick { - return extHostQuickOpen.createQuickPick(extension.identifier, extension.enableProposedApi); + return extHostQuickOpen.createQuickPick(extension.identifier, !!extension.enableProposedApi); }, createInputBox(): vscode.InputBox { return extHostQuickOpen.createInputBox(extension.identifier); @@ -533,7 +529,7 @@ export function createApiFactory( onDidChangeWorkspaceFolders: function (listener, thisArgs?, disposables?) { return extHostWorkspace.onDidChangeWorkspace(listener, thisArgs, disposables); }, - asRelativePath: (pathOrUri, includeWorkspace) => { + asRelativePath: (pathOrUri, includeWorkspace?) => { return extHostWorkspace.getRelativePath(pathOrUri, includeWorkspace); }, findFiles: (include, exclude, maxResults?, token?) => { @@ -572,7 +568,7 @@ export function createApiFactory( openTextDocument(uriOrFileNameOrOptions?: vscode.Uri | string | { language?: string; content?: string; }) { let uriPromise: Thenable; - let options = uriOrFileNameOrOptions as { language?: string; content?: string; }; + const options = uriOrFileNameOrOptions as { language?: string; content?: string; }; if (typeof uriOrFileNameOrOptions === 'string') { uriPromise = Promise.resolve(URI.file(uriOrFileNameOrOptions)); } else if (uriOrFileNameOrOptions instanceof URI) { @@ -585,8 +581,7 @@ export function createApiFactory( return uriPromise.then(uri => { return extHostDocuments.ensureDocumentData(uri).then(() => { - const data = extHostDocuments.getDocumentData(uri); - return data && data.document; + return extHostDocuments.getDocument(uri); }); }); }, @@ -631,14 +626,17 @@ export function createApiFactory( registerTextSearchProvider: proposedApiFunction(extension, (scheme, provider) => { return extHostSearch.registerTextSearchProvider(scheme, provider); }), - registerFileIndexProvider: proposedApiFunction(extension, (scheme, provider) => { - return extHostSearch.registerFileIndexProvider(scheme, provider); - }), registerDocumentCommentProvider: proposedApiFunction(extension, (provider: vscode.DocumentCommentProvider) => { - return exthostCommentProviders.registerDocumentCommentProvider(extension.identifier, provider); + return extHostComment.registerDocumentCommentProvider(extension.identifier, provider); }), registerWorkspaceCommentProvider: proposedApiFunction(extension, (provider: vscode.WorkspaceCommentProvider) => { - return exthostCommentProviders.registerWorkspaceCommentProvider(extension.identifier, provider); + return extHostComment.registerWorkspaceCommentProvider(extension.identifier, provider); + }), + registerRemoteAuthorityResolver: proposedApiFunction(extension, (authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver) => { + return extensionService.registerRemoteAuthorityResolver(authorityPrefix, resolver); + }), + registerResourceLabelFormatter: proposedApiFunction(extension, (formatter: vscode.ResourceLabelFormatter) => { + return extHostFileSystem.registerResourceLabelFormatter(formatter); }), onDidRenameFile: proposedApiFunction(extension, (listener, thisArg?, disposables?) => { return extHostFileSystemEvent.onDidRenameFile(listener, thisArg, disposables); @@ -651,13 +649,19 @@ export function createApiFactory( // namespace: scm const scm: typeof vscode.scm = { get inputBox() { - return extHostSCM.getLastInputBox(extension); + return extHostSCM.getLastInputBox(extension)!; // Strict null override - Deprecated api }, createSourceControl(id: string, label: string, rootUri?: vscode.Uri) { return extHostSCM.createSourceControl(extension, id, label, rootUri); } }; + const comment: typeof vscode.comment = { + createCommentController(id: string, label: string) { + return extHostComment.createCommentController(extension, id, label); + } + }; + // {{SQL CARBON EDIT}} -- no-op debug extensibility API // namespace: debug const debug: typeof vscode.debug = { @@ -694,7 +698,7 @@ export function createApiFactory( registerDebugAdapterTrackerFactory(debugType: string, factory: vscode.DebugAdapterTrackerFactory) { return undefined; }, - startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration) { + startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSession?: vscode.DebugSession) { return undefined; }, addBreakpoints(breakpoints: vscode.Breakpoint[]) { @@ -742,6 +746,7 @@ export function createApiFactory( extensions, languages, scm, + comment, tasks, window, workspace, @@ -752,6 +757,7 @@ export function createApiFactory( CodeActionKind: extHostTypes.CodeActionKind, CodeActionTrigger: extHostTypes.CodeActionTrigger, CodeLens: extHostTypes.CodeLens, + CodeInset: extHostTypes.CodeInset, Color: extHostTypes.Color, ColorInformation: extHostTypes.ColorInformation, ColorPresentation: extHostTypes.ColorPresentation, @@ -775,6 +781,7 @@ export function createApiFactory( DocumentSymbol: extHostTypes.DocumentSymbol, EndOfLine: extHostTypes.EndOfLine, EventEmitter: Emitter, + CustomExecution: extHostTypes.CustomExecution, FileChangeType: extHostTypes.FileChangeType, FileSystemError: extHostTypes.FileSystemError, FileType: files.FileType, @@ -794,9 +801,9 @@ export function createApiFactory( QuickInputButtons: extHostTypes.QuickInputButtons, Range: extHostTypes.Range, RelativePattern: extHostTypes.RelativePattern, + ResolvedAuthority: extHostTypes.ResolvedAuthority, Selection: extHostTypes.Selection, SelectionRange: extHostTypes.SelectionRange, - SelectionRangeKind: extHostTypes.SelectionRangeKind, ShellExecution: extHostTypes.ShellExecution, ShellQuoting: extHostTypes.ShellQuoting, SignatureHelpTriggerKind: extHostTypes.SignatureHelpTriggerKind, @@ -833,18 +840,6 @@ export function createApiFactory( }; } -/** - * Returns the original fs path (using the original casing for the drive letter) - */ -export function originalFSPath(uri: URI): string { - const result = uri.fsPath; - if (/^[a-zA-Z]:/.test(result) && uri.path.charAt(1).toLowerCase() === result.charAt(0)) { - // Restore original drive letter casing - return uri.path.charAt(1) + result.substr(1); - } - return result; -} - class Extension implements vscode.Extension { private _extensionService: ExtHostExtensionService; @@ -852,13 +847,13 @@ class Extension implements vscode.Extension { public id: string; public extensionPath: string; - public packageJSON: any; + public packageJSON: IExtensionDescription; constructor(extensionService: ExtHostExtensionService, description: IExtensionDescription) { this._extensionService = extensionService; this._identifier = description.identifier; this.id = description.identifier.value; - this.extensionPath = paths.normalize(originalFSPath(description.extensionLocation), true); + this.extensionPath = path.normalize(originalFSPath(description.extensionLocation)); this.packageJSON = description; } @@ -867,6 +862,9 @@ class Extension implements vscode.Extension { } get exports(): T { + if (this.packageJSON.api === 'none') { + return undefined!; // Strict nulloverride - Public api + } return this._extensionService.getExtensionExports(this._identifier); } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 586aabf7d4..f73d7bfd31 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -25,7 +25,7 @@ import { ResourceLabelFormatter } from 'vs/platform/label/common/label'; import { LogLevel } from 'vs/platform/log/common/log'; import { IMarkerData } from 'vs/platform/markers/common/markers'; import { IPickOptions, IQuickInputButton, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { IPatternInfo, IRawFileMatch2, IRawQuery, IRawTextQuery, ISearchCompleteStats } from 'vs/platform/search/common/search'; +import { IPatternInfo, IRawFileMatch2, IRawQuery, IRawTextQuery, ISearchCompleteStats } from 'vs/workbench/services/search/common/search'; import { StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/platform/statusbar/common/statusbar'; import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; @@ -33,44 +33,50 @@ import { EndOfLine, IFileOperationOptions, TextEditorLineNumbersStyle } from 'vs import { EditorViewColumn } from 'vs/workbench/api/shared/editor'; import { TaskDTO, TaskExecutionDTO, TaskFilterDTO, TaskHandleDTO, TaskProcessEndedDTO, TaskProcessStartedDTO, TaskSystemInfoDTO, TaskSetDTO } from 'vs/workbench/api/shared/tasks'; import { ITreeItem, IRevealOptions } from 'vs/workbench/common/views'; -import { IAdapterDescriptor, IConfig, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug'; -import { ITextQueryBuilderOptions } from 'vs/workbench/parts/search/common/queryBuilder'; -import { ITerminalDimensions } from 'vs/workbench/parts/terminal/common/terminal'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { IAdapterDescriptor, IConfig, ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug'; +import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; +import { ITerminalDimensions } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; import { IRPCProtocol, createExtHostContextProxyIdentifier as createExtId, createMainContextProxyIdentifier as createMainId } from 'vs/workbench/services/extensions/node/proxyIdentifier'; import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import * as vscode from 'vscode'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IRemoteConsoleLog } from 'vs/base/node/console'; +import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset'; // {{SQL CARBON EDIT}} import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; - appRoot: URI; - appSettingsHome: URI; - extensionDevelopmentLocationURI: URI; - extensionTestsPath: string; + appRoot?: URI; + appSettingsHome?: URI; + extensionDevelopmentLocationURI?: URI; + extensionTestsLocationURI?: URI; globalStorageHome: URI; + userHome: URI; } -export interface IWorkspaceData { +export interface IStaticWorkspaceData { id: string; name: string; + configuration?: UriComponents | null; +} + +export interface IWorkspaceData extends IStaticWorkspaceData { folders: { uri: UriComponents, name: string, index: number }[]; - configuration?: UriComponents; } export interface IInitData { - commit: string; + commit?: string; parentPid: number; environment: IEnvironment; - workspace: IWorkspaceData; + workspace?: IStaticWorkspaceData | null; resolvedExtensions: ExtensionIdentifier[]; + hostExtensions: ExtensionIdentifier[]; extensions: IExtensionDescription[]; telemetryInfo: ITelemetryInfo; logLevel: LogLevel; @@ -105,7 +111,7 @@ export interface MainThreadClipboardShape extends IDisposable { export interface MainThreadCommandsShape extends IDisposable { $registerCommand(id: string): void; $unregisterCommand(id: string): void; - $executeCommand(id: string, args: any[]): Promise; + $executeCommand(id: string, args: any[]): Promise; $getCommands(): Promise; } @@ -113,10 +119,22 @@ export interface CommentProviderFeatures { startDraftLabel?: string; deleteDraftLabel?: string; finishDraftLabel?: string; - reactionGroup?: vscode.CommentReaction[]; + reactionGroup?: modes.CommentReaction[]; } export interface MainThreadCommentsShape extends IDisposable { + $registerCommentController(handle: number, id: string, label: string): void; + $unregisterCommentController(handle: number): void; + $updateCommentControllerFeatures(handle: number, features: CommentProviderFeatures): void; + $createCommentThread(handle: number, commentThreadHandle: number, threadId: string, resource: UriComponents, range: IRange, comments: modes.Comment[], acceptInputCommand: modes.Command | undefined, additionalCommands: modes.Command[], collapseState: modes.CommentThreadCollapsibleState): modes.CommentThread2 | undefined; + $deleteCommentThread(handle: number, commentThreadHandle: number): void; + $updateComments(handle: number, commentThreadHandle: number, comments: modes.Comment[]): void; + $setInputValue(handle: number, input: string): void; + $updateCommentThreadAcceptInputCommand(handle: number, commentThreadHandle: number, acceptInputCommand: modes.Command): void; + $updateCommentThreadAdditionalCommands(handle: number, commentThreadHandle: number, additionalCommands: modes.Command[]): void; + $updateCommentThreadCollapsibleState(handle: number, commentThreadHandle: number, collapseState: modes.CommentThreadCollapsibleState): void; + $updateCommentThreadRange(handle: number, commentThreadHandle: number, range: IRange): void; + $updateCommentThreadLabel(handle: number, commentThreadHandle: number, label: string): void; $registerDocumentCommentProvider(handle: number, features: CommentProviderFeatures): void; $unregisterDocumentCommentProvider(handle: number): void; $registerWorkspaceCommentProvider(handle: number, extensionId: ExtensionIdentifier): void; @@ -125,12 +143,12 @@ export interface MainThreadCommentsShape extends IDisposable { } export interface MainThreadConfigurationShape extends IDisposable { - $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any, resource: UriComponents): Promise; - $removeConfigurationOption(target: ConfigurationTarget, key: string, resource: UriComponents): Promise; + $updateConfigurationOption(target: ConfigurationTarget | null, key: string, value: any, resource: UriComponents | undefined): Promise; + $removeConfigurationOption(target: ConfigurationTarget | null, key: string, resource: UriComponents | undefined): Promise; } export interface MainThreadDiagnosticsShape extends IDisposable { - $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void; + $changeMany(owner: string, entries: [UriComponents, IMarkerData[] | undefined][]): void; $clear(owner: string): void; } @@ -150,14 +168,14 @@ export interface MainThreadDialogSaveOptions { } export interface MainThreadDiaglogsShape extends IDisposable { - $showOpenDialog(options: MainThreadDialogOpenOptions): Promise; - $showSaveDialog(options: MainThreadDialogSaveOptions): Promise; + $showOpenDialog(options: MainThreadDialogOpenOptions): Promise; + $showSaveDialog(options: MainThreadDialogSaveOptions): Promise; } export interface MainThreadDecorationsShape extends IDisposable { $registerDecorationProvider(handle: number, label: string): void; $unregisterDecorationProvider(handle: number): void; - $onDidChange(handle: number, resources: UriComponents[]): void; + $onDidChange(handle: number, resources: UriComponents[] | null): void; } export interface MainThreadDocumentContentProvidersShape extends IDisposable { @@ -174,6 +192,7 @@ export interface MainThreadDocumentsShape extends IDisposable { export interface ITextEditorConfigurationUpdate { tabSize?: number | 'auto'; + indentSize?: number | 'tabSize'; insertSpaces?: boolean | 'auto'; cursorStyle?: TextEditorCursorStyle; lineNumbers?: TextEditorLineNumbersStyle; @@ -181,6 +200,7 @@ export interface ITextEditorConfigurationUpdate { export interface IResolvedTextEditorConfiguration { tabSize: number; + indentSize: number; insertSpaces: boolean; cursorStyle: TextEditorCursorStyle; lineNumbers: TextEditorLineNumbersStyle; @@ -210,7 +230,7 @@ export interface ITextDocumentShowOptions { } export interface MainThreadTextEditorsShape extends IDisposable { - $tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): Promise; + $tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): Promise; $registerTextEditorDecorationType(key: string, options: editorCommon.IDecorationRenderOptions): void; $removeTextEditorDecorationType(key: string): void; $tryShowEditor(id: string, position: EditorViewColumn): Promise; @@ -297,7 +317,8 @@ export interface ISerializedSignatureHelpProviderMetadata { export interface MainThreadLanguageFeaturesShape extends IDisposable { $unregister(handle: number): void; $registerDocumentSymbolProvider(handle: number, selector: ISerializedDocumentFilter[], label: string): void; - $registerCodeLensSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number): void; + $registerCodeLensSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number | undefined): void; + $registerCodeInsetSupport(handle: number, selector: ISerializedDocumentFilter[], eventHandle: number | undefined): void; $emitCodeLensEvent(eventHandle: number, event?: any): void; $registerDefinitionSupport(handle: number, selector: ISerializedDocumentFilter[]): void; $registerDeclarationSupport(handle: number, selector: ISerializedDocumentFilter[]): void; @@ -307,9 +328,9 @@ export interface MainThreadLanguageFeaturesShape extends IDisposable { $registerDocumentHighlightProvider(handle: number, selector: ISerializedDocumentFilter[]): void; $registerReferenceSupport(handle: number, selector: ISerializedDocumentFilter[]): void; $registerQuickFixSupport(handle: number, selector: ISerializedDocumentFilter[], supportedKinds?: string[]): void; - $registerDocumentFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], label: string): void; - $registerRangeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], label: string): void; - $registerOnTypeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], autoFormatTriggerCharacters: string[]): void; + $registerDocumentFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: ExtensionIdentifier): void; + $registerRangeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], extensionId: ExtensionIdentifier): void; + $registerOnTypeFormattingSupport(handle: number, selector: ISerializedDocumentFilter[], autoFormatTriggerCharacters: string[], extensionId: ExtensionIdentifier): void; $registerNavigateTypeSupport(handle: number): void; $registerRenameSupport(handle: number, selector: ISerializedDocumentFilter[], supportsResolveInitialValues: boolean): void; $registerSuggestSupport(handle: number, selector: ISerializedDocumentFilter[], triggerCharacters: string[], supportsResolveDetails: boolean): void; @@ -332,17 +353,17 @@ export interface MainThreadMessageOptions { } export interface MainThreadMessageServiceShape extends IDisposable { - $showMessage(severity: Severity, message: string, options: MainThreadMessageOptions, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise; + $showMessage(severity: Severity, message: string, options: MainThreadMessageOptions, commands: { title: string; isCloseAffordance: boolean; handle: number; }[]): Promise; } export interface MainThreadOutputServiceShape extends IDisposable { $register(label: string, log: boolean, file?: UriComponents): Promise; - $append(channelId: string, value: string): Promise; - $update(channelId: string): Promise; - $clear(channelId: string, till: number): Promise; - $reveal(channelId: string, preserveFocus: boolean): Promise; - $close(channelId: string): Promise; - $dispose(channelId: string): Promise; + $append(channelId: string, value: string): Promise | undefined; + $update(channelId: string): Promise | undefined; + $clear(channelId: string, till: number): Promise | undefined; + $reveal(channelId: string, preserveFocus: boolean): Promise | undefined; + $close(channelId: string): Promise | undefined; + $dispose(channelId: string): Promise | undefined; } export interface MainThreadProgressShape extends IDisposable { @@ -353,7 +374,7 @@ export interface MainThreadProgressShape extends IDisposable { } export interface MainThreadTerminalServiceShape extends IDisposable { - $createTerminal(name?: string, shellPath?: string, shellArgs?: string[], cwd?: string | URI, env?: { [key: string]: string }, waitOnExit?: boolean, strictEnv?: boolean): Promise<{ id: number, name: string }>; + $createTerminal(name?: string, shellPath?: string, shellArgs?: string[], cwd?: string | UriComponents, env?: { [key: string]: string | null }, waitOnExit?: boolean, strictEnv?: boolean): Promise<{ id: number, name: string }>; $createTerminalRenderer(name: string): Promise; $dispose(terminalId: number): void; $hide(terminalId: number): void; @@ -442,21 +463,21 @@ export interface TransferInputBox extends BaseTransferQuickInput { } export interface MainThreadQuickOpenShape extends IDisposable { - $show(instance: number, options: IPickOptions, token: CancellationToken): Promise; + $show(instance: number, options: IPickOptions, token: CancellationToken): Promise; $setItems(instance: number, items: TransferQuickPickItems[]): Promise; $setError(instance: number, error: Error): Promise; - $input(options: vscode.InputBoxOptions, validateInput: boolean, token: CancellationToken): Promise; + $input(options: vscode.InputBoxOptions | undefined, validateInput: boolean, token: CancellationToken): Promise; $createOrUpdate(params: TransferQuickInput): Promise; $dispose(id: number): Promise; } export interface MainThreadStatusBarShape extends IDisposable { - $setEntry(id: number, extensionId: ExtensionIdentifier, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: MainThreadStatusBarAlignment, priority: number): void; + $setEntry(id: number, extensionId: ExtensionIdentifier | undefined, text: string, tooltip: string, command: string, color: string | ThemeColor, alignment: MainThreadStatusBarAlignment, priority: number | undefined): void; $dispose(id: number): void; } export interface MainThreadStorageShape extends IDisposable { - $getValue(shared: boolean, key: string): Promise; + $getValue(shared: boolean, key: string): Promise; $setValue(shared: boolean, key: string, value: object): Promise; } @@ -466,6 +487,8 @@ export interface MainThreadTelemetryShape extends IDisposable { export type WebviewPanelHandle = string; +export type WebviewInsetHandle = number; + export interface WebviewPanelShowOptions { readonly viewColumn?: EditorViewColumn; readonly preserveFocus?: boolean; @@ -473,13 +496,15 @@ export interface WebviewPanelShowOptions { export interface MainThreadWebviewsShape extends IDisposable { $createWebviewPanel(handle: WebviewPanelHandle, viewType: string, title: string, showOptions: WebviewPanelShowOptions, options: vscode.WebviewPanelOptions & vscode.WebviewOptions, extensionId: ExtensionIdentifier, extensionLocation: UriComponents): void; + $createWebviewCodeInset(handle: WebviewInsetHandle, symbolId: string, options: vscode.WebviewOptions, extensionLocation: UriComponents | undefined): void; $disposeWebview(handle: WebviewPanelHandle): void; $reveal(handle: WebviewPanelHandle, showOptions: WebviewPanelShowOptions): void; $setTitle(handle: WebviewPanelHandle, value: string): void; $setIconPath(handle: WebviewPanelHandle, value: { light: UriComponents, dark: UriComponents } | undefined): void; - $setHtml(handle: WebviewPanelHandle, value: string): void; - $setOptions(handle: WebviewPanelHandle, options: vscode.WebviewOptions): void; - $postMessage(handle: WebviewPanelHandle, value: any): Promise; + + $setHtml(handle: WebviewPanelHandle | WebviewInsetHandle, value: string): void; + $setOptions(handle: WebviewPanelHandle | WebviewInsetHandle, options: vscode.WebviewOptions): void; + $postMessage(handle: WebviewPanelHandle | WebviewInsetHandle, value: any): Promise; $registerSerializer(viewType: string): void; $unregisterSerializer(viewType: string): void; @@ -508,12 +533,12 @@ export interface ExtHostUrlsShape { } export interface MainThreadWorkspaceShape extends IDisposable { - $startFileSearch(includePattern: string, includeFolder: URI, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise; + $startFileSearch(includePattern: string | undefined, includeFolder: UriComponents | undefined, excludePatternOrDisregardExcludes: string | false | undefined, maxResults: number | undefined, token: CancellationToken): Promise; $startTextSearch(query: IPatternInfo, options: ITextQueryBuilderOptions, requestId: number, token: CancellationToken): Promise; $checkExists(includes: string[], token: CancellationToken): Promise; $saveAll(includeUntitled?: boolean): Promise; $updateWorkspaceFolders(extensionName: string, index: number, deleteCount: number, workspaceFoldersToAdd: { uri: UriComponents, name?: string }[]): Promise; - $resolveProxy(url: string): Promise; + $resolveProxy(url: string): Promise; } export interface IFileChangeDto { @@ -524,14 +549,14 @@ export interface IFileChangeDto { export interface MainThreadFileSystemShape extends IDisposable { $registerFileSystemProvider(handle: number, scheme: string, capabilities: FileSystemProviderCapabilities): void; $unregisterProvider(handle: number): void; - $setUriFormatter(formatter: ResourceLabelFormatter): void; + $registerResourceLabelFormatter(handle: number, formatter: ResourceLabelFormatter): void; + $unregisterResourceLabelFormatter(handle: number): void; $onFileSystemChange(handle: number, resource: IFileChangeDto[]): void; } export interface MainThreadSearchShape extends IDisposable { $registerFileSearchProvider(handle: number, scheme: string): void; $registerTextSearchProvider(handle: number, scheme: string): void; - $registerFileIndexProvider(handle: number, scheme: string): void; $unregisterProvider(handle: number): void; $handleFileMatch(handle: number, session: number, data: UriComponents[]): void; $handleTextMatch(handle: number, session: number, data: IRawFileMatch2[]): void; @@ -539,21 +564,22 @@ export interface MainThreadSearchShape extends IDisposable { } export interface MainThreadTaskShape extends IDisposable { + $createTaskId(task: TaskDTO): Promise; $registerTaskProvider(handle: number): Promise; $unregisterTaskProvider(handle: number): Promise; $fetchTasks(filter?: TaskFilterDTO): Promise; $executeTask(task: TaskHandleDTO | TaskDTO): Promise; $terminateTask(id: string): Promise; $registerTaskSystem(scheme: string, info: TaskSystemInfoDTO): void; + $customExecutionComplete(id: string, result?: number): Promise; } export interface MainThreadExtensionServiceShape extends IDisposable { - $localShowMessage(severity: Severity, msg: string): void; + $activateExtension(extensionId: ExtensionIdentifier, activationEvent: string | null): Promise; $onWillActivateExtension(extensionId: ExtensionIdentifier): void; - $onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string): void; - $onExtensionActivationFailed(extensionId: ExtensionIdentifier): void; + $onDidActivateExtension(extensionId: ExtensionIdentifier, startup: boolean, codeLoadingTime: number, activateCallTime: number, activateResolvedTime: number, activationEvent: string | null): void; + $onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): Promise; $onExtensionRuntimeError(extensionId: ExtensionIdentifier, error: SerializedError): void; - $addMessage(extensionId: ExtensionIdentifier, severity: Severity, message: string): void; } export interface SCMProviderFeatures { @@ -561,7 +587,7 @@ export interface SCMProviderFeatures { count?: number; commitTemplate?: string; acceptInputCommand?: modes.Command; - statusBarCommands?: modes.Command[]; + statusBarCommands?: CommandDto[]; } export interface SCMGroupFeatures { @@ -614,16 +640,17 @@ export type DebugSessionUUID = string; export interface MainThreadDebugServiceShape extends IDisposable { $registerDebugTypes(debugTypes: string[]): void; + $sessionCached(sessionID: string): void; $acceptDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): void; - $acceptDAError(handle: number, name: string, message: string, stack: string): void; - $acceptDAExit(handle: number, code: number, signal: string): void; + $acceptDAError(handle: number, name: string, message: string, stack: string | undefined): void; + $acceptDAExit(handle: number, code: number | undefined, signal: string | undefined): void; $registerDebugConfigurationProvider(type: string, hasProvideMethod: boolean, hasResolveMethod: boolean, hasProvideDaMethod: boolean, handle: number): Promise; $registerDebugAdapterDescriptorFactory(type: string, handle: number): Promise; $registerDebugAdapterTrackerFactory(type: string, handle: number); $unregisterDebugConfigurationProvider(handle: number): void; $unregisterDebugAdapterDescriptorFactory(handle: number): void; $unregisterDebugAdapterTrackerFactory(handle: number): void; - $startDebugging(folder: UriComponents | undefined, nameOrConfig: string | vscode.DebugConfiguration): Promise; + $startDebugging(folder: UriComponents | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSessionID: string | undefined): Promise; $customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): Promise; $appendDebugConsole(value: string): void; $startBreakpointEvents(): void; @@ -653,7 +680,7 @@ export interface ExtHostDiagnosticsShape { } export interface ExtHostDocumentContentProvidersShape { - $provideTextDocumentContent(handle: number, uri: UriComponents): Promise; + $provideTextDocumentContent(handle: number, uri: UriComponents): Promise; } export interface IModelAddedData { @@ -681,7 +708,7 @@ export interface ITextEditorAddData { options: IResolvedTextEditorConfiguration; selections: ISelection[]; visibleRanges: IRange[]; - editorPosition: EditorViewColumn; + editorPosition: EditorViewColumn | undefined; } export interface ITextEditorPositionData { [id: string]: EditorViewColumn; @@ -706,7 +733,7 @@ export interface IDocumentsAndEditorsDelta { addedDocuments?: IModelAddedData[]; removedEditors?: string[]; addedEditors?: ITextEditorAddData[]; - newActiveEditor?: string; + newActiveEditor?: string | null; } export interface ExtHostDocumentsAndEditorsShape { @@ -722,7 +749,8 @@ export interface ExtHostTreeViewsShape { } export interface ExtHostWorkspaceShape { - $acceptWorkspaceData(workspace: IWorkspaceData): void; + $initializeWorkspace(workspace: IWorkspaceData | null): void; + $acceptWorkspaceData(workspace: IWorkspaceData | null): void; $handleTextSearchResult(result: IRawFileMatch2, requestId: number): void; } @@ -753,7 +781,7 @@ export interface ExtHostExtensionServiceShape { $resolveAuthority(remoteAuthority: string): Promise; $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise; $activateByEvent(activationEvent: string): Promise; - $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise; + $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise; $deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise; @@ -774,7 +802,7 @@ export interface ExtHostFileSystemEventServiceShape { } export interface ObjectIdentifier { - $ident: number; + $ident?: number; } export namespace ObjectIdentifier { @@ -839,9 +867,9 @@ export interface WorkspaceSymbolsDto extends IdObject { } export interface ResourceFileEditDto { - oldUri: UriComponents; - newUri: UriComponents; - options: IFileOperationOptions; + oldUri?: UriComponents; + newUri?: UriComponents; + options?: IFileOperationOptions; } export interface ResourceTextEditDto { @@ -857,7 +885,7 @@ export interface WorkspaceEditDto { rejectReason?: string; } -export function reviveWorkspaceEditDto(data: WorkspaceEditDto): modes.WorkspaceEdit { +export function reviveWorkspaceEditDto(data: WorkspaceEditDto | undefined): modes.WorkspaceEdit { if (data && data.edits) { for (const edit of data.edits) { if (typeof (edit).resource === 'object') { @@ -871,50 +899,67 @@ export function reviveWorkspaceEditDto(data: WorkspaceEditDto): modes.WorkspaceE return data; } +export type CommandDto = ObjectIdentifier & modes.Command; + export interface CodeActionDto { title: string; edit?: WorkspaceEditDto; diagnostics?: IMarkerData[]; - command?: modes.Command; + command?: CommandDto; kind?: string; isPreferred?: boolean; } +export interface LinkDto extends ObjectIdentifier { + range: IRange; + url?: string | UriComponents; +} + +export interface CodeLensDto extends ObjectIdentifier { + range: IRange; + id?: string; + command?: CommandDto; +} + +export type CodeInsetDto = ObjectIdentifier & codeInset.ICodeInsetSymbol; + export interface ExtHostLanguageFeaturesShape { - $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $resolveCodeLens(handle: number, resource: UriComponents, symbol: modes.ICodeLensSymbol, token: CancellationToken): Promise; + $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise; + $provideCodeLenses(handle: number, resource: UriComponents, token: CancellationToken): Promise; + $resolveCodeLens(handle: number, resource: UriComponents, symbol: CodeLensDto, token: CancellationToken): Promise; + $provideCodeInsets(handle: number, resource: UriComponents, token: CancellationToken): Promise; + $resolveCodeInset(handle: number, resource: UriComponents, symbol: CodeInsetDto, token: CancellationToken): Promise; $provideDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideDeclaration(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideImplementation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; $provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise; - $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise; - $provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: modes.FormattingOptions, token: CancellationToken): Promise; - $provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: modes.FormattingOptions, token: CancellationToken): Promise; - $provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Promise; + $provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise; + $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise; + $provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: modes.FormattingOptions, token: CancellationToken): Promise; + $provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: modes.FormattingOptions, token: CancellationToken): Promise; + $provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Promise; $provideWorkspaceSymbols(handle: number, search: string, token: CancellationToken): Promise; - $resolveWorkspaceSymbol(handle: number, symbol: WorkspaceSymbolDto, token: CancellationToken): Promise; + $resolveWorkspaceSymbol(handle: number, symbol: WorkspaceSymbolDto, token: CancellationToken): Promise; $releaseWorkspaceSymbols(handle: number, id: number): void; - $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise; - $resolveRenameLocation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; - $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise; + $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise; + $resolveRenameLocation(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise; $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, suggestion: modes.CompletionItem, token: CancellationToken): Promise; $releaseCompletionItems(handle: number, id: number): void; - $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise; - $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $resolveDocumentLink(handle: number, link: modes.ILink, token: CancellationToken): Promise; + $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise; + $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise; + $resolveDocumentLink(handle: number, link: LinkDto, token: CancellationToken): Promise; $provideDocumentColors(handle: number, resource: UriComponents, token: CancellationToken): Promise; - $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise; - $provideFoldingRanges(handle: number, resource: UriComponents, context: modes.FoldingContext, token: CancellationToken): Promise; - $provideSelectionRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise; + $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise; + $provideFoldingRanges(handle: number, resource: UriComponents, context: modes.FoldingContext, token: CancellationToken): Promise; + $provideSelectionRanges(handle: number, resource: UriComponents, positions: IPosition[], token: CancellationToken): Promise; } export interface ExtHostQuickOpenShape { $onItemSelected(handle: number): void; - $validateInput(input: string): Promise; + $validateInput(input: string): Promise; $onDidChangeActive(sessionId: number, handles: number[]): void; $onDidChangeSelection(sessionId: number, handles: number[]): void; $onDidAccept(sessionId: number): void; @@ -927,8 +972,8 @@ export interface ShellLaunchConfigDto { name?: string; executable?: string; args?: string[] | string; - cwd?: string | URI; - env?: { [key: string]: string }; + cwd?: string | UriComponents; + env?: { [key: string]: string | null }; } export interface ExtHostTerminalServiceShape { @@ -939,8 +984,8 @@ export interface ExtHostTerminalServiceShape { $acceptTerminalProcessData(id: number, data: string): void; $acceptTerminalRendererInput(id: number, data: string): void; $acceptTerminalTitleChange(id: number, name: string): void; - $acceptTerminalRendererDimensions(id: number, cols: number, rows: number): void; - $createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, activeWorkspaceRootUri: URI, cols: number, rows: number): void; + $acceptTerminalDimensions(id: number, cols: number, rows: number): void; + $createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, activeWorkspaceRootUri: UriComponents, cols: number, rows: number): void; $acceptProcessInput(id: number, data: string): void; $acceptProcessResize(id: number, cols: number, rows: number): void; $acceptProcessShutdown(id: number, immediate: boolean): void; @@ -949,7 +994,7 @@ export interface ExtHostTerminalServiceShape { } export interface ExtHostSCMShape { - $provideOriginalResource(sourceControlHandle: number, uri: UriComponents, token: CancellationToken): Promise; + $provideOriginalResource(sourceControlHandle: number, uri: UriComponents, token: CancellationToken): Promise; $onInputBoxValueChange(sourceControlHandle: number, value: string): void; $executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): Promise; $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): Promise<[string, number] | undefined>; @@ -958,7 +1003,7 @@ export interface ExtHostSCMShape { export interface ExtHostTaskShape { $provideTasks(handle: number, validTypes: { [key: string]: boolean; }): Thenable; - $onDidStartTask(execution: TaskExecutionDTO): void; + $onDidStartTask(execution: TaskExecutionDTO, terminalId: number): void; $onDidStartTaskProcess(value: TaskProcessStartedDTO): void; $onDidEndTaskProcess(value: TaskProcessEndedDTO): void; $OnDidEndTask(execution: TaskExecutionDTO): void; @@ -1024,13 +1069,13 @@ export interface ExtHostDebugServiceShape { $startDASession(handle: number, session: IDebugSessionDto): Promise; $stopDASession(handle: number): Promise; $sendDAMessage(handle: number, message: DebugProtocol.ProtocolMessage): void; - $resolveDebugConfiguration(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig): Promise; + $resolveDebugConfiguration(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig): Promise; $provideDebugConfigurations(handle: number, folder: UriComponents | undefined): Promise; $legacyDebugAdapterExecutable(handle: number, folderUri: UriComponents | undefined): Promise; // TODO@AW legacy $provideDebugAdapter(handle: number, session: IDebugSessionDto): Promise; $acceptDebugSessionStarted(session: IDebugSessionDto): void; $acceptDebugSessionTerminated(session: IDebugSessionDto): void; - $acceptDebugSessionActiveChanged(session: IDebugSessionDto): void; + $acceptDebugSessionActiveChanged(session: IDebugSessionDto | undefined): void; $acceptDebugSessionCustomEvent(session: IDebugSessionDto, event: any): void; $acceptBreakpointsDelta(delta: IBreakpointsDeltaDto): void; } @@ -1067,9 +1112,14 @@ export interface ExtHostProgressShape { } export interface ExtHostCommentsShape { - $provideDocumentComments(handle: number, document: UriComponents): Promise; - $createNewCommentThread(handle: number, document: UriComponents, range: IRange, text: string): Promise; - $replyToCommentThread(handle: number, document: UriComponents, range: IRange, commentThread: modes.CommentThread, text: string): Promise; + $provideDocumentComments(handle: number, document: UriComponents): Promise; + $createNewCommentThread(handle: number, document: UriComponents, range: IRange, text: string): Promise; + $onCommentWidgetInputChange(commentControllerHandle: number, input: string | undefined): Promise; + $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise; + $provideReactionGroup(commentControllerHandle: number): Promise; + $toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise; + $createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): void; + $replyToCommentThread(handle: number, document: UriComponents, range: IRange, commentThread: modes.CommentThread, text: string): Promise; $editComment(handle: number, document: UriComponents, comment: modes.Comment, text: string): Promise; $deleteComment(handle: number, document: UriComponents, comment: modes.Comment): Promise; $startDraft(handle: number, document: UriComponents): Promise; @@ -1077,11 +1127,11 @@ export interface ExtHostCommentsShape { $finishDraft(handle: number, document: UriComponents): Promise; $addReaction(handle: number, document: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise; $deleteReaction(handle: number, document: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise; - $provideWorkspaceComments(handle: number): Promise; + $provideWorkspaceComments(handle: number): Promise; } export interface ExtHostStorageShape { - $acceptValue(shared: boolean, key: string, value: object): void; + $acceptValue(shared: boolean, key: string, value: object | undefined): void; } // --- proxy identifiers diff --git a/src/vs/workbench/api/node/extHostApiCommands.ts b/src/vs/workbench/api/node/extHostApiCommands.ts index 17b7447dc9..c5cb7c5a50 100644 --- a/src/vs/workbench/api/node/extHostApiCommands.ts +++ b/src/vs/workbench/api/node/extHostApiCommands.ts @@ -11,13 +11,13 @@ import * as types from 'vs/workbench/api/node/extHostTypes'; import { IRawColorInfo, WorkspaceEditDto } from 'vs/workbench/api/node/extHost.protocol'; import { ISingleEditOperation } from 'vs/editor/common/model'; import * as modes from 'vs/editor/common/modes'; -import * as search from 'vs/workbench/parts/search/common/search'; +import * as search from 'vs/workbench/contrib/search/common/search'; import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; import { CustomCodeAction } from 'vs/workbench/api/node/extHostLanguageFeatures'; -import { ICommandsExecutor, PreviewHTMLAPICommand, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand } from './apiCommands'; -import { EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService'; -import { isFalsyOrEmpty, isNonEmptyArray } from 'vs/base/common/arrays'; +import { ICommandsExecutor, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand } from './apiCommands'; +import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { isFalsyOrEmpty } from 'vs/base/common/arrays'; export class ExtHostApiCommands { @@ -134,7 +134,8 @@ export class ExtHostApiCommands { description: 'Execute code action provider.', args: [ { name: 'uri', description: 'Uri of a text document', constraint: URI }, - { name: 'range', description: 'Range in a text document', constraint: types.Range } + { name: 'range', description: 'Range in a text document', constraint: types.Range }, + { name: 'kind', description: '(optional) Code action kind to return code actions for', constraint: (value: any) => !value || typeof value.value === 'string' }, ], returns: 'A promise that resolves to an array of Command-instances.' }); @@ -199,7 +200,7 @@ export class ExtHostApiCommands { description: 'Execute selection range provider.', args: [ { name: 'uri', description: 'Uri of a text document', constraint: URI }, - { name: 'position', description: 'Position in a text document', constraint: types.Position } + { name: 'positions', description: 'Positions in a text document', constraint: a => Array.isArray(a) } ], returns: 'A promise that resolves to an array of ranges.' }); @@ -218,25 +219,11 @@ export class ExtHostApiCommands { }; }; - this._register(PreviewHTMLAPICommand.ID, adjustHandler(PreviewHTMLAPICommand.execute), { - description: ` - Render the HTML of the resource in an editor view. - - See [working with the HTML preview](https://code.visualstudio.com/docs/extensionAPI/vscode-api-commands#working-with-the-html-preview) for more information about the HTML preview's integration with the editor and for best practices for extension authors. - `, - args: [ - { name: 'uri', description: 'Uri of the resource to preview.', constraint: (value: any) => value instanceof URI || typeof value === 'string' }, - { name: 'column', description: '(optional) Column in which to preview.', constraint: (value: any) => typeof value === 'undefined' || (typeof value === 'number' && typeof types.ViewColumn[value] === 'string') }, - { name: 'label', description: '(optional) An human readable string that is used as title for the preview.', constraint: (v: any) => typeof v === 'string' || typeof v === 'undefined' }, - { name: 'options', description: '(optional) Options for controlling webview environment.', constraint: (v: any) => typeof v === 'object' || typeof v === 'undefined' } - ] - }); - this._register(OpenFolderAPICommand.ID, adjustHandler(OpenFolderAPICommand.execute), { description: 'Open a folder or workspace in the current window or new window depending on the newWindow argument. Note that opening in the same window will shutdown the current extension host process and start a new one on the given folder/workspace unless the newWindow parameter is set to true.', args: [ { name: 'uri', description: '(optional) Uri of the folder or workspace file to open. If not provided, a native dialog will ask the user for the folder', constraint: (value: any) => value === undefined || value instanceof URI }, - { name: 'newWindow', description: '(optional) Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window.', constraint: (value: any) => value === undefined || typeof value === 'boolean' } + { name: 'options', description: '(optional) Options. Object with the following properties: `forceNewWindow `: Whether to open the folder/workspace in a new window or the same. Defaults to opening in the same window. `noRecentEntry`: Wheter the opened URI will appear in the \'Open Recent\' list. Defaults to true. Note, for backward compatibility, options can also be of type boolean, representing the `forceNewWindow` setting.', constraint: (value: any) => value === undefined || typeof value === 'object' || typeof value === 'boolean' } ] }); @@ -276,7 +263,7 @@ export class ExtHostApiCommands { // --- command impl private _register(id: string, handler: (...args: any[]) => any, description?: ICommandHandlerDescription): void { - let disposable = this._commands.registerCommand(false, id, handler, this, description); + const disposable = this._commands.registerCommand(false, id, handler, this, description); this._disposables.push(disposable); } @@ -298,7 +285,7 @@ export class ExtHostApiCommands { }); } - private _executeDefinitionProvider(resource: URI, position: types.Position): Promise { + private _executeDefinitionProvider(resource: URI, position: types.Position): Promise { const args = { resource, position: position && typeConverters.Position.from(position) @@ -307,7 +294,7 @@ export class ExtHostApiCommands { .then(tryMapWith(typeConverters.location.to)); } - private _executeDeclaraionProvider(resource: URI, position: types.Position): Promise { + private _executeDeclaraionProvider(resource: URI, position: types.Position): Promise { const args = { resource, position: position && typeConverters.Position.from(position) @@ -316,7 +303,7 @@ export class ExtHostApiCommands { .then(tryMapWith(typeConverters.location.to)); } - private _executeTypeDefinitionProvider(resource: URI, position: types.Position): Promise { + private _executeTypeDefinitionProvider(resource: URI, position: types.Position): Promise { const args = { resource, position: position && typeConverters.Position.from(position) @@ -325,7 +312,7 @@ export class ExtHostApiCommands { .then(tryMapWith(typeConverters.location.to)); } - private _executeImplementationProvider(resource: URI, position: types.Position): Promise { + private _executeImplementationProvider(resource: URI, position: types.Position): Promise { const args = { resource, position: position && typeConverters.Position.from(position) @@ -334,7 +321,7 @@ export class ExtHostApiCommands { .then(tryMapWith(typeConverters.location.to)); } - private _executeHoverProvider(resource: URI, position: types.Position): Promise { + private _executeHoverProvider(resource: URI, position: types.Position): Promise { const args = { resource, position: position && typeConverters.Position.from(position) @@ -343,7 +330,7 @@ export class ExtHostApiCommands { .then(tryMapWith(typeConverters.Hover.to)); } - private _executeDocumentHighlights(resource: URI, position: types.Position): Promise { + private _executeDocumentHighlights(resource: URI, position: types.Position): Promise { const args = { resource, position: position && typeConverters.Position.from(position) @@ -352,7 +339,7 @@ export class ExtHostApiCommands { .then(tryMapWith(typeConverters.DocumentHighlight.to)); } - private _executeReferenceProvider(resource: URI, position: types.Position): Promise { + private _executeReferenceProvider(resource: URI, position: types.Position): Promise { const args = { resource, position: position && typeConverters.Position.from(position) @@ -372,13 +359,13 @@ export class ExtHostApiCommands { return undefined; } if (value.rejectReason) { - return Promise.reject(new Error(value.rejectReason)); + return Promise.reject(new Error(value.rejectReason)); } return typeConverters.WorkspaceEdit.to(value); }); } - private _executeSignatureHelpProvider(resource: URI, position: types.Position, triggerCharacter: string): Promise { + private _executeSignatureHelpProvider(resource: URI, position: types.Position, triggerCharacter: string): Promise { const args = { resource, position: position && typeConverters.Position.from(position), @@ -392,7 +379,7 @@ export class ExtHostApiCommands { }); } - private _executeCompletionItemProvider(resource: URI, position: types.Position, triggerCharacter: string, maxItemsToResolve: number): Promise { + private _executeCompletionItemProvider(resource: URI, position: types.Position, triggerCharacter: string, maxItemsToResolve: number): Promise { const args = { resource, position: position && typeConverters.Position.from(position), @@ -420,16 +407,15 @@ export class ExtHostApiCommands { }); } - private _executeSelectionRangeProvider(resource: URI, position: types.Position): Promise { + private _executeSelectionRangeProvider(resource: URI, positions: types.Position[]): Promise { + const pos = positions.map(typeConverters.Position.from); const args = { resource, - position: position && typeConverters.Position.from(position) + position: pos[0], + positions: pos }; - return this._commands.executeCommand('_executeSelectionRangeProvider', args).then(result => { - if (isNonEmptyArray(result)) { - return result.map(typeConverters.SelectionRange.to); - } - return []; + return this._commands.executeCommand('_executeSelectionRangeProvider', args).then(result => { + return result.map(oneResult => oneResult.map(typeConverters.SelectionRange.to)); }); } @@ -447,26 +433,26 @@ export class ExtHostApiCommands { }); } - private _executeDocumentSymbolProvider(resource: URI): Promise { + private _executeDocumentSymbolProvider(resource: URI): Promise { const args = { resource }; - return this._commands.executeCommand('_executeDocumentSymbolProvider', args).then(value => { + return this._commands.executeCommand('_executeDocumentSymbolProvider', args).then((value): vscode.SymbolInformation[] | undefined => { if (isFalsyOrEmpty(value)) { return undefined; } class MergedInfo extends types.SymbolInformation implements vscode.DocumentSymbol { static to(symbol: modes.DocumentSymbol): MergedInfo { - let res = new MergedInfo( + const res = new MergedInfo( symbol.name, typeConverters.SymbolKind.to(symbol.kind), - symbol.containerName, + symbol.containerName || '', new types.Location(resource, typeConverters.Range.to(symbol.range)) ); res.detail = symbol.detail; res.range = res.location.range; res.selectionRange = typeConverters.Range.to(symbol.selectionRange); - res.children = symbol.children && symbol.children.map(MergedInfo.to); + res.children = symbol.children ? symbol.children.map(MergedInfo.to) : []; return res; } @@ -474,19 +460,24 @@ export class ExtHostApiCommands { range: vscode.Range; selectionRange: vscode.Range; children: vscode.DocumentSymbol[]; + containerName: string; } return value.map(MergedInfo.to); }); } - private _executeCodeActionProvider(resource: URI, range: types.Range): Promise<(vscode.CodeAction | vscode.Command)[]> { + private _executeCodeActionProvider(resource: URI, range: types.Range, kind?: string): Promise<(vscode.CodeAction | vscode.Command)[] | undefined> { const args = { resource, - range: typeConverters.Range.from(range) + range: typeConverters.Range.from(range), + kind }; return this._commands.executeCommand('_executeCodeActionProvider', args) .then(tryMapWith(codeAction => { if (codeAction._isSynthetic) { + if (!codeAction.command) { + throw new Error('Synthetic code actions must have a command'); + } return this._commands.converter.fromInternal(codeAction.command); } else { const ret = new types.CodeAction( @@ -504,18 +495,18 @@ export class ExtHostApiCommands { })); } - private _executeCodeLensProvider(resource: URI, itemResolveCount: number): Promise { + private _executeCodeLensProvider(resource: URI, itemResolveCount: number): Promise { const args = { resource, itemResolveCount }; return this._commands.executeCommand('_executeCodeLensProvider', args) .then(tryMapWith(item => { return new types.CodeLens( typeConverters.Range.to(item.range), - this._commands.converter.fromInternal(item.command)); + item.command ? this._commands.converter.fromInternal(item.command) : undefined); })); } - private _executeFormatDocumentProvider(resource: URI, options: vscode.FormattingOptions): Promise { + private _executeFormatDocumentProvider(resource: URI, options: vscode.FormattingOptions): Promise { const args = { resource, options @@ -524,7 +515,7 @@ export class ExtHostApiCommands { .then(tryMapWith(edit => new types.TextEdit(typeConverters.Range.to(edit.range), edit.text))); } - private _executeFormatRangeProvider(resource: URI, range: types.Range, options: vscode.FormattingOptions): Promise { + private _executeFormatRangeProvider(resource: URI, range: types.Range, options: vscode.FormattingOptions): Promise { const args = { resource, range: typeConverters.Range.from(range), @@ -534,7 +525,7 @@ export class ExtHostApiCommands { .then(tryMapWith(edit => new types.TextEdit(typeConverters.Range.to(edit.range), edit.text))); } - private _executeFormatOnTypeProvider(resource: URI, position: types.Position, ch: string, options: vscode.FormattingOptions): Promise { + private _executeFormatOnTypeProvider(resource: URI, position: types.Position, ch: string, options: vscode.FormattingOptions): Promise { const args = { resource, position: typeConverters.Position.from(position), @@ -545,7 +536,7 @@ export class ExtHostApiCommands { .then(tryMapWith(edit => new types.TextEdit(typeConverters.Range.to(edit.range), edit.text))); } - private _executeDocumentLinkProvider(resource: URI): Promise { + private _executeDocumentLinkProvider(resource: URI): Promise { return this._commands.executeCommand('_executeLinkProvider', resource) .then(tryMapWith(typeConverters.DocumentLink.to)); } diff --git a/src/vs/workbench/api/node/extHostCLIServer.ts b/src/vs/workbench/api/node/extHostCLIServer.ts new file mode 100644 index 0000000000..faa0748d6c --- /dev/null +++ b/src/vs/workbench/api/node/extHostCLIServer.ts @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net'; +import * as http from 'http'; +import * as fs from 'fs'; +import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; +import { IURIToOpen, URIType } from 'vs/platform/windows/common/windows'; +import { URI } from 'vs/base/common/uri'; +import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; + + +export class CLIServer { + + private _server: http.Server; + private _ipcHandlePath: string | undefined; + + constructor(private _commands: ExtHostCommands) { + this._server = http.createServer((req, res) => this.onRequest(req, res)); + this.setup().catch(err => { + console.error(err); + return ''; + }); + } + + public get ipcHandlePath() { + return this._ipcHandlePath; + } + + private async setup(): Promise { + this._ipcHandlePath = generateRandomPipeName(); + + try { + this._server.listen(this.ipcHandlePath); + this._server.on('error', err => console.error(err)); + } catch (err) { + console.error('Could not start open from terminal server.'); + } + + return this._ipcHandlePath; + } + private collectURIToOpen(strs: string[], typeHint: URIType, result: IURIToOpen[]): void { + if (Array.isArray(strs)) { + for (const s of strs) { + try { + result.push({ uri: URI.parse(s), typeHint }); + } catch (e) { + // ignore + } + } + } + } + + private onRequest(req: http.IncomingMessage, res: http.ServerResponse): void { + const chunks: string[] = []; + req.setEncoding('utf8'); + req.on('data', (d: string) => chunks.push(d)); + req.on('end', () => { + const data = JSON.parse(chunks.join('')); + switch (data.type) { + case 'open': + this.open(data, res); + break; + default: + res.writeHead(404); + res.write(`Unkown message type: ${data.type}`, err => { + if (err) { + console.error(err); + } + }); + res.end(); + break; + } + }); + } + + private open(data: any, res: http.ServerResponse) { + let { fileURIs, folderURIs, forceNewWindow, diffMode, addMode, forceReuseWindow } = data; + if (folderURIs && folderURIs.length || fileURIs && fileURIs.length) { + const urisToOpen: IURIToOpen[] = []; + this.collectURIToOpen(folderURIs, 'folder', urisToOpen); + this.collectURIToOpen(fileURIs, 'file', urisToOpen); + if (!forceReuseWindow && urisToOpen.some(o => o.typeHint === 'folder' || (o.typeHint === 'file' && hasWorkspaceFileExtension(o.uri.path)))) { + forceNewWindow = true; + } + this._commands.executeCommand('_files.windowOpen', urisToOpen, { forceNewWindow, diffMode, addMode, forceReuseWindow }); + } + res.writeHead(200); + res.end(); + } + + dispose(): void { + this._server.close(); + + if (this._ipcHandlePath && process.platform !== 'win32' && fs.existsSync(this._ipcHandlePath)) { + fs.unlinkSync(this._ipcHandlePath); + } + } +} diff --git a/src/vs/workbench/api/node/extHostCommands.ts b/src/vs/workbench/api/node/extHostCommands.ts index 911c9fc750..b808d25097 100644 --- a/src/vs/workbench/api/node/extHostCommands.ts +++ b/src/vs/workbench/api/node/extHostCommands.ts @@ -8,7 +8,7 @@ import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands import * as extHostTypes from 'vs/workbench/api/node/extHostTypes'; import * as extHostTypeConverter from 'vs/workbench/api/node/extHostTypeConverters'; import { cloneAndChange } from 'vs/base/common/objects'; -import { MainContext, MainThreadCommandsShape, ExtHostCommandsShape, ObjectIdentifier, IMainContext } from './extHost.protocol'; +import { MainContext, MainThreadCommandsShape, ExtHostCommandsShape, ObjectIdentifier, IMainContext, CommandDto } from './extHost.protocol'; import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import * as modes from 'vs/editor/common/modes'; @@ -22,7 +22,7 @@ import { URI } from 'vs/base/common/uri'; interface CommandHandler { callback: Function; thisArg: any; - description: ICommandHandlerDescription; + description?: ICommandHandlerDescription; } export interface ArgumentProcessor { @@ -138,7 +138,11 @@ export class ExtHostCommands implements ExtHostCommandsShape { } private _executeContributedCommand(id: string, args: any[]): Promise { - let { callback, thisArg, description } = this._commands.get(id); + const command = this._commands.get(id); + if (!command) { + throw new Error('Unknown command'); + } + let { callback, thisArg, description } = command; if (description) { for (let i = 0; i < description.args.length; i++) { try { @@ -150,7 +154,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { } try { - let result = callback.apply(thisArg, args); + const result = callback.apply(thisArg, args); return Promise.resolve(result); } catch (err) { this._logService.error(err, id); @@ -207,15 +211,19 @@ export class CommandsConverter { this._commands.registerCommand(true, this._delegatingCommandId, this._executeConvertedCommand, this); } - toInternal(command: vscode.Command): modes.Command { + toInternal(command: vscode.Command): CommandDto; + toInternal(command: undefined): undefined; + toInternal(command: vscode.Command | undefined): CommandDto | undefined; + toInternal(command: vscode.Command | undefined): CommandDto | undefined { if (!command) { return undefined; } - const result: modes.Command = { + const result: CommandDto = { + $ident: undefined, id: command.command, - title: command.title + title: command.title, }; if (command.command && isNonEmptyArray(command.arguments)) { @@ -223,7 +231,7 @@ export class CommandsConverter { // means we don't want to send the arguments around const id = this._heap.keep(command); - ObjectIdentifier.mixin(result, id); + result.$ident = id; result.id = this._delegatingCommandId; result.arguments = [id]; @@ -238,10 +246,6 @@ export class CommandsConverter { fromInternal(command: modes.Command): vscode.Command { - if (!command) { - return undefined; - } - const id = ObjectIdentifier.of(command); if (typeof id === 'number') { return this._heap.get(id); @@ -257,7 +261,7 @@ export class CommandsConverter { private _executeConvertedCommand(...args: any[]): Promise { const actualCmd = this._heap.get(args[0]); - return this._commands.executeCommand(actualCmd.command, ...actualCmd.arguments); + return this._commands.executeCommand(actualCmd.command, ...(actualCmd.arguments || [])); } } diff --git a/src/vs/workbench/api/node/extHostComments.ts b/src/vs/workbench/api/node/extHostComments.ts index 51e2bf7d96..7d26fc5451 100644 --- a/src/vs/workbench/api/node/extHostComments.ts +++ b/src/vs/workbench/api/node/extHostComments.ts @@ -8,12 +8,14 @@ import { URI, UriComponents } from 'vs/base/common/uri'; import * as modes from 'vs/editor/common/modes'; import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; import * as extHostTypeConverter from 'vs/workbench/api/node/extHostTypeConverters'; +import * as types from 'vs/workbench/api/node/extHostTypes'; import * as vscode from 'vscode'; import { ExtHostCommentsShape, IMainContext, MainContext, MainThreadCommentsShape } from './extHost.protocol'; -import { CommandsConverter } from './extHostCommands'; +import { CommandsConverter, ExtHostCommands } from './extHostCommands'; import { IRange } from 'vs/editor/common/core/range'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { Event, Emitter } from 'vs/base/common/event'; interface HandlerData { @@ -21,20 +23,137 @@ interface HandlerData { provider: T; } +type ProviderHandle = number; + export class ExtHostComments implements ExtHostCommentsShape { private static handlePool = 0; private _proxy: MainThreadCommentsShape; + private _commentControllers: Map = new Map(); + + private _commentControllersByExtension: Map = new Map(); + private _documentProviders = new Map>(); private _workspaceProviders = new Map>(); constructor( mainContext: IMainContext, - private readonly _commandsConverter: CommandsConverter, + private _commands: ExtHostCommands, private readonly _documents: ExtHostDocuments, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadComments); + + _commands.registerArgumentProcessor({ + processArgument: arg => { + if (arg && arg.$mid === 6) { + const commentController = this._commentControllers.get(arg.handle); + + if (!commentController) { + return arg; + } + + return commentController; + } else if (arg && arg.$mid === 7) { + const commentController = this._commentControllers.get(arg.commentControlHandle); + + if (!commentController) { + return arg; + } + + const commentThread = commentController.getCommentThread(arg.commentThreadHandle); + + if (!commentThread) { + return arg; + } + + return commentThread; + } + + return arg; + } + }); + } + + createCommentController(extension: IExtensionDescription, id: string, label: string): vscode.CommentController { + const handle = ExtHostComments.handlePool++; + const commentController = new ExtHostCommentController(extension, handle, this._commands.converter, this._proxy, id, label); + this._commentControllers.set(commentController.handle, commentController); + + const commentControllers = this._commentControllersByExtension.get(ExtensionIdentifier.toKey(extension.identifier)) || []; + commentControllers.push(commentController); + this._commentControllersByExtension.set(ExtensionIdentifier.toKey(extension.identifier), commentControllers); + + return commentController; + } + + $onCommentWidgetInputChange(commentControllerHandle: number, input: string): Promise { + const commentController = this._commentControllers.get(commentControllerHandle); + + if (!commentController) { + return Promise.resolve(undefined); + } + + commentController.$onCommentWidgetInputChange(input); + return Promise.resolve(commentControllerHandle); + } + + $provideCommentingRanges(commentControllerHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise { + const commentController = this._commentControllers.get(commentControllerHandle); + + if (!commentController || !commentController.commentingRangeProvider) { + return Promise.resolve(undefined); + } + + const document = this._documents.getDocument(URI.revive(uriComponents)); + return asPromise(() => { + return commentController.commentingRangeProvider!.provideCommentingRanges(document, token); + }).then(ranges => ranges ? ranges.map(x => extHostTypeConverter.Range.from(x)) : undefined); + } + + $provideReactionGroup(commentControllerHandle: number): Promise { + const commentController = this._commentControllers.get(commentControllerHandle); + + if (!commentController || !commentController.reactionProvider) { + return Promise.resolve(undefined); + } + + return asPromise(() => { + return commentController!.reactionProvider!.availableReactions; + }).then(reactions => reactions.map(reaction => convertToReaction2(commentController.reactionProvider, reaction))); + } + + $toggleReaction(commentControllerHandle: number, threadHandle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise { + const document = this._documents.getDocument(URI.revive(uri)); + const commentController = this._commentControllers.get(commentControllerHandle); + + if (!commentController || !commentController.reactionProvider || !commentController.reactionProvider.toggleReaction) { + return Promise.resolve(undefined); + } + + return asPromise(() => { + const commentThread = commentController.getCommentThread(threadHandle); + if (commentThread) { + const vscodeComment = commentThread.getComment(comment.commentId); + + if (commentController !== undefined && commentController.reactionProvider && commentController.reactionProvider.toggleReaction && vscodeComment) { + return commentController.reactionProvider.toggleReaction(document, vscodeComment, convertFromReaction(reaction)); + } + } + + return Promise.resolve(undefined); + }); + } + + $createNewCommentWidgetCallback(commentControllerHandle: number, uriComponents: UriComponents, range: IRange, token: CancellationToken): void { + const commentController = this._commentControllers.get(commentControllerHandle); + + if (!commentController || !commentController.emptyCommentThreadFactory) { + return; + } + + const document = this._documents.getDocument(URI.revive(uriComponents)); + commentController.emptyCommentThreadFactory.createEmptyCommentThread(document, extHostTypeConverter.Range.to(range)); } registerWorkspaceCommentProvider( @@ -70,7 +189,7 @@ export class ExtHostComments implements ExtHostCommentsShape { startDraftLabel: provider.startDraftLabel, deleteDraftLabel: provider.deleteDraftLabel, finishDraftLabel: provider.finishDraftLabel, - reactionGroup: provider.reactionGroup + reactionGroup: provider.reactionGroup ? provider.reactionGroup.map(reaction => convertToReaction(provider, reaction)) : undefined }); this.registerListeners(handle, extensionId, provider); @@ -90,10 +209,10 @@ export class ExtHostComments implements ExtHostCommentsShape { return Promise.resolve(null); } - const handlerData = this._documentProviders.get(handle); + const handlerData = this.getDocumentProvider(handle); return asPromise(() => { return handlerData.provider.createNewCommentThread(data.document, ran, text, CancellationToken.None); - }).then(commentThread => commentThread ? convertToCommentThread(handlerData.extensionId, handlerData.provider, commentThread, this._commandsConverter) : null); + }).then(commentThread => commentThread ? convertToCommentThread(handlerData.extensionId, handlerData.provider, commentThread, this._commands.converter) : null); } $replyToCommentThread(handle: number, uri: UriComponents, range: IRange, thread: modes.CommentThread, text: string): Promise { @@ -104,115 +223,96 @@ export class ExtHostComments implements ExtHostCommentsShape { return Promise.resolve(null); } - const handlerData = this._documentProviders.get(handle); + const handlerData = this.getDocumentProvider(handle); return asPromise(() => { return handlerData.provider.replyToCommentThread(data.document, ran, convertFromCommentThread(thread), text, CancellationToken.None); - }).then(commentThread => commentThread ? convertToCommentThread(handlerData.extensionId, handlerData.provider, commentThread, this._commandsConverter) : null); + }).then(commentThread => commentThread ? convertToCommentThread(handlerData.extensionId, handlerData.provider, commentThread, this._commands.converter) : null); } $editComment(handle: number, uri: UriComponents, comment: modes.Comment, text: string): Promise { - const data = this._documents.getDocumentData(URI.revive(uri)); - - if (!data || !data.document) { - throw new Error('Unable to retrieve document from URI'); + const document = this._documents.getDocument(URI.revive(uri)); + const handlerData = this.getDocumentProvider(handle); + if (!handlerData.provider.editComment) { + return Promise.reject(new Error('not implemented')); } - - const handlerData = this._documentProviders.get(handle); return asPromise(() => { - return handlerData.provider.editComment(data.document, convertFromComment(comment), text, CancellationToken.None); + return handlerData.provider.editComment!(document, convertFromComment(comment), text, CancellationToken.None); }); } $deleteComment(handle: number, uri: UriComponents, comment: modes.Comment): Promise { - const data = this._documents.getDocumentData(URI.revive(uri)); - - if (!data || !data.document) { - throw new Error('Unable to retrieve document from URI'); + const document = this._documents.getDocument(URI.revive(uri)); + const handlerData = this.getDocumentProvider(handle); + if (!handlerData.provider.deleteComment) { + return Promise.reject(new Error('not implemented')); } - - const handlerData = this._documentProviders.get(handle); return asPromise(() => { - return handlerData.provider.deleteComment(data.document, convertFromComment(comment), CancellationToken.None); + return handlerData.provider.deleteComment!(document, convertFromComment(comment), CancellationToken.None); }); } $startDraft(handle: number, uri: UriComponents): Promise { - const data = this._documents.getDocumentData(URI.revive(uri)); + const document = this._documents.getDocument(URI.revive(uri)); - if (!data || !data.document) { - throw new Error('Unable to retrieve document from URI'); + const handlerData = this.getDocumentProvider(handle); + if (!handlerData.provider.startDraft) { + return Promise.reject(new Error('not implemented')); } - - const handlerData = this._documentProviders.get(handle); return asPromise(() => { - return handlerData.provider.startDraft(data.document, CancellationToken.None); + return handlerData.provider.startDraft!(document, CancellationToken.None); }); } $deleteDraft(handle: number, uri: UriComponents): Promise { - const data = this._documents.getDocumentData(URI.revive(uri)); - - if (!data || !data.document) { - throw new Error('Unable to retrieve document from URI'); + const document = this._documents.getDocument(URI.revive(uri)); + const handlerData = this.getDocumentProvider(handle); + if (!handlerData.provider.deleteDraft) { + return Promise.reject(new Error('not implemented')); } - - const handlerData = this._documentProviders.get(handle); return asPromise(() => { - return handlerData.provider.deleteDraft(data.document, CancellationToken.None); + return handlerData.provider.deleteDraft!(document, CancellationToken.None); }); } $finishDraft(handle: number, uri: UriComponents): Promise { - const data = this._documents.getDocumentData(URI.revive(uri)); - - if (!data || !data.document) { - throw new Error('Unable to retrieve document from URI'); + const document = this._documents.getDocument(URI.revive(uri)); + const handlerData = this.getDocumentProvider(handle); + if (!handlerData.provider.finishDraft) { + return Promise.reject(new Error('not implemented')); } - - const handlerData = this._documentProviders.get(handle); return asPromise(() => { - return handlerData.provider.finishDraft(data.document, CancellationToken.None); + return handlerData.provider.finishDraft!(document, CancellationToken.None); }); } $addReaction(handle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise { - const data = this._documents.getDocumentData(URI.revive(uri)); - - if (!data || !data.document) { - throw new Error('Unable to retrieve document from URI'); + const document = this._documents.getDocument(URI.revive(uri)); + const handlerData = this.getDocumentProvider(handle); + if (!handlerData.provider.addReaction) { + return Promise.reject(new Error('not implemented')); } - - const handlerData = this._documentProviders.get(handle); - return asPromise(() => { - return handlerData.provider.addReaction(data.document, convertFromComment(comment), reaction); + return handlerData.provider.addReaction!(document, convertFromComment(comment), convertFromReaction(reaction)); }); } $deleteReaction(handle: number, uri: UriComponents, comment: modes.Comment, reaction: modes.CommentReaction): Promise { - const data = this._documents.getDocumentData(URI.revive(uri)); - - if (!data || !data.document) { - throw new Error('Unable to retrieve document from URI'); + const document = this._documents.getDocument(URI.revive(uri)); + const handlerData = this.getDocumentProvider(handle); + if (!handlerData.provider.deleteReaction) { + return Promise.reject(new Error('not implemented')); } - - const handlerData = this._documentProviders.get(handle); - return asPromise(() => { - return handlerData.provider.deleteReaction(data.document, convertFromComment(comment), reaction); + return handlerData.provider.deleteReaction!(document, convertFromComment(comment), convertFromReaction(reaction)); }); } - $provideDocumentComments(handle: number, uri: UriComponents): Promise { - const data = this._documents.getDocumentData(URI.revive(uri)); - if (!data || !data.document) { - return Promise.resolve(null); - } - - const handlerData = this._documentProviders.get(handle); + $provideDocumentComments(handle: number, uri: UriComponents): Promise { + const document = this._documents.getDocument(URI.revive(uri)); + const handlerData = this.getDocumentProvider(handle); return asPromise(() => { - return handlerData.provider.provideDocumentComments(data.document, CancellationToken.None); - }).then(commentInfo => commentInfo ? convertCommentInfo(handle, handlerData.extensionId, handlerData.provider, commentInfo, this._commandsConverter) : null); + return handlerData.provider.provideDocumentComments(document, CancellationToken.None); + }).then(commentInfo => commentInfo ? convertCommentInfo(handle, handlerData.extensionId, handlerData.provider, commentInfo, this._commands.converter) : null); } $provideWorkspaceComments(handle: number): Promise { @@ -224,7 +324,7 @@ export class ExtHostComments implements ExtHostCommentsShape { return asPromise(() => { return handlerData.provider.provideWorkspaceComments(CancellationToken.None); }).then(comments => - comments.map(comment => convertToCommentThread(handlerData.extensionId, handlerData.provider, comment, this._commandsConverter) + comments.map(comment => convertToCommentThread(handlerData.extensionId, handlerData.provider, comment, this._commands.converter) )); } @@ -232,13 +332,241 @@ export class ExtHostComments implements ExtHostCommentsShape { provider.onDidChangeCommentThreads(event => { this._proxy.$onDidCommentThreadsChange(handle, { - changed: event.changed.map(thread => convertToCommentThread(extensionId, provider, thread, this._commandsConverter)), - added: event.added.map(thread => convertToCommentThread(extensionId, provider, thread, this._commandsConverter)), - removed: event.removed.map(thread => convertToCommentThread(extensionId, provider, thread, this._commandsConverter)), + changed: event.changed.map(thread => convertToCommentThread(extensionId, provider, thread, this._commands.converter)), + added: event.added.map(thread => convertToCommentThread(extensionId, provider, thread, this._commands.converter)), + removed: event.removed.map(thread => convertToCommentThread(extensionId, provider, thread, this._commands.converter)), draftMode: !!(provider as vscode.DocumentCommentProvider).startDraft && !!(provider as vscode.DocumentCommentProvider).finishDraft ? (event.inDraftMode ? modes.DraftMode.InDraft : modes.DraftMode.NotInDraft) : modes.DraftMode.NotSupported }); }); } + + private getDocumentProvider(handle: number): HandlerData { + const provider = this._documentProviders.get(handle); + if (!provider) { + throw new Error('unknown provider'); + } + return provider; + } +} + +export class ExtHostCommentThread implements vscode.CommentThread { + private static _handlePool: number = 0; + readonly handle = ExtHostCommentThread._handlePool++; + get threadId(): string { + return this._threadId; + } + + get resource(): vscode.Uri { + return this._resource; + } + + set range(range: vscode.Range) { + if (range.isEqual(this._range)) { + this._range = range; + this._proxy.$updateCommentThreadRange(this._commentController.handle, this.handle, extHostTypeConverter.Range.from(this._range)); + } + } + + get range(): vscode.Range { + return this._range; + } + + private _label: string; + + get label(): string { + return this._label; + } + + set label(label: string) { + this._label = label; + this._proxy.$updateCommentThreadLabel(this._commentController.handle, this.handle, this._label); + } + + get comments(): vscode.Comment[] { + return this._comments; + } + + set comments(newComments: vscode.Comment[]) { + this._proxy.$updateComments(this._commentController.handle, this.handle, newComments.map(cmt => { return convertToModeComment(this._commentController, cmt, this._commandsConverter); })); + this._comments = newComments; + } + + private _acceptInputCommand: vscode.Command; + get acceptInputCommand(): vscode.Command { + return this._acceptInputCommand; + } + + set acceptInputCommand(acceptInputCommand: vscode.Command) { + this._acceptInputCommand = acceptInputCommand; + + const internal = this._commandsConverter.toInternal(acceptInputCommand); + this._proxy.$updateCommentThreadAcceptInputCommand(this._commentController.handle, this.handle, internal); + } + + private _additionalCommands: vscode.Command[] = []; + get additionalCommands(): vscode.Command[] { + return this._additionalCommands; + } + + set additionalCommands(additionalCommands: vscode.Command[]) { + this._additionalCommands = additionalCommands; + + const internals = additionalCommands.map(x => this._commandsConverter.toInternal(x)); + this._proxy.$updateCommentThreadAdditionalCommands(this._commentController.handle, this.handle, internals); + } + + private _collapseState?: vscode.CommentThreadCollapsibleState; + + get collapsibleState(): vscode.CommentThreadCollapsibleState { + return this._collapseState!; + } + + set collapsibleState(newState: vscode.CommentThreadCollapsibleState) { + this._collapseState = newState; + this._proxy.$updateCommentThreadCollapsibleState(this._commentController.handle, this.handle, convertToCollapsibleState(newState)); + } + + constructor( + private _proxy: MainThreadCommentsShape, + private readonly _commandsConverter: CommandsConverter, + private _commentController: ExtHostCommentController, + private _threadId: string, + private _resource: vscode.Uri, + private _range: vscode.Range, + private _comments: vscode.Comment[] + ) { + this._proxy.$createCommentThread( + this._commentController.handle, + this.handle, + this._threadId, + this._resource, + extHostTypeConverter.Range.from(this._range), + this._comments.map(comment => { return convertToModeComment(this._commentController, comment, this._commandsConverter); }), + this._acceptInputCommand ? this._commandsConverter.toInternal(this._acceptInputCommand) : undefined, + this._additionalCommands ? this._additionalCommands.map(x => this._commandsConverter.toInternal(x)) : [], + this._collapseState! + ); + } + + getComment(commentId: string): vscode.Comment | undefined { + const comments = this._comments.filter(comment => comment.commentId === commentId); + + if (comments && comments.length) { + return comments[0]; + } + + return undefined; + } + + dispose() { + this._proxy.$deleteCommentThread( + this._commentController.handle, + this.handle + ); + } + +} + +export class ExtHostCommentInputBox implements vscode.CommentInputBox { + private _onDidChangeValue = new Emitter(); + + get onDidChangeValue(): Event { + return this._onDidChangeValue.event; + } + private _value: string = ''; + get value(): string { + return this._value; + } + + set value(newInput: string) { + this._value = newInput; + this._onDidChangeValue.fire(this._value); + this._proxy.$setInputValue(this.commentControllerHandle, newInput); + } + + constructor( + private _proxy: MainThreadCommentsShape, + + public commentControllerHandle: number, + input: string + ) { + this._value = input; + } + + setInput(input: string) { + this._value = input; + } +} +class ExtHostCommentController implements vscode.CommentController { + get id(): string { + return this._id; + } + + get label(): string { + return this._label; + } + + public inputBox?: ExtHostCommentInputBox; + public activeCommentingRange?: vscode.Range; + + public get handle(): number { + return this._handle; + } + + private _threads: Map = new Map(); + commentingRangeProvider?: vscode.CommentingRangeProvider; + emptyCommentThreadFactory?: vscode.EmptyCommentThreadFactory; + + + private _commentReactionProvider?: vscode.CommentReactionProvider; + + get reactionProvider(): vscode.CommentReactionProvider | undefined { + return this._commentReactionProvider; + } + + set reactionProvider(provider: vscode.CommentReactionProvider | undefined) { + this._commentReactionProvider = provider; + if (provider) { + this._proxy.$updateCommentControllerFeatures(this.handle, { reactionGroup: provider.availableReactions.map(reaction => convertToReaction2(provider, reaction)) }); + } + } + + constructor( + _extension: IExtensionDescription, + private _handle: number, + private readonly _commandsConverter: CommandsConverter, + private _proxy: MainThreadCommentsShape, + private _id: string, + private _label: string + ) { + this._proxy.$registerCommentController(this.handle, _id, _label); + } + + createCommentThread(id: string, resource: vscode.Uri, range: vscode.Range, comments: vscode.Comment[]): vscode.CommentThread { + const commentThread = new ExtHostCommentThread(this._proxy, this._commandsConverter, this, id, resource, range, comments); + this._threads.set(commentThread.handle, commentThread); + return commentThread; + } + + $onCommentWidgetInputChange(input: string) { + if (!this.inputBox) { + this.inputBox = new ExtHostCommentInputBox(this._proxy, this.handle, input); + } else { + this.inputBox.setInput(input); + } + } + + getCommentThread(handle: number) { + return this._threads.get(handle); + } + + dispose(): void { + this._threads.forEach(value => { + value.dispose(); + }); + + this._proxy.$unregisterCommentController(this.handle); + } } function convertCommentInfo(owner: number, extensionId: ExtensionIdentifier, provider: vscode.DocumentCommentProvider, vscodeCommentInfo: vscode.CommentInfo, commandsConverter: CommandsConverter): modes.CommentInfo { @@ -263,8 +591,8 @@ function convertToCommentThread(extensionId: ExtensionIdentifier, provider: vsco function convertFromCommentThread(commentThread: modes.CommentThread): vscode.CommentThread { return { - threadId: commentThread.threadId, - resource: URI.parse(commentThread.resource), + threadId: commentThread.threadId!, + resource: URI.parse(commentThread.resource!), range: extHostTypeConverter.Range.to(commentThread.range), comments: commentThread.comments.map(convertFromComment), collapsibleState: commentThread.collapsibleState @@ -272,7 +600,7 @@ function convertFromCommentThread(commentThread: modes.CommentThread): vscode.Co } function convertFromComment(comment: modes.Comment): vscode.Comment { - let userIconPath: URI; + let userIconPath: URI | undefined; if (comment.userIconPath) { try { userIconPath = URI.parse(comment.userIconPath); @@ -289,7 +617,30 @@ function convertFromComment(comment: modes.Comment): vscode.Comment { canEdit: comment.canEdit, canDelete: comment.canDelete, isDraft: comment.isDraft, - commentReactions: comment.commentReactions + commentReactions: comment.commentReactions ? comment.commentReactions.map(reaction => { + return { + label: reaction.label, + count: reaction.count, + hasReacted: reaction.hasReacted + }; + }) : undefined + }; +} + +function convertToModeComment(commentController: ExtHostCommentController, vscodeComment: vscode.Comment, commandsConverter: CommandsConverter): modes.Comment { + const iconPath = vscodeComment.userIconPath ? vscodeComment.userIconPath.toString() : vscodeComment.gravatar; + + return { + commentId: vscodeComment.commentId, + body: extHostTypeConverter.MarkdownString.from(vscodeComment.body), + userName: vscodeComment.userName, + userIconPath: iconPath, + isDraft: vscodeComment.isDraft, + selectCommand: vscodeComment.selectCommand ? commandsConverter.toInternal(vscodeComment.selectCommand) : undefined, + editCommand: vscodeComment.editCommand ? commandsConverter.toInternal(vscodeComment.editCommand) : undefined, + deleteCommand: vscodeComment.editCommand ? commandsConverter.toInternal(vscodeComment.deleteCommand) : undefined, + label: vscodeComment.label, + commentReactions: vscodeComment.commentReactions ? vscodeComment.commentReactions.map(reaction => convertToReaction2(commentController.reactionProvider, reaction)) : undefined }; } @@ -297,6 +648,7 @@ function convertToComment(provider: vscode.DocumentCommentProvider | vscode.Work const canEdit = !!(provider as vscode.DocumentCommentProvider).editComment && vscodeComment.canEdit; const canDelete = !!(provider as vscode.DocumentCommentProvider).deleteComment && vscodeComment.canDelete; const iconPath = vscodeComment.userIconPath ? vscodeComment.userIconPath.toString() : vscodeComment.gravatar; + return { commentId: vscodeComment.commentId, body: extHostTypeConverter.MarkdownString.from(vscodeComment.body), @@ -304,8 +656,51 @@ function convertToComment(provider: vscode.DocumentCommentProvider | vscode.Work userIconPath: iconPath, canEdit: canEdit, canDelete: canDelete, - command: vscodeComment.command ? commandsConverter.toInternal(vscodeComment.command) : null, + selectCommand: vscodeComment.command ? commandsConverter.toInternal(vscodeComment.command) : undefined, isDraft: vscodeComment.isDraft, - commentReactions: vscodeComment.commentReactions + commentReactions: vscodeComment.commentReactions ? vscodeComment.commentReactions.map(reaction => convertToReaction(provider, reaction)) : undefined }; } + +function convertToReaction(provider: vscode.DocumentCommentProvider | vscode.WorkspaceCommentProvider, reaction: vscode.CommentReaction): modes.CommentReaction { + const providerCanDeleteReaction = !!(provider as vscode.DocumentCommentProvider).deleteReaction; + const providerCanAddReaction = !!(provider as vscode.DocumentCommentProvider).addReaction; + + return { + label: reaction.label, + iconPath: reaction.iconPath ? extHostTypeConverter.pathOrURIToURI(reaction.iconPath) : undefined, + count: reaction.count, + hasReacted: reaction.hasReacted, + canEdit: (reaction.hasReacted && providerCanDeleteReaction) || (!reaction.hasReacted && providerCanAddReaction) + }; +} + +function convertToReaction2(provider: vscode.CommentReactionProvider | undefined, reaction: vscode.CommentReaction): modes.CommentReaction { + return { + label: reaction.label, + iconPath: reaction.iconPath ? extHostTypeConverter.pathOrURIToURI(reaction.iconPath) : undefined, + count: reaction.count, + hasReacted: reaction.hasReacted, + canEdit: provider !== undefined ? !!provider.toggleReaction : false + }; +} + +function convertFromReaction(reaction: modes.CommentReaction): vscode.CommentReaction { + return { + label: reaction.label, + count: reaction.count, + hasReacted: reaction.hasReacted + }; +} + +function convertToCollapsibleState(kind: vscode.CommentThreadCollapsibleState | undefined): modes.CommentThreadCollapsibleState { + if (kind !== undefined) { + switch (kind) { + case types.CommentThreadCollapsibleState.Expanded: + return modes.CommentThreadCollapsibleState.Expanded; + case types.CommentThreadCollapsibleState.Collapsed: + return modes.CommentThreadCollapsibleState.Collapsed; + } + } + return modes.CommentThreadCollapsibleState.Collapsed; +} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHostConfiguration.ts b/src/vs/workbench/api/node/extHostConfiguration.ts index 103bbb9d83..208c0af7de 100644 --- a/src/vs/workbench/api/node/extHostConfiguration.ts +++ b/src/vs/workbench/api/node/extHostConfiguration.ts @@ -43,7 +43,7 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { private readonly _proxy: MainThreadConfigurationShape; private readonly _extHostWorkspace: ExtHostWorkspace; private readonly _barrier: Barrier; - private _actual: ExtHostConfigProvider; + private _actual: ExtHostConfigProvider | null; constructor(proxy: MainThreadConfigurationShape, extHostWorkspace: ExtHostWorkspace) { this._proxy = proxy; @@ -53,7 +53,7 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { } public getConfigProvider(): Promise { - return this._barrier.wait().then(_ => this._actual); + return this._barrier.wait().then(_ => this._actual!); } $initializeConfiguration(data: IConfigurationInitData): void { @@ -62,7 +62,7 @@ export class ExtHostConfiguration implements ExtHostConfigurationShape { } $acceptConfigurationChanged(data: IConfigurationInitData, eventData: IWorkspaceConfigurationChangeEventData): void { - this._actual.$acceptConfigurationChanged(data, eventData); + this.getConfigProvider().then(provider => provider.$acceptConfigurationChanged(data, eventData)); } } @@ -93,14 +93,14 @@ export class ExtHostConfigProvider { getConfiguration(section?: string, resource?: URI, extensionId?: ExtensionIdentifier): vscode.WorkspaceConfiguration { const config = this._toReadonlyValue(section - ? lookUp(this._configuration.getValue(null, { resource }, this._extHostWorkspace.workspace), section) - : this._configuration.getValue(null, { resource }, this._extHostWorkspace.workspace)); + ? lookUp(this._configuration.getValue(undefined, { resource }, this._extHostWorkspace.workspace), section) + : this._configuration.getValue(undefined, { resource }, this._extHostWorkspace.workspace)); if (section) { this._validateConfigurationAccess(section, resource, extensionId); } - function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget { + function parseConfigurationTarget(arg: boolean | ExtHostConfigurationTarget): ConfigurationTarget | null { if (arg === undefined || arg === null) { return null; } @@ -127,7 +127,7 @@ export class ExtHostConfigProvider { } else { let clonedConfig = undefined; const cloneOnWriteProxy = (target: any, accessor: string): any => { - let clonedTarget = undefined; + let clonedTarget: any | undefined = undefined; const cloneTarget = () => { clonedConfig = clonedConfig ? clonedConfig : deepClone(config); clonedTarget = clonedTarget ? clonedTarget : lookUp(clonedConfig, accessor); @@ -151,17 +151,23 @@ export class ExtHostConfigProvider { }, set: (_target: any, property: string, value: any) => { cloneTarget(); - clonedTarget[property] = value; + if (clonedTarget) { + clonedTarget[property] = value; + } return true; }, deleteProperty: (_target: any, property: string) => { cloneTarget(); - delete clonedTarget[property]; + if (clonedTarget) { + delete clonedTarget[property]; + } return true; }, defineProperty: (_target: any, property: string, descriptor: any) => { cloneTarget(); - Object.defineProperty(clonedTarget, property, descriptor); + if (clonedTarget) { + Object.defineProperty(clonedTarget, property, descriptor); + } return true; } }) : target; @@ -179,7 +185,7 @@ export class ExtHostConfigProvider { return this._proxy.$removeConfigurationOption(target, key, resource); } }, - inspect: (key: string): ConfigurationInspect => { + inspect: (key: string): ConfigurationInspect | undefined => { key = section ? `${section}.${key}` : key; const config = deepClone(this._configuration.inspect(key, { resource }, this._extHostWorkspace.workspace)); if (config) { @@ -218,7 +224,7 @@ export class ExtHostConfigProvider { return readonlyProxy(result); } - private _validateConfigurationAccess(key: string, resource: URI, extensionId: ExtensionIdentifier): void { + private _validateConfigurationAccess(key: string, resource: URI | undefined, extensionId?: ExtensionIdentifier): void { const scope = OVERRIDE_PROPERTY_PATTERN.test(key) ? ConfigurationScope.RESOURCE : this._configurationScopes[key]; const extensionIdText = extensionId ? `[${extensionId.value}] ` : ''; if (ConfigurationScope.RESOURCE === scope) { diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index 9e69f2859f..5f6163ab3b 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -5,7 +5,7 @@ // {{SQL CARBON EDIT}} /* -import * as paths from 'vs/base/common/paths'; +import * as path from 'vs/base/common/path'; import { Schemas } from 'vs/base/common/network'; import { URI, UriComponents } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; @@ -17,24 +17,24 @@ import { } from 'vs/workbench/api/node/extHost.protocol'; import * as vscode from 'vscode'; import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint, DebugAdapterServer, DebugAdapterExecutable } from 'vs/workbench/api/node/extHostTypes'; -import { ExecutableDebugAdapter, SocketDebugAdapter, AbstractDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter'; -import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; +import { ExecutableDebugAdapter, SocketDebugAdapter, AbstractDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter'; +import { IExtHostWorkspaceProvider } from 'vs/workbench/api/node/extHostWorkspace'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors'; -import { ITerminalSettings, IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor } from 'vs/workbench/parts/debug/common/debug'; -import { getTerminalLauncher, hasChildProcesses, prepareCommand } from 'vs/workbench/parts/debug/node/terminals'; +import { ITerminalSettings, IDebuggerContribution, IConfig, IDebugAdapter, IDebugAdapterServer, IDebugAdapterExecutable, IAdapterDescriptor } from 'vs/workbench/contrib/debug/common/debug'; +import { getTerminalLauncher, hasChildProcesses, prepareCommand } from 'vs/workbench/contrib/debug/node/terminals'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/node/variableResolver'; +import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver'; import { ExtHostConfiguration, ExtHostConfigProvider } from './extHostConfiguration'; -import { convertToVSCPaths, convertToDAPaths } from 'vs/workbench/parts/debug/common/debugUtils'; +import { convertToVSCPaths, convertToDAPaths, isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils'; import { ExtHostTerminalService } from 'vs/workbench/api/node/extHostTerminalService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry'; - +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export class ExtHostDebugService implements ExtHostDebugServiceShape { @@ -79,12 +79,12 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { private _variableResolver: IConfigurationResolverService; - private _integratedTerminalInstance: vscode.Terminal; + private _integratedTerminalInstance?: vscode.Terminal; private _terminalDisposedListener: IDisposable; constructor(mainContext: IMainContext, - private _workspaceService: ExtHostWorkspace, + private _workspaceService: IExtHostWorkspaceProvider, private _extensionService: ExtHostExtensionService, private _editorsService: ExtHostDocumentsAndEditors, private _configurationService: ExtHostConfiguration, @@ -123,28 +123,35 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { this._breakpointEventsActive = false; this._extensionService.getExtensionRegistry().then((extensionRegistry: ExtensionDescriptionRegistry) => { - // register all debug extensions - const debugTypes: string[] = []; - for (const ed of extensionRegistry.getAllExtensionDescriptions()) { - if (ed.contributes) { - const debuggers = ed.contributes['debuggers']; - if (debuggers && debuggers.length > 0) { - for (const dbg of debuggers) { - // only debugger contributions with a label, program, or runtime attribute are considered a "defining" debugger contribution - if (dbg.type && (dbg.label || dbg.program || dbg.runtime)) { - debugTypes.push(dbg.type); - if (dbg.adapterExecutableCommand) { - this._aexCommands.set(dbg.type, dbg.adapterExecutableCommand); - } + extensionRegistry.onDidChange(_ => { + this.registerAllDebugTypes(extensionRegistry); + }); + this.registerAllDebugTypes(extensionRegistry); + }); + } + + private registerAllDebugTypes(extensionRegistry: ExtensionDescriptionRegistry) { + + const debugTypes: string[] = []; + this._aexCommands.clear(); + + for (const ed of extensionRegistry.getAllExtensionDescriptions()) { + if (ed.contributes) { + const debuggers = ed.contributes['debuggers']; + if (debuggers && debuggers.length > 0) { + for (const dbg of debuggers) { + if (isDebuggerMainContribution(dbg)) { + debugTypes.push(dbg.type); + if (dbg.adapterExecutableCommand) { + this._aexCommands.set(dbg.type, dbg.adapterExecutableCommand); } } } } } - if (debugTypes.length > 0) { - this._debugServiceProxy.$registerDebugTypes(debugTypes); - } - }); + } + + this._debugServiceProxy.$registerDebugTypes(debugTypes); } // extension debug API @@ -236,8 +243,8 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return this._debugServiceProxy.$unregisterBreakpoints(ids, fids); } - public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration): Promise { - return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig); + public startDebugging(folder: vscode.WorkspaceFolder | undefined, nameOrConfig: string | vscode.DebugConfiguration, parentSession?: vscode.DebugSession): Promise { + return this._debugServiceProxy.$startDebugging(folder ? folder.uri : undefined, nameOrConfig, parentSession ? parentSession.id : undefined); } public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable { @@ -250,7 +257,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { console.error('DebugConfigurationProvider.debugAdapterExecutable is deprecated and will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.'); } - let handle = this._configProviderHandleCounter++; + const handle = this._configProviderHandleCounter++; this._configProviders.push({ type, handle, provider }); this._debugServiceProxy.$registerDebugConfigurationProvider(type, @@ -281,7 +288,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { throw new Error(`a DebugAdapterDescriptorFactory can only be registered once per a type.`); } - let handle = this._adapterFactoryHandleCounter++; + const handle = this._adapterFactoryHandleCounter++; this._adapterFactories.push({ type, handle, factory }); this._debugServiceProxy.$registerDebugAdapterDescriptorFactory(type, handle); @@ -298,7 +305,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return new Disposable(() => { }); } - let handle = this._trackerFactoryHandleCounter++; + const handle = this._trackerFactoryHandleCounter++; this._trackerFactories.push({ type, handle, factory }); this._debugServiceProxy.$registerDebugAdapterTrackerFactory(type, handle); @@ -319,7 +326,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { // React on terminal disposed and check if that is the debug terminal #12956 this._terminalDisposedListener = this._terminalService.onDidCloseTerminal(terminal => { if (this._integratedTerminalInstance && this._integratedTerminalInstance === terminal) { - this._integratedTerminalInstance = null; + this._integratedTerminalInstance = undefined; } }); } @@ -336,16 +343,17 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } }).then(needNewTerminal => { - if (needNewTerminal) { + if (needNewTerminal || !this._integratedTerminalInstance) { this._integratedTerminalInstance = this._terminalService.createTerminal(args.title || nls.localize('debug.terminal.title', "debuggee")); } + const terminal: vscode.Terminal = this._integratedTerminalInstance; - this._integratedTerminalInstance.show(); + terminal.show(); return this._integratedTerminalInstance.processId.then(shellProcessId => { const command = prepareCommand(args, config); - this._integratedTerminalInstance.sendText(command, true); + terminal.sendText(command, true); return shellProcessId; }); @@ -358,16 +366,16 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return terminalLauncher.runInTerminal(args, config); } } - return undefined; + return Promise.resolve(undefined); } public async $substituteVariables(folderUri: UriComponents | undefined, config: IConfig): Promise { - const configProvider = await this._configurationService.getConfigProvider(); if (!this._variableResolver) { - this._variableResolver = new ExtHostVariableResolverService(this._workspaceService, this._editorsService, configProvider); + const [workspaceFolders, configProvider] = await Promise.all([this._workspaceService.getWorkspaceFolders2(), this._configurationService.getConfigProvider()]); + this._variableResolver = new ExtHostVariableResolverService(workspaceFolders || [], this._editorsService, configProvider); } - let ws: IWorkspaceFolder; - const folder = this.getFolder(folderUri); + let ws: IWorkspaceFolder | undefined; + const folder = await this.getFolder(folderUri); if (folder) { ws = { uri: folder.uri, @@ -381,13 +389,14 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return this._variableResolver.resolveAny(ws, config); } - public $startDASession(debugAdapterHandle: number, sessionDto: IDebugSessionDto): Promise { + public async $startDASession(debugAdapterHandle: number, sessionDto: IDebugSessionDto): Promise { const mythis = this; - const session = this.getSession(sessionDto); - return this.getAdapterDescriptor(this.getAdapterFactoryByType(session.type), session).then(x => { + const session = await this.getSession(sessionDto); - const adapter = this.convertToDto(x); + return this.getAdapterDescriptor(this.getAdapterFactoryByType(session.type), session).then(daDescriptor => { + + const adapter = this.convertToDto(daDescriptor); let da: AbstractDebugAdapter | undefined = undefined; switch (adapter.type) { @@ -408,8 +417,10 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { break; } - if (da) { - this._debugAdapters.set(debugAdapterHandle, da); + const debugAdapter = da; + + if (debugAdapter) { + this._debugAdapters.set(debugAdapterHandle, debugAdapter); return this.getDebugAdapterTrackers(session).then(tracker => { @@ -417,9 +428,9 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { this._debugAdaptersTrackers.set(debugAdapterHandle, tracker); } - da.onMessage(message => { + debugAdapter.onMessage(message => { - if (tracker) { + if (tracker && tracker.onDidSendMessage) { tracker.onDidSendMessage(message); } @@ -428,24 +439,24 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { mythis._debugServiceProxy.$acceptDAMessage(debugAdapterHandle, message); }); - da.onError(err => { - if (tracker) { + debugAdapter.onError(err => { + if (tracker && tracker.onError) { tracker.onError(err); } this._debugServiceProxy.$acceptDAError(debugAdapterHandle, err.name, err.message, err.stack); }); - da.onExit(code => { - if (tracker) { + debugAdapter.onExit((code: number) => { + if (tracker && tracker.onExit) { tracker.onExit(code, undefined); } - this._debugServiceProxy.$acceptDAExit(debugAdapterHandle, code, null); + this._debugServiceProxy.$acceptDAExit(debugAdapterHandle, code, undefined); }); - if (tracker) { + if (tracker && tracker.onWillStartSession) { tracker.onWillStartSession(); } - return da.startSession(); + return debugAdapter.startSession(); }); } @@ -453,13 +464,13 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { }); } - public $sendDAMessage(debugAdapterHandle: number, message: DebugProtocol.ProtocolMessage): Promise { + public $sendDAMessage(debugAdapterHandle: number, message: DebugProtocol.ProtocolMessage): void { // VS Code -> DA message = convertToDAPaths(message, false); const tracker = this._debugAdaptersTrackers.get(debugAdapterHandle); // TODO@AW: same handle? - if (tracker) { + if (tracker && tracker.onWillReceiveMessage) { tracker.onWillReceiveMessage(message); } @@ -467,14 +478,13 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { if (da) { da.sendMessage(message); } - return undefined; } public $stopDASession(debugAdapterHandle: number): Promise { const tracker = this._debugAdaptersTrackers.get(debugAdapterHandle); this._debugAdaptersTrackers.delete(debugAdapterHandle); - if (tracker) { + if (tracker && tracker.onWillStopSession) { tracker.onWillStopSession(); } @@ -483,20 +493,20 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { if (da) { return da.stopSession(); } else { - return undefined; + return Promise.resolve(void 0); } } public $acceptBreakpointsDelta(delta: IBreakpointsDeltaDto): void { - let a: vscode.Breakpoint[] = []; - let r: vscode.Breakpoint[] = []; - let c: vscode.Breakpoint[] = []; + const a: vscode.Breakpoint[] = []; + const r: vscode.Breakpoint[] = []; + const c: vscode.Breakpoint[] = []; if (delta.added) { for (const bpd of delta.added) { - - if (!this._breakpoints.has(bpd.id)) { + const id = bpd.id; + if (id && !this._breakpoints.has(id)) { let bp: vscode.Breakpoint; if (bpd.type === 'function') { bp = new FunctionBreakpoint(bpd.functionName, bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); @@ -504,8 +514,8 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { const uri = URI.revive(bpd.uri); bp = new SourceBreakpoint(new Location(uri, new Position(bpd.line, bpd.character)), bpd.enabled, bpd.condition, bpd.hitCondition, bpd.logMessage); } - (bp as any)._id = bpd.id; - this._breakpoints.set(bpd.id, bp); + (bp as any)._id = id; + this._breakpoints.set(id, bp); a.push(bp); } } @@ -523,24 +533,26 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { if (delta.changed) { for (const bpd of delta.changed) { - let bp = this._breakpoints.get(bpd.id); - if (bp) { - if (bp instanceof FunctionBreakpoint && bpd.type === 'function') { - const fbp = bp; - fbp.enabled = bpd.enabled; - fbp.condition = bpd.condition; - fbp.hitCondition = bpd.hitCondition; - fbp.logMessage = bpd.logMessage; - fbp.functionName = bpd.functionName; - } else if (bp instanceof SourceBreakpoint && bpd.type === 'source') { - const sbp = bp; - sbp.enabled = bpd.enabled; - sbp.condition = bpd.condition; - sbp.hitCondition = bpd.hitCondition; - sbp.logMessage = bpd.logMessage; - sbp.location = new Location(URI.revive(bpd.uri), new Position(bpd.line, bpd.character)); + if (bpd.id) { + const bp = this._breakpoints.get(bpd.id); + if (bp) { + if (bp instanceof FunctionBreakpoint && bpd.type === 'function') { + const fbp = bp; + fbp.enabled = bpd.enabled; + fbp.condition = bpd.condition; + fbp.hitCondition = bpd.hitCondition; + fbp.logMessage = bpd.logMessage; + fbp.functionName = bpd.functionName; + } else if (bp instanceof SourceBreakpoint && bpd.type === 'source') { + const sbp = bp; + sbp.enabled = bpd.enabled; + sbp.condition = bpd.condition; + sbp.hitCondition = bpd.hitCondition; + sbp.logMessage = bpd.logMessage; + sbp.location = new Location(URI.revive(bpd.uri), new Position(bpd.line, bpd.character)); + } + c.push(bp); } - c.push(bp); } } } @@ -549,71 +561,89 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } public $provideDebugConfigurations(configProviderHandle: number, folderUri: UriComponents | undefined): Promise { - let provider = this.getConfigProviderByHandle(configProviderHandle); - if (!provider) { - return Promise.reject(new Error('no handler found')); - } - if (!provider.provideDebugConfigurations) { - return Promise.reject(new Error('handler has no method provideDebugConfigurations')); - } - return asPromise(() => provider.provideDebugConfigurations(this.getFolder(folderUri), CancellationToken.None)); + return asPromise(async () => { + const provider = this.getConfigProviderByHandle(configProviderHandle); + if (!provider) { + throw new Error('no DebugConfigurationProvider found'); + } + if (!provider.provideDebugConfigurations) { + throw new Error('DebugConfigurationProvider has no method provideDebugConfigurations'); + } + const folder = await this.getFolder(folderUri); + return provider.provideDebugConfigurations(folder, CancellationToken.None); + }).then(debugConfigurations => { + if (!debugConfigurations) { + throw new Error('nothing returned from DebugConfigurationProvider.provideDebugConfigurations'); + } + return debugConfigurations; + }); } - public $resolveDebugConfiguration(configProviderHandle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration): Promise { - let provider = this.getConfigProviderByHandle(configProviderHandle); - if (!provider) { - return Promise.reject(new Error('no handler found')); - } - if (!provider.resolveDebugConfiguration) { - return Promise.reject(new Error('handler has no method resolveDebugConfiguration')); - } - return asPromise(() => provider.resolveDebugConfiguration(this.getFolder(folderUri), debugConfiguration, CancellationToken.None)); + public $resolveDebugConfiguration(configProviderHandle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration): Promise { + return asPromise(async () => { + const provider = this.getConfigProviderByHandle(configProviderHandle); + if (!provider) { + throw new Error('no DebugConfigurationProvider found'); + } + if (!provider.resolveDebugConfiguration) { + throw new Error('DebugConfigurationProvider has no method resolveDebugConfiguration'); + } + const folder = await this.getFolder(folderUri); + return provider.resolveDebugConfiguration(folder, debugConfiguration, CancellationToken.None); + }); } - // TODO@AW legacy + // TODO@AW deprecated and legacy public $legacyDebugAdapterExecutable(configProviderHandle: number, folderUri: UriComponents | undefined): Promise { - let provider = this.getConfigProviderByHandle(configProviderHandle); - if (!provider) { - return Promise.reject(new Error('no handler found')); - } - if (!provider.debugAdapterExecutable) { - return Promise.reject(new Error('handler has no method debugAdapterExecutable')); - } - return asPromise(() => provider.debugAdapterExecutable(this.getFolder(folderUri), CancellationToken.None)).then(x => this.convertToDto(x)); + return asPromise(async () => { + const provider = this.getConfigProviderByHandle(configProviderHandle); + if (!provider) { + throw new Error('no DebugConfigurationProvider found'); + } + if (!provider.debugAdapterExecutable) { + throw new Error('DebugConfigurationProvider has no method debugAdapterExecutable'); + } + const folder = await this.getFolder(folderUri); + return provider.debugAdapterExecutable(folder, CancellationToken.None); + }).then(executable => { + if (!executable) { + throw new Error('nothing returned from DebugConfigurationProvider.debugAdapterExecutable'); + } + return this.convertToDto(executable); + }); } - public $provideDebugAdapter(adapterProviderHandle: number, sessionDto: IDebugSessionDto): Promise { - let adapterProvider = this.getAdapterProviderByHandle(adapterProviderHandle); + public async $provideDebugAdapter(adapterProviderHandle: number, sessionDto: IDebugSessionDto): Promise { + const adapterProvider = this.getAdapterProviderByHandle(adapterProviderHandle); if (!adapterProvider) { return Promise.reject(new Error('no handler found')); } - return this.getAdapterDescriptor(adapterProvider, this.getSession(sessionDto)).then(x => this.convertToDto(x)); + const session = await this.getSession(sessionDto); + return this.getAdapterDescriptor(adapterProvider, session).then(x => this.convertToDto(x)); } - public $acceptDebugSessionStarted(sessionDto: IDebugSessionDto): void { - - this._onDidStartDebugSession.fire(this.getSession(sessionDto)); + public async $acceptDebugSessionStarted(sessionDto: IDebugSessionDto): Promise { + const session = await this.getSession(sessionDto); + this._onDidStartDebugSession.fire(session); } - public $acceptDebugSessionTerminated(sessionDto: IDebugSessionDto): void { - - const session = this.getSession(sessionDto); + public async $acceptDebugSessionTerminated(sessionDto: IDebugSessionDto): Promise { + const session = await this.getSession(sessionDto); if (session) { this._onDidTerminateDebugSession.fire(session); this._debugSessions.delete(session.id); } } - public $acceptDebugSessionActiveChanged(sessionDto: IDebugSessionDto): void { - - this._activeDebugSession = sessionDto ? this.getSession(sessionDto) : undefined; + public async $acceptDebugSessionActiveChanged(sessionDto: IDebugSessionDto | undefined): Promise { + this._activeDebugSession = sessionDto ? await this.getSession(sessionDto) : undefined; this._onDidChangeActiveDebugSession.fire(this._activeDebugSession); } - public $acceptDebugSessionCustomEvent(sessionDto: IDebugSessionDto, event: any): void { - + public async $acceptDebugSessionCustomEvent(sessionDto: IDebugSessionDto, event: any): Promise { + const session = await this.getSession(sessionDto); const ee: vscode.DebugSessionCustomEvent = { - session: this.getSession(sessionDto), + session: session, event: event.event, body: event.body }; @@ -622,7 +652,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { // private & dto helpers - private convertToDto(x: vscode.DebugAdapterDescriptor): IAdapterDescriptor { + private convertToDto(x: vscode.DebugAdapterDescriptor | undefined): IAdapterDescriptor { if (x instanceof DebugAdapterExecutable) { return { type: 'executable', @@ -642,11 +672,11 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { implementation: x.implementation }; } else */ /* {{SQL CARBON EDIT}} { - throw new Error('unexpected type'); + throw new Error('convertToDto unexpected type'); } } - private getAdapterFactoryByType(type: string): vscode.DebugAdapterDescriptorFactory { + private getAdapterFactoryByType(type: string): vscode.DebugAdapterDescriptorFactory | undefined { const results = this._adapterFactories.filter(p => p.type === type); if (results.length > 0) { return results[0].factory; @@ -654,7 +684,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return undefined; } - private getAdapterProviderByHandle(handle: number): vscode.DebugAdapterDescriptorFactory { + private getAdapterProviderByHandle(handle: number): vscode.DebugAdapterDescriptorFactory | undefined { const results = this._adapterFactories.filter(p => p.handle === handle); if (results.length > 0) { return results[0].factory; @@ -662,7 +692,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return undefined; } - private getConfigProviderByHandle(handle: number): vscode.DebugConfigurationProvider { + private getConfigProviderByHandle(handle: number): vscode.DebugConfigurationProvider | undefined { const results = this._configProviders.filter(p => p.handle === handle); if (results.length > 0) { return results[0].provider; @@ -687,18 +717,18 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return false; } - private getDebugAdapterTrackers(session: ExtHostDebugSession): Promise { + private getDebugAdapterTrackers(session: ExtHostDebugSession): Promise { const config = session.configuration; const type = config.type; const promises = this._trackerFactories .filter(tuple => tuple.type === type || tuple.type === '*') - .map(tuple => asPromise(() => tuple.factory.createDebugAdapterTracker(session)).then(p => p).catch(err => null)); + .map(tuple => asPromise>(() => tuple.factory.createDebugAdapterTracker(session)).then(p => p, err => null)); return Promise.race([ - Promise.all(promises).then(trackers => { - trackers = trackers.filter(t => t); // filter null + Promise.all(promises).then(result => { + const trackers = result.filter(t => !!t); // filter null if (trackers.length > 0) { return new MultiTracker(trackers); } @@ -716,7 +746,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { }); } - private async getAdapterDescriptor(adapterProvider: vscode.DebugAdapterDescriptorFactory, session: ExtHostDebugSession): Promise { + private async getAdapterDescriptor(adapterProvider: vscode.DebugAdapterDescriptorFactory | undefined, session: ExtHostDebugSession): Promise { // a "debugServer" attribute in the launch config takes precedence const serverPort = session.configuration.debugServer; @@ -725,16 +755,25 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } // TODO@AW legacy - const pairs = this._configProviders.filter(p => p.type === session.type); - if (pairs.length > 0) { - if (pairs[0].provider.debugAdapterExecutable) { - return asPromise(() => pairs[0].provider.debugAdapterExecutable(session.workspaceFolder, CancellationToken.None)); - } + const pair = this._configProviders.filter(p => p.type === session.type).pop(); + if (pair && pair.provider.debugAdapterExecutable) { + const func = pair.provider.debugAdapterExecutable; + return asPromise(() => func(session.workspaceFolder, CancellationToken.None)).then(executable => { + if (executable) { + return executable; + } + return undefined; + }); } if (adapterProvider) { const extensionRegistry = await this._extensionService.getExtensionRegistry(); - return asPromise(() => adapterProvider.createDebugAdapterDescriptor(session, this.daExecutableFromPackage(session, extensionRegistry))); + return asPromise(() => adapterProvider.createDebugAdapterDescriptor(session, this.daExecutableFromPackage(session, extensionRegistry))).then(daDescriptor => { + if (daDescriptor) { + return daDescriptor; + } + return undefined; + }); } // try deprecated command based extension API "adapterExecutableCommand" to determine the executable @@ -778,25 +817,33 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { } } - private getSession(dto: IDebugSessionDto): ExtHostDebugSession { + private async getSession(dto: IDebugSessionDto): Promise { if (dto) { if (typeof dto === 'string') { - return this._debugSessions.get(dto); + const ds = this._debugSessions.get(dto); + if (ds) { + return ds; + } } else { - const debugSession = new ExtHostDebugSession(this._debugServiceProxy, dto.id, dto.type, dto.name, this.getFolder(dto.folderUri), dto.configuration); - this._debugSessions.set(debugSession.id, debugSession); - return debugSession; + let ds = this._debugSessions.get(dto.id); + if (!ds) { + const folder = await this.getFolder(dto.folderUri); + ds = new ExtHostDebugSession(this._debugServiceProxy, dto.id, dto.type, dto.name, folder, dto.configuration); + this._debugSessions.set(ds.id, ds); + this._debugServiceProxy.$sessionCached(ds.id); + } + return ds; } } - return undefined; + throw new Error('cannot find session'); } - private getFolder(_folderUri: UriComponents | undefined): vscode.WorkspaceFolder | undefined { + private getFolder(_folderUri: UriComponents | undefined): Promise { if (_folderUri) { const folderURI = URI.revive(_folderUri); return this._workspaceService.resolveWorkspaceFolder(folderURI); } - return undefined; + return Promise.resolve(undefined); } } @@ -855,10 +902,9 @@ export class ExtHostDebugConsole implements vscode.DebugConsole { export class ExtHostVariableResolverService extends AbstractVariableResolverService { - constructor(workspaceService: ExtHostWorkspace, editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider) { + constructor(folders: vscode.WorkspaceFolder[], editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfigProvider) { super({ - getFolderUri: (folderName: string): URI => { - const folders = workspaceService.getWorkspaceFolders(); + getFolderUri: (folderName: string): URI | undefined => { const found = folders.filter(f => f.name === folderName); if (found && found.length > 0) { return found[0].uri; @@ -866,9 +912,9 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ return undefined; }, getWorkspaceFolderCount: (): number => { - return workspaceService.getWorkspaceFolders().length; + return folders.length; }, - getConfigurationValue: (folderUri: URI, section: string) => { + getConfigurationValue: (folderUri: URI, section: string): string | undefined => { return configurationService.getConfiguration(undefined, folderUri).get(section); }, getExecPath: (): string | undefined => { @@ -879,7 +925,7 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ if (activeEditor) { const resource = activeEditor.document.uri; if (resource.scheme === Schemas.file) { - return paths.normalize(resource.fsPath, true); + return path.normalize(resource.fsPath); } } return undefined; @@ -891,14 +937,14 @@ export class ExtHostVariableResolverService extends AbstractVariableResolverServ } return undefined; }, - getLineNumber: (): string => { + getLineNumber: (): string | undefined => { const activeEditor = editorService.activeEditor(); if (activeEditor) { return String(activeEditor.selection.end.line + 1); } return undefined; } - }); + }, process.env as IProcessEnvironment); } } @@ -1005,4 +1051,4 @@ class DirectDebugAdapter extends AbstractDebugAdapter implements IDapTransport { } // {{SQL CARBON EDIT}} -*/ \ No newline at end of file +*/ diff --git a/src/vs/workbench/api/node/extHostDecorations.ts b/src/vs/workbench/api/node/extHostDecorations.ts index 9cc22601bb..7382ef267f 100644 --- a/src/vs/workbench/api/node/extHostDecorations.ts +++ b/src/vs/workbench/api/node/extHostDecorations.ts @@ -46,16 +46,20 @@ export class ExtHostDecorations implements ExtHostDecorationsShape { const result: DecorationReply = Object.create(null); return Promise.all(requests.map(request => { const { handle, uri, id } = request; - if (!this._provider.has(handle)) { + const entry = this._provider.get(handle); + if (!entry) { // might have been unregistered in the meantime return undefined; } - const { provider, extensionId } = this._provider.get(handle); + const { provider, extensionId } = entry; return Promise.resolve(provider.provideDecoration(URI.revive(uri), token)).then(data => { if (data && data.letter && data.letter.length !== 1) { console.warn(`INVALID decoration from extension '${extensionId.value}'. The 'letter' must be set and be one character, not '${data.letter}'.`); } - result[id] = data && [data.priority, data.bubble, data.title, data.letter, data.color, data.source]; + if (data) { + result[id] = [data.priority, data.bubble, data.title, data.letter, data.color, data.source]; + + } }, err => { console.error(err); }); diff --git a/src/vs/workbench/api/node/extHostDiagnostics.ts b/src/vs/workbench/api/node/extHostDiagnostics.ts index 0a5ce2ac43..d193742306 100644 --- a/src/vs/workbench/api/node/extHostDiagnostics.ts +++ b/src/vs/workbench/api/node/extHostDiagnostics.ts @@ -8,9 +8,9 @@ import { IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers' import { URI } from 'vs/base/common/uri'; import * as vscode from 'vscode'; import { MainContext, MainThreadDiagnosticsShape, ExtHostDiagnosticsShape, IMainContext } from './extHost.protocol'; -import { DiagnosticSeverity, Diagnostic } from './extHostTypes'; +import { DiagnosticSeverity } from './extHostTypes'; import * as converter from './extHostTypeConverters'; -import { mergeSort, equals } from 'vs/base/common/arrays'; +import { mergeSort } from 'vs/base/common/arrays'; import { Event, Emitter } from 'vs/base/common/event'; import { keys } from 'vs/base/common/map'; @@ -37,7 +37,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { if (!this._isDisposed) { this._onDidChangeDiagnostics.fire(keys(this._data)); this._proxy.$clear(this._owner); - this._data = undefined; + this._data = undefined!; this._isDisposed = true; } } @@ -60,14 +60,10 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { // the actual implementation for #set this._checkDisposed(); - let toSync: vscode.Uri[]; - let hasChanged = true; + let toSync: vscode.Uri[] = []; if (first instanceof URI) { - // check if this has actually changed - hasChanged = hasChanged && !equals(diagnostics, this.get(first), Diagnostic.isEqual); - if (!diagnostics) { // remove this entry this.delete(first); @@ -81,7 +77,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { } else if (Array.isArray(first)) { // update many rows toSync = []; - let lastUri: vscode.Uri; + let lastUri: vscode.Uri | undefined; // ensure stable-sort mergeSort(first, DiagnosticCollection._compareIndexedTuplesByUri); @@ -89,7 +85,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { for (const tuple of first) { const [uri, diagnostics] = tuple; if (!lastUri || uri.toString() !== lastUri.toString()) { - if (lastUri && this._data.get(lastUri.toString()).length === 0) { + if (lastUri && this._data.get(lastUri.toString())!.length === 0) { this._data.delete(lastUri.toString()); } lastUri = uri; @@ -99,9 +95,15 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { if (!diagnostics) { // [Uri, undefined] means clear this - this._data.get(uri.toString()).length = 0; + const currentDiagnostics = this._data.get(uri.toString()); + if (currentDiagnostics) { + currentDiagnostics.length = 0; + } } else { - this._data.get(uri.toString()).push(...diagnostics); + const currentDiagnostics = this._data.get(uri.toString()); + if (currentDiagnostics) { + currentDiagnostics.push(...diagnostics); + } } } } @@ -109,19 +111,11 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { // send event for extensions this._onDidChangeDiagnostics.fire(toSync); - // if nothing has changed then there is nothing else to do - // we have updated the diagnostics but we don't send a message - // to the renderer. tho we have still send an event for other - // extensions because the diagnostic might carry more information - // than known to us - if (!hasChanged) { - return; - } // compute change and send to main side const entries: [URI, IMarkerData[]][] = []; for (let uri of toSync) { - let marker: IMarkerData[]; - let diagnostics = this._data.get(uri.toString()); + let marker: IMarkerData[] = []; + const diagnostics = this._data.get(uri.toString()); if (diagnostics) { // no more than N diagnostics per file @@ -149,7 +143,7 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { endColumn: marker[marker.length - 1].endColumn }); } else { - marker = diagnostics.map(converter.Diagnostic.from); + marker = diagnostics.map(diag => converter.Diagnostic.from(diag)); } } @@ -176,18 +170,18 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { forEach(callback: (uri: URI, diagnostics: vscode.Diagnostic[], collection: DiagnosticCollection) => any, thisArg?: any): void { this._checkDisposed(); this._data.forEach((value, key) => { - let uri = URI.parse(key); + const uri = URI.parse(key); callback.apply(thisArg, [uri, this.get(uri), this]); }); } get(uri: URI): vscode.Diagnostic[] { this._checkDisposed(); - let result = this._data.get(uri.toString()); + const result = this._data.get(uri.toString()); if (Array.isArray(result)) { return Object.freeze(result.slice(0)); } - return undefined; + return []; } has(uri: URI): boolean { @@ -230,8 +224,8 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { } static _mapper(last: (vscode.Uri | string)[]): { uris: vscode.Uri[] } { - let uris: vscode.Uri[] = []; - let map = new Set(); + const uris: vscode.Uri[] = []; + const map = new Set(); for (const uri of last) { if (typeof uri === 'string') { if (!map.has(uri)) { @@ -255,7 +249,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { this._proxy = mainContext.getProxy(MainContext.MainThreadDiagnostics); } - createDiagnosticCollection(name: string): vscode.DiagnosticCollection { + createDiagnosticCollection(name?: string): vscode.DiagnosticCollection { let { _collections, _proxy, _onDidChangeDiagnostics } = this; let owner: string; if (!name) { @@ -272,7 +266,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { const result = new class extends DiagnosticCollection { constructor() { - super(name, owner, ExtHostDiagnostics._maxDiagnosticsPerFile, _proxy, _onDidChangeDiagnostics); + super(name!, owner, ExtHostDiagnostics._maxDiagnosticsPerFile, _proxy, _onDidChangeDiagnostics); _collections.set(owner, this); } dispose() { @@ -286,12 +280,13 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { getDiagnostics(resource: vscode.Uri): vscode.Diagnostic[]; getDiagnostics(): [vscode.Uri, vscode.Diagnostic[]][]; + getDiagnostics(resource?: vscode.Uri): vscode.Diagnostic[] | [vscode.Uri, vscode.Diagnostic[]][]; getDiagnostics(resource?: vscode.Uri): vscode.Diagnostic[] | [vscode.Uri, vscode.Diagnostic[]][] { if (resource) { return this._getDiagnostics(resource); } else { - let index = new Map(); - let res: [vscode.Uri, vscode.Diagnostic[]][] = []; + const index = new Map(); + const res: [vscode.Uri, vscode.Diagnostic[]][] = []; this._collections.forEach(collection => { collection.forEach((uri, diagnostics) => { let idx = index.get(uri.toString()); diff --git a/src/vs/workbench/api/node/extHostDialogs.ts b/src/vs/workbench/api/node/extHostDialogs.ts index 57f05d3489..09006c992e 100644 --- a/src/vs/workbench/api/node/extHostDialogs.ts +++ b/src/vs/workbench/api/node/extHostDialogs.ts @@ -15,15 +15,15 @@ export class ExtHostDialogs { this._proxy = mainContext.getProxy(MainContext.MainThreadDialogs); } - showOpenDialog(options: vscode.OpenDialogOptions): Promise { + showOpenDialog(options: vscode.OpenDialogOptions): Promise { return this._proxy.$showOpenDialog(options).then(filepaths => { - return filepaths && filepaths.map(URI.revive); + return filepaths ? filepaths.map(URI.revive) : undefined; }); } - showSaveDialog(options: vscode.SaveDialogOptions): Promise { + showSaveDialog(options: vscode.SaveDialogOptions): Promise { return this._proxy.$showSaveDialog(options).then(filepath => { - return filepath && URI.revive(filepath); + return filepath ? URI.revive(filepath) : undefined; }); } } diff --git a/src/vs/workbench/api/node/extHostDocumentContentProviders.ts b/src/vs/workbench/api/node/extHostDocumentContentProviders.ts index 1039f8aebe..66d0ec7d67 100644 --- a/src/vs/workbench/api/node/extHostDocumentContentProviders.ts +++ b/src/vs/workbench/api/node/extHostDocumentContentProviders.ts @@ -45,17 +45,20 @@ export class ExtHostDocumentContentProvider implements ExtHostDocumentContentPro this._documentContentProviders.set(handle, provider); this._proxy.$registerTextContentProvider(handle, scheme); - let subscription: IDisposable; + let subscription: IDisposable | undefined; if (typeof provider.onDidChange === 'function') { subscription = provider.onDidChange(uri => { if (uri.scheme !== scheme) { this._logService.warn(`Provider for scheme '${scheme}' is firing event for schema '${uri.scheme}' which will be IGNORED`); return; } - if (this._documentsAndEditors.getDocument(uri.toString())) { + if (this._documentsAndEditors.getDocument(uri)) { this.$provideTextDocumentContent(handle, uri).then(value => { + if (!value) { + return; + } - const document = this._documentsAndEditors.getDocument(uri.toString()); + const document = this._documentsAndEditors.getDocument(uri); if (!document) { // disposed in the meantime return; @@ -84,7 +87,7 @@ export class ExtHostDocumentContentProvider implements ExtHostDocumentContentPro }); } - $provideTextDocumentContent(handle: number, uri: UriComponents): Promise { + $provideTextDocumentContent(handle: number, uri: UriComponents): Promise { const provider = this._documentContentProviders.get(handle); if (!provider) { return Promise.reject(new Error(`unsupported uri-scheme: ${uri.scheme}`)); diff --git a/src/vs/workbench/api/node/extHostDocumentData.ts b/src/vs/workbench/api/node/extHostDocumentData.ts index f13099fba8..68d2b1e81b 100644 --- a/src/vs/workbench/api/node/extHostDocumentData.ts +++ b/src/vs/workbench/api/node/extHostDocumentData.ts @@ -14,10 +14,10 @@ import { EndOfLine, Position, Range } from 'vs/workbench/api/node/extHostTypes'; import * as vscode from 'vscode'; const _modeId2WordDefinition = new Map(); -export function setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void { +export function setWordDefinitionFor(modeId: string, wordDefinition: RegExp | undefined): void { _modeId2WordDefinition.set(modeId, wordDefinition); } -export function getWordDefinitionFor(modeId: string): RegExp { +export function getWordDefinitionFor(modeId: string): RegExp | undefined { return _modeId2WordDefinition.get(modeId); } @@ -105,7 +105,7 @@ export class ExtHostDocumentData extends MirrorTextModel { } private _getTextInRange(_range: vscode.Range): string { - let range = this._validateRange(_range); + const range = this._validateRange(_range); if (range.isEmpty) { return ''; @@ -115,7 +115,7 @@ export class ExtHostDocumentData extends MirrorTextModel { return this._lines[range.start.line].substring(range.start.character, range.end.character); } - let lineEnding = this._eol, + const lineEnding = this._eol, startLineIndex = range.start.line, endLineIndex = range.end.line, resultLines: string[] = []; @@ -131,14 +131,14 @@ export class ExtHostDocumentData extends MirrorTextModel { private _lineAt(lineOrPosition: number | vscode.Position): vscode.TextLine { - let line: number; + let line: number | undefined; if (lineOrPosition instanceof Position) { line = lineOrPosition.line; } else if (typeof lineOrPosition === 'number') { line = lineOrPosition; } - if (line < 0 || line >= this._lines.length) { + if (typeof line !== 'number' || line < 0 || line >= this._lines.length) { throw new Error('Illegal value for `line`'); } @@ -146,7 +146,7 @@ export class ExtHostDocumentData extends MirrorTextModel { if (!result || result.lineNumber !== line || result.text !== this._lines[line]) { const text = this._lines[line]; - const firstNonWhitespaceCharacterIndex = /^(\s*)/.exec(text)[1].length; + const firstNonWhitespaceCharacterIndex = /^(\s*)/.exec(text)![1].length; const range = new Range(line, 0, line, text.length); const rangeIncludingLineBreak = line < this._lines.length - 1 ? new Range(line, 0, line + 1, 0) @@ -170,7 +170,7 @@ export class ExtHostDocumentData extends MirrorTextModel { private _offsetAt(position: vscode.Position): number { position = this._validatePosition(position); this._ensureLineStarts(); - return this._lineStarts.getAccumulatedValue(position.line - 1) + position.character; + return this._lineStarts!.getAccumulatedValue(position.line - 1) + position.character; } private _positionAt(offset: number): vscode.Position { @@ -178,9 +178,9 @@ export class ExtHostDocumentData extends MirrorTextModel { offset = Math.max(0, offset); this._ensureLineStarts(); - let out = this._lineStarts.getIndexOf(offset); + const out = this._lineStarts!.getIndexOf(offset); - let lineLength = this._lines[out.index].length; + const lineLength = this._lines[out.index].length; // Ensure we return a valid position return new Position(out.index, Math.min(out.remainder, lineLength)); @@ -193,8 +193,8 @@ export class ExtHostDocumentData extends MirrorTextModel { throw new Error('Invalid argument'); } - let start = this._validatePosition(range.start); - let end = this._validatePosition(range.end); + const start = this._validatePosition(range.start); + const end = this._validatePosition(range.end); if (start === range.start && end === range.end) { return range; @@ -221,7 +221,7 @@ export class ExtHostDocumentData extends MirrorTextModel { hasChanged = true; } else { - let maxCharacter = this._lines[line].length; + const maxCharacter = this._lines[line].length; if (character < 0) { character = 0; hasChanged = true; @@ -238,8 +238,8 @@ export class ExtHostDocumentData extends MirrorTextModel { return new Position(line, character); } - private _getWordRangeAtPosition(_position: vscode.Position, regexp?: RegExp): vscode.Range { - let position = this._validatePosition(_position); + private _getWordRangeAtPosition(_position: vscode.Position, regexp?: RegExp): vscode.Range | undefined { + const position = this._validatePosition(_position); if (!regexp) { // use default when custom-regexp isn't provided @@ -251,7 +251,7 @@ export class ExtHostDocumentData extends MirrorTextModel { regexp = getWordDefinitionFor(this._languageId); } - let wordAtText = getWordAtText( + const wordAtText = getWordAtText( position.character + 1, ensureValidWordDefinition(regexp), this._lines[position.line], diff --git a/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts index 81f3798886..275a5fd4c4 100644 --- a/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts @@ -14,8 +14,8 @@ import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import * as vscode from 'vscode'; import { LinkedList } from 'vs/base/common/linkedList'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; type Listener = [Function, any, IExtensionDescription]; @@ -53,17 +53,17 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic const entries = this._callbacks.toArray(); let didTimeout = false; - let didTimeoutHandle = setTimeout(() => didTimeout = true, this._thresholds.timeout); + const didTimeoutHandle = setTimeout(() => didTimeout = true, this._thresholds.timeout); const promise = sequence(entries.map(listener => { return () => { if (didTimeout) { // timeout - no more listeners - return undefined; + return Promise.resolve(); } - const document = this._documents.getDocumentData(resource).document; + const document = this._documents.getDocument(resource); return this._deliverEventAsyncAndBlameBadListeners(listener, { document, reason: TextDocumentSaveReason.to(reason) }); }; })); @@ -72,7 +72,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic private _deliverEventAsyncAndBlameBadListeners([listener, thisArg, extension]: Listener, stubEvent: vscode.TextDocumentWillSaveEvent): Promise { const errors = this._badListeners.get(listener); - if (errors > this._thresholds.errors) { + if (typeof errors === 'number' && errors > this._thresholds.errors) { // bad listener - ignore return Promise.resolve(false); } @@ -90,7 +90,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic const errors = this._badListeners.get(listener); this._badListeners.set(listener, !errors ? 1 : errors + 1); - if (errors > this._thresholds.errors) { + if (typeof errors === 'number' && errors > this._thresholds.errors) { this._logService.info(`onWillSaveTextDocument-listener from extension '${extension.identifier.value}' will now be IGNORED because of timeouts and/or errors`); } } @@ -169,7 +169,6 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic return this._mainThreadEditors.$tryApplyWorkspaceEdit({ edits: [resourceEdit] }); } - // TODO@joh bubble this to listener? return Promise.reject(new Error('concurrent_edits')); }); } diff --git a/src/vs/workbench/api/node/extHostDocuments.ts b/src/vs/workbench/api/node/extHostDocuments.ts index f3c91a2c32..f8f0df4a7e 100644 --- a/src/vs/workbench/api/node/extHostDocuments.ts +++ b/src/vs/workbench/api/node/extHostDocuments.ts @@ -56,20 +56,28 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { return this._documentsAndEditors.allDocuments(); } - public getDocumentData(resource: vscode.Uri): ExtHostDocumentData { + public getDocumentData(resource: vscode.Uri): ExtHostDocumentData | undefined { if (!resource) { return undefined; } - const data = this._documentsAndEditors.getDocument(resource.toString()); + const data = this._documentsAndEditors.getDocument(resource); if (data) { return data; } return undefined; } + public getDocument(resource: vscode.Uri): vscode.TextDocument { + const data = this.getDocumentData(resource); + if (!data || !data.document) { + throw new Error('Unable to retrieve document from URI'); + } + return data.document; + } + public ensureDocumentData(uri: URI): Promise { - let cached = this._documentsAndEditors.getDocument(uri.toString()); + const cached = this._documentsAndEditors.getDocument(uri); if (cached) { return Promise.resolve(cached); } @@ -78,7 +86,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { if (!promise) { promise = this._proxy.$tryOpenDocument(uri).then(() => { this._documentLoader.delete(uri.toString()); - return this._documentsAndEditors.getDocument(uri.toString()); + return this._documentsAndEditors.getDocument(uri); }, err => { this._documentLoader.delete(uri.toString()); return Promise.reject(err); @@ -95,9 +103,10 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { public $acceptModelModeChanged(uriComponents: UriComponents, oldModeId: string, newModeId: string): void { const uri = URI.revive(uriComponents); - const strURL = uri.toString(); - let data = this._documentsAndEditors.getDocument(strURL); - + const data = this._documentsAndEditors.getDocument(uri); + if (!data) { + throw new Error('unknown document'); + } // Treat a mode change as a remove + add this._onDidRemoveDocument.fire(data.document); @@ -107,16 +116,20 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { public $acceptModelSaved(uriComponents: UriComponents): void { const uri = URI.revive(uriComponents); - const strURL = uri.toString(); - let data = this._documentsAndEditors.getDocument(strURL); + const data = this._documentsAndEditors.getDocument(uri); + if (!data) { + throw new Error('unknown document'); + } this.$acceptDirtyStateChanged(uriComponents, false); this._onDidSaveDocument.fire(data.document); } public $acceptDirtyStateChanged(uriComponents: UriComponents, isDirty: boolean): void { const uri = URI.revive(uriComponents); - const strURL = uri.toString(); - let data = this._documentsAndEditors.getDocument(strURL); + const data = this._documentsAndEditors.getDocument(uri); + if (!data) { + throw new Error('unknown document'); + } data._acceptIsDirty(isDirty); this._onDidChangeDocument.fire({ document: data.document, @@ -126,8 +139,10 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { public $acceptModelChanged(uriComponents: UriComponents, events: IModelChangedEvent, isDirty: boolean): void { const uri = URI.revive(uriComponents); - const strURL = uri.toString(); - let data = this._documentsAndEditors.getDocument(strURL); + const data = this._documentsAndEditors.getDocument(uri); + if (!data) { + throw new Error('unknown document'); + } data._acceptIsDirty(isDirty); data.onEvents(events); this._onDidChangeDocument.fire({ @@ -143,7 +158,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { }); } - public setWordDefinitionFor(modeId: string, wordDefinition: RegExp): void { + public setWordDefinitionFor(modeId: string, wordDefinition: RegExp | undefined): void { setWordDefinitionFor(modeId, wordDefinition); } } diff --git a/src/vs/workbench/api/node/extHostDocumentsAndEditors.ts b/src/vs/workbench/api/node/extHostDocumentsAndEditors.ts index 6319e784d6..4bbd6b96bb 100644 --- a/src/vs/workbench/api/node/extHostDocumentsAndEditors.ts +++ b/src/vs/workbench/api/node/extHostDocumentsAndEditors.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; +import * as assert from 'vs/base/common/assert'; import { Emitter, Event } from 'vs/base/common/event'; import { dispose } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; @@ -17,7 +17,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha private _disposables: Disposable[] = []; - private _activeEditorId: string; + private _activeEditorId: string | null; private readonly _editors = new Map(); private readonly _documents = new Map(); @@ -25,12 +25,12 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha private readonly _onDidAddDocuments = new Emitter(); private readonly _onDidRemoveDocuments = new Emitter(); private readonly _onDidChangeVisibleTextEditors = new Emitter(); - private readonly _onDidChangeActiveTextEditor = new Emitter(); + private readonly _onDidChangeActiveTextEditor = new Emitter(); readonly onDidAddDocuments: Event = this._onDidAddDocuments.event; readonly onDidRemoveDocuments: Event = this._onDidRemoveDocuments.event; readonly onDidChangeVisibleTextEditors: Event = this._onDidChangeVisibleTextEditors.event; - readonly onDidChangeActiveTextEditor: Event = this._onDidChangeActiveTextEditor.event; + readonly onDidChangeActiveTextEditor: Event = this._onDidChangeActiveTextEditor.event; constructor( private readonly _mainContext: IMainContext, @@ -52,7 +52,9 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha const id = uri.toString(); const data = this._documents.get(id); this._documents.delete(id); - removedDocuments.push(data); + if (data) { + removedDocuments.push(data); + } } } @@ -79,7 +81,9 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha for (const id of delta.removedEditors) { const editor = this._editors.get(id); this._editors.delete(id); - removedEditors.push(editor); + if (editor) { + removedEditors.push(editor); + } } } @@ -89,15 +93,15 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha assert.ok(this._documents.has(resource.toString()), `document '${resource}' does not exist`); assert.ok(!this._editors.has(data.id), `editor '${data.id}' already exists!`); - const documentData = this._documents.get(resource.toString()); + const documentData = this._documents.get(resource.toString())!; const editor = new ExtHostTextEditor( this._mainContext.getProxy(MainContext.MainThreadTextEditors), data.id, documentData, data.selections.map(typeConverters.Selection.to), data.options, - data.visibleRanges.map(typeConverters.Range.to), - typeConverters.ViewColumn.to(data.editorPosition) + data.visibleRanges.map(range => typeConverters.Range.to(range)), + typeof data.editorPosition === 'number' ? typeConverters.ViewColumn.to(data.editorPosition) : undefined ); this._editors.set(data.id, editor); } @@ -127,8 +131,8 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha } } - getDocument(strUrl: string): ExtHostDocumentData { - return this._documents.get(strUrl); + getDocument(uri: URI): ExtHostDocumentData | undefined { + return this._documents.get(uri.toString()); } allDocuments(): ExtHostDocumentData[] { @@ -137,7 +141,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha return result; } - getEditor(id: string): ExtHostTextEditor { + getEditor(id: string): ExtHostTextEditor | undefined { return this._editors.get(id); } diff --git a/src/vs/workbench/api/node/extHostExtensionActivator.ts b/src/vs/workbench/api/node/extHostExtensionActivator.ts index cdb9bf0969..52d15c0a93 100644 --- a/src/vs/workbench/api/node/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/node/extHostExtensionActivator.ts @@ -5,10 +5,9 @@ import * as nls from 'vs/nls'; import { IDisposable } from 'vs/base/common/lifecycle'; -import Severity from 'vs/base/common/severity'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionActivationError, MissingDependencyError } from 'vs/workbench/services/extensions/common/extensions'; const NO_OP_VOID_PROMISE = Promise.resolve(undefined); @@ -161,6 +160,12 @@ export class EmptyExtension extends ActivatedExtension { } } +export class HostExtension extends ActivatedExtension { + constructor() { + super(false, null, ExtensionActivationTimes.NONE, { activate: undefined, deactivate: undefined }, undefined, []); + } +} + export class FailedExtension extends ActivatedExtension { constructor(activationError: Error) { super(true, activationError, ExtensionActivationTimes.NONE, { activate: undefined, deactivate: undefined }, undefined, []); @@ -168,9 +173,8 @@ export class FailedExtension extends ActivatedExtension { } export interface IExtensionsActivatorHost { - showMessage(severity: Severity, message: string): void; - - actualActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise; + onExtensionActivationError(extensionId: ExtensionIdentifier, error: ExtensionActivationError): void; + actualActivateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise; } export class ExtensionActivatedByEvent { @@ -192,6 +196,7 @@ export class ExtensionsActivator { private readonly _registry: ExtensionDescriptionRegistry; private readonly _resolvedExtensionsSet: Set; + private readonly _hostExtensionsMap: Map; private readonly _host: IExtensionsActivatorHost; private readonly _activatingExtensions: Map>; private readonly _activatedExtensions: Map; @@ -200,10 +205,12 @@ export class ExtensionsActivator { */ private readonly _alreadyActivatedEvents: { [activationEvent: string]: boolean; }; - constructor(registry: ExtensionDescriptionRegistry, resolvedExtensions: ExtensionIdentifier[], host: IExtensionsActivatorHost) { + constructor(registry: ExtensionDescriptionRegistry, resolvedExtensions: ExtensionIdentifier[], hostExtensions: ExtensionIdentifier[], host: IExtensionsActivatorHost) { this._registry = registry; this._resolvedExtensionsSet = new Set(); resolvedExtensions.forEach((extensionId) => this._resolvedExtensionsSet.add(ExtensionIdentifier.toKey(extensionId))); + this._hostExtensionsMap = new Map(); + hostExtensions.forEach((extensionId) => this._hostExtensionsMap.set(ExtensionIdentifier.toKey(extensionId), extensionId)); this._host = host; this._activatingExtensions = new Map>(); this._activatedExtensions = new Map(); @@ -230,27 +237,33 @@ export class ExtensionsActivator { if (this._alreadyActivatedEvents[activationEvent]) { return NO_OP_VOID_PROMISE; } - let activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent); - return this._activateExtensions(activateExtensions, reason, 0).then(() => { + const activateExtensions = this._registry.getExtensionDescriptionsForActivationEvent(activationEvent); + return this._activateExtensions(activateExtensions.map(e => e.identifier), reason).then(() => { this._alreadyActivatedEvents[activationEvent] = true; }); } public activateById(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { - let desc = this._registry.getExtensionDescription(extensionId); + const desc = this._registry.getExtensionDescription(extensionId); if (!desc) { throw new Error('Extension `' + extensionId + '` is not known'); } - return this._activateExtensions([desc], reason, 0); + return this._activateExtensions([desc.identifier], reason); } /** * Handle semantics related to dependencies for `currentExtension`. * semantics: `redExtensions` must wait for `greenExtensions`. */ - private _handleActivateRequest(currentExtension: IExtensionDescription, greenExtensions: { [id: string]: IExtensionDescription; }, redExtensions: IExtensionDescription[]): void { - let depIds = (typeof currentExtension.extensionDependencies === 'undefined' ? [] : currentExtension.extensionDependencies); + private _handleActivateRequest(currentExtensionId: ExtensionIdentifier, greenExtensions: { [id: string]: ExtensionIdentifier; }, redExtensions: ExtensionIdentifier[]): void { + if (this._hostExtensionsMap.has(ExtensionIdentifier.toKey(currentExtensionId))) { + greenExtensions[ExtensionIdentifier.toKey(currentExtensionId)] = currentExtensionId; + return; + } + + const currentExtension = this._registry.getExtensionDescription(currentExtensionId)!; + const depIds = (typeof currentExtension.extensionDependencies === 'undefined' ? [] : currentExtension.extensionDependencies); let currentExtensionGetsGreenLight = true; for (let j = 0, lenJ = depIds.length; j < lenJ; j++) { @@ -261,78 +274,77 @@ export class ExtensionsActivator { continue; } - const depDesc = this._registry.getExtensionDescription(depId); + const dep = this._activatedExtensions.get(ExtensionIdentifier.toKey(depId)); + if (dep && !dep.activationFailed) { + // the dependency is already activated OK + continue; + } - if (!depDesc) { - // Error condition 1: unknown dependency - this._host.showMessage(Severity.Error, nls.localize('unknownDep', "Cannot activate extension '{0}' because it depends on extension '{1}', which is not installed or disabled. Please install or enable '{1}' and reload the window.", currentExtension.displayName || currentExtension.identifier.value, depId)); - const error = new Error(`Unknown dependency '${depId}'`); + if (dep && dep.activationFailed) { + // Error condition 2: a dependency has already failed activation + this._host.onExtensionActivationError(currentExtension.identifier, nls.localize('failedDep1', "Cannot activate extension '{0}' because it depends on extension '{1}', which failed to activate.", currentExtension.displayName || currentExtension.identifier.value, depId)); + const error = new Error(`Dependency ${depId} failed to activate`); + (error).detail = dep.activationFailedError; this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error)); return; } - const dep = this._activatedExtensions.get(ExtensionIdentifier.toKey(depId)); - if (dep) { - if (dep.activationFailed) { - // Error condition 2: a dependency has already failed activation - this._host.showMessage(Severity.Error, nls.localize('failedDep1', "Cannot activate extension '{0}' because it depends on extension '{1}', which failed to activate.", currentExtension.displayName || currentExtension.identifier.value, depId)); - const error = new Error(`Dependency ${depId} failed to activate`); - (error).detail = dep.activationFailedError; - this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error)); - return; - } - } else { + if (this._hostExtensionsMap.has(ExtensionIdentifier.toKey(depId))) { // must first wait for the dependency to activate currentExtensionGetsGreenLight = false; - greenExtensions[ExtensionIdentifier.toKey(depId)] = depDesc; + greenExtensions[ExtensionIdentifier.toKey(depId)] = this._hostExtensionsMap.get(ExtensionIdentifier.toKey(depId))!; + continue; } + + const depDesc = this._registry.getExtensionDescription(depId); + if (depDesc) { + // must first wait for the dependency to activate + currentExtensionGetsGreenLight = false; + greenExtensions[ExtensionIdentifier.toKey(depId)] = depDesc.identifier; + continue; + } + + // Error condition 1: unknown dependency + this._host.onExtensionActivationError(currentExtension.identifier, new MissingDependencyError(depId)); + const error = new Error(`Unknown dependency '${depId}'`); + this._activatedExtensions.set(ExtensionIdentifier.toKey(currentExtension.identifier), new FailedExtension(error)); + return; } if (currentExtensionGetsGreenLight) { - greenExtensions[ExtensionIdentifier.toKey(currentExtension.identifier)] = currentExtension; + greenExtensions[ExtensionIdentifier.toKey(currentExtension.identifier)] = currentExtensionId; } else { - redExtensions.push(currentExtension); + redExtensions.push(currentExtensionId); } } - private _activateExtensions(extensionDescriptions: IExtensionDescription[], reason: ExtensionActivationReason, recursionLevel: number): Promise { - // console.log(recursionLevel, '_activateExtensions: ', extensionDescriptions.map(p => p.id)); - if (extensionDescriptions.length === 0) { + private _activateExtensions(extensionIds: ExtensionIdentifier[], reason: ExtensionActivationReason): Promise { + // console.log('_activateExtensions: ', extensionIds.map(p => p.value)); + if (extensionIds.length === 0) { return Promise.resolve(undefined); } - extensionDescriptions = extensionDescriptions.filter((p) => !this._activatedExtensions.has(ExtensionIdentifier.toKey(p.identifier))); - if (extensionDescriptions.length === 0) { + extensionIds = extensionIds.filter((p) => !this._activatedExtensions.has(ExtensionIdentifier.toKey(p))); + if (extensionIds.length === 0) { return Promise.resolve(undefined); } - if (recursionLevel > 10) { - // More than 10 dependencies deep => most likely a dependency loop - for (let i = 0, len = extensionDescriptions.length; i < len; i++) { - // Error condition 3: dependency loop - this._host.showMessage(Severity.Error, nls.localize('failedDep2', "Extension '{0}' failed to activate. Reason: more than 10 levels of dependencies (most likely a dependency loop).", extensionDescriptions[i].identifier.value)); - const error = new Error('More than 10 levels of dependencies (most likely a dependency loop)'); - this._activatedExtensions.set(ExtensionIdentifier.toKey(extensionDescriptions[i].identifier), new FailedExtension(error)); - } - return Promise.resolve(undefined); - } + const greenMap: { [id: string]: ExtensionIdentifier; } = Object.create(null), + red: ExtensionIdentifier[] = []; - let greenMap: { [id: string]: IExtensionDescription; } = Object.create(null), - red: IExtensionDescription[] = []; - - for (let i = 0, len = extensionDescriptions.length; i < len; i++) { - this._handleActivateRequest(extensionDescriptions[i], greenMap, red); + for (let i = 0, len = extensionIds.length; i < len; i++) { + this._handleActivateRequest(extensionIds[i], greenMap, red); } // Make sure no red is also green for (let i = 0, len = red.length; i < len; i++) { - const redExtensionKey = ExtensionIdentifier.toKey(red[i].identifier); + const redExtensionKey = ExtensionIdentifier.toKey(red[i]); if (greenMap[redExtensionKey]) { delete greenMap[redExtensionKey]; } } - let green = Object.keys(greenMap).map(id => greenMap[id]); + const green = Object.keys(greenMap).map(id => greenMap[id]); // console.log('greenExtensions: ', green.map(p => p.id)); // console.log('redExtensions: ', red.map(p => p.id)); @@ -342,13 +354,13 @@ export class ExtensionsActivator { return Promise.all(green.map((p) => this._activateExtension(p, reason))).then(_ => undefined); } - return this._activateExtensions(green, reason, recursionLevel + 1).then(_ => { - return this._activateExtensions(red, reason, recursionLevel + 1); + return this._activateExtensions(green, reason).then(_ => { + return this._activateExtensions(red, reason); }); } - private _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { - const extensionKey = ExtensionIdentifier.toKey(extensionDescription.identifier); + private _activateExtension(extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise { + const extensionKey = ExtensionIdentifier.toKey(extensionId); if (this._activatedExtensions.has(extensionKey)) { return Promise.resolve(undefined); @@ -359,9 +371,9 @@ export class ExtensionsActivator { return currentlyActivatingExtension; } - const newlyActivatingExtension = this._host.actualActivateExtension(extensionDescription, reason).then(undefined, (err) => { - this._host.showMessage(Severity.Error, nls.localize('activationError', "Activating extension '{0}' failed: {1}.", extensionDescription.identifier.value, err.message)); - console.error('Activating extension `' + extensionDescription.identifier.value + '` failed: ', err.message); + const newlyActivatingExtension = this._host.actualActivateExtension(extensionId, reason).then(undefined, (err) => { + this._host.onExtensionActivationError(extensionId, nls.localize('activationError', "Activating extension '{0}' failed: {1}.", extensionId.value, err.message)); + console.error('Activating extension `' + extensionId.value + '` failed: ', err.message); console.log('Here is the error stack: ', err.stack); // Treat the extension as being empty return new FailedExtension(err); diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 7c28a729f7..6c28e54f8d 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -4,30 +4,33 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; // {{SQL CARBON EDIT}} import { createApiFactory, initializeExtensionApi, ISqlExtensionApiFactory } from 'sql/workbench/api/node/sqlExtHost.api.impl'; +import { originalFSPath } from 'vs/base/common/resources'; import { Barrier } from 'vs/base/common/async'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { TernarySearchTree } from 'vs/base/common/map'; -import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; import { ILogService } from 'vs/platform/log/common/log'; // {{SQL CARBON EDIT}} - Remove createApiFactory initializeExtensionApi, and IExtensionApiFactory imports -import { ExtHostExtensionServiceShape, IEnvironment, IInitData, IMainContext, IWorkspaceData, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape } from 'vs/workbench/api/node/extHost.protocol'; +import { ExtHostExtensionServiceShape, IEnvironment, IInitData, IMainContext, MainContext, MainThreadExtensionServiceShape, MainThreadTelemetryShape, MainThreadWorkspaceShape, IStaticWorkspaceData } from 'vs/workbench/api/node/extHost.protocol'; import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; -import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionContext, IExtensionMemento, IExtensionModule } from 'vs/workbench/api/node/extHostExtensionActivator'; +import { ActivatedExtension, EmptyExtension, ExtensionActivatedByAPI, ExtensionActivatedByEvent, ExtensionActivationReason, ExtensionActivationTimes, ExtensionActivationTimesBuilder, ExtensionsActivator, IExtensionAPI, IExtensionContext, IExtensionMemento, IExtensionModule, HostExtension } from 'vs/workbench/api/node/extHostExtensionActivator'; import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage'; import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionActivationError } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry'; import { connectProxyResolver } from 'vs/workbench/services/extensions/node/proxyResolver'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import * as vscode from 'vscode'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IWorkspace } from 'vs/platform/workspace/common/workspace'; +import { Schemas } from 'vs/base/common/network'; class ExtensionMemento implements IExtensionMemento { @@ -82,13 +85,13 @@ class ExtensionMemento implements IExtensionMemento { class ExtensionStoragePath { - private readonly _workspace: IWorkspaceData; + private readonly _workspace?: IStaticWorkspaceData; private readonly _environment: IEnvironment; - private readonly _ready: Promise; - private _value: string; + private readonly _ready: Promise; + private _value?: string; - constructor(workspace: IWorkspaceData, environment: IEnvironment) { + constructor(workspace: IStaticWorkspaceData | undefined, environment: IEnvironment) { this._workspace = workspace; this._environment = environment; this._ready = this._getOrCreateWorkspaceStoragePath().then(value => this._value = value); @@ -98,7 +101,7 @@ class ExtensionStoragePath { return this._ready; } - workspaceValue(extension: IExtensionDescription): string { + workspaceValue(extension: IExtensionDescription): string | undefined { if (this._value) { return path.join(this._value, extension.identifier.value); } @@ -109,11 +112,14 @@ class ExtensionStoragePath { return path.join(this._environment.globalStorageHome.fsPath, extension.identifier.value.toLowerCase()); } - private async _getOrCreateWorkspaceStoragePath(): Promise { + private async _getOrCreateWorkspaceStoragePath(): Promise { if (!this._workspace) { return Promise.resolve(undefined); } + if (!this._environment.appSettingsHome) { + return undefined; + } const storageName = this._workspace.id; const storagePath = path.join(this._environment.appSettingsHome.fsPath, 'workspaceStorage', storageName); @@ -161,15 +167,17 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { private readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape; private readonly _mainThreadExtensionsProxy: MainThreadExtensionServiceShape; - private readonly _barrier: Barrier; + private readonly _almostReadyToRunExtensions: Barrier; + private readonly _readyToRunExtensions: Barrier; private readonly _registry: ExtensionDescriptionRegistry; private readonly _storage: ExtHostStorage; private readonly _storagePath: ExtensionStoragePath; private readonly _activator: ExtensionsActivator; - private _extensionPathIndex: Promise>; - // {{SQL CARBON EDIT}} + private _extensionPathIndex: Promise> | null; private readonly _extensionApiFactory: ISqlExtensionApiFactory; + private readonly _resolvers: { [authorityPrefix: string]: vscode.RemoteAuthorityResolver; }; + private _started: boolean; constructor( @@ -191,27 +199,27 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { this._mainThreadTelemetryProxy = this._extHostContext.getProxy(MainContext.MainThreadTelemetry); this._mainThreadExtensionsProxy = this._extHostContext.getProxy(MainContext.MainThreadExtensionService); - this._barrier = new Barrier(); + this._almostReadyToRunExtensions = new Barrier(); + this._readyToRunExtensions = new Barrier(); this._registry = new ExtensionDescriptionRegistry(initData.extensions); this._storage = new ExtHostStorage(this._extHostContext); this._storagePath = new ExtensionStoragePath(initData.workspace, initData.environment); - this._activator = new ExtensionsActivator(this._registry, initData.resolvedExtensions, { - showMessage: (severity: Severity, message: string): void => { - this._mainThreadExtensionsProxy.$localShowMessage(severity, message); - switch (severity) { - case Severity.Error: - console.error(message); - break; - case Severity.Warning: - console.warn(message); - break; - default: - console.log(message); - } + const hostExtensions = new Set(); + initData.hostExtensions.forEach((extensionId) => hostExtensions.add(ExtensionIdentifier.toKey(extensionId))); + + this._activator = new ExtensionsActivator(this._registry, initData.resolvedExtensions, initData.hostExtensions, { + onExtensionActivationError: (extensionId: ExtensionIdentifier, error: ExtensionActivationError): void => { + this._mainThreadExtensionsProxy.$onExtensionActivationError(extensionId, error); }, - actualActivateExtension: (extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise => { + actualActivateExtension: async (extensionId: ExtensionIdentifier, reason: ExtensionActivationReason): Promise => { + if (hostExtensions.has(ExtensionIdentifier.toKey(extensionId))) { + const activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null); + await this._mainThreadExtensionsProxy.$activateExtension(extensionId, activationEvent); + return new HostExtension(); + } + const extensionDescription = this._registry.getExtensionDescription(extensionId)!; return this._activateExtension(extensionDescription, reason); } }); @@ -220,6 +228,8 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { // initialize API first (i.e. do not release barrier until the API is initialized) this._extensionApiFactory = createApiFactory(this._initData, this._extHostContext, this._extHostWorkspace, this._extHostConfiguration, this, this._extHostLogService, this._storage); + this._resolvers = Object.create(null); + this._started = false; this._initialize(); @@ -235,7 +245,10 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { await initializeExtensionApi(this, this._extensionApiFactory, this._registry, configProvider); // Do this when extension service exists, but extensions are not being activated yet. await connectProxyResolver(this._extHostWorkspace, configProvider, this, this._extHostLogService, this._mainThreadTelemetryProxy); - this._barrier.open(); + this._almostReadyToRunExtensions.open(); + + await this._extHostWorkspace.waitForInitializeCall(); + this._readyToRunExtensions.open(); } catch (err) { errors.onUnexpectedError(err); } @@ -258,7 +271,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { } public isActivated(extensionId: ExtensionIdentifier): boolean { - if (this._barrier.isOpen()) { + if (this._readyToRunExtensions.isOpen()) { return this._activator.isActivated(extensionId); } return false; @@ -285,11 +298,11 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { } public getExtensionRegistry(): Promise { - return this._barrier.wait().then(_ => this._registry); + return this._readyToRunExtensions.wait().then(_ => this._registry); } - public getExtensionExports(extensionId: ExtensionIdentifier): IExtensionAPI { - if (this._barrier.isOpen()) { + public getExtensionExports(extensionId: ExtensionIdentifier): IExtensionAPI | null | undefined { + if (this._readyToRunExtensions.isOpen()) { return this._activator.getActivatedExtension(extensionId).exports; } else { return null; @@ -314,7 +327,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { private _deactivate(extensionId: ExtensionIdentifier): Promise { let result = Promise.resolve(undefined); - if (!this._barrier.isOpen()) { + if (!this._readyToRunExtensions.isOpen()) { return result; } @@ -322,7 +335,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { return result; } - let extension = this._activator.getActivatedExtension(extensionId); + const extension = this._activator.getActivatedExtension(extensionId); if (!extension) { return result; } @@ -349,29 +362,24 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { return result; } - public addMessage(extensionId: ExtensionIdentifier, severity: Severity, message: string): void { - this._mainThreadExtensionsProxy.$addMessage(extensionId, severity, message); - } - // --- impl private _activateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { this._mainThreadExtensionsProxy.$onWillActivateExtension(extensionDescription.identifier); return this._doActivateExtension(extensionDescription, reason).then((activatedExtension) => { const activationTimes = activatedExtension.activationTimes; - let activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null); + const activationEvent = (reason instanceof ExtensionActivatedByEvent ? reason.activationEvent : null); this._mainThreadExtensionsProxy.$onDidActivateExtension(extensionDescription.identifier, activationTimes.startup, activationTimes.codeLoadingTime, activationTimes.activateCallTime, activationTimes.activateResolvedTime, activationEvent); this._logExtensionActivationTimes(extensionDescription, reason, 'success', activationTimes); return activatedExtension; }, (err) => { - this._mainThreadExtensionsProxy.$onExtensionActivationFailed(extensionDescription.identifier); this._logExtensionActivationTimes(extensionDescription, reason, 'failure'); throw err; }); } private _logExtensionActivationTimes(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason, outcome: string, activationTimes?: ExtensionActivationTimes) { - let event = getTelemetryActivationEvent(extensionDescription, reason); + const event = getTelemetryActivationEvent(extensionDescription, reason); /* __GDPR__ "extensionActivationTimes" : { "${include}": [ @@ -389,7 +397,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { } private _doActivateExtension(extensionDescription: IExtensionDescription, reason: ExtensionActivationReason): Promise { - let event = getTelemetryActivationEvent(extensionDescription, reason); + const event = getTelemetryActivationEvent(extensionDescription, reason); /* __GDPR__ "activatePlugin" : { "${include}": [ @@ -416,8 +424,8 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { private _loadExtensionContext(extensionDescription: IExtensionDescription): Promise { - let globalState = new ExtensionMemento(extensionDescription.identifier.value, true, this._storage); - let workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage); + const globalState = new ExtensionMemento(extensionDescription.identifier.value, true, this._storage); + const workspaceState = new ExtensionMemento(extensionDescription.identifier.value, false, this._storage); this._extHostLogService.trace(`ExtensionService#loadExtensionContext ${extensionDescription.identifier.value}`); return Promise.all([ @@ -481,10 +489,10 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { console.error(err); }); - return this._handleWorkspaceContainsEagerExtensions(this._initData.workspace); + return this._handleWorkspaceContainsEagerExtensions(this._extHostWorkspace.workspace); } - private _handleWorkspaceContainsEagerExtensions(workspace: IWorkspaceData): Promise { + private _handleWorkspaceContainsEagerExtensions(workspace: IWorkspace | undefined): Promise { if (!workspace || workspace.folders.length === 0) { return Promise.resolve(undefined); } @@ -496,7 +504,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { ).then(() => { }); } - private _handleWorkspaceContainsEagerExtension(workspace: IWorkspaceData, desc: IExtensionDescription): Promise { + private _handleWorkspaceContainsEagerExtension(workspace: IWorkspace, desc: IExtensionDescription): Promise { const activationEvents = desc.activationEvents; if (!activationEvents) { return Promise.resolve(undefined); @@ -526,7 +534,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { return Promise.all([fileNamePromise, globPatternPromise]).then(() => { }); } - private async _activateIfFileName(workspace: IWorkspaceData, extensionId: ExtensionIdentifier, fileName: string): Promise { + private async _activateIfFileName(workspace: IWorkspace, extensionId: ExtensionIdentifier, fileName: string): Promise { // find exact path for (const { uri } of workspace.folders) { @@ -558,7 +566,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { .then(undefined, err => console.error(err)); }, ExtHostExtensionService.WORKSPACE_CONTAINS_TIMEOUT); - let exists: boolean; + let exists: boolean = false; try { exists = await searchP; } catch (err) { @@ -590,19 +598,18 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { } private _doHandleExtensionTests(): Promise { - if (!this._initData.environment.extensionTestsPath || !this._initData.environment.extensionDevelopmentLocationURI) { + const { extensionDevelopmentLocationURI, extensionTestsLocationURI } = this._initData.environment; + if (!(extensionDevelopmentLocationURI && extensionTestsLocationURI && extensionTestsLocationURI.scheme === Schemas.file)) { return Promise.resolve(undefined); } - if (this._initData.autoStart) { - return Promise.resolve(undefined); // https://github.com/Microsoft/vscode/issues/66936 - } + const extensionTestsPath = originalFSPath(extensionTestsLocationURI); // Require the test runner via node require from the provided path - let testRunner: ITestRunner; - let requireError: Error; + let testRunner: ITestRunner | undefined; + let requireError: Error | undefined; try { - testRunner = require.__$__nodeRequire(this._initData.environment.extensionTestsPath); + testRunner = require.__$__nodeRequire(extensionTestsPath); } catch (error) { requireError = error; } @@ -610,7 +617,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { // Execute the runner if it follows our spec if (testRunner && typeof testRunner.run === 'function') { return new Promise((c, e) => { - testRunner.run(this._initData.environment.extensionTestsPath, (error, failures) => { + testRunner!.run(extensionTestsPath, (error, failures) => { if (error) { e(error.toString()); } else { @@ -628,7 +635,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { this._gracefulExit(1 /* ERROR */); } - return Promise.reject(new Error(requireError ? requireError.toString() : nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", this._initData.environment.extensionTestsPath))); + return Promise.reject(new Error(requireError ? requireError.toString() : nls.localize('extensionTestError', "Path {0} does not point to a valid extension test runner.", extensionTestsPath))); } private _gracefulExit(code: number): void { @@ -643,7 +650,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { } this._started = true; - return this._barrier.wait() + return this._readyToRunExtensions.wait() .then(() => this._handleEagerExtensions()) .then(() => this._handleExtensionTests()) .then(() => { @@ -651,10 +658,40 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { }); } + // -- called by extensions + + public registerRemoteAuthorityResolver(authorityPrefix: string, resolver: vscode.RemoteAuthorityResolver): vscode.Disposable { + this._resolvers[authorityPrefix] = resolver; + return toDisposable(() => { + delete this._resolvers[authorityPrefix]; + }); + } + // -- called by main thread public async $resolveAuthority(remoteAuthority: string): Promise { - throw new Error(`Not implemented`); + const authorityPlusIndex = remoteAuthority.indexOf('+'); + if (authorityPlusIndex === -1) { + throw new Error(`Not an authority that can be resolved!`); + } + const authorityPrefix = remoteAuthority.substr(0, authorityPlusIndex); + + await this._almostReadyToRunExtensions.wait(); + await this._activateByEvent(`onResolveRemoteAuthority:${authorityPrefix}`, false); + + const resolver = this._resolvers[authorityPrefix]; + if (!resolver) { + throw new Error(`No resolver available for ${authorityPrefix}`); + } + + const result = await resolver.resolve(remoteAuthority); + return { + authority: remoteAuthority, + host: result.host, + port: result.port, + debugListenPort: result.debugListenPort, + debugConnectPort: result.debugConnectPort, + }; } public $startExtensionHost(enabledExtensionIds: ExtensionIdentifier[]): Promise { @@ -664,16 +701,19 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { public $activateByEvent(activationEvent: string): Promise { return ( - this._barrier.wait() + this._readyToRunExtensions.wait() .then(_ => this._activateByEvent(activationEvent, false)) ); } - public $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise { - return ( - this._barrier.wait() - .then(_ => this._activateById(extensionId, new ExtensionActivatedByEvent(false, activationEvent))) - ); + public async $activate(extensionId: ExtensionIdentifier, activationEvent: string): Promise { + await this._readyToRunExtensions.wait(); + if (!this._registry.getExtensionDescription(extensionId)) { + // unknown extension => ignore + return false; + } + await this._activateById(extensionId, new ExtensionActivatedByEvent(false, activationEvent)); + return true; } public async $deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { @@ -708,7 +748,7 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { } public async $test_down(size: number): Promise { - let b = Buffer.alloc(size, Math.random() % 256); + const b = Buffer.alloc(size, Math.random() % 256); return b; } @@ -744,7 +784,7 @@ function getTelemetryActivationEvent(extensionDescription: IExtensionDescription "reason": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ - let event = { + const event = { id: extensionDescription.identifier.value, name: extensionDescription.name, extensionVersion: extensionDescription.version, @@ -755,4 +795,4 @@ function getTelemetryActivationEvent(extensionDescription: IExtensionDescription }; return event; -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/node/extHostFileSystem.ts b/src/vs/workbench/api/node/extHostFileSystem.ts index d6c0be7d99..794e22a79e 100644 --- a/src/vs/workbench/api/node/extHostFileSystem.ts +++ b/src/vs/workbench/api/node/extHostFileSystem.ts @@ -8,19 +8,19 @@ import { MainContext, IMainContext, ExtHostFileSystemShape, MainThreadFileSystem import * as vscode from 'vscode'; import * as files from 'vs/platform/files/common/files'; import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; -import { FileChangeType, DocumentLink } from 'vs/workbench/api/node/extHostTypes'; +import { FileChangeType } from 'vs/workbench/api/node/extHostTypes'; import * as typeConverter from 'vs/workbench/api/node/extHostTypeConverters'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures'; import { Schemas } from 'vs/base/common/network'; import { ResourceLabelFormatter } from 'vs/platform/label/common/label'; -import { State, StateMachine, LinkComputer } from 'vs/editor/common/modes/linkComputer'; +import { State, StateMachine, LinkComputer, Edge } from 'vs/editor/common/modes/linkComputer'; import { commonPrefixLength } from 'vs/base/common/strings'; import { CharCode } from 'vs/base/common/charCode'; class FsLinkProvider { private _schemes: string[] = []; - private _stateMachine: StateMachine; + private _stateMachine?: StateMachine; add(scheme: string): void { this._stateMachine = undefined; @@ -28,7 +28,7 @@ class FsLinkProvider { } delete(scheme: string): void { - let idx = this._schemes.indexOf(scheme); + const idx = this._schemes.indexOf(scheme); if (idx >= 0) { this._schemes.splice(idx, 1); this._stateMachine = undefined; @@ -41,8 +41,8 @@ class FsLinkProvider { // sort and compute common prefix with previous scheme // then build state transitions based on the data const schemes = this._schemes.sort(); - const edges = []; - let prevScheme: string; + const edges: Edge[] = []; + let prevScheme: string | undefined; let prevState: State; let nextState = State.LastKnownState; for (const scheme of schemes) { @@ -94,11 +94,9 @@ class FsLinkProvider { }, this._stateMachine); for (const link of links) { - try { - let uri = URI.parse(link.url, true); - result.push(new DocumentLink(typeConverter.Range.to(link.range), uri)); - } catch (err) { - // ignore + const docLink = typeConverter.DocumentLink.to(link); + if (docLink.target) { + result.push(docLink); } } return result; @@ -114,6 +112,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { private readonly _watches = new Map(); private _linkProviderRegistration: IDisposable; + // Used as a handle both for file system providers and resource label formatters (being lazy) private _handlePool: number = 0; constructor(mainContext: IMainContext, private _extHostLanguageFeatures: ExtHostLanguageFeatures) { @@ -173,14 +172,14 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { this._proxy.$registerFileSystemProvider(handle, scheme, capabilites); const subscription = provider.onDidChangeFile(event => { - let mapped: IFileChangeDto[] = []; + const mapped: IFileChangeDto[] = []; for (const e of event) { let { uri: resource, type } = e; if (resource.scheme !== scheme) { // dropping events for wrong scheme continue; } - let newType: files.FileChangeType; + let newType: files.FileChangeType | undefined; switch (type) { case FileChangeType.Changed: newType = files.FileChangeType.UPDATED; @@ -191,6 +190,8 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { case FileChangeType.Deleted: newType = files.FileChangeType.DELETED; break; + default: + throw new Error('Unknown FileChangeType'); } mapped.push({ resource, type: newType }); } @@ -206,8 +207,13 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { }); } - setUriFormatter(formatter: ResourceLabelFormatter): void { - this._proxy.$setUriFormatter(formatter); + registerResourceLabelFormatter(formatter: ResourceLabelFormatter): IDisposable { + const handle = this._handlePool++; + this._proxy.$registerResourceLabelFormatter(handle, formatter); + + return toDisposable(() => { + this._proxy.$unregisterResourceLabelFormatter(handle); + }); } private static _asIStat(stat: vscode.FileStat): files.IStat { @@ -215,65 +221,51 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { return { type, ctime, mtime, size }; } - private _checkProviderExists(handle: number): void { - if (!this._fsProvider.has(handle)) { - const err = new Error(); - err.name = 'ENOPRO'; - err.message = `no provider`; - throw err; - } - } - $stat(handle: number, resource: UriComponents): Promise { - this._checkProviderExists(handle); - return Promise.resolve(this._fsProvider.get(handle).stat(URI.revive(resource))).then(ExtHostFileSystem._asIStat); + return Promise.resolve(this.getProvider(handle).stat(URI.revive(resource))).then(ExtHostFileSystem._asIStat); } $readdir(handle: number, resource: UriComponents): Promise<[string, files.FileType][]> { - this._checkProviderExists(handle); - return Promise.resolve(this._fsProvider.get(handle).readDirectory(URI.revive(resource))); + return Promise.resolve(this.getProvider(handle).readDirectory(URI.revive(resource))); } $readFile(handle: number, resource: UriComponents): Promise { - this._checkProviderExists(handle); - return Promise.resolve(this._fsProvider.get(handle).readFile(URI.revive(resource))).then(data => { + return Promise.resolve(this.getProvider(handle).readFile(URI.revive(resource))).then(data => { return Buffer.isBuffer(data) ? data : Buffer.from(data.buffer, data.byteOffset, data.byteLength); }); } $writeFile(handle: number, resource: UriComponents, content: Buffer, opts: files.FileWriteOptions): Promise { - this._checkProviderExists(handle); - return Promise.resolve(this._fsProvider.get(handle).writeFile(URI.revive(resource), content, opts)); + return Promise.resolve(this.getProvider(handle).writeFile(URI.revive(resource), content, opts)); } $delete(handle: number, resource: UriComponents, opts: files.FileDeleteOptions): Promise { - this._checkProviderExists(handle); - return Promise.resolve(this._fsProvider.get(handle).delete(URI.revive(resource), opts)); + return Promise.resolve(this.getProvider(handle).delete(URI.revive(resource), opts)); } $rename(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): Promise { - this._checkProviderExists(handle); - return Promise.resolve(this._fsProvider.get(handle).rename(URI.revive(oldUri), URI.revive(newUri), opts)); + return Promise.resolve(this.getProvider(handle).rename(URI.revive(oldUri), URI.revive(newUri), opts)); } $copy(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): Promise { - this._checkProviderExists(handle); - return Promise.resolve(this._fsProvider.get(handle).copy(URI.revive(oldUri), URI.revive(newUri), opts)); + const provider = this.getProvider(handle); + if (!provider.copy) { + throw new Error('FileSystemProvider does not implement "copy"'); + } + return Promise.resolve(provider.copy(URI.revive(oldUri), URI.revive(newUri), opts)); } $mkdir(handle: number, resource: UriComponents): Promise { - this._checkProviderExists(handle); - return Promise.resolve(this._fsProvider.get(handle).createDirectory(URI.revive(resource))); + return Promise.resolve(this.getProvider(handle).createDirectory(URI.revive(resource))); } $watch(handle: number, session: number, resource: UriComponents, opts: files.IWatchOptions): void { - this._checkProviderExists(handle); - let subscription = this._fsProvider.get(handle).watch(URI.revive(resource), opts); + const subscription = this.getProvider(handle).watch(URI.revive(resource), opts); this._watches.set(session, subscription); } - $unwatch(session: number): void { - let subscription = this._watches.get(session); + $unwatch(_handle: number, session: number): void { + const subscription = this._watches.get(session); if (subscription) { subscription.dispose(); this._watches.delete(session); @@ -281,26 +273,48 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { } $open(handle: number, resource: UriComponents, opts: files.FileOpenOptions): Promise { - this._checkProviderExists(handle); - return Promise.resolve(this._fsProvider.get(handle).open(URI.revive(resource), opts)); + const provider = this.getProvider(handle); + if (!provider.open) { + throw new Error('FileSystemProvider does not implement "open"'); + } + return Promise.resolve(provider.open(URI.revive(resource), opts)); } $close(handle: number, fd: number): Promise { - this._checkProviderExists(handle); - return Promise.resolve(this._fsProvider.get(handle).close(fd)); + const provider = this.getProvider(handle); + if (!provider.close) { + throw new Error('FileSystemProvider does not implement "close"'); + } + return Promise.resolve(provider.close(fd)); } $read(handle: number, fd: number, pos: number, length: number): Promise { - this._checkProviderExists(handle); + const provider = this.getProvider(handle); + if (!provider.read) { + throw new Error('FileSystemProvider does not implement "read"'); + } const data = Buffer.allocUnsafe(length); - return Promise.resolve(this._fsProvider.get(handle).read(fd, pos, data, 0, length)).then(read => { + return Promise.resolve(provider.read(fd, pos, data, 0, length)).then(read => { return data.slice(0, read); // don't send zeros }); } $write(handle: number, fd: number, pos: number, data: Buffer): Promise { - this._checkProviderExists(handle); - return Promise.resolve(this._fsProvider.get(handle).write(fd, pos, data, 0, data.length)); + const provider = this.getProvider(handle); + if (!provider.write) { + throw new Error('FileSystemProvider does not implement "write"'); + } + return Promise.resolve(provider.write(fd, pos, data, 0, data.length)); } + private getProvider(handle: number): vscode.FileSystemProvider { + const provider = this._fsProvider.get(handle); + if (!provider) { + const err = new Error(); + err.name = 'ENOPRO'; + err.message = `no provider`; + throw err; + } + return provider; + } } diff --git a/src/vs/workbench/api/node/extHostFileSystemEventService.ts b/src/vs/workbench/api/node/extHostFileSystemEventService.ts index a7340364f9..164f6f2c27 100644 --- a/src/vs/workbench/api/node/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/node/extHostFileSystemEventService.ts @@ -8,11 +8,11 @@ import { AsyncEmitter, Emitter, Event } from 'vs/base/common/event'; import { IRelativePattern, parse } from 'vs/base/common/glob'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import * as vscode from 'vscode'; import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, ResourceFileEditDto, ResourceTextEditDto, MainThreadTextEditorsShape } from './extHost.protocol'; import * as typeConverter from './extHostTypeConverters'; import { Disposable, WorkspaceEdit } from './extHostTypes'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; class FileSystemWatcher implements vscode.FileSystemWatcher { @@ -49,10 +49,10 @@ class FileSystemWatcher implements vscode.FileSystemWatcher { const parsedPattern = parse(globPattern); - let subscription = dispatcher(events => { + const subscription = dispatcher(events => { if (!ignoreCreateEvents) { for (let created of events.created) { - let uri = URI.revive(created); + const uri = URI.revive(created); if (parsedPattern(uri.fsPath)) { this._onDidCreate.fire(uri); } @@ -60,7 +60,7 @@ class FileSystemWatcher implements vscode.FileSystemWatcher { } if (!ignoreChangeEvents) { for (let changed of events.changed) { - let uri = URI.revive(changed); + const uri = URI.revive(changed); if (parsedPattern(uri.fsPath)) { this._onDidChange.fire(uri); } @@ -68,7 +68,7 @@ class FileSystemWatcher implements vscode.FileSystemWatcher { } if (!ignoreDeleteEvents) { for (let deleted of events.deleted) { - let uri = URI.revive(deleted); + const uri = URI.revive(deleted); if (parsedPattern(uri.fsPath)) { this._onDidDelete.fire(uri); } @@ -163,13 +163,13 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ bucket.push(wrappedThenable); } }; - }).then(() => { + }).then((): any => { if (edits.length === 0) { return undefined; } // flatten all WorkspaceEdits collected via waitUntil-call // and apply them in one go. - let allEdits = new Array>(); + const allEdits = new Array>(); for (let edit of edits) { if (edit) { // sparse array let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors); diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index cc8b2c8d64..effbd963d6 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -15,17 +15,19 @@ import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands'; import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics'; import { asPromise } from 'vs/base/common/async'; -import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, SuggestionDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata } from './extHost.protocol'; +import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, WorkspaceSymbolDto, SuggestResultDto, WorkspaceSymbolsDto, SuggestionDto, CodeActionDto, ISerializedDocumentFilter, WorkspaceEditDto, ISerializedSignatureHelpProviderMetadata, LinkDto, CodeLensDto, MainThreadWebviewsShape, CodeInsetDto } from './extHost.protocol'; import { regExpLeadsToEndlessLoop, regExpFlags } from 'vs/base/common/strings'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange, Range as EditorRange } from 'vs/editor/common/core/range'; -import { isFalsyOrEmpty, isNonEmptyArray } from 'vs/base/common/arrays'; +import { isFalsyOrEmpty, isNonEmptyArray, coalesce } from 'vs/base/common/arrays'; import { isObject } from 'vs/base/common/types'; import { ISelection, Selection } from 'vs/editor/common/core/selection'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtHostWebview } from 'vs/workbench/api/node/extHostWebview'; +import * as codeInset from 'vs/workbench/contrib/codeinset/common/codeInset'; +import { generateUuid } from 'vs/base/common/uuid'; // --- adapter @@ -39,12 +41,12 @@ class DocumentSymbolAdapter { this._provider = provider; } - provideDocumentSymbols(resource: URI, token: CancellationToken): Promise { - let doc = this._documents.getDocumentData(resource).document; + provideDocumentSymbols(resource: URI, token: CancellationToken): Promise { + const doc = this._documents.getDocument(resource); return asPromise(() => this._provider.provideDocumentSymbols(doc, token)).then(value => { if (isFalsyOrEmpty(value)) { return undefined; - } else if (value[0] instanceof DocumentSymbol) { + } else if (value![0] instanceof DocumentSymbol) { return (value).map(typeConvert.DocumentSymbol.from); } else { return DocumentSymbolAdapter._asDocumentSymbolTree(value); @@ -62,12 +64,13 @@ class DocumentSymbolAdapter { } return res; }); - let res: modes.DocumentSymbol[] = []; - let parentStack: modes.DocumentSymbol[] = []; + const res: modes.DocumentSymbol[] = []; + const parentStack: modes.DocumentSymbol[] = []; for (const info of infos) { - let element = { + const element = { name: info.name || '!!MISSING: name!!', kind: typeConvert.SymbolKind.from(info.kind), + detail: undefined!, // Strict null override — avoid changing behavior containerName: info.containerName, range: typeConvert.Range.from(info.location.range), selectionRange: typeConvert.Range.from(info.location.range), @@ -80,9 +83,11 @@ class DocumentSymbolAdapter { res.push(element); break; } - let parent = parentStack[parentStack.length - 1]; + const parent = parentStack[parentStack.length - 1]; if (EditorRange.containsRange(parent.range, element.range) && !EditorRange.equalsRange(parent.range, element.range)) { - parent.children.push(element); + if (parent.children) { + parent.children.push(element); + } parentStack.push(element); break; } @@ -104,40 +109,82 @@ class CodeLensAdapter { private readonly _provider: vscode.CodeLensProvider ) { } - provideCodeLenses(resource: URI, token: CancellationToken): Promise { - const doc = this._documents.getDocumentData(resource).document; + provideCodeLenses(resource: URI, token: CancellationToken): Promise { + const doc = this._documents.getDocument(resource); return asPromise(() => this._provider.provideCodeLenses(doc, token)).then(lenses => { - if (Array.isArray(lenses)) { - return lenses.map(lens => { + const result: CodeLensDto[] = []; + if (isNonEmptyArray(lenses)) { + for (const lens of lenses) { const id = this._heapService.keep(lens); - return ObjectIdentifier.mixin({ + result.push(ObjectIdentifier.mixin({ range: typeConvert.Range.from(lens.range), command: this._commands.toInternal(lens.command) - }, id); + }, id)); + } + } + return result; + }); + } + + resolveCodeLens(resource: URI, symbol: CodeLensDto, token: CancellationToken): Promise { + + const lens = this._heapService.get(ObjectIdentifier.of(symbol)); + if (!lens) { + return Promise.resolve(undefined); + } + + let resolve: Promise; + if (typeof this._provider.resolveCodeLens !== 'function' || lens.isResolved) { + resolve = Promise.resolve(lens); + } else { + resolve = asPromise(() => this._provider.resolveCodeLens!(lens, token)); + } + + return resolve.then(newLens => { + newLens = newLens || lens; + symbol.command = this._commands.toInternal(newLens.command || CodeLensAdapter._badCmd); + return symbol; + }); + } +} + +class CodeInsetAdapter { + + constructor( + private readonly _documents: ExtHostDocuments, + private readonly _heapService: ExtHostHeapService, + private readonly _provider: vscode.CodeInsetProvider + ) { } + + provideCodeInsets(resource: URI, token: CancellationToken): Promise { + const doc = this._documents.getDocument(resource); + return asPromise(() => this._provider.provideCodeInsets(doc, token)).then(insets => { + if (Array.isArray(insets)) { + return insets.map(inset => { + const $ident = this._heapService.keep(inset); + const id = generateUuid(); + return { + $ident, + id, + range: typeConvert.Range.from(inset.range), + height: inset.height + }; }); } return undefined; }); } - resolveCodeLens(resource: URI, symbol: modes.ICodeLensSymbol, token: CancellationToken): Promise { + resolveCodeInset(symbol: CodeInsetDto, webview: vscode.Webview, token: CancellationToken): Promise { - const lens = this._heapService.get(ObjectIdentifier.of(symbol)); - if (!lens) { - return undefined; + const inset = this._heapService.get(ObjectIdentifier.of(symbol)); + if (!inset) { + return Promise.resolve(symbol); } - let resolve: Promise; - if (typeof this._provider.resolveCodeLens !== 'function' || lens.isResolved) { - resolve = Promise.resolve(lens); - } else { - resolve = asPromise(() => this._provider.resolveCodeLens(lens, token)); - } - - return resolve.then(newLens => { - newLens = newLens || lens; - symbol.command = this._commands.toInternal(newLens.command || CodeLensAdapter._badCmd); + return asPromise(() => this._provider.resolveCodeInset(inset, webview, token)).then(newInset => { + newInset = newInset || inset; return symbol; }); } @@ -149,7 +196,7 @@ function convertToLocationLinks(value: vscode.Definition): modes.LocationLink[] } else if (value) { return [typeConvert.DefinitionLink.from(value)]; } - return undefined; + return []; } class DefinitionAdapter { @@ -160,8 +207,8 @@ class DefinitionAdapter { ) { } provideDefinition(resource: URI, position: IPosition, token: CancellationToken): Promise { - let doc = this._documents.getDocumentData(resource).document; - let pos = typeConvert.Position.to(position); + const doc = this._documents.getDocument(resource); + const pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideDefinition(doc, pos, token)).then(convertToLocationLinks); } } @@ -174,8 +221,8 @@ class DeclarationAdapter { ) { } provideDeclaration(resource: URI, position: IPosition, token: CancellationToken): Promise { - let doc = this._documents.getDocumentData(resource).document; - let pos = typeConvert.Position.to(position); + const doc = this._documents.getDocument(resource); + const pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideDeclaration(doc, pos, token)).then(convertToLocationLinks); } } @@ -188,8 +235,8 @@ class ImplementationAdapter { ) { } provideImplementation(resource: URI, position: IPosition, token: CancellationToken): Promise { - let doc = this._documents.getDocumentData(resource).document; - let pos = typeConvert.Position.to(position); + const doc = this._documents.getDocument(resource); + const pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideImplementation(doc, pos, token)).then(convertToLocationLinks); } } @@ -202,7 +249,7 @@ class TypeDefinitionAdapter { ) { } provideTypeDefinition(resource: URI, position: IPosition, token: CancellationToken): Promise { - const doc = this._documents.getDocumentData(resource).document; + const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideTypeDefinition(doc, pos, token)).then(convertToLocationLinks); } @@ -215,10 +262,10 @@ class HoverAdapter { private readonly _provider: vscode.HoverProvider, ) { } - public provideHover(resource: URI, position: IPosition, token: CancellationToken): Promise { + public provideHover(resource: URI, position: IPosition, token: CancellationToken): Promise { - let doc = this._documents.getDocumentData(resource).document; - let pos = typeConvert.Position.to(position); + const doc = this._documents.getDocument(resource); + const pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideHover(doc, pos, token)).then(value => { if (!value || isFalsyOrEmpty(value.contents)) { @@ -243,10 +290,10 @@ class DocumentHighlightAdapter { private readonly _provider: vscode.DocumentHighlightProvider ) { } - provideDocumentHighlights(resource: URI, position: IPosition, token: CancellationToken): Promise { + provideDocumentHighlights(resource: URI, position: IPosition, token: CancellationToken): Promise { - let doc = this._documents.getDocumentData(resource).document; - let pos = typeConvert.Position.to(position); + const doc = this._documents.getDocument(resource); + const pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideDocumentHighlights(doc, pos, token)).then(value => { if (Array.isArray(value)) { @@ -264,9 +311,9 @@ class ReferenceAdapter { private readonly _provider: vscode.ReferenceProvider ) { } - provideReferences(resource: URI, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise { - let doc = this._documents.getDocumentData(resource).document; - let pos = typeConvert.Position.to(position); + provideReferences(resource: URI, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise { + const doc = this._documents.getDocument(resource); + const pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideReferences(doc, pos, context, token)).then(value => { if (Array.isArray(value)) { @@ -293,9 +340,9 @@ class CodeActionAdapter { private readonly _extensionId: ExtensionIdentifier ) { } - provideCodeActions(resource: URI, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise { + provideCodeActions(resource: URI, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise { - const doc = this._documents.getDocumentData(resource).document; + const doc = this._documents.getDocument(resource); const ran = Selection.isISelection(rangeOrSelection) ? typeConvert.Selection.to(rangeOrSelection) : typeConvert.Range.to(rangeOrSelection); @@ -316,7 +363,7 @@ class CodeActionAdapter { }; return asPromise(() => this._provider.provideCodeActions(doc, ran, codeActionContext, token)).then(commandsOrActions => { - if (isFalsyOrEmpty(commandsOrActions)) { + if (!isNonEmptyArray(commandsOrActions)) { return undefined; } const result: CustomCodeAction[] = []; @@ -368,9 +415,9 @@ class DocumentFormattingAdapter { private readonly _provider: vscode.DocumentFormattingEditProvider ) { } - provideDocumentFormattingEdits(resource: URI, options: modes.FormattingOptions, token: CancellationToken): Promise { + provideDocumentFormattingEdits(resource: URI, options: modes.FormattingOptions, token: CancellationToken): Promise { - const { document } = this._documents.getDocumentData(resource); + const document = this._documents.getDocument(resource); return asPromise(() => this._provider.provideDocumentFormattingEdits(document, options, token)).then(value => { if (Array.isArray(value)) { @@ -388,9 +435,9 @@ class RangeFormattingAdapter { private readonly _provider: vscode.DocumentRangeFormattingEditProvider ) { } - provideDocumentRangeFormattingEdits(resource: URI, range: IRange, options: modes.FormattingOptions, token: CancellationToken): Promise { + provideDocumentRangeFormattingEdits(resource: URI, range: IRange, options: modes.FormattingOptions, token: CancellationToken): Promise { - const { document } = this._documents.getDocumentData(resource); + const document = this._documents.getDocument(resource); const ran = typeConvert.Range.to(range); return asPromise(() => this._provider.provideDocumentRangeFormattingEdits(document, ran, options, token)).then(value => { @@ -411,9 +458,9 @@ class OnTypeFormattingAdapter { autoFormatTriggerCharacters: string[] = []; // not here - provideOnTypeFormattingEdits(resource: URI, position: IPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Promise { + provideOnTypeFormattingEdits(resource: URI, position: IPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Promise { - const { document } = this._documents.getDocumentData(resource); + const document = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideOnTypeFormattingEdits(document, pos, ch, options, token)).then(value => { @@ -449,31 +496,31 @@ class NavigateTypeAdapter { continue; } const symbol = IdObject.mixin(typeConvert.WorkspaceSymbol.from(item)); - this._symbolCache[symbol._id] = item; + this._symbolCache[symbol._id!] = item; result.symbols.push(symbol); } } }).then(() => { if (result.symbols.length > 0) { - this._resultCache[result._id] = [result.symbols[0]._id, result.symbols[result.symbols.length - 1]._id]; + this._resultCache[result._id!] = [result.symbols[0]._id!, result.symbols[result.symbols.length - 1]._id!]; } return result; }); } - resolveWorkspaceSymbol(symbol: WorkspaceSymbolDto, token: CancellationToken): Promise { + resolveWorkspaceSymbol(symbol: WorkspaceSymbolDto, token: CancellationToken): Promise { if (typeof this._provider.resolveWorkspaceSymbol !== 'function') { return Promise.resolve(symbol); } - const item = this._symbolCache[symbol._id]; + const item = this._symbolCache[symbol._id!]; if (item) { - return asPromise(() => this._provider.resolveWorkspaceSymbol(item, token)).then(value => { + return asPromise(() => this._provider.resolveWorkspaceSymbol!(item, token)).then(value => { return value && mixin(symbol, typeConvert.WorkspaceSymbol.from(value), true); }); } - return undefined; + return Promise.resolve(undefined); } releaseWorkspaceSymbols(id: number): any { @@ -498,10 +545,10 @@ class RenameAdapter { private readonly _provider: vscode.RenameProvider ) { } - provideRenameEdits(resource: URI, position: IPosition, newName: string, token: CancellationToken): Promise { + provideRenameEdits(resource: URI, position: IPosition, newName: string, token: CancellationToken): Promise { - let doc = this._documents.getDocumentData(resource).document; - let pos = typeConvert.Position.to(position); + const doc = this._documents.getDocument(resource); + const pos = typeConvert.Position.to(position); return asPromise(() => this._provider.provideRenameEdits(doc, pos, newName, token)).then(value => { if (!value) { @@ -509,9 +556,9 @@ class RenameAdapter { } return typeConvert.WorkspaceEdit.from(value); }, err => { - let rejectReason = RenameAdapter._asMessage(err); + const rejectReason = RenameAdapter._asMessage(err); if (rejectReason) { - return { rejectReason, edits: undefined }; + return { rejectReason, edits: undefined! }; } else { // generic error return Promise.reject(err); @@ -519,18 +566,18 @@ class RenameAdapter { }); } - resolveRenameLocation(resource: URI, position: IPosition, token: CancellationToken): Promise { + resolveRenameLocation(resource: URI, position: IPosition, token: CancellationToken): Promise<(modes.RenameLocation & modes.Rejection) | undefined> { if (typeof this._provider.prepareRename !== 'function') { return Promise.resolve(undefined); } - let doc = this._documents.getDocumentData(resource).document; - let pos = typeConvert.Position.to(position); + const doc = this._documents.getDocument(resource); + const pos = typeConvert.Position.to(position); - return asPromise(() => this._provider.prepareRename(doc, pos, token)).then(rangeOrLocation => { + return asPromise(() => this._provider.prepareRename!(doc, pos, token)).then(rangeOrLocation => { - let range: vscode.Range; - let text: string; + let range: vscode.Range | undefined; + let text: string | undefined; if (Range.isRange(rangeOrLocation)) { range = rangeOrLocation; text = doc.getText(rangeOrLocation); @@ -549,16 +596,16 @@ class RenameAdapter { } return { range: typeConvert.Range.from(range), text }; }, err => { - let rejectReason = RenameAdapter._asMessage(err); + const rejectReason = RenameAdapter._asMessage(err); if (rejectReason) { - return { rejectReason, range: undefined, text: undefined }; + return { rejectReason, range: undefined!, text: undefined! }; } else { - return Promise.reject(err); + return Promise.reject(err); } }); } - private static _asMessage(err: any): string { + private static _asMessage(err: any): string | undefined { if (typeof err === 'string') { return err; } else if (err instanceof Error && typeof err.message === 'string') { @@ -588,12 +635,12 @@ class SuggestAdapter { this._provider = provider; } - provideCompletionItems(resource: URI, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { + provideCompletionItems(resource: URI, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { - const doc = this._documents.getDocumentData(resource).document; + const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); - return asPromise( + return asPromise( () => this._provider.provideCompletionItems(doc, pos, token, typeConvert.CompletionContext.to(context)) ).then(value => { @@ -642,18 +689,18 @@ class SuggestAdapter { } const { _parentId, _id } = (suggestion); - const item = this._cache.has(_parentId) && this._cache.get(_parentId)[_id]; + const item = this._cache.has(_parentId) ? this._cache.get(_parentId)![_id] : undefined; if (!item) { return Promise.resolve(suggestion); } - return asPromise(() => this._provider.resolveCompletionItem(item, token)).then(resolvedItem => { + return asPromise(() => this._provider.resolveCompletionItem!(item, token)).then(resolvedItem => { if (!resolvedItem) { return suggestion; } - const doc = this._documents.getDocumentData(resource).document; + const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); const wordRangeBeforePos = (doc.getWordRangeAtPosition(pos) as Range || new Range(pos, pos)).with({ end: pos }); const newSuggestion = this._convertCompletionItem(resolvedItem, pos, wordRangeBeforePos, _id, _parentId); @@ -669,7 +716,7 @@ class SuggestAdapter { this._cache.delete(id); } - private _convertCompletionItem(item: vscode.CompletionItem, position: vscode.Position, defaultRange: vscode.Range, _id: number, _parentId: number): SuggestionDto { + private _convertCompletionItem(item: vscode.CompletionItem, position: vscode.Position, defaultRange: vscode.Range, _id: number, _parentId: number): SuggestionDto | undefined { if (typeof item.label !== 'string' || item.label.length === 0) { console.warn('INVALID text edit -> must have at least a label'); return undefined; @@ -683,13 +730,13 @@ class SuggestAdapter { label: item.label, kind: typeConvert.CompletionItemKind.from(item.kind), detail: item.detail, - documentation: typeConvert.MarkdownString.fromStrict(item.documentation), + documentation: typeof item.documentation === 'undefined' ? undefined : typeConvert.MarkdownString.fromStrict(item.documentation), filterText: item.filterText, sortText: item.sortText, preselect: item.preselect, // - range: undefined, - insertText: undefined, + range: undefined!, // populated below + insertText: undefined!, // populated below insertTextRules: item.keepWhitespace ? modes.CompletionItemInsertTextRule.KeepWhitespace : 0, additionalTextEdits: item.additionalTextEdits && item.additionalTextEdits.map(typeConvert.TextEdit.from), command: this._commands.toInternal(item.command), @@ -705,7 +752,7 @@ class SuggestAdapter { } else if (item.insertText instanceof SnippetString) { result.insertText = item.insertText.value; - result.insertTextRules += modes.CompletionItemInsertTextRule.InsertAsSnippet; + result.insertTextRules! |= modes.CompletionItemInsertTextRule.InsertAsSnippet; } else { result.insertText = item.label; @@ -739,8 +786,8 @@ class SignatureHelpAdapter { private readonly _heap: ExtHostHeapService, ) { } - provideSignatureHelp(resource: URI, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise { - const doc = this._documents.getDocumentData(resource).document; + provideSignatureHelp(resource: URI, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise { + const doc = this._documents.getDocument(resource); const pos = typeConvert.Position.to(position); const vscodeContext = this.reviveContext(context); @@ -778,36 +825,35 @@ class LinkProviderAdapter { private readonly _provider: vscode.DocumentLinkProvider ) { } - provideLinks(resource: URI, token: CancellationToken): Promise { - const doc = this._documents.getDocumentData(resource).document; + provideLinks(resource: URI, token: CancellationToken): Promise { + const doc = this._documents.getDocument(resource); return asPromise(() => this._provider.provideDocumentLinks(doc, token)).then(links => { if (!Array.isArray(links)) { return undefined; } - const result: modes.ILink[] = []; + const result: LinkDto[] = []; for (const link of links) { - let data = typeConvert.DocumentLink.from(link); - let id = this._heapService.keep(link); - ObjectIdentifier.mixin(data, id); - result.push(data); + const data = typeConvert.DocumentLink.from(link); + const id = this._heapService.keep(link); + result.push(ObjectIdentifier.mixin(data, id)); } return result; }); } - resolveLink(link: modes.ILink, token: CancellationToken): Promise { + resolveLink(link: LinkDto, token: CancellationToken): Promise { if (typeof this._provider.resolveDocumentLink !== 'function') { - return undefined; + return Promise.resolve(undefined); } const id = ObjectIdentifier.of(link); const item = this._heapService.get(id); if (!item) { - return undefined; + return Promise.resolve(undefined); } - return asPromise(() => this._provider.resolveDocumentLink(item, token)).then(value => { + return asPromise(() => this._provider.resolveDocumentLink!(item, token)).then(value => { if (value) { return typeConvert.DocumentLink.from(value); } @@ -824,7 +870,7 @@ class ColorProviderAdapter { ) { } provideColors(resource: URI, token: CancellationToken): Promise { - const doc = this._documents.getDocumentData(resource).document; + const doc = this._documents.getDocument(resource); return asPromise(() => this._provider.provideDocumentColors(doc, token)).then(colors => { if (!Array.isArray(colors)) { return []; @@ -841,11 +887,14 @@ class ColorProviderAdapter { }); } - provideColorPresentations(resource: URI, raw: IRawColorInfo, token: CancellationToken): Promise { - const document = this._documents.getDocumentData(resource).document; + provideColorPresentations(resource: URI, raw: IRawColorInfo, token: CancellationToken): Promise { + const document = this._documents.getDocument(resource); const range = typeConvert.Range.to(raw.range); const color = typeConvert.Color.to(raw.color); return asPromise(() => this._provider.provideColorPresentations(color, { document, range }, token)).then(value => { + if (!Array.isArray(value)) { + return undefined; + } return value.map(typeConvert.ColorPresentation.from); }); } @@ -858,8 +907,8 @@ class FoldingProviderAdapter { private _provider: vscode.FoldingRangeProvider ) { } - provideFoldingRanges(resource: URI, context: modes.FoldingContext, token: CancellationToken): Promise { - const doc = this._documents.getDocumentData(resource).document; + provideFoldingRanges(resource: URI, context: modes.FoldingContext, token: CancellationToken): Promise { + const doc = this._documents.getDocument(resource); return asPromise(() => this._provider.provideFoldingRanges(doc, context, token)).then(ranges => { if (!Array.isArray(ranges)) { return undefined; @@ -876,23 +925,40 @@ class SelectionRangeAdapter { private readonly _provider: vscode.SelectionRangeProvider ) { } - provideSelectionRanges(resource: URI, position: IPosition, token: CancellationToken): Promise { - const { document } = this._documents.getDocumentData(resource); - const pos = typeConvert.Position.to(position); - return asPromise(() => this._provider.provideSelectionRanges(document, pos, token)).then(selectionRanges => { - if (isFalsyOrEmpty(selectionRanges)) { - return undefined; + provideSelectionRanges(resource: URI, pos: IPosition[], token: CancellationToken): Promise { + const document = this._documents.getDocument(resource); + const positions = pos.map(typeConvert.Position.to); + + return asPromise(() => this._provider.provideSelectionRanges(document, positions, token)).then(allProviderRanges => { + if (!isNonEmptyArray(allProviderRanges)) { + return []; } - let result: modes.SelectionRange[] = []; - let last: vscode.Position | vscode.Range = pos; - for (const sel of selectionRanges) { - if (!sel.range.contains(last)) { - throw new Error('INVALID selection range, must contain the previous range'); + if (allProviderRanges.length !== positions.length) { + console.warn('BAD selection ranges, provider must return ranges for each position'); + return []; + } + + const allResults: modes.SelectionRange[][] = []; + for (let i = 0; i < positions.length; i++) { + const oneResult: modes.SelectionRange[] = []; + allResults.push(oneResult); + + let last: vscode.Position | vscode.Range = positions[i]; + let selectionRange = allProviderRanges[i]; + + while (true) { + if (!selectionRange.range.contains(last)) { + throw new Error('INVALID selection range, must contain the previous range'); + } + oneResult.push(typeConvert.SelectionRange.from(selectionRange)); + if (!selectionRange.parent) { + break; + } + last = selectionRange.range; + selectionRange = selectionRange.parent; } - result.push(typeConvert.SelectionRange.from(sel)); - last = sel.range; } - return result; + return allResults; }); } } @@ -901,7 +967,7 @@ type Adapter = DocumentSymbolAdapter | CodeLensAdapter | DefinitionAdapter | Hov | DocumentHighlightAdapter | ReferenceAdapter | CodeActionAdapter | DocumentFormattingAdapter | RangeFormattingAdapter | OnTypeFormattingAdapter | NavigateTypeAdapter | RenameAdapter | SuggestAdapter | SignatureHelpAdapter | LinkProviderAdapter | ImplementationAdapter | TypeDefinitionAdapter - | ColorProviderAdapter | FoldingProviderAdapter | DeclarationAdapter | SelectionRangeAdapter; + | ColorProviderAdapter | FoldingProviderAdapter | CodeInsetAdapter | DeclarationAdapter | SelectionRangeAdapter; class AdapterData { constructor( @@ -918,7 +984,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { private static _handlePool: number = 0; - private readonly _schemeTransformer: ISchemeTransformer; + private readonly _schemeTransformer: ISchemeTransformer | null; private _proxy: MainThreadLanguageFeaturesShape; private _documents: ExtHostDocuments; private _commands: ExtHostCommands; @@ -926,10 +992,11 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { private _diagnostics: ExtHostDiagnostics; private _adapter = new Map(); private readonly _logService: ILogService; + private _webviewProxy: MainThreadWebviewsShape; constructor( mainContext: IMainContext, - schemeTransformer: ISchemeTransformer, + schemeTransformer: ISchemeTransformer | null, documents: ExtHostDocuments, commands: ExtHostCommands, heapMonitor: ExtHostHeapService, @@ -943,17 +1010,18 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { this._heapService = heapMonitor; this._diagnostics = diagnostics; this._logService = logService; + this._webviewProxy = mainContext.getProxy(MainContext.MainThreadWebviews); } - private _transformDocumentSelector(selector: vscode.DocumentSelector): ISerializedDocumentFilter[] { + private _transformDocumentSelector(selector: vscode.DocumentSelector): Array { if (Array.isArray(selector)) { - return selector.map(sel => this._doTransformDocumentSelector(sel)); + return coalesce(selector.map(sel => this._doTransformDocumentSelector(sel))); } - return [this._doTransformDocumentSelector(selector)]; + return coalesce([this._doTransformDocumentSelector(selector)]); } - private _doTransformDocumentSelector(selector: string | vscode.DocumentFilter): ISerializedDocumentFilter { + private _doTransformDocumentSelector(selector: string | vscode.DocumentFilter): ISerializedDocumentFilter | undefined { if (typeof selector === 'string') { return { $serialized: true, @@ -974,7 +1042,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return undefined; } - private _transformScheme(scheme: string): string { + private _transformScheme(scheme: string | undefined): string | undefined { if (this._schemeTransformer && typeof scheme === 'string') { return this._schemeTransformer.transformOutgoing(scheme); } @@ -992,20 +1060,25 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return ExtHostLanguageFeatures._handlePool++; } - private _withAdapter(handle: number, ctor: { new(...args: any[]): A }, callback: (adapter: A) => Promise): Promise { - let data = this._adapter.get(handle); + private _withAdapter(handle: number, ctor: { new(...args: any[]): A }, callback: (adapter: A, extension: IExtensionDescription | undefined) => Promise): Promise { + const data = this._adapter.get(handle); + if (!data) { + return Promise.reject(new Error('no adapter found')); + } + if (data.adapter instanceof ctor) { let t1: number; if (data.extension) { t1 = Date.now(); this._logService.trace(`[${data.extension.identifier.value}] INVOKE provider '${(ctor as any).name}'`); } - let p = callback(data.adapter); - if (data.extension) { + const p = callback(data.adapter, data.extension); + const extension = data.extension; + if (extension) { Promise.resolve(p).then( - () => this._logService.trace(`[${data.extension.identifier.value}] provider DONE after ${Date.now() - t1}ms`), + () => this._logService.trace(`[${extension.identifier.value}] provider DONE after ${Date.now() - t1}ms`), err => { - this._logService.error(`[${data.extension.identifier.value}] provider FAILED`); + this._logService.error(`[${extension.identifier.value}] provider FAILED`); this._logService.error(err); } ); @@ -1015,7 +1088,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return Promise.reject(new Error('no adapter found')); } - private _addNewAdapter(adapter: Adapter, extension: IExtensionDescription): number { + private _addNewAdapter(adapter: Adapter, extension: IExtensionDescription | undefined): number { const handle = this._nextHandle(); this._adapter.set(handle, new AdapterData(adapter, extension)); return handle; @@ -1034,7 +1107,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise { + $provideDocumentSymbols(handle: number, resource: UriComponents, token: CancellationToken): Promise { return this._withAdapter(handle, DocumentSymbolAdapter, adapter => adapter.provideDocumentSymbols(URI.revive(resource), token)); } @@ -1049,7 +1122,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { let result = this._createDisposable(handle); if (eventHandle !== undefined) { - const subscription = provider.onDidChangeCodeLenses(_ => this._proxy.$emitCodeLensEvent(eventHandle)); + const subscription = provider.onDidChangeCodeLenses!(_ => this._proxy.$emitCodeLensEvent(eventHandle)); result = Disposable.from(result, subscription); } @@ -1060,10 +1133,41 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource), token)); } - $resolveCodeLens(handle: number, resource: UriComponents, symbol: modes.ICodeLensSymbol, token: CancellationToken): Promise { + $resolveCodeLens(handle: number, resource: UriComponents, symbol: modes.ICodeLensSymbol, token: CancellationToken): Promise { return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(URI.revive(resource), symbol, token)); } + // --- code insets + + registerCodeInsetProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CodeInsetProvider): vscode.Disposable { + const handle = this._nextHandle(); + const eventHandle = typeof provider.onDidChangeCodeInsets === 'function' ? this._nextHandle() : undefined; + + this._adapter.set(handle, new AdapterData(new CodeInsetAdapter(this._documents, this._heapService, provider), extension)); + this._proxy.$registerCodeInsetSupport(handle, this._transformDocumentSelector(selector), eventHandle); + let result = this._createDisposable(handle); + + if (eventHandle !== undefined && provider.onDidChangeCodeInsets) { + const subscription = provider.onDidChangeCodeInsets(_ => this._proxy.$emitCodeLensEvent(eventHandle)); + result = Disposable.from(result, subscription); + } + + return result; + } + + $provideCodeInsets(handle: number, resource: UriComponents, token: CancellationToken): Promise { + return this._withAdapter(handle, CodeInsetAdapter, adapter => adapter.provideCodeInsets(URI.revive(resource), token)); + } + + $resolveCodeInset(handle: number, _resource: UriComponents, symbol: codeInset.ICodeInsetSymbol, token: CancellationToken): Promise { + const webviewHandle = Math.random(); + const webview = new ExtHostWebview(webviewHandle, this._webviewProxy, { enableScripts: true }); + return this._withAdapter(handle, CodeInsetAdapter, async (adapter, extension) => { + await this._webviewProxy.$createWebviewCodeInset(webviewHandle, symbol.id, { enableCommandUris: true, enableScripts: true }, extension ? extension.extensionLocation : undefined); + return adapter.resolveCodeInset(symbol, webview, token); + }); + } + // --- declaration registerDefinitionProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable { @@ -1114,7 +1218,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { + $provideHover(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { return this._withAdapter(handle, HoverAdapter, adapter => adapter.provideHover(URI.revive(resource), position, token)); } @@ -1126,7 +1230,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { + $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { return this._withAdapter(handle, DocumentHighlightAdapter, adapter => adapter.provideDocumentHighlights(URI.revive(resource), position, token)); } @@ -1138,7 +1242,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise { + $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext, token: CancellationToken): Promise { return this._withAdapter(handle, ReferenceAdapter, adapter => adapter.provideReferences(URI.revive(resource), position, context, token)); } @@ -1146,12 +1250,12 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { registerCodeActionProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider, metadata?: vscode.CodeActionProviderMetadata): vscode.Disposable { const handle = this._addNewAdapter(new CodeActionAdapter(this._documents, this._commands.converter, this._diagnostics, provider, this._logService, extension.identifier), extension); - this._proxy.$registerQuickFixSupport(handle, this._transformDocumentSelector(selector), metadata && metadata.providedCodeActionKinds ? metadata.providedCodeActionKinds.map(kind => kind.value) : undefined); + this._proxy.$registerQuickFixSupport(handle, this._transformDocumentSelector(selector), (metadata && metadata.providedCodeActionKinds) ? metadata.providedCodeActionKinds.map(kind => kind.value) : undefined); return this._createDisposable(handle); } - $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise { + $provideCodeActions(handle: number, resource: UriComponents, rangeOrSelection: IRange | ISelection, context: modes.CodeActionContext, token: CancellationToken): Promise { return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(URI.revive(resource), rangeOrSelection, context, token)); } @@ -1159,31 +1263,31 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { registerDocumentFormattingEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentFormattingEditProvider): vscode.Disposable { const handle = this._addNewAdapter(new DocumentFormattingAdapter(this._documents, provider), extension); - this._proxy.$registerDocumentFormattingSupport(handle, this._transformDocumentSelector(selector), ExtHostLanguageFeatures._extLabel(extension)); + this._proxy.$registerDocumentFormattingSupport(handle, this._transformDocumentSelector(selector), extension.identifier); return this._createDisposable(handle); } - $provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: modes.FormattingOptions, token: CancellationToken): Promise { + $provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: modes.FormattingOptions, token: CancellationToken): Promise { return this._withAdapter(handle, DocumentFormattingAdapter, adapter => adapter.provideDocumentFormattingEdits(URI.revive(resource), options, token)); } registerDocumentRangeFormattingEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentRangeFormattingEditProvider): vscode.Disposable { const handle = this._addNewAdapter(new RangeFormattingAdapter(this._documents, provider), extension); - this._proxy.$registerRangeFormattingSupport(handle, this._transformDocumentSelector(selector), ExtHostLanguageFeatures._extLabel(extension)); + this._proxy.$registerRangeFormattingSupport(handle, this._transformDocumentSelector(selector), extension.identifier); return this._createDisposable(handle); } - $provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: modes.FormattingOptions, token: CancellationToken): Promise { + $provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: modes.FormattingOptions, token: CancellationToken): Promise { return this._withAdapter(handle, RangeFormattingAdapter, adapter => adapter.provideDocumentRangeFormattingEdits(URI.revive(resource), range, options, token)); } registerOnTypeFormattingEditProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.OnTypeFormattingEditProvider, triggerCharacters: string[]): vscode.Disposable { const handle = this._addNewAdapter(new OnTypeFormattingAdapter(this._documents, provider), extension); - this._proxy.$registerOnTypeFormattingSupport(handle, this._transformDocumentSelector(selector), triggerCharacters); + this._proxy.$registerOnTypeFormattingSupport(handle, this._transformDocumentSelector(selector), triggerCharacters, extension.identifier); return this._createDisposable(handle); } - $provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Promise { + $provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: modes.FormattingOptions, token: CancellationToken): Promise { return this._withAdapter(handle, OnTypeFormattingAdapter, adapter => adapter.provideOnTypeFormattingEdits(URI.revive(resource), position, ch, options, token)); } @@ -1199,7 +1303,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.provideWorkspaceSymbols(search, token)); } - $resolveWorkspaceSymbol(handle: number, symbol: WorkspaceSymbolDto, token: CancellationToken): Promise { + $resolveWorkspaceSymbol(handle: number, symbol: WorkspaceSymbolDto, token: CancellationToken): Promise { return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.resolveWorkspaceSymbol(symbol, token)); } @@ -1215,11 +1319,11 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise { + $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string, token: CancellationToken): Promise { return this._withAdapter(handle, RenameAdapter, adapter => adapter.provideRenameEdits(URI.revive(resource), position, newName, token)); } - $resolveRenameLocation(handle: number, resource: URI, position: IPosition, token: CancellationToken): Promise { + $resolveRenameLocation(handle: number, resource: URI, position: IPosition, token: CancellationToken): Promise { return this._withAdapter(handle, RenameAdapter, adapter => adapter.resolveRenameLocation(URI.revive(resource), position, token)); } @@ -1231,7 +1335,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { + $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise { return this._withAdapter(handle, SuggestAdapter, adapter => adapter.provideCompletionItems(URI.revive(resource), position, context, token)); } @@ -1245,8 +1349,8 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { // --- parameter hints - registerSignatureHelpProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, metadataOrTriggerChars?: string[] | vscode.SignatureHelpProviderMetadata): vscode.Disposable { - const metadata: ISerializedSignatureHelpProviderMetadata = Array.isArray(metadataOrTriggerChars) + registerSignatureHelpProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, metadataOrTriggerChars: string[] | vscode.SignatureHelpProviderMetadata): vscode.Disposable { + const metadata: ISerializedSignatureHelpProviderMetadata | undefined = Array.isArray(metadataOrTriggerChars) ? { triggerCharacters: metadataOrTriggerChars, retriggerCharacters: [] } : metadataOrTriggerChars; @@ -1255,23 +1359,23 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise { + $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition, context: modes.SignatureHelpContext, token: CancellationToken): Promise { return this._withAdapter(handle, SignatureHelpAdapter, adapter => adapter.provideSignatureHelp(URI.revive(resource), position, context, token)); } // --- links - registerDocumentLinkProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable { + registerDocumentLinkProvider(extension: IExtensionDescription | undefined, selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable { const handle = this._addNewAdapter(new LinkProviderAdapter(this._documents, this._heapService, provider), extension); this._proxy.$registerDocumentLinkProvider(handle, this._transformDocumentSelector(selector)); return this._createDisposable(handle); } - $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise { + $provideDocumentLinks(handle: number, resource: UriComponents, token: CancellationToken): Promise { return this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.provideLinks(URI.revive(resource), token)); } - $resolveDocumentLink(handle: number, link: modes.ILink, token: CancellationToken): Promise { + $resolveDocumentLink(handle: number, link: modes.ILink, token: CancellationToken): Promise { return this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.resolveLink(link, token)); } @@ -1285,7 +1389,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.provideColors(URI.revive(resource), token)); } - $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise { + $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo, token: CancellationToken): Promise { return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.provideColorPresentations(URI.revive(resource), colorInfo, token)); } @@ -1295,7 +1399,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideFoldingRanges(handle: number, resource: UriComponents, context: vscode.FoldingContext, token: CancellationToken): Promise { + $provideFoldingRanges(handle: number, resource: UriComponents, context: vscode.FoldingContext, token: CancellationToken): Promise { return this._withAdapter(handle, FoldingProviderAdapter, adapter => adapter.provideFoldingRanges(URI.revive(resource), context, token)); } @@ -1307,19 +1411,13 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return this._createDisposable(handle); } - $provideSelectionRanges(handle: number, resource: UriComponents, position: IPosition, token: CancellationToken): Promise { - return this._withAdapter(handle, SelectionRangeAdapter, adapter => adapter.provideSelectionRanges(URI.revive(resource), position, token)); + $provideSelectionRanges(handle: number, resource: UriComponents, positions: IPosition[], token: CancellationToken): Promise { + return this._withAdapter(handle, SelectionRangeAdapter, adapter => adapter.provideSelectionRanges(URI.revive(resource), positions, token)); } // --- configuration private static _serializeRegExp(regExp: RegExp): ISerializedRegExp { - if (typeof regExp === 'undefined') { - return undefined; - } - if (regExp === null) { - return null; - } return { pattern: regExp.source, flags: regExpFlags(regExp), @@ -1327,36 +1425,24 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { } private static _serializeIndentationRule(indentationRule: vscode.IndentationRule): ISerializedIndentationRule { - if (typeof indentationRule === 'undefined') { - return undefined; - } - if (indentationRule === null) { - return null; - } return { decreaseIndentPattern: ExtHostLanguageFeatures._serializeRegExp(indentationRule.decreaseIndentPattern), increaseIndentPattern: ExtHostLanguageFeatures._serializeRegExp(indentationRule.increaseIndentPattern), - indentNextLinePattern: ExtHostLanguageFeatures._serializeRegExp(indentationRule.indentNextLinePattern), - unIndentedLinePattern: ExtHostLanguageFeatures._serializeRegExp(indentationRule.unIndentedLinePattern), + indentNextLinePattern: indentationRule.indentNextLinePattern ? ExtHostLanguageFeatures._serializeRegExp(indentationRule.indentNextLinePattern) : undefined, + unIndentedLinePattern: indentationRule.unIndentedLinePattern ? ExtHostLanguageFeatures._serializeRegExp(indentationRule.unIndentedLinePattern) : undefined, }; } private static _serializeOnEnterRule(onEnterRule: vscode.OnEnterRule): ISerializedOnEnterRule { return { beforeText: ExtHostLanguageFeatures._serializeRegExp(onEnterRule.beforeText), - afterText: ExtHostLanguageFeatures._serializeRegExp(onEnterRule.afterText), - oneLineAboveText: ExtHostLanguageFeatures._serializeRegExp(onEnterRule.oneLineAboveText), + afterText: onEnterRule.afterText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.afterText) : undefined, + oneLineAboveText: onEnterRule.oneLineAboveText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.oneLineAboveText) : undefined, action: onEnterRule.action }; } private static _serializeOnEnterRules(onEnterRules: vscode.OnEnterRule[]): ISerializedOnEnterRule[] { - if (typeof onEnterRules === 'undefined') { - return undefined; - } - if (onEnterRules === null) { - return null; - } return onEnterRules.map(ExtHostLanguageFeatures._serializeOnEnterRule); } @@ -1372,16 +1458,16 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { if (wordPattern) { this._documents.setWordDefinitionFor(languageId, wordPattern); } else { - this._documents.setWordDefinitionFor(languageId, null); + this._documents.setWordDefinitionFor(languageId, undefined); } const handle = this._nextHandle(); const serializedConfiguration: ISerializedLanguageConfiguration = { comments: configuration.comments, brackets: configuration.brackets, - wordPattern: ExtHostLanguageFeatures._serializeRegExp(configuration.wordPattern), - indentationRules: ExtHostLanguageFeatures._serializeIndentationRule(configuration.indentationRules), - onEnterRules: ExtHostLanguageFeatures._serializeOnEnterRules(configuration.onEnterRules), + wordPattern: configuration.wordPattern ? ExtHostLanguageFeatures._serializeRegExp(configuration.wordPattern) : undefined, + indentationRules: configuration.indentationRules ? ExtHostLanguageFeatures._serializeIndentationRule(configuration.indentationRules) : undefined, + onEnterRules: configuration.onEnterRules ? ExtHostLanguageFeatures._serializeOnEnterRules(configuration.onEnterRules) : undefined, __electricCharacterSupport: configuration.__electricCharacterSupport, __characterPairSupport: configuration.__characterPairSupport, }; diff --git a/src/vs/workbench/api/node/extHostLanguages.ts b/src/vs/workbench/api/node/extHostLanguages.ts index 314a486e17..0d0ad1f423 100644 --- a/src/vs/workbench/api/node/extHostLanguages.ts +++ b/src/vs/workbench/api/node/extHostLanguages.ts @@ -24,9 +24,10 @@ export class ExtHostLanguages { return this._proxy.$getLanguages(); } - changeLanguage(uri: vscode.Uri, languageId: string): Promise { + changeLanguage(uri: vscode.Uri, languageId: string): Promise { return this._proxy.$changeLanguage(uri, languageId).then(() => { - return this._documents.getDocumentData(uri).document; + const data = this._documents.getDocumentData(uri); + return data ? data.document : undefined; }); } } diff --git a/src/vs/workbench/api/node/extHostLogService.ts b/src/vs/workbench/api/node/extHostLogService.ts index 98f5e50665..8f8167bbf9 100644 --- a/src/vs/workbench/api/node/extHostLogService.ts +++ b/src/vs/workbench/api/node/extHostLogService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { join } from 'vs/base/common/paths'; +import { join } from 'vs/base/common/path'; import { ILogService, DelegatedLogService, LogLevel } from 'vs/platform/log/common/log'; import { createSpdLogService } from 'vs/platform/log/node/spdlogService'; import { ExtHostLogServiceShape } from 'vs/workbench/api/node/extHost.protocol'; @@ -11,7 +11,6 @@ import { ExtensionHostLogFileName } from 'vs/workbench/services/extensions/commo import { URI } from 'vs/base/common/uri'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; - export class ExtHostLogService extends DelegatedLogService implements ILogService, ExtHostLogServiceShape { private _logsPath: string; diff --git a/src/vs/workbench/api/node/extHostMessageService.ts b/src/vs/workbench/api/node/extHostMessageService.ts index a6aa35882a..e335aa2df9 100644 --- a/src/vs/workbench/api/node/extHostMessageService.ts +++ b/src/vs/workbench/api/node/extHostMessageService.ts @@ -6,7 +6,7 @@ import Severity from 'vs/base/common/severity'; import * as vscode from 'vscode'; import { MainContext, MainThreadMessageServiceShape, MainThreadMessageOptions, IMainContext } from './extHost.protocol'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; function isMessageItem(item: any): item is vscode.MessageItem { return item && item.title; @@ -24,7 +24,7 @@ export class ExtHostMessageService { showMessage(extension: IExtensionDescription, severity: Severity, message: string, optionsOrFirstItem: vscode.MessageOptions | vscode.MessageItem, rest: vscode.MessageItem[]): Promise; showMessage(extension: IExtensionDescription, severity: Severity, message: string, optionsOrFirstItem: vscode.MessageOptions | string | vscode.MessageItem, rest: (string | vscode.MessageItem)[]): Promise { - let options: MainThreadMessageOptions = { extension }; + const options: MainThreadMessageOptions = { extension }; let items: (string | vscode.MessageItem)[]; if (typeof optionsOrFirstItem === 'string' || isMessageItem(optionsOrFirstItem)) { @@ -37,12 +37,12 @@ export class ExtHostMessageService { const commands: { title: string; isCloseAffordance: boolean; handle: number; }[] = []; for (let handle = 0; handle < items.length; handle++) { - let command = items[handle]; + const command = items[handle]; if (typeof command === 'string') { commands.push({ title: command, handle, isCloseAffordance: false }); } else if (typeof command === 'object') { let { title, isCloseAffordance } = command; - commands.push({ title, isCloseAffordance, handle }); + commands.push({ title, isCloseAffordance: !!isCloseAffordance, handle }); } else { console.warn('Invalid message item:', command); } diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index a0f18f9af5..01fb099385 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -6,8 +6,8 @@ import { MainContext, MainThreadOutputServiceShape, IMainContext, ExtHostOutputServiceShape } from './extHost.protocol'; import * as vscode from 'vscode'; import { URI } from 'vs/base/common/uri'; -import { posix } from 'path'; -import { OutputAppender } from 'vs/platform/output/node/outputAppender'; +import { join } from 'vs/base/common/path'; +import { OutputAppender } from 'vs/workbench/services/output/node/outputAppender'; import { toLocalISOString } from 'vs/base/common/date'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -23,7 +23,7 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements protected readonly _onDidAppend: Emitter = this._register(new Emitter()); readonly onDidAppend: Event = this._onDidAppend.event; - constructor(name: string, log: boolean, file: URI, proxy: MainThreadOutputServiceShape) { + constructor(name: string, log: boolean, file: URI | undefined, proxy: MainThreadOutputServiceShape) { super(); this._name = name; @@ -58,7 +58,7 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements show(columnOrPreserveFocus?: vscode.ViewColumn | boolean, preserveFocus?: boolean): void { this.validate(); - this._id.then(id => this._proxy.$reveal(id, typeof columnOrPreserveFocus === 'boolean' ? columnOrPreserveFocus : preserveFocus)); + this._id.then(id => this._proxy.$reveal(id, !!(typeof columnOrPreserveFocus === 'boolean' ? columnOrPreserveFocus : preserveFocus))); } hide(): void { @@ -86,7 +86,7 @@ export abstract class AbstractExtHostOutputChannel extends Disposable implements export class ExtHostPushOutputChannel extends AbstractExtHostOutputChannel { constructor(name: string, proxy: MainThreadOutputServiceShape) { - super(name, false, null, proxy); + super(name, false, undefined, proxy); } append(value: string): void { @@ -103,7 +103,7 @@ export class ExtHostOutputChannelBackedByFile extends AbstractExtHostOutputChann constructor(name: string, outputDir: string, proxy: MainThreadOutputServiceShape) { const fileName = `${ExtHostOutputChannelBackedByFile._namePool++}-${name}`; - const file = URI.file(posix.join(outputDir, `${fileName}.log`)); + const file = URI.file(join(outputDir, `${fileName}.log`)); super(name, false, file, proxy); this._appender = new OutputAppender(fileName, file.fsPath); @@ -150,7 +150,7 @@ export class ExtHostOutputService implements ExtHostOutputServiceShape { private _visibleChannelDisposable: IDisposable; constructor(logsLocation: URI, mainContext: IMainContext) { - this._outputDir = posix.join(logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); + this._outputDir = join(logsLocation.fsPath, `output_logging_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); this._proxy = mainContext.getProxy(MainContext.MainThreadOutputService); } diff --git a/src/vs/workbench/api/node/extHostProgress.ts b/src/vs/workbench/api/node/extHostProgress.ts index de4fef22f0..54413567e3 100644 --- a/src/vs/workbench/api/node/extHostProgress.ts +++ b/src/vs/workbench/api/node/extHostProgress.ts @@ -6,11 +6,11 @@ import { ProgressOptions } from 'vscode'; import { MainThreadProgressShape, ExtHostProgressShape } from './extHost.protocol'; import { ProgressLocation } from './extHostTypeConverters'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { Progress, IProgressStep } from 'vs/platform/progress/common/progress'; import { localize } from 'vs/nls'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { debounce } from 'vs/base/common/decorators'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export class ExtHostProgress implements ExtHostProgressShape { @@ -27,11 +27,11 @@ export class ExtHostProgress implements ExtHostProgressShape { const { title, location, cancellable } = options; const source = localize('extensionSource', "{0} (Extension)", extension.displayName || extension.name); this._proxy.$startProgress(handle, { location: ProgressLocation.from(location), title, source, cancellable }); - return this._withProgress(handle, task, cancellable); + return this._withProgress(handle, task, !!cancellable); } private _withProgress(handle: number, task: (progress: Progress, token: CancellationToken) => Thenable, cancellable: boolean): Thenable { - let source: CancellationTokenSource; + let source: CancellationTokenSource | undefined; if (cancellable) { source = new CancellationTokenSource(); this._mapHandleToCancellationSource.set(handle, source); @@ -48,7 +48,7 @@ export class ExtHostProgress implements ExtHostProgressShape { let p: Thenable; try { - p = task(new ProgressCallback(this._proxy, handle), cancellable ? source.token : CancellationToken.None); + p = task(new ProgressCallback(this._proxy, handle), cancellable && source ? source.token : CancellationToken.None); } catch (err) { progressEnd(handle); throw err; diff --git a/src/vs/workbench/api/node/extHostQuickOpen.ts b/src/vs/workbench/api/node/extHostQuickOpen.ts index 50fd99cfd8..06e9606e8f 100644 --- a/src/vs/workbench/api/node/extHostQuickOpen.ts +++ b/src/vs/workbench/api/node/extHostQuickOpen.ts @@ -8,30 +8,31 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; -import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; +import { IExtHostWorkspaceProvider } from 'vs/workbench/api/node/extHostWorkspace'; import { InputBox, InputBoxOptions, QuickInput, QuickInputButton, QuickPick, QuickPickItem, QuickPickOptions, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode'; import { ExtHostQuickOpenShape, IMainContext, MainContext, MainThreadQuickOpenShape, TransferQuickPickItems, TransferQuickInput, TransferQuickInputButton } from './extHost.protocol'; import { URI } from 'vs/base/common/uri'; import { ThemeIcon, QuickInputButtons } from 'vs/workbench/api/node/extHostTypes'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { coalesce } from 'vs/base/common/arrays'; export type Item = string | QuickPickItem; export class ExtHostQuickOpen implements ExtHostQuickOpenShape { private _proxy: MainThreadQuickOpenShape; - private _workspace: ExtHostWorkspace; + private _workspace: IExtHostWorkspaceProvider; private _commands: ExtHostCommands; - private _onDidSelectItem: (handle: number) => void; - private _validateInput: (input: string) => string | Thenable; + private _onDidSelectItem?: (handle: number) => void; + private _validateInput?: (input: string) => string | undefined | null | Thenable; private _sessions = new Map(); private _instances = 0; - constructor(mainContext: IMainContext, workspace: ExtHostWorkspace, commands: ExtHostCommands) { + constructor(mainContext: IMainContext, workspace: IExtHostWorkspaceProvider, commands: ExtHostCommands) { this._proxy = mainContext.getProxy(MainContext.MainThreadQuickOpen); this._workspace = workspace; this._commands = commands; @@ -67,15 +68,15 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { return itemsPromise.then(items => { - let pickItems: TransferQuickPickItems[] = []; + const pickItems: TransferQuickPickItems[] = []; for (let handle = 0; handle < items.length; handle++) { - let item = items[handle]; + const item = items[handle]; let label: string; - let description: string; - let detail: string; - let picked: boolean; - let alwaysShow: boolean; + let description: string | undefined; + let detail: string | undefined; + let picked: boolean | undefined; + let alwaysShow: boolean | undefined; if (typeof item === 'string') { label = item; @@ -99,7 +100,7 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { // handle selection changes if (options && typeof options.onDidSelectItem === 'function') { this._onDidSelectItem = (handle) => { - options.onDidSelectItem(items[handle]); + options.onDidSelectItem!(items[handle]); }; } @@ -137,7 +138,7 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { showInput(options?: InputBoxOptions, token: CancellationToken = CancellationToken.None): Promise { // global validate fn used in callback below - this._validateInput = options && options.validateInput; + this._validateInput = options ? options.validateInput : undefined; return this._proxy.$input(options, typeof this._validateInput === 'function', token) .then(undefined, err => { @@ -149,22 +150,25 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { }); } - $validateInput(input: string): Promise { + $validateInput(input: string): Promise { if (this._validateInput) { - return asPromise(() => this._validateInput(input)); + return asPromise(() => this._validateInput!(input)); } - return undefined; + return Promise.resolve(undefined); } // ---- workspace folder picker - showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions, token = CancellationToken.None): Promise { - return this._commands.executeCommand('_workbench.pickWorkspaceFolder', [options]).then((selectedFolder: WorkspaceFolder) => { + showWorkspaceFolderPick(options?: WorkspaceFolderPickOptions, token = CancellationToken.None): Promise { + return this._commands.executeCommand('_workbench.pickWorkspaceFolder', [options]).then(async (selectedFolder: WorkspaceFolder) => { if (!selectedFolder) { return undefined; } - - return this._workspace.getWorkspaceFolders().filter(folder => folder.uri.toString() === selectedFolder.uri.toString())[0]; + const workspaceFolders = await this._workspace.getWorkspaceFolders2(); + if (!workspaceFolders) { + return undefined; + } + return workspaceFolders.filter(folder => folder.uri.toString() === selectedFolder.uri.toString())[0]; }); } @@ -382,7 +386,9 @@ class ExtHostQuickInput implements QuickInput { _fireDidTriggerButton(handle: number) { const button = this._handlesToButtons.get(handle); - this._onDidTriggerButtonEmitter.fire(button); + if (button) { + this._onDidTriggerButtonEmitter.fire(button); + } } _fireDidHide() { @@ -437,9 +443,13 @@ class ExtHostQuickInput implements QuickInput { } } -function getIconUris(iconPath: QuickInputButton['iconPath']) { +function getIconUris(iconPath: QuickInputButton['iconPath']): { dark: URI, light?: URI } | undefined { + const dark = getDarkIconUri(iconPath); const light = getLightIconUri(iconPath); - return { dark: getDarkIconUri(iconPath) || light, light }; + if (!light && !dark) { + return undefined; + } + return { dark: (dark || light)!, light }; } function getLightIconUri(iconPath: QuickInputButton['iconPath']) { @@ -563,13 +573,13 @@ class ExtHostQuickPick extends ExtHostQuickInput implem onDidChangeSelection = this._onDidChangeSelectionEmitter.event; _fireDidChangeActive(handles: number[]) { - const items = handles.map(handle => this._handlesToItems.get(handle)); + const items = coalesce(handles.map(handle => this._handlesToItems.get(handle))); this._activeItems = items; this._onDidChangeActiveEmitter.fire(items); } _fireDidChangeSelection(handles: number[]) { - const items = handles.map(handle => this._handlesToItems.get(handle)); + const items = coalesce(handles.map(handle => this._handlesToItems.get(handle))); this._selectedItems = items; this._onDidChangeSelectionEmitter.fire(items); } diff --git a/src/vs/workbench/api/node/extHostSCM.ts b/src/vs/workbench/api/node/extHostSCM.ts index fc7aff0d29..fe8c760594 100644 --- a/src/vs/workbench/api/node/extHostSCM.ts +++ b/src/vs/workbench/api/node/extHostSCM.ts @@ -8,22 +8,21 @@ import { Event, Emitter } from 'vs/base/common/event'; import { debounce } from 'vs/base/common/decorators'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { asPromise } from 'vs/base/common/async'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; -import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape } from './extHost.protocol'; +import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape, CommandDto } from './extHost.protocol'; import { sortedDiff } from 'vs/base/common/arrays'; import { comparePaths } from 'vs/base/common/comparers'; import * as vscode from 'vscode'; import { ISplice } from 'vs/base/common/sequence'; import { ILogService } from 'vs/platform/log/common/log'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; type ProviderHandle = number; type GroupHandle = number; type ResourceStateHandle = number; -function getIconPath(decorations: vscode.SourceControlResourceThemableDecorations) { +function getIconPath(decorations?: vscode.SourceControlResourceThemableDecorations): string | undefined { if (!decorations) { return undefined; } else if (typeof decorations.iconPath === 'string') { @@ -60,7 +59,7 @@ function compareResourceStatesDecorations(a: vscode.SourceControlResourceDecorat } if (a.tooltip !== b.tooltip) { - return (a.tooltip || '').localeCompare(b.tooltip); + return (a.tooltip || '').localeCompare(b.tooltip || ''); } result = compareResourceThemableDecorations(a, b); @@ -205,7 +204,7 @@ export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { return this._visible; } - set visible(visible: boolean | undefined) { + set visible(visible: boolean) { visible = !!visible; this._visible = visible; this._proxy.$setInputBoxVisibility(this._sourceControlHandle, visible); @@ -287,7 +286,7 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG return Promise.resolve(undefined); } - return asPromise(() => this._commands.executeCommand(command.command, ...command.arguments)); + return asPromise(() => this._commands.executeCommand(command.command, ...(command.arguments || []))); } _takeResourceStateSnapshot(): SCMRawResourceSplice[] { @@ -309,11 +308,11 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG this._resourceStatesCommandsMap.set(handle, r.command); } - if (lightIconPath || darkIconPath) { + if (lightIconPath) { icons.push(lightIconPath); } - if (darkIconPath !== lightIconPath) { + if (darkIconPath && (darkIconPath !== lightIconPath)) { icons.push(darkIconPath); } @@ -442,7 +441,7 @@ class ExtHostSourceControl implements vscode.SourceControl { this._statusBarCommands = statusBarCommands; - const internal = (statusBarCommands || []).map(c => this._commands.converter.toInternal(c)); + const internal = (statusBarCommands || []).map(c => this._commands.converter.toInternal(c)) as CommandDto[]; this._proxy.$updateSourceControl(this.handle, { statusBarCommands: internal }); } @@ -599,14 +598,12 @@ export class ExtHostSCM implements ExtHostSCMShape { } // Deprecated - getLastInputBox(extension: IExtensionDescription): ExtHostSCMInputBox { + getLastInputBox(extension: IExtensionDescription): ExtHostSCMInputBox | undefined { this.logService.trace('ExtHostSCM#getLastInputBox', extension.identifier.value); const sourceControls = this._sourceControlsByExtension.get(ExtensionIdentifier.toKey(extension.identifier)); const sourceControl = sourceControls && sourceControls[sourceControls.length - 1]; - const inputBox = sourceControl && sourceControl.inputBox; - - return inputBox; + return sourceControl && sourceControl.inputBox; } $provideOriginalResource(sourceControlHandle: number, uriComponents: UriComponents, token: CancellationToken): Promise { @@ -615,11 +612,12 @@ export class ExtHostSCM implements ExtHostSCMShape { const sourceControl = this._sourceControls.get(sourceControlHandle); - if (!sourceControl || !sourceControl.quickDiffProvider) { + if (!sourceControl || !sourceControl.quickDiffProvider || !sourceControl.quickDiffProvider.provideOriginalResource) { return Promise.resolve(null); } - return asPromise(() => sourceControl.quickDiffProvider.provideOriginalResource(uri, token)); + return asPromise(() => sourceControl.quickDiffProvider!.provideOriginalResource!(uri, token)) + .then(r => r || null); } $onInputBoxValueChange(sourceControlHandle: number, value: string): Promise { diff --git a/src/vs/workbench/api/node/extHostSearch.fileIndex.ts b/src/vs/workbench/api/node/extHostSearch.fileIndex.ts deleted file mode 100644 index b9e13563e4..0000000000 --- a/src/vs/workbench/api/node/extHostSearch.fileIndex.ts +++ /dev/null @@ -1,611 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as path from 'path'; -import * as arrays from 'vs/base/common/arrays'; -import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; -import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { canceled } from 'vs/base/common/errors'; -import * as glob from 'vs/base/common/glob'; -import * as resources from 'vs/base/common/resources'; -import { StopWatch } from 'vs/base/common/stopwatch'; -import * as strings from 'vs/base/common/strings'; -import { URI } from 'vs/base/common/uri'; -import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer'; -import { ICachedSearchStats, IFileIndexProviderStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, ISearchCompleteStats } from 'vs/platform/search/common/search'; -import { IDirectoryEntry, IDirectoryTree, IInternalFileMatch } from 'vs/workbench/services/search/node/fileSearchManager'; -import { QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/node/search'; -import * as vscode from 'vscode'; - -interface IInternalSearchComplete { - limitHit: boolean; - results: IInternalFileMatch[]; - stats: T; -} - -export class FileIndexSearchEngine { - private filePattern?: string; - private normalizedFilePatternLowercase: string; - private includePattern?: glob.ParsedExpression; - private maxResults: number | null; - private exists: boolean; - private isLimitHit: boolean; - private resultCount: number; - private isCanceled: boolean; - - private filesWalked = 0; - private dirsWalked = 0; - - private activeCancellationTokens: Set; - - private globalExcludePattern?: glob.ParsedExpression; - - constructor(private config: IFileQuery, private provider: vscode.FileIndexProvider) { - this.filePattern = config.filePattern; - this.includePattern = config.includePattern && glob.parse(config.includePattern); - this.maxResults = config.maxResults || null; - this.exists = !!config.exists; - this.resultCount = 0; - this.isLimitHit = false; - this.activeCancellationTokens = new Set(); - - if (this.filePattern) { - this.normalizedFilePatternLowercase = strings.stripWildcards(this.filePattern).toLowerCase(); - } - - this.globalExcludePattern = config.excludePattern && glob.parse(config.excludePattern); - } - - public cancel(): void { - this.isCanceled = true; - this.activeCancellationTokens.forEach(t => t.cancel()); - this.activeCancellationTokens = new Set(); - } - - public search(_onResult: (match: IInternalFileMatch) => void): Promise<{ isLimitHit: boolean, stats: IFileIndexProviderStats }> { - // Searches a single folder - const folderQuery = this.config.folderQueries[0]; - - return new Promise<{ isLimitHit: boolean, stats: IFileIndexProviderStats }>((resolve, reject) => { - const onResult = (match: IInternalFileMatch) => { - this.resultCount++; - _onResult(match); - }; - - if (this.isCanceled) { - throw canceled(); - } - - // For each extra file - if (this.config.extraFileResources) { - this.config.extraFileResources - .forEach(extraFile => { - const extraFileStr = extraFile.toString(); // ? - const basename = path.basename(extraFileStr); - if (this.globalExcludePattern && this.globalExcludePattern(extraFileStr, basename)) { - return; // excluded - } - - // File: Check for match on file pattern and include pattern - this.matchFile(onResult, { base: extraFile, basename }); - }); - } - - return Promise.all(this.config.folderQueries.map(fq => this.searchInFolder(folderQuery, onResult))).then(stats => { - resolve({ - isLimitHit: this.isLimitHit, - stats: { - directoriesWalked: this.dirsWalked, - filesWalked: this.filesWalked, - fileWalkTime: stats.map(s => s.fileWalkTime).reduce((s, c) => s + c, 0), - providerTime: stats.map(s => s.providerTime).reduce((s, c) => s + c, 0), - providerResultCount: stats.map(s => s.providerResultCount).reduce((s, c) => s + c, 0) - } - }); - }, (errs: Error[]) => { - if (!Array.isArray(errs)) { - errs = [errs]; - } - - errs = arrays.coalesce(errs); - return Promise.reject(errs[0]); - }); - }); - } - - private searchInFolder(fq: IFolderQuery, onResult: (match: IInternalFileMatch) => void): Promise { - let cancellation = new CancellationTokenSource(); - return new Promise((resolve, reject) => { - const options = this.getSearchOptionsForFolder(fq); - const tree = this.initDirectoryTree(); - - const queryTester = new QueryGlobTester(this.config, fq); - const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses(); - - const onProviderResult = (uri: URI) => { - if (this.isCanceled) { - return; - } - - // TODO@rob - ??? - const relativePath = path.relative(fq.folder.path, uri.path); - if (noSiblingsClauses) { - const basename = path.basename(uri.path); - this.matchFile(onResult, { base: fq.folder, relativePath, basename, original: uri }); - - return; - } - - // TODO: Optimize siblings clauses with ripgrep here. - this.addDirectoryEntries(tree, fq.folder, relativePath, onResult); - }; - - let providerSW: StopWatch; - let providerTime: number; - let fileWalkTime: number; - new Promise(resolve => process.nextTick(resolve)) - .then(() => { - this.activeCancellationTokens.add(cancellation); - providerSW = StopWatch.create(); - return this.provider.provideFileIndex(options, cancellation.token); - }) - .then(results => { - providerTime = providerSW.elapsed(); - const postProcessSW = StopWatch.create(); - this.activeCancellationTokens.delete(cancellation); - if (this.isCanceled) { - return null; - } - - results!.forEach(onProviderResult); - - this.matchDirectoryTree(tree, queryTester, onResult); - fileWalkTime = postProcessSW.elapsed(); - return null; - }).then( - () => { - cancellation.dispose(); - resolve({ - providerTime, - fileWalkTime, - directoriesWalked: this.dirsWalked, - filesWalked: this.filesWalked - }); - }, - err => { - cancellation.dispose(); - reject(err); - }); - }); - } - - private getSearchOptionsForFolder(fq: IFolderQuery): vscode.FileIndexOptions { - const includes = resolvePatternsForProvider(this.config.includePattern, fq.includePattern); - const excludes = resolvePatternsForProvider(this.config.excludePattern, fq.excludePattern); - - return { - folder: fq.folder, - excludes, - includes, - useIgnoreFiles: !fq.disregardIgnoreFiles, - useGlobalIgnoreFiles: !fq.disregardGlobalIgnoreFiles, - followSymlinks: !fq.ignoreSymlinks - }; - } - - private initDirectoryTree(): IDirectoryTree { - const tree: IDirectoryTree = { - rootEntries: [], - pathToEntries: Object.create(null) - }; - tree.pathToEntries['.'] = tree.rootEntries; - return tree; - } - - private addDirectoryEntries({ pathToEntries }: IDirectoryTree, base: URI, relativeFile: string, onResult: (result: IInternalFileMatch) => void) { - // Support relative paths to files from a root resource (ignores excludes) - if (relativeFile === this.filePattern) { - const basename = path.basename(this.filePattern); - this.matchFile(onResult, { base: base, relativePath: this.filePattern, basename }); - } - - function add(relativePath: string) { - const basename = path.basename(relativePath); - const dirname = path.dirname(relativePath); - let entries = pathToEntries[dirname]; - if (!entries) { - entries = pathToEntries[dirname] = []; - add(dirname); - } - entries.push({ - base, - relativePath, - basename - }); - } - - add(relativeFile); - } - - private matchDirectoryTree({ rootEntries, pathToEntries }: IDirectoryTree, queryTester: QueryGlobTester, onResult: (result: IInternalFileMatch) => void) { - const self = this; - const filePattern = this.filePattern; - function matchDirectory(entries: IDirectoryEntry[]) { - self.dirsWalked++; - for (let i = 0, n = entries.length; i < n; i++) { - const entry = entries[i]; - const { relativePath, basename } = entry; - - // Check exclude pattern - // If the user searches for the exact file name, we adjust the glob matching - // to ignore filtering by siblings because the user seems to know what she - // is searching for and we want to include the result in that case anyway - const hasSibling = glob.hasSiblingFn(() => entries.map(entry => entry.basename)); - if (!queryTester.includedInQuerySync(relativePath, basename, filePattern !== basename ? hasSibling : undefined)) { - continue; - } - - const sub = pathToEntries[relativePath]; - if (sub) { - matchDirectory(sub); - } else { - self.filesWalked++; - if (relativePath === filePattern) { - continue; // ignore file if its path matches with the file pattern because that is already matched above - } - - self.matchFile(onResult, entry); - } - - if (self.isLimitHit) { - break; - } - } - } - matchDirectory(rootEntries); - } - - private matchFile(onResult: (result: IInternalFileMatch) => void, candidate: IInternalFileMatch): void { - if (this.isFilePatternMatch(candidate.relativePath!) && (!this.includePattern || this.includePattern(candidate.relativePath!, candidate.basename))) { - if (this.exists || (this.maxResults && this.resultCount >= this.maxResults)) { - this.isLimitHit = true; - this.cancel(); - } - - if (!this.isLimitHit) { - onResult(candidate); - } - } - } - - private isFilePatternMatch(path: string): boolean { - // Check for search pattern - if (this.filePattern) { - if (this.filePattern === '*') { - return true; // support the all-matching wildcard - } - - return strings.fuzzyContains(path, this.normalizedFilePatternLowercase); - } - - // No patterns means we match all - return true; - } -} - -export class FileIndexSearchManager { - - private static readonly BATCH_SIZE = 512; - - private caches: { [cacheKey: string]: Cache; } = Object.create(null); - - private readonly folderCacheKeys = new Map>(); - - public fileSearch(config: IFileQuery, provider: vscode.FileIndexProvider, onBatch: (matches: IFileMatch[]) => void, token: CancellationToken): Promise { - if (config.sortByScore) { - let sortedSearch = this.trySortedSearchFromCache(config, token); - if (!sortedSearch) { - const engineConfig = config.maxResults ? - { - ...config, - ...{ maxResults: null } - } : - config; - - const engine = new FileIndexSearchEngine(engineConfig, provider); - sortedSearch = this.doSortedSearch(engine, config, token); - } - - return sortedSearch.then(complete => { - this.sendAsBatches(complete.results, onBatch, FileIndexSearchManager.BATCH_SIZE); - return complete; - }); - } - - const engine = new FileIndexSearchEngine(config, provider); - return this.doSearch(engine, token) - .then(complete => { - this.sendAsBatches(complete.results, onBatch, FileIndexSearchManager.BATCH_SIZE); - return { - limitHit: complete.limitHit, - stats: { - type: 'fileIndexProvider', - detailStats: complete.stats, - fromCache: false, - resultCount: complete.results.length - } - }; - }); - } - - private getFolderCacheKey(config: IFileQuery): string { - const uri = config.folderQueries[0].folder.toString(); - const folderCacheKey = config.cacheKey && `${uri}_${config.cacheKey}`; - if (!this.folderCacheKeys.get(config.cacheKey!)) { - this.folderCacheKeys.set(config.cacheKey!, new Set()); - } - - this.folderCacheKeys.get(config.cacheKey!)!.add(folderCacheKey!); - - return folderCacheKey!; - } - - private rawMatchToSearchItem(match: IInternalFileMatch): IFileMatch { - return { - resource: match.original || resources.joinPath(match.base, match.relativePath!) - }; - } - - private doSortedSearch(engine: FileIndexSearchEngine, config: IFileQuery, token: CancellationToken): Promise { - let allResultsPromise = createCancelablePromise>(token => { - return this.doSearch(engine, token); - }); - - const folderCacheKey = this.getFolderCacheKey(config); - let cache: Cache; - if (folderCacheKey) { - cache = this.getOrCreateCache(folderCacheKey); - const cacheRow: ICacheRow = { - promise: allResultsPromise, - resolved: false - }; - cache.resultsToSearchCache[config.filePattern!] = cacheRow; - allResultsPromise.then(() => { - cacheRow.resolved = true; - }, err => { - delete cache.resultsToSearchCache[config.filePattern!]; - }); - allResultsPromise = this.preventCancellation(allResultsPromise); - } - - return Promise.resolve( - allResultsPromise.then(complete => { - const scorerCache: ScorerCache = cache ? cache.scorerCache : Object.create(null); - const sortSW = (typeof config.maxResults !== 'number' || config.maxResults > 0) && StopWatch.create(); - return this.sortResults(config, complete.results, scorerCache, token) - .then(sortedResults => { - // sortingTime: -1 indicates a "sorted" search that was not sorted, i.e. populating the cache when quickopen is opened. - // Contrasting with findFiles which is not sorted and will have sortingTime: undefined - const sortingTime = sortSW ? sortSW.elapsed() : -1; - return { - limitHit: complete.limitHit || typeof config.maxResults === 'number' && complete.results.length > config.maxResults, // ?? - results: sortedResults, - stats: { - detailStats: complete.stats, - fromCache: false, - resultCount: sortedResults.length, - sortingTime, - type: 'fileIndexProvider' - } - }; - }); - })); - } - - private getOrCreateCache(cacheKey: string): Cache { - const existing = this.caches[cacheKey]; - if (existing) { - return existing; - } - return this.caches[cacheKey] = new Cache(); - } - - private trySortedSearchFromCache(config: IFileQuery, token: CancellationToken): Promise | undefined { - const folderCacheKey = this.getFolderCacheKey(config); - const cache = folderCacheKey && this.caches[folderCacheKey]; - if (!cache) { - return undefined; - } - - const cached = this.getResultsFromCache(cache, config.filePattern!, token); - if (cached) { - return cached.then(complete => { - const sortSW = StopWatch.create(); - return this.sortResults(config, complete.results, cache.scorerCache, token) - .then(sortedResults => { - if (token && token.isCancellationRequested) { - throw canceled(); - } - - return >{ - limitHit: complete.limitHit || typeof config.maxResults === 'number' && complete.results.length > config.maxResults, - results: sortedResults, - stats: { - fromCache: true, - detailStats: complete.stats, - type: 'fileIndexProvider', - resultCount: sortedResults.length, - sortingTime: sortSW.elapsed() - } - }; - }); - }); - } - return undefined; - } - - private sortResults(config: IFileQuery, results: IInternalFileMatch[], scorerCache: ScorerCache, token: CancellationToken): Promise { - // we use the same compare function that is used later when showing the results using fuzzy scoring - // this is very important because we are also limiting the number of results by config.maxResults - // and as such we want the top items to be included in this result set if the number of items - // exceeds config.maxResults. - const query = prepareQuery(config.filePattern!); - const compare = (matchA: IInternalFileMatch, matchB: IInternalFileMatch) => compareItemsByScore(matchA, matchB, query, true, FileMatchItemAccessor, scorerCache); - - return arrays.topAsync(results, compare, config.maxResults!, 10000, token); - } - - private sendAsBatches(rawMatches: IInternalFileMatch[], onBatch: (batch: IFileMatch[]) => void, batchSize: number) { - const serializedMatches = rawMatches.map(rawMatch => this.rawMatchToSearchItem(rawMatch)); - if (batchSize && batchSize > 0) { - for (let i = 0; i < serializedMatches.length; i += batchSize) { - onBatch(serializedMatches.slice(i, i + batchSize)); - } - } else { - onBatch(serializedMatches); - } - } - - private getResultsFromCache(cache: Cache, searchValue: string, token: CancellationToken): Promise> | null { - const cacheLookupSW = StopWatch.create(); - - if (path.isAbsolute(searchValue)) { - return null; // bypass cache if user looks up an absolute path where matching goes directly on disk - } - - // Find cache entries by prefix of search value - const hasPathSep = searchValue.indexOf(path.sep) >= 0; - let cacheRow: ICacheRow | undefined; - for (let previousSearch in cache.resultsToSearchCache) { - - // If we narrow down, we might be able to reuse the cached results - if (strings.startsWith(searchValue, previousSearch)) { - if (hasPathSep && previousSearch.indexOf(path.sep) < 0) { - continue; // since a path character widens the search for potential more matches, require it in previous search too - } - - const row = cache.resultsToSearchCache[previousSearch]; - cacheRow = { - promise: this.preventCancellation(row.promise), - resolved: row.resolved - }; - break; - } - } - - if (!cacheRow) { - return null; - } - - const cacheLookupTime = cacheLookupSW.elapsed(); - const cacheFilterSW = StopWatch.create(); - - return new Promise>((c, e) => { - token.onCancellationRequested(() => e(canceled())); - - cacheRow!.promise.then(complete => { - if (token && token.isCancellationRequested) { - e(canceled()); - } - - // Pattern match on results - let results: IInternalFileMatch[] = []; - const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase(); - for (let i = 0; i < complete.results.length; i++) { - let entry = complete.results[i]; - - // Check if this entry is a match for the search value - if (!strings.fuzzyContains(entry.relativePath!, normalizedSearchValueLowercase)) { - continue; - } - - results.push(entry); - } - - c(>{ - limitHit: complete.limitHit, - results, - stats: { - cacheWasResolved: cacheRow!.resolved, - cacheLookupTime, - cacheFilterTime: cacheFilterSW.elapsed(), - cacheEntryCount: complete.results.length - } - }); - }, e); - }); - } - - private doSearch(engine: FileIndexSearchEngine, token: CancellationToken): Promise> { - token.onCancellationRequested(() => engine.cancel()); - const results: IInternalFileMatch[] = []; - const onResult = match => results.push(match); - - return engine.search(onResult).then(result => { - return >{ - limitHit: result.isLimitHit, - results, - stats: result.stats - }; - }); - } - - public clearCache(cacheKey: string): void { - const expandedKeys = this.folderCacheKeys.get(cacheKey); - if (!expandedKeys) { - return undefined; - } - - expandedKeys.forEach(key => delete this.caches[key]); - - this.folderCacheKeys.delete(cacheKey); - - return undefined; - } - - private preventCancellation(promise: CancelablePromise): CancelablePromise { - return new class implements CancelablePromise { - cancel() { - // Do nothing - } - then(resolve, reject) { - return promise.then(resolve, reject); - } - catch(reject?) { - return this.then(undefined, reject); - } - finally(onFinally) { - return promise.finally(onFinally); - } - }; - } -} - -interface ICacheRow { - promise: CancelablePromise>; - resolved: boolean; -} - -class Cache { - - public resultsToSearchCache: { [searchValue: string]: ICacheRow; } = Object.create(null); - - public scorerCache: ScorerCache = Object.create(null); -} - -const FileMatchItemAccessor = new class implements IItemAccessor { - - public getItemLabel(match: IInternalFileMatch): string { - return match.basename; // e.g. myFile.txt - } - - public getItemDescription(match: IInternalFileMatch): string { - return match.relativePath!.substr(0, match.relativePath!.length - match.basename.length - 1); // e.g. some/path/to/file - } - - public getItemPath(match: IInternalFileMatch): string { - return match.relativePath!; // e.g. some/path/to/file/myFile.txt - } -}; diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index 3822603ad4..fea7183c3e 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -8,13 +8,11 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as extfs from 'vs/base/node/extfs'; import { ILogService } from 'vs/platform/log/common/log'; -import { IFileQuery, IFolderQuery, IRawFileQuery, IRawQuery, IRawTextQuery, ISearchCompleteStats, ITextQuery } from 'vs/platform/search/common/search'; -import { FileIndexSearchManager } from 'vs/workbench/api/node/extHostSearch.fileIndex'; +import { IFileQuery, IFolderQuery, IRawFileQuery, IRawQuery, IRawTextQuery, ISearchCompleteStats, ITextQuery, isSerializedFileMatch } from 'vs/workbench/services/search/common/search'; import { FileSearchManager } from 'vs/workbench/services/search/node/fileSearchManager'; import { SearchService } from 'vs/workbench/services/search/node/rawSearchService'; import { RipgrepSearchProvider } from 'vs/workbench/services/search/node/ripgrepSearchProvider'; import { OutputChannel } from 'vs/workbench/services/search/node/ripgrepSearchUtils'; -import { isSerializedFileMatch } from 'vs/workbench/services/search/node/search'; import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; import * as vscode from 'vscode'; import { ExtHostSearchShape, IMainContext, MainContext, MainThreadSearchShape } from './extHost.protocol'; @@ -30,20 +28,16 @@ export class ExtHostSearch implements ExtHostSearchShape { private readonly _textSearchUsedSchemes = new Set(); private readonly _fileSearchProvider = new Map(); private readonly _fileSearchUsedSchemes = new Set(); - private readonly _fileIndexProvider = new Map(); - private readonly _fileIndexUsedSchemes = new Set(); private _handlePool: number = 0; private _internalFileSearchHandle: number; - private _internalFileSearchProvider: SearchService; + private _internalFileSearchProvider: SearchService | null; private _fileSearchManager: FileSearchManager; - private _fileIndexSearchManager: FileIndexSearchManager; - constructor(mainContext: IMainContext, private _schemeTransformer: ISchemeTransformer, private _logService: ILogService, private _extfs = extfs) { + constructor(mainContext: IMainContext, private _schemeTransformer: ISchemeTransformer | null, private _logService: ILogService, private _extfs = extfs) { this._proxy = mainContext.getProxy(MainContext.MainThreadSearch); this._fileSearchManager = new FileSearchManager(); - this._fileIndexSearchManager = new FileIndexSearchManager(); } private _transformScheme(scheme: string): string { @@ -96,22 +90,6 @@ export class ExtHostSearch implements ExtHostSearchShape { }); } - registerFileIndexProvider(scheme: string, provider: vscode.FileIndexProvider): IDisposable { - if (this._fileIndexUsedSchemes.has(scheme)) { - throw new Error(`a provider for the scheme '${scheme}' is already registered`); - } - - this._fileIndexUsedSchemes.add(scheme); - const handle = this._handlePool++; - this._fileIndexProvider.set(handle, provider); - this._proxy.$registerFileIndexProvider(handle, this._transformScheme(scheme)); - return toDisposable(() => { - this._fileIndexUsedSchemes.delete(scheme); - this._fileSearchProvider.delete(handle); - this._proxy.$unregisterProvider(handle); // TODO@roblou - unregisterFileIndexProvider - }); - } - $provideFileSearchResults(handle: number, session: number, rawQuery: IRawFileQuery, token: CancellationToken): Promise { const query = reviveQuery(rawQuery); if (handle === this._internalFileSearchHandle) { @@ -123,10 +101,7 @@ export class ExtHostSearch implements ExtHostSearchShape { this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource)); }, token); } else { - const indexProvider = this._fileIndexProvider.get(handle); - return this._fileIndexSearchManager.fileSearch(query, indexProvider, batch => { - this._proxy.$handleFileMatch(handle, session, batch.map(p => p.resource)); - }, token); + throw new Error('unknown provider: ' + handle); } } } @@ -147,7 +122,11 @@ export class ExtHostSearch implements ExtHostSearchShape { } }; - return this._internalFileSearchProvider.doFileSearch(rawQuery, onResult, token); + if (!this._internalFileSearchProvider) { + throw new Error('No internal file search handler'); + } + + return >this._internalFileSearchProvider.doFileSearch(rawQuery, onResult, token); } $clearCache(cacheKey: string): Promise { @@ -156,15 +135,14 @@ export class ExtHostSearch implements ExtHostSearchShape { } this._fileSearchManager.clearCache(cacheKey); - this._fileIndexSearchManager.clearCache(cacheKey); return Promise.resolve(undefined); } $provideTextSearchResults(handle: number, session: number, rawQuery: IRawTextQuery, token: CancellationToken): Promise { const provider = this._textSearchProvider.get(handle); - if (!provider.provideTextSearchResults) { - return Promise.resolve(undefined); + if (!provider || !provider.provideTextSearchResults) { + throw new Error(`Unknown provider ${handle}`); } const query = reviveQuery(rawQuery); diff --git a/src/vs/workbench/api/node/extHostStatusBar.ts b/src/vs/workbench/api/node/extHostStatusBar.ts index 495ca6dff0..547b6c0886 100644 --- a/src/vs/workbench/api/node/extHostStatusBar.ts +++ b/src/vs/workbench/api/node/extHostStatusBar.ts @@ -14,7 +14,7 @@ export class ExtHostStatusBarEntry implements StatusBarItem { private _id: number; private _alignment: number; - private _priority: number; + private _priority?: number; private _disposed: boolean; private _visible: boolean; @@ -26,9 +26,9 @@ export class ExtHostStatusBarEntry implements StatusBarItem { private _timeoutHandle: any; private _proxy: MainThreadStatusBarShape; - private _extensionId: ExtensionIdentifier; + private _extensionId?: ExtensionIdentifier; - constructor(proxy: MainThreadStatusBarShape, extensionId: ExtensionIdentifier, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) { + constructor(proxy: MainThreadStatusBarShape, extensionId: ExtensionIdentifier | undefined, alignment: ExtHostStatusBarAlignment = ExtHostStatusBarAlignment.Left, priority?: number) { this._id = ExtHostStatusBarEntry.ID_GEN++; this._proxy = proxy; this._alignment = alignment; @@ -44,7 +44,7 @@ export class ExtHostStatusBarEntry implements StatusBarItem { return this._alignment; } - public get priority(): number { + public get priority(): number | undefined { return this._priority; } @@ -139,7 +139,7 @@ class StatusBarMessage { this._update(); return new Disposable(() => { - let idx = this._messages.indexOf(data); + const idx = this._messages.indexOf(data); if (idx >= 0) { this._messages.splice(idx, 1); this._update(); @@ -167,13 +167,13 @@ export class ExtHostStatusBar { this._statusMessage = new StatusBarMessage(this); } - createStatusBarEntry(extensionId: ExtensionIdentifier, alignment?: ExtHostStatusBarAlignment, priority?: number): StatusBarItem { + createStatusBarEntry(extensionId: ExtensionIdentifier | undefined, alignment?: ExtHostStatusBarAlignment, priority?: number): StatusBarItem { return new ExtHostStatusBarEntry(this._proxy, extensionId, alignment, priority); } setStatusBarMessage(text: string, timeoutOrThenable?: number | Thenable): Disposable { - let d = this._statusMessage.setMessage(text); + const d = this._statusMessage.setMessage(text); let handle: any; if (typeof timeoutOrThenable === 'number') { diff --git a/src/vs/workbench/api/node/extHostStorage.ts b/src/vs/workbench/api/node/extHostStorage.ts index feda3c796d..228c70b9b2 100644 --- a/src/vs/workbench/api/node/extHostStorage.ts +++ b/src/vs/workbench/api/node/extHostStorage.ts @@ -23,7 +23,7 @@ export class ExtHostStorage implements ExtHostStorageShape { this._proxy = mainContext.getProxy(MainContext.MainThreadStorage); } - getValue(shared: boolean, key: string, defaultValue?: T): Promise { + getValue(shared: boolean, key: string, defaultValue?: T): Promise { return this._proxy.$getValue(shared, key).then(value => value || defaultValue); } diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 6eb15db062..86fbcf87d3 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as Objects from 'vs/base/common/objects'; @@ -11,16 +11,18 @@ import { asPromise } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import { win32 } from 'vs/base/node/processes'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { MainContext, MainThreadTaskShape, ExtHostTaskShape, IMainContext } from 'vs/workbench/api/node/extHost.protocol'; import * as types from 'vs/workbench/api/node/extHostTypes'; -import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; +import { ExtHostWorkspace, IExtHostWorkspaceProvider } from 'vs/workbench/api/node/extHostWorkspace'; import * as vscode from 'vscode'; import { - TaskDefinitionDTO, TaskExecutionDTO, TaskPresentationOptionsDTO, ProcessExecutionOptionsDTO, ProcessExecutionDTO, - ShellExecutionOptionsDTO, ShellExecutionDTO, TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, TaskSetDTO + TaskDefinitionDTO, TaskExecutionDTO, TaskPresentationOptionsDTO, + ProcessExecutionOptionsDTO, ProcessExecutionDTO, + ShellExecutionOptionsDTO, ShellExecutionDTO, + CustomExecutionDTO, + TaskDTO, TaskHandleDTO, TaskFilterDTO, TaskProcessStartedDTO, TaskProcessEndedDTO, TaskSystemInfoDTO, TaskSetDTO } from '../shared/tasks'; // {{SQL CARBON EDIT}} @@ -28,17 +30,20 @@ import { import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors'; import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; +import { ExtHostTerminalService, ExtHostTerminal } from 'vs/workbench/api/node/extHostTerminalService'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { CancellationToken } from 'vs/base/common/cancellation'; +import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; namespace TaskDefinitionDTO { - export function from(value: vscode.TaskDefinition): TaskDefinitionDTO { + export function from(value: vscode.TaskDefinition): TaskDefinitionDTO | undefined { if (value === undefined || value === null) { return undefined; } return value; } - export function to(value: TaskDefinitionDTO): vscode.TaskDefinition { + export function to(value: TaskDefinitionDTO): vscode.TaskDefinition | undefined { if (value === undefined || value === null) { return undefined; } @@ -47,13 +52,13 @@ namespace TaskDefinitionDTO { } namespace TaskPresentationOptionsDTO { - export function from(value: vscode.TaskPresentationOptions): TaskPresentationOptionsDTO { + export function from(value: vscode.TaskPresentationOptions): TaskPresentationOptionsDTO | undefined { if (value === undefined || value === null) { return undefined; } return value; } - export function to(value: TaskPresentationOptionsDTO): vscode.TaskPresentationOptions { + export function to(value: TaskPresentationOptionsDTO): vscode.TaskPresentationOptions | undefined { if (value === undefined || value === null) { return undefined; } @@ -62,13 +67,13 @@ namespace TaskPresentationOptionsDTO { } namespace ProcessExecutionOptionsDTO { - export function from(value: vscode.ProcessExecutionOptions): ProcessExecutionOptionsDTO { + export function from(value: vscode.ProcessExecutionOptions): ProcessExecutionOptionsDTO | undefined { if (value === undefined || value === null) { return undefined; } return value; } - export function to(value: ProcessExecutionOptionsDTO): vscode.ProcessExecutionOptions { + export function to(value: ProcessExecutionOptionsDTO): vscode.ProcessExecutionOptions | undefined { if (value === undefined || value === null) { return undefined; } @@ -77,15 +82,19 @@ namespace ProcessExecutionOptionsDTO { } namespace ProcessExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO): value is ProcessExecutionDTO { - let candidate = value as ProcessExecutionDTO; - return candidate && !!candidate.process; + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | undefined): value is ProcessExecutionDTO { + if (value) { + const candidate = value as ProcessExecutionDTO; + return candidate && !!candidate.process; + } else { + return false; + } } - export function from(value: vscode.ProcessExecution): ProcessExecutionDTO { + export function from(value: vscode.ProcessExecution): ProcessExecutionDTO | undefined { if (value === undefined || value === null) { return undefined; } - let result: ProcessExecutionDTO = { + const result: ProcessExecutionDTO = { process: value.process, args: value.args }; @@ -94,7 +103,7 @@ namespace ProcessExecutionDTO { } return result; } - export function to(value: ProcessExecutionDTO): types.ProcessExecution { + export function to(value: ProcessExecutionDTO): types.ProcessExecution | undefined { if (value === undefined || value === null) { return undefined; } @@ -103,13 +112,13 @@ namespace ProcessExecutionDTO { } namespace ShellExecutionOptionsDTO { - export function from(value: vscode.ShellExecutionOptions): ShellExecutionOptionsDTO { + export function from(value: vscode.ShellExecutionOptions): ShellExecutionOptionsDTO | undefined { if (value === undefined || value === null) { return undefined; } return value; } - export function to(value: ShellExecutionOptionsDTO): vscode.ShellExecutionOptions { + export function to(value: ShellExecutionOptionsDTO): vscode.ShellExecutionOptions | undefined { if (value === undefined || value === null) { return undefined; } @@ -118,15 +127,19 @@ namespace ShellExecutionOptionsDTO { } namespace ShellExecutionDTO { - export function is(value: ShellExecutionDTO | ProcessExecutionDTO): value is ShellExecutionDTO { - let candidate = value as ShellExecutionDTO; - return candidate && (!!candidate.commandLine || !!candidate.command); + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | undefined): value is ShellExecutionDTO { + if (value) { + const candidate = value as ShellExecutionDTO; + return candidate && (!!candidate.commandLine || !!candidate.command); + } else { + return false; + } } - export function from(value: vscode.ShellExecution): ShellExecutionDTO { + export function from(value: vscode.ShellExecution): ShellExecutionDTO | undefined { if (value === undefined || value === null) { return undefined; } - let result: ShellExecutionDTO = { + const result: ShellExecutionDTO = { }; if (value.commandLine !== undefined) { result.commandLine = value.commandLine; @@ -139,27 +152,44 @@ namespace ShellExecutionDTO { } return result; } - export function to(value: ShellExecutionDTO): types.ShellExecution { - if (value === undefined || value === null) { + export function to(value: ShellExecutionDTO): types.ShellExecution | undefined { + if (value === undefined || value === null || (value.command === undefined && value.commandLine === undefined)) { return undefined; } if (value.commandLine) { return new types.ShellExecution(value.commandLine, value.options); } else { - return new types.ShellExecution(value.command, value.args ? value.args : [], value.options); + return new types.ShellExecution(value.command!, value.args ? value.args : [], value.options); } } } +namespace CustomExecutionDTO { + export function is(value: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | undefined): value is CustomExecutionDTO { + if (value) { + let candidate = value as CustomExecutionDTO; + return candidate && candidate.customExecution === 'customExecution'; + } else { + return false; + } + } + + export function from(value: vscode.CustomExecution): CustomExecutionDTO { + return { + customExecution: 'customExecution' + }; + } +} + namespace TaskHandleDTO { export function from(value: types.Task): TaskHandleDTO { - let folder: UriComponents; + let folder: UriComponents | undefined; if (value.scope !== undefined && typeof value.scope !== 'number') { folder = value.scope.uri; } return { - id: value._id, - workspaceFolder: folder + id: value._id!, + workspaceFolder: folder! }; } } @@ -170,9 +200,9 @@ namespace TaskDTO { if (tasks === undefined || tasks === null) { return []; } - let result: TaskDTO[] = []; + const result: TaskDTO[] = []; for (let task of tasks) { - let converted = from(task, extension); + const converted = from(task, extension); if (converted) { result.push(converted); } @@ -180,17 +210,19 @@ namespace TaskDTO { return result; } - export function from(value: vscode.Task, extension: IExtensionDescription): TaskDTO { + export function from(value: vscode.Task, extension: IExtensionDescription): TaskDTO | undefined { if (value === undefined || value === null) { return undefined; } - let execution: ShellExecutionDTO | ProcessExecutionDTO; + let execution: ShellExecutionDTO | ProcessExecutionDTO | CustomExecutionDTO | undefined; if (value.execution instanceof types.ProcessExecution) { execution = ProcessExecutionDTO.from(value.execution); } else if (value.execution instanceof types.ShellExecution) { execution = ShellExecutionDTO.from(value.execution); + } else if ((value).execution2 && (value).execution2 instanceof types.CustomExecution) { + execution = CustomExecutionDTO.from((value).execution2); } - let definition: TaskDefinitionDTO = TaskDefinitionDTO.from(value.definition); + const definition: TaskDefinitionDTO | undefined = TaskDefinitionDTO.from(value.definition); let scope: number | UriComponents; if (value.scope) { if (typeof value.scope === 'number') { @@ -205,9 +237,9 @@ namespace TaskDTO { if (!definition || !scope) { return undefined; } - let group = (value.group as types.TaskGroup) ? (value.group as types.TaskGroup).id : undefined; - let result: TaskDTO = { - _id: (value as types.Task)._id, + const group = (value.group as types.TaskGroup) ? (value.group as types.TaskGroup).id : undefined; + const result: TaskDTO = { + _id: (value as types.Task)._id!, definition, name: value.name, source: { @@ -215,7 +247,7 @@ namespace TaskDTO { label: value.source, scope: scope }, - execution, + execution: execution!, isBackground: value.isBackground, group: group, presentationOptions: TaskPresentationOptionsDTO.from(value.presentationOptions), @@ -225,24 +257,24 @@ namespace TaskDTO { }; return result; } - export function to(value: TaskDTO, workspace: ExtHostWorkspace): types.Task { + export async function to(value: TaskDTO | undefined, workspace: IExtHostWorkspaceProvider): Promise { if (value === undefined || value === null) { return undefined; } - let execution: types.ShellExecution | types.ProcessExecution; + let execution: types.ShellExecution | types.ProcessExecution | undefined; if (ProcessExecutionDTO.is(value.execution)) { execution = ProcessExecutionDTO.to(value.execution); } else if (ShellExecutionDTO.is(value.execution)) { execution = ShellExecutionDTO.to(value.execution); } - let definition: vscode.TaskDefinition = TaskDefinitionDTO.to(value.definition); - let scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder; + const definition: vscode.TaskDefinition | undefined = TaskDefinitionDTO.to(value.definition); + let scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder | undefined; if (value.source) { if (value.source.scope !== undefined) { if (typeof value.source.scope === 'number') { scope = value.source.scope; } else { - scope = workspace.resolveWorkspaceFolder(URI.revive(value.source.scope)); + scope = await workspace.resolveWorkspaceFolder(URI.revive(value.source.scope)); } } else { scope = types.TaskScope.Workspace; @@ -251,7 +283,7 @@ namespace TaskDTO { if (!definition || !scope) { return undefined; } - let result = new types.Task(definition, scope, value.name, value.source.label, execution, value.problemMatchers); + const result = new types.Task(definition, scope, value.name!, value.source.label, execution, value.problemMatchers); if (value.isBackground !== undefined) { result.isBackground = value.isBackground; } @@ -259,7 +291,7 @@ namespace TaskDTO { result.group = types.TaskGroup.from(value.group); } if (value.presentationOptions) { - result.presentationOptions = TaskPresentationOptionsDTO.to(value.presentationOptions); + result.presentationOptions = TaskPresentationOptionsDTO.to(value.presentationOptions)!; } if (value._id) { result._id = value._id; @@ -269,11 +301,11 @@ namespace TaskDTO { } namespace TaskFilterDTO { - export function from(value: vscode.TaskFilter): TaskFilterDTO { + export function from(value: vscode.TaskFilter | undefined): TaskFilterDTO | undefined { return value; } - export function to(value: TaskFilterDTO): vscode.TaskFilter { + export function to(value: TaskFilterDTO): vscode.TaskFilter | undefined { if (!value) { return undefined; } @@ -302,8 +334,12 @@ class TaskExecutionImpl implements vscode.TaskExecution { } namespace TaskExecutionDTO { - export function to(value: TaskExecutionDTO, tasks: ExtHostTask): vscode.TaskExecution { - return new TaskExecutionImpl(tasks, value.id, TaskDTO.to(value.task, tasks.extHostWorkspace)); + export async function to(value: TaskExecutionDTO, tasks: ExtHostTask, workspaceProvider: IExtHostWorkspaceProvider): Promise { + const task = await TaskDTO.to(value.task, workspaceProvider); + if (!task) { + throw new Error('Unexpected: Task cannot be created.'); + } + return new TaskExecutionImpl(tasks, value.id, task); } export function from(value: vscode.TaskExecution): TaskExecutionDTO { return { @@ -318,15 +354,121 @@ interface HandlerData { extension: IExtensionDescription; } +class CustomExecutionData implements IDisposable { + private static waitForDimensionsTimeoutInMs: number = 5000; + private _cancellationSource?: CancellationTokenSource; + private readonly _onTaskExecutionComplete: Emitter = new Emitter(); + private readonly _disposables: IDisposable[] = []; + private terminal?: vscode.Terminal; + private terminalId?: number; + public result: number | undefined; + + constructor( + private readonly customExecution: vscode.CustomExecution, + private readonly terminalService: ExtHostTerminalService) { + } + + public dispose(): void { + dispose(this._disposables); + } + + public get onTaskExecutionComplete(): Event { + return this._onTaskExecutionComplete.event; + } + + private onDidCloseTerminal(terminal: vscode.Terminal): void { + if ((this.terminal === terminal) && this._cancellationSource) { + this._cancellationSource.cancel(); + } + } + + private onDidOpenTerminal(terminal: vscode.Terminal): void { + if (!(terminal instanceof ExtHostTerminal)) { + throw new Error('How could this not be a extension host terminal?'); + } + + if (this.terminalId && terminal._id === this.terminalId) { + this.startCallback(this.terminalId); + } + } + + public async startCallback(terminalId: number): Promise { + this.terminalId = terminalId; + + // If we have already started the extension task callback, then + // do not start it again. + // It is completely valid for multiple terminals to be opened + // before the one for our task. + if (this._cancellationSource) { + return undefined; + } + + const callbackTerminals: vscode.Terminal[] = this.terminalService.terminals.filter((terminal) => terminal._id === terminalId); + + if (!callbackTerminals || callbackTerminals.length === 0) { + this._disposables.push(this.terminalService.onDidOpenTerminal(this.onDidOpenTerminal.bind(this))); + return; + } + + if (callbackTerminals.length !== 1) { + throw new Error(`Expected to only have one terminal at this point`); + } + + this.terminal = callbackTerminals[0]; + const terminalRenderer: vscode.TerminalRenderer = await this.terminalService.resolveTerminalRenderer(terminalId); + + // If we don't have the maximum dimensions yet, then we need to wait for them (but not indefinitely). + // Custom executions will expect the dimensions to be set properly before they are launched. + // BUT, due to the API contract VSCode has for terminals and dimensions, they are still responsible for + // handling cases where they are not set. + if (!terminalRenderer.maximumDimensions) { + const dimensionTimeout: Promise = new Promise((resolve) => { + setTimeout(() => { + resolve(); + }, CustomExecutionData.waitForDimensionsTimeoutInMs); + }); + + let dimensionsRegistration: IDisposable | undefined; + const dimensionsPromise: Promise = new Promise((resolve) => { + dimensionsRegistration = terminalRenderer.onDidChangeMaximumDimensions((newDimensions) => { + resolve(); + }); + }); + + await Promise.race([dimensionTimeout, dimensionsPromise]); + if (dimensionsRegistration) { + dimensionsRegistration.dispose(); + } + } + + this._cancellationSource = new CancellationTokenSource(); + this._disposables.push(this._cancellationSource); + + this._disposables.push(this.terminalService.onDidCloseTerminal(this.onDidCloseTerminal.bind(this))); + + // Regardless of how the task completes, we are done with this custom execution task. + this.customExecution.callback(terminalRenderer, this._cancellationSource.token).then( + (success) => { + this.result = success; + this._onTaskExecutionComplete.fire(this); + }, (rejected) => { + this._onTaskExecutionComplete.fire(this); + }); + } +} + export class ExtHostTask implements ExtHostTaskShape { private _proxy: MainThreadTaskShape; - private _workspaceService: ExtHostWorkspace; + private _workspaceProvider: IExtHostWorkspaceProvider; private _editorService: ExtHostDocumentsAndEditors; private _configurationService: ExtHostConfiguration; + private _terminalService: ExtHostTerminalService; private _handleCounter: number; private _handlers: Map; private _taskExecutions: Map; + private _providedCustomExecutions: Map; + private _activeCustomExecutions: Map; private readonly _onDidExecuteTask: Emitter = new Emitter(); private readonly _onDidTerminateTask: Emitter = new Emitter(); @@ -334,25 +476,29 @@ export class ExtHostTask implements ExtHostTaskShape { private readonly _onDidTaskProcessStarted: Emitter = new Emitter(); private readonly _onDidTaskProcessEnded: Emitter = new Emitter(); - constructor(mainContext: IMainContext, workspaceService: ExtHostWorkspace, editorService: ExtHostDocumentsAndEditors, configurationService: ExtHostConfiguration) { + constructor( + mainContext: IMainContext, + workspaceService: ExtHostWorkspace, + editorService: ExtHostDocumentsAndEditors, + configurationService: ExtHostConfiguration, + extHostTerminalService: ExtHostTerminalService) { this._proxy = mainContext.getProxy(MainContext.MainThreadTask); - this._workspaceService = workspaceService; + this._workspaceProvider = workspaceService; this._editorService = editorService; this._configurationService = configurationService; + this._terminalService = extHostTerminalService; this._handleCounter = 0; this._handlers = new Map(); this._taskExecutions = new Map(); - } - - public get extHostWorkspace(): ExtHostWorkspace { - return this._workspaceService; + this._providedCustomExecutions = new Map(); + this._activeCustomExecutions = new Map(); } public registerTaskProvider(extension: IExtensionDescription, provider: vscode.TaskProvider): vscode.Disposable { if (!provider) { return new types.Disposable(() => { }); } - let handle = this.nextHandle(); + const handle = this.nextHandle(); this._handlers.set(handle, { provider, extension }); this._proxy.$registerTaskProvider(handle); return new types.Disposable(() => { @@ -366,10 +512,10 @@ export class ExtHostTask implements ExtHostTaskShape { } public fetchTasks(filter?: vscode.TaskFilter): Promise { - return this._proxy.$fetchTasks(TaskFilterDTO.from(filter)).then((values) => { - let result: vscode.Task[] = []; + return this._proxy.$fetchTasks(TaskFilterDTO.from(filter)).then(async (values) => { + const result: vscode.Task[] = []; for (let value of values) { - let task = TaskDTO.to(value, this._workspaceService); + const task = await TaskDTO.to(value, this._workspaceProvider); if (task) { result.push(task); } @@ -378,13 +524,13 @@ export class ExtHostTask implements ExtHostTaskShape { }); } - public executeTask(extension: IExtensionDescription, task: vscode.Task): Promise { - let tTask = (task as types.Task); + public async executeTask(extension: IExtensionDescription, task: vscode.Task): Promise { + const tTask = (task as types.Task); // We have a preserved ID. So the task didn't change. if (tTask._id !== undefined) { return this._proxy.$executeTask(TaskHandleDTO.from(tTask)).then(value => this.getTaskExecution(value, task)); } else { - let dto = TaskDTO.from(task, extension); + const dto = TaskDTO.from(task, extension); if (dto === undefined) { return Promise.reject(new Error('Task is not valid')); } @@ -393,7 +539,7 @@ export class ExtHostTask implements ExtHostTaskShape { } public get taskExecutions(): vscode.TaskExecution[] { - let result: vscode.TaskExecution[] = []; + const result: vscode.TaskExecution[] = []; this._taskExecutions.forEach(value => result.push(value)); return result; } @@ -409,9 +555,28 @@ export class ExtHostTask implements ExtHostTaskShape { return this._onDidExecuteTask.event; } - public $onDidStartTask(execution: TaskExecutionDTO): void { + public async $onDidStartTask(execution: TaskExecutionDTO, terminalId: number): Promise { + // Once a terminal is spun up for the custom execution task this event will be fired. + // At that point, we need to actually start the callback, but + // only if it hasn't already begun. + const extensionCallback: CustomExecutionData | undefined = this._providedCustomExecutions.get(execution.id); + if (extensionCallback) { + if (this._activeCustomExecutions.get(execution.id) !== undefined) { + throw new Error('We should not be trying to start the same custom task executions twice.'); + } + + this._activeCustomExecutions.set(execution.id, extensionCallback); + + const taskExecutionComplete: IDisposable = extensionCallback.onTaskExecutionComplete(() => { + this.customExecutionComplete(execution); + taskExecutionComplete.dispose(); + }); + + extensionCallback.startCallback(terminalId); + } + this._onDidExecuteTask.fire({ - execution: this.getTaskExecution(execution) + execution: await this.getTaskExecution(execution) }); } @@ -419,9 +584,10 @@ export class ExtHostTask implements ExtHostTaskShape { return this._onDidTerminateTask.event; } - public $OnDidEndTask(execution: TaskExecutionDTO): void { - const _execution = this.getTaskExecution(execution); + public async $OnDidEndTask(execution: TaskExecutionDTO): Promise { + const _execution = await this.getTaskExecution(execution); this._taskExecutions.delete(execution.id); + this.customExecutionComplete(execution); this._onDidTerminateTask.fire({ execution: _execution }); @@ -431,8 +597,8 @@ export class ExtHostTask implements ExtHostTaskShape { return this._onDidTaskProcessStarted.event; } - public $onDidStartTaskProcess(value: TaskProcessStartedDTO): void { - const execution = this.getTaskExecution(value.id); + public async $onDidStartTaskProcess(value: TaskProcessStartedDTO): Promise { + const execution = await this.getTaskExecution(value.id); if (execution) { this._onDidTaskProcessStarted.fire({ execution: execution, @@ -445,8 +611,8 @@ export class ExtHostTask implements ExtHostTaskShape { return this._onDidTaskProcessEnded.event; } - public $onDidEndTaskProcess(value: TaskProcessEndedDTO): void { - const execution = this.getTaskExecution(value.id); + public async $onDidEndTaskProcess(value: TaskProcessEndedDTO): Promise { + const execution = await this.getTaskExecution(value.id); if (execution) { this._onDidTaskProcessEnded.fire({ execution: execution, @@ -456,63 +622,106 @@ export class ExtHostTask implements ExtHostTaskShape { } public $provideTasks(handle: number, validTypes: { [key: string]: boolean; }): Thenable { - let handler = this._handlers.get(handle); + const handler = this._handlers.get(handle); if (!handler) { return Promise.reject(new Error('no handler found')); } - return asPromise(() => handler.provider.provideTasks(CancellationToken.None)).then(value => { - let sanitized: vscode.Task[] = []; - for (let task of value) { - if (task.definition && validTypes[task.definition.type] === true) { - sanitized.push(task); - } else { - sanitized.push(task); - console.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); + + // For custom execution tasks, we need to store the execution objects locally + // since we obviously cannot send callback functions through the proxy. + // So, clear out any existing ones. + this._providedCustomExecutions.clear(); + + // Set up a list of task ID promises that we can wait on + // before returning the provided tasks. The ensures that + // our task IDs are calculated for any custom execution tasks. + // Knowing this ID ahead of time is needed because when a task + // start event is fired this is when the custom execution is called. + // The task start event is also the first time we see the ID from the main + // thread, which is too late for us because we need to save an map + // from an ID to the custom execution function. (Kind of a cart before the horse problem). + const taskIdPromises: Promise[] = []; + const fetchPromise = asPromise(() => handler.provider.provideTasks(CancellationToken.None)).then(value => { + const taskDTOs: TaskDTO[] = []; + if (value) { + for (let task of value) { + if (!task.definition || !validTypes[task.definition.type]) { + console.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); + } + + const taskDTO: TaskDTO | undefined = TaskDTO.from(task, handler.extension); + if (taskDTO) { + taskDTOs.push(taskDTO); + + if (CustomExecutionDTO.is(taskDTO.execution)) { + taskIdPromises.push(new Promise((resolve) => { + // The ID is calculated on the main thread task side, so, let's call into it here. + // We need the task id's pre-computed for custom task executions because when OnDidStartTask + // is invoked, we have to be able to map it back to our data. + this._proxy.$createTaskId(taskDTO).then((taskId) => { + this._providedCustomExecutions.set(taskId, new CustomExecutionData((task).execution2, this._terminalService)); + resolve(); + }); + })); + } + } } } return { - tasks: TaskDTO.fromMany(sanitized, handler.extension), + tasks: taskDTOs, extension: handler.extension }; }); + + return new Promise((resolve) => { + fetchPromise.then((result) => { + Promise.all(taskIdPromises).then(() => { + resolve(result); + }); + }); + }); } // {{SQL CARBON EDIT}} disable debug related method public async $resolveVariables(uriComponents: UriComponents, toResolve: { process?: { name: string; cwd?: string; path?: string }, variables: string[] }): Promise<{ process?: string, variables: { [key: string]: string; } }> { - // const configProvider = await this._configurationService.getConfigProvider(); - // let uri: URI = URI.revive(uriComponents); - // let result = { - // process: undefined as string, - // variables: Object.create(null) - // }; - // let workspaceFolder = this._workspaceService.resolveWorkspaceFolder(uri); - // let resolver = new ExtHostVariableResolverService(this._workspaceService, this._editorService, configProvider); - // let ws: IWorkspaceFolder = { - // uri: workspaceFolder.uri, - // name: workspaceFolder.name, - // index: workspaceFolder.index, - // toResource: () => { - // throw new Error('Not implemented'); - // } - // }; - // for (let variable of toResolve.variables) { - // result.variables[variable] = resolver.resolve(ws, variable); - // } - // if (toResolve.process !== undefined) { - // let paths: string[] | undefined = undefined; - // if (toResolve.process.path !== undefined) { - // paths = toResolve.process.path.split(path.delimiter); - // for (let i = 0; i < paths.length; i++) { - // paths[i] = resolver.resolve(ws, paths[i]); - // } - // } - // result.process = win32.findExecutable( - // resolver.resolve(ws, toResolve.process.name), - // toResolve.process.cwd !== undefined ? resolver.resolve(ws, toResolve.process.cwd) : undefined, - // paths - // ); - // } - // return result; + /*const configProvider = await this._configurationService.getConfigProvider(); + const uri: URI = URI.revive(uriComponents); + const result = { + process: undefined as string, + variables: Object.create(null) + }; + const workspaceFolder = await this._workspaceProvider.resolveWorkspaceFolder(uri); + const workspaceFolders = await this._workspaceProvider.getWorkspaceFolders2(); + if (!workspaceFolders || !workspaceFolder) { + throw new Error('Unexpected: Tasks can only be run in a workspace folder'); + } + const resolver = new ExtHostVariableResolverService(workspaceFolders, this._editorService, configProvider); + const ws: IWorkspaceFolder = { + uri: workspaceFolder.uri, + name: workspaceFolder.name, + index: workspaceFolder.index, + toResource: () => { + throw new Error('Not implemented'); + } + }; + for (let variable of toResolve.variables) { + result.variables[variable] = resolver.resolve(ws, variable); + } + if (toResolve.process !== undefined) { + let paths: string[] | undefined = undefined; + if (toResolve.process.path !== undefined) { + paths = toResolve.process.path.split(path.delimiter); + for (let i = 0; i < paths.length; i++) { + paths[i] = resolver.resolve(ws, paths[i]); + } + } + result.process = win32.findExecutable( + resolver.resolve(ws, toResolve.process.name), + toResolve.process.cwd !== undefined ? resolver.resolve(ws, toResolve.process.cwd) : undefined, + paths + ); + } + return result;*/ return undefined; } @@ -520,17 +729,34 @@ export class ExtHostTask implements ExtHostTaskShape { return this._handleCounter++; } - private getTaskExecution(execution: TaskExecutionDTO | string, task?: vscode.Task): TaskExecutionImpl { + private async getTaskExecution(execution: TaskExecutionDTO | string, task?: vscode.Task): Promise { if (typeof execution === 'string') { - return this._taskExecutions.get(execution); + const taskExecution = this._taskExecutions.get(execution); + if (!taskExecution) { + throw new Error('Unexpected: The specified task is missing an execution'); + } + return taskExecution; } - let result: TaskExecutionImpl = this._taskExecutions.get(execution.id); + let result: TaskExecutionImpl | undefined = this._taskExecutions.get(execution.id); if (result) { return result; } - result = new TaskExecutionImpl(this, execution.id, task ? task : TaskDTO.to(execution.task, this._workspaceService)); - this._taskExecutions.set(execution.id, result); - return result; + const taskToCreate = task ? task : await TaskDTO.to(execution.task, this._workspaceProvider); + if (!taskToCreate) { + throw new Error('Unexpected: Task does not exist.'); + } + const createdResult: TaskExecutionImpl = new TaskExecutionImpl(this, execution.id, taskToCreate); + this._taskExecutions.set(execution.id, createdResult); + return createdResult; + } + + private customExecutionComplete(execution: TaskExecutionDTO): void { + const extensionCallback: CustomExecutionData | undefined = this._activeCustomExecutions.get(execution.id); + if (extensionCallback) { + this._activeCustomExecutions.delete(execution.id); + this._proxy.$customExecutionComplete(execution.id, extensionCallback.result); + extensionCallback.dispose(); + } } } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 024abb3180..73de621263 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -4,21 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; +import pkg from 'vs/platform/product/node/package'; +import * as os from 'os'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as platform from 'vs/base/common/platform'; -import * as terminalEnvironment from 'vs/workbench/parts/terminal/node/terminalEnvironment'; +import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { Event, Emitter } from 'vs/base/common/event'; import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext, ShellLaunchConfigDto } from 'vs/workbench/api/node/extHost.protocol'; import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; import { ILogService } from 'vs/platform/log/common/log'; -import { EXT_HOST_CREATION_DELAY } from 'vs/workbench/parts/terminal/common/terminal'; -import { TerminalProcess } from 'vs/workbench/parts/terminal/node/terminalProcess'; +import { EXT_HOST_CREATION_DELAY, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess'; import { timeout } from 'vs/base/common/async'; -import { generateRandomPipeName } from 'vs/base/parts/ipc/node/ipc.net'; -import * as http from 'http'; -import * as fs from 'fs'; -import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; -import { sanitizeProcessEnvironment } from 'vs/base/node/processes'; +import { sanitizeProcessEnvironment } from 'vs/base/common/processes'; const RENDERER_NO_PROCESS_ID = -1; @@ -76,8 +74,10 @@ export class BaseExtHostTerminal { } export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Terminal { - private _pidPromise: Promise; - private _pidPromiseComplete: (value: number) => any; + private _pidPromise: Promise; + private _cols: number | undefined; + private _pidPromiseComplete: ((value: number | undefined) => any) | null; + private _rows: number | undefined; private readonly _onData = new Emitter(); public get onDidWriteData(): Event { @@ -90,7 +90,7 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi constructor( proxy: MainThreadTerminalServiceShape, - private _name: string, + private _name?: string, id?: number, pid?: number ) { @@ -108,7 +108,7 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi shellPath?: string, shellArgs?: string[], cwd?: string | URI, - env?: { [key: string]: string }, + env?: { [key: string]: string | null }, waitOnExit?: boolean, strictEnv?: boolean ): void { @@ -119,14 +119,34 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi } public get name(): string { - return this._name; + return this._name || ''; } public set name(name: string) { this._name = name; } - public get processId(): Promise { + public get dimensions(): vscode.TerminalDimensions | undefined { + if (this._cols === undefined || this._rows === undefined) { + return undefined; + } + return { + columns: this._cols, + rows: this._rows + }; + } + + public setDimensions(cols: number, rows: number): boolean { + if (cols === this._cols && rows === this._rows) { + // Nothing changed + return false; + } + this._cols = cols; + this._rows = rows; + return true; + } + + public get processId(): Promise { return this._pidPromise; } @@ -145,7 +165,7 @@ export class ExtHostTerminal extends BaseExtHostTerminal implements vscode.Termi this._queueApiRequest(this._proxy.$hide, []); } - public _setProcessId(processId: number): void { + public _setProcessId(processId: number | undefined): void { // The event may fire 2 times when the panel is restored if (this._pidPromiseComplete) { this._pidPromiseComplete(processId); @@ -183,15 +203,15 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco } private _dimensions: vscode.TerminalDimensions | undefined; - public get dimensions(): vscode.TerminalDimensions { return this._dimensions; } - public set dimensions(dimensions: vscode.TerminalDimensions) { + public get dimensions(): vscode.TerminalDimensions | undefined { return this._dimensions; } + public set dimensions(dimensions: vscode.TerminalDimensions | undefined) { this._checkDisposed(); this._dimensions = dimensions; this._queueApiRequest(this._proxy.$terminalRendererSetDimensions, [dimensions]); } - private _maximumDimensions: vscode.TerminalDimensions; - public get maximumDimensions(): vscode.TerminalDimensions { + private _maximumDimensions: vscode.TerminalDimensions | undefined; + public get maximumDimensions(): vscode.TerminalDimensions | undefined { if (!this._maximumDimensions) { return undefined; } @@ -213,13 +233,17 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco constructor( proxy: MainThreadTerminalServiceShape, private _name: string, - private _terminal: ExtHostTerminal + private _terminal: ExtHostTerminal, + id?: number ) { - super(proxy); - this._proxy.$createTerminalRenderer(this._name).then(id => { - this._runQueuedRequests(id); - (this._terminal)._runQueuedRequests(id); - }); + super(proxy, id); + + if (!id) { + this._proxy.$createTerminalRenderer(this._name).then(id => { + this._runQueuedRequests(id); + (this._terminal)._runQueuedRequests(id); + }); + } } public write(data: string): void { @@ -235,21 +259,21 @@ export class ExtHostTerminalRenderer extends BaseExtHostTerminal implements vsco if (this._maximumDimensions && this._maximumDimensions.columns === columns && this._maximumDimensions.rows === rows) { return; } - this._maximumDimensions = { columns, rows }; - this._onDidChangeMaximumDimensions.fire(this.maximumDimensions); + const newValue = { columns, rows }; + this._maximumDimensions = newValue; + this._onDidChangeMaximumDimensions.fire(newValue); } } export class ExtHostTerminalService implements ExtHostTerminalServiceShape { private _proxy: MainThreadTerminalServiceShape; - private _activeTerminal: ExtHostTerminal; + private _activeTerminal: ExtHostTerminal | undefined; private _terminals: ExtHostTerminal[] = []; private _terminalProcesses: { [id: number]: TerminalProcess } = {}; private _terminalRenderers: ExtHostTerminalRenderer[] = []; private _getTerminalPromises: { [id: number]: Promise } = {}; - private _cliServer: CLIServer | undefined; - public get activeTerminal(): ExtHostTerminal { return this._activeTerminal; } + public get activeTerminal(): ExtHostTerminal | undefined { return this._activeTerminal; } public get terminals(): ExtHostTerminal[] { return this._terminals; } private readonly _onDidCloseTerminal: Emitter = new Emitter(); @@ -258,12 +282,13 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { public get onDidOpenTerminal(): Event { return this._onDidOpenTerminal && this._onDidOpenTerminal.event; } private readonly _onDidChangeActiveTerminal: Emitter = new Emitter(); public get onDidChangeActiveTerminal(): Event { return this._onDidChangeActiveTerminal && this._onDidChangeActiveTerminal.event; } + private readonly _onDidChangeTerminalDimensions: Emitter = new Emitter(); + public get onDidChangeTerminalDimensions(): Event { return this._onDidChangeTerminalDimensions && this._onDidChangeTerminalDimensions.event; } constructor( mainContext: IMainContext, private _extHostConfiguration: ExtHostConfiguration, private _logService: ILogService, - private _commands: ExtHostCommands ) { this._proxy = mainContext.getProxy(MainContext.MainThreadTerminalService); } @@ -293,6 +318,24 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { return renderer; } + public async resolveTerminalRenderer(id: number): Promise { + // Check to see if the extension host already knows about this terminal. + for (const terminalRenderer of this._terminalRenderers) { + if (terminalRenderer._id === id) { + return terminalRenderer; + } + } + + const terminal = this._getTerminalById(id); + if (!terminal) { + throw new Error(`Cannot resolve terminal renderer for terminal id ${id}`); + } + const renderer = new ExtHostTerminalRenderer(this._proxy, terminal.name, terminal, terminal._id); + this._terminalRenderers.push(renderer); + + return renderer; + } + public $acceptActiveTerminalChanged(id: number | null): void { const original = this._activeTerminal; if (id === null) { @@ -300,6 +343,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { if (original !== this._activeTerminal) { this._onDidChangeActiveTerminal.fire(this._activeTerminal); } + return; } this._performTerminalIdAction(id, terminal => { if (terminal) { @@ -319,7 +363,17 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { }); } - public $acceptTerminalRendererDimensions(id: number, cols: number, rows: number): void { + public async $acceptTerminalDimensions(id: number, cols: number, rows: number): Promise { + const terminal = this._getTerminalById(id); + if (terminal) { + if (terminal.setDimensions(cols, rows)) { + this._onDidChangeTerminalDimensions.fire({ + terminal: terminal, + dimensions: terminal.dimensions as vscode.TerminalDimensions + }); + } + } + // When a terminal's dimensions change, a renderer's _maximum_ dimensions change const renderer = this._getTerminalRendererById(id); if (renderer) { renderer._setMaximumDimensions(cols, rows); @@ -342,11 +396,10 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { public $acceptTerminalClosed(id: number): void { const index = this._getTerminalObjectIndexById(this.terminals, id); - if (index === null) { - return; + if (index !== null) { + const terminal = this._terminals.splice(index, 1)[0]; + this._onDidCloseTerminal.fire(terminal); } - const terminal = this._terminals.splice(index, 1)[0]; - this._onDidCloseTerminal.fire(terminal); } public $acceptTerminalOpened(id: number, name: string): void { @@ -356,6 +409,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { this._onDidOpenTerminal.fire(this.terminals[index]); return; } + const renderer = this._getTerminalRendererById(id); const terminal = new ExtHostTerminal(this._proxy, name, id, renderer ? RENDERER_NO_PROCESS_ID : undefined); this._terminals.push(terminal); @@ -381,7 +435,15 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { } } - public async $createProcess(id: number, shellLaunchConfig: ShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number): Promise { + public async $createProcess(id: number, shellLaunchConfigDto: ShellLaunchConfigDto, activeWorkspaceRootUriComponents: UriComponents, cols: number, rows: number): Promise { + const shellLaunchConfig: IShellLaunchConfig = { + name: shellLaunchConfigDto.name, + executable: shellLaunchConfigDto.executable, + args: shellLaunchConfigDto.args, + cwd: typeof shellLaunchConfigDto.cwd === 'string' ? shellLaunchConfigDto.cwd : URI.revive(shellLaunchConfigDto.cwd), + env: shellLaunchConfigDto.env + }; + // TODO: This function duplicates a lot of TerminalProcessManager.createProcess, ideally // they would be merged into a single implementation. const configProvider = await this._extHostConfiguration.getConfigProvider(); @@ -392,8 +454,8 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { // this._configHelper.mergeDefaultShellPathAndArgs(shellLaunchConfig); const platformKey = platform.isWindows ? 'windows' : platform.isMacintosh ? 'osx' : 'linux'; - const shellConfigValue: string = terminalConfig.get(`shell.${platformKey}`); - const shellArgsConfigValue: string = terminalConfig.get(`shellArgs.${platformKey}`); + const shellConfigValue: string | undefined = terminalConfig.get(`shell.${platformKey}`); + const shellArgsConfigValue: string | undefined = terminalConfig.get(`shellArgs.${platformKey}`); shellLaunchConfig.executable = shellConfigValue; shellLaunchConfig.args = shellArgsConfigValue; @@ -401,7 +463,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { // TODO: @daniel const activeWorkspaceRootUri = URI.revive(activeWorkspaceRootUriComponents); - const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, activeWorkspaceRootUri, terminalConfig.cwd); + const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, os.homedir(), activeWorkspaceRootUri, terminalConfig.cwd); // TODO: Pull in and resolve config settings // // Resolve env vars from config and shell @@ -413,25 +475,24 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { // Merge process env with the env from config const env = { ...process.env }; - terminalEnvironment.mergeEnvironments(env, envFromConfig); - terminalEnvironment.mergeEnvironments(env, shellLaunchConfig.env); + Object.keys(env).filter(k => env[k] === undefined).forEach(k => { + delete env[k]; + }); + const castedEnv = env as platform.IProcessEnvironment; + terminalEnvironment.mergeEnvironments(castedEnv, envFromConfig); + terminalEnvironment.mergeEnvironments(castedEnv, shellLaunchConfig.env); // Sanitize the environment, removing any undesirable VS Code and Electron environment // variables - sanitizeProcessEnvironment(env); + sanitizeProcessEnvironment(castedEnv, 'VSCODE_IPC_HOOK_CLI'); // Continue env initialization, merging in the env from the launch // config and adding keys that are needed to create the process - terminalEnvironment.addTerminalEnvironmentKeys(env, platform.locale, terminalConfig.get('setLocaleVariables')); - - if (!this._cliServer) { - this._cliServer = new CLIServer(this._commands); - } - env['VSCODE_IPC_HOOK_CLI'] = this._cliServer.ipcHandlePath; + terminalEnvironment.addTerminalEnvironmentKeys(castedEnv, pkg.version, platform.locale, terminalConfig.get('setLocaleVariables') as boolean); // Fork the process and listen for messages - this._logService.debug(`Terminal process launching on ext host`, shellLaunchConfig, initialCwd, cols, rows, env); - const p = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, terminalConfig.get('windowsEnableConpty')); + this._logService.debug(`Terminal process launching on ext host`, shellLaunchConfig, initialCwd, cols, rows, castedEnv); + const p = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, castedEnv, terminalConfig.get('windowsEnableConpty') as boolean); p.onProcessIdReady(pid => this._proxy.$sendProcessPid(id, pid)); p.onProcessTitleChanged(title => this._proxy.$sendProcessTitle(id, title)); p.onProcessData(data => this._proxy.$sendProcessData(id, data)); @@ -477,11 +538,6 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { // Send exit event to main side this._proxy.$sendProcessExit(id, exitCode); - if (this._cliServer && !Object.keys(this._terminalProcesses).length) { - this._cliServer.dispose(); - this._cliServer = undefined; - } - } private _getTerminalByIdEventually(id: number, retries: number = 5): Promise { @@ -513,20 +569,20 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { }); } - private _getTerminalById(id: number): ExtHostTerminal { + private _getTerminalById(id: number): ExtHostTerminal | null { return this._getTerminalObjectById(this._terminals, id); } - private _getTerminalRendererById(id: number): ExtHostTerminalRenderer { + private _getTerminalRendererById(id: number): ExtHostTerminalRenderer | null { return this._getTerminalObjectById(this._terminalRenderers, id); } - private _getTerminalObjectById(array: T[], id: number): T { + private _getTerminalObjectById(array: T[], id: number): T | null { const index = this._getTerminalObjectIndexById(array, id); return index !== null ? array[index] : null; } - private _getTerminalObjectIndexById(array: T[], id: number): number { + private _getTerminalObjectIndexById(array: T[], id: number): number | null { let index: number | null = null; array.some((item, i) => { const thisId = item._id; @@ -553,73 +609,3 @@ class ApiRequest { this._callback.apply(proxy, [id].concat(this._args)); } } - - -class CLIServer { - - private _server: http.Server; - private _ipcHandlePath: string | undefined; - - constructor(private _commands: ExtHostCommands) { - this._server = http.createServer((req, res) => this.onRequest(req, res)); - this.setup().catch(err => { - console.error(err); - return ''; - }); - } - - public get ipcHandlePath() { - return this._ipcHandlePath; - } - - private async setup(): Promise { - this._ipcHandlePath = generateRandomPipeName(); - - try { - this._server.listen(this.ipcHandlePath); - this._server.on('error', err => console.error(err)); - } catch (err) { - console.error('Could not start open from terminal server.'); - } - - return this.ipcHandlePath; - } - private toURIs(strs: string[]): URI[] { - const result: URI[] = []; - if (Array.isArray(strs)) { - for (const s of strs) { - try { - result.push(URI.parse(s)); - } catch (e) { - // ignore - } - } - } - return result; - } - - private onRequest(req: http.IncomingMessage, res: http.ServerResponse): void { - const chunks: string[] = []; - req.setEncoding('utf8'); - req.on('data', (d: string) => chunks.push(d)); - req.on('end', () => { - let { fileURIs, folderURIs, forceNewWindow, diffMode, addMode, forceReuseWindow } = JSON.parse(chunks.join('')); - if (folderURIs && folderURIs.length || fileURIs && fileURIs.length) { - if (folderURIs && folderURIs.length && !forceReuseWindow) { - forceNewWindow = true; - } - this._commands.executeCommand('_files.windowOpen', { folderURIs: this.toURIs(folderURIs), fileURIs: this.toURIs(fileURIs), forceNewWindow, diffMode, addMode, forceReuseWindow }); - } - res.writeHead(200); - res.end(); - }); - } - - dispose(): void { - this._server.close(); - - if (this._ipcHandlePath && process.platform !== 'win32' && fs.existsSync(this._ipcHandlePath)) { - fs.unlinkSync(this._ipcHandlePath); - } - } -} diff --git a/src/vs/workbench/api/node/extHostTextEditor.ts b/src/vs/workbench/api/node/extHostTextEditor.ts index 16abd045ce..bc479a25ad 100644 --- a/src/vs/workbench/api/node/extHostTextEditor.ts +++ b/src/vs/workbench/api/node/extHostTextEditor.ts @@ -35,7 +35,7 @@ export class TextEditorDecorationType implements vscode.TextEditorDecorationType export interface ITextEditOperation { range: vscode.Range; - text: string; + text: string | null; forceMoveMarkers: boolean; } @@ -105,8 +105,8 @@ export class TextEditorEdit { this._pushEdit(range, null, true); } - private _pushEdit(range: Range, text: string, forceMoveMarkers: boolean): void { - let validRange = this._document.validateRange(range); + private _pushEdit(range: Range, text: string | null, forceMoveMarkers: boolean): void { + const validRange = this._document.validateRange(range); this._collectedEdits.push({ range: validRange, text: text, @@ -142,6 +142,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { private _id: string; private _tabSize: number; + private _indentSize: number; private _insertSpaces: boolean; private _cursorStyle: TextEditorCursorStyle; private _lineNumbers: TextEditorLineNumbersStyle; @@ -154,6 +155,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { public _accept(source: IResolvedTextEditorConfiguration): void { this._tabSize = source.tabSize; + this._indentSize = source.indentSize; this._insertSpaces = source.insertSpaces; this._cursorStyle = source.cursorStyle; this._lineNumbers = source.lineNumbers; @@ -168,11 +170,11 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { return 'auto'; } if (typeof value === 'number') { - let r = Math.floor(value); + const r = Math.floor(value); return (r > 0 ? r : null); } if (typeof value === 'string') { - let r = parseInt(value, 10); + const r = parseInt(value, 10); if (isNaN(r)) { return null; } @@ -182,7 +184,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { } public set tabSize(value: number | string) { - let tabSize = this._validateTabSize(value); + const tabSize = this._validateTabSize(value); if (tabSize === null) { // ignore invalid call return; @@ -200,6 +202,47 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { })); } + public get indentSize(): number | string { + return this._indentSize; + } + + private _validateIndentSize(value: number | string): number | 'tabSize' | null { + if (value === 'tabSize') { + return 'tabSize'; + } + if (typeof value === 'number') { + const r = Math.floor(value); + return (r > 0 ? r : null); + } + if (typeof value === 'string') { + const r = parseInt(value, 10); + if (isNaN(r)) { + return null; + } + return (r > 0 ? r : null); + } + return null; + } + + public set indentSize(value: number | string) { + const indentSize = this._validateIndentSize(value); + if (indentSize === null) { + // ignore invalid call + return; + } + if (typeof indentSize === 'number') { + if (this._indentSize === indentSize) { + // nothing to do + return; + } + // reflect the new indentSize value immediately + this._indentSize = indentSize; + } + warnOnError(this._proxy.$trySetOptions(this._id, { + indentSize: indentSize + })); + } + public get insertSpaces(): boolean | string { return this._insertSpaces; } @@ -212,7 +255,7 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { } public set insertSpaces(value: boolean | string) { - let insertSpaces = this._validateInsertSpaces(value); + const insertSpaces = this._validateInsertSpaces(value); if (typeof insertSpaces === 'boolean') { if (this._insertSpaces === insertSpaces) { // nothing to do @@ -257,11 +300,11 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { } public assign(newOptions: vscode.TextEditorOptions) { - let bulkConfigurationUpdate: ITextEditorConfigurationUpdate = {}; + const bulkConfigurationUpdate: ITextEditorConfigurationUpdate = {}; let hasUpdate = false; if (typeof newOptions.tabSize !== 'undefined') { - let tabSize = this._validateTabSize(newOptions.tabSize); + const tabSize = this._validateTabSize(newOptions.tabSize); if (tabSize === 'auto') { hasUpdate = true; bulkConfigurationUpdate.tabSize = tabSize; @@ -273,8 +316,21 @@ export class ExtHostTextEditorOptions implements vscode.TextEditorOptions { } } + // if (typeof newOptions.indentSize !== 'undefined') { + // const indentSize = this._validateIndentSize(newOptions.indentSize); + // if (indentSize === 'tabSize') { + // hasUpdate = true; + // bulkConfigurationUpdate.indentSize = indentSize; + // } else if (typeof indentSize === 'number' && this._indentSize !== indentSize) { + // // reflect the new indentSize value immediately + // this._indentSize = indentSize; + // hasUpdate = true; + // bulkConfigurationUpdate.indentSize = indentSize; + // } + // } + if (typeof newOptions.insertSpaces !== 'undefined') { - let insertSpaces = this._validateInsertSpaces(newOptions.insertSpaces); + const insertSpaces = this._validateInsertSpaces(newOptions.insertSpaces); if (insertSpaces === 'auto') { hasUpdate = true; bulkConfigurationUpdate.insertSpaces = insertSpaces; @@ -317,7 +373,7 @@ export class ExtHostTextEditor implements vscode.TextEditor { private _selections: Selection[]; private _options: ExtHostTextEditorOptions; private _visibleRanges: Range[]; - private _viewColumn: vscode.ViewColumn; + private _viewColumn: vscode.ViewColumn | undefined; private _disposed: boolean = false; private _hasDecorationsForKey: { [key: string]: boolean; }; @@ -326,7 +382,7 @@ export class ExtHostTextEditor implements vscode.TextEditor { constructor( proxy: MainThreadTextEditorsShape, id: string, document: ExtHostDocumentData, selections: Selection[], options: IResolvedTextEditorConfiguration, - visibleRanges: Range[], viewColumn: vscode.ViewColumn + visibleRanges: Range[], viewColumn: vscode.ViewColumn | undefined ) { this._proxy = proxy; this._id = id; @@ -395,7 +451,7 @@ export class ExtHostTextEditor implements vscode.TextEditor { // ---- view column - get viewColumn(): vscode.ViewColumn { + get viewColumn(): vscode.ViewColumn | undefined { return this._viewColumn; } @@ -454,7 +510,7 @@ export class ExtHostTextEditor implements vscode.TextEditor { TypeConverters.fromRangeOrRangeWithMessage(ranges) ); } else { - let _ranges: number[] = new Array(4 * ranges.length); + const _ranges: number[] = new Array(4 * ranges.length); for (let i = 0, len = ranges.length; i < len; i++) { const range = ranges[i]; _ranges[4 * i] = range.start.line + 1; @@ -482,8 +538,8 @@ export class ExtHostTextEditor implements vscode.TextEditor { ); } - private _trySetSelection(): Promise { - let selection = this._selections.map(TypeConverters.Selection.from); + private _trySetSelection(): Promise { + const selection = this._selections.map(TypeConverters.Selection.from); return this._runOnProxy(() => this._proxy.$trySetSelections(this._id, selection)); } @@ -498,13 +554,13 @@ export class ExtHostTextEditor implements vscode.TextEditor { if (this._disposed) { return Promise.reject(new Error('TextEditor#edit not possible on closed editors')); } - let edit = new TextEditorEdit(this._documentData.document, options); + const edit = new TextEditorEdit(this._documentData.document, options); callback(edit); return this._applyEdit(edit); } private _applyEdit(editBuilder: TextEditorEdit): Promise { - let editData = editBuilder.finalize(); + const editData = editBuilder.finalize(); // return when there is nothing to do if (editData.edits.length === 0 && !editData.setEndOfLine) { @@ -512,7 +568,7 @@ export class ExtHostTextEditor implements vscode.TextEditor { } // check that the edits are not overlapping (i.e. illegal) - let editRanges = editData.edits.map(edit => edit.range); + const editRanges = editData.edits.map(edit => edit.range); // sort ascending (by end and then by start) editRanges.sort((a, b) => { @@ -542,7 +598,7 @@ export class ExtHostTextEditor implements vscode.TextEditor { } // prepare data for serialization - let edits: ISingleEditOperation[] = editData.edits.map((edit) => { + const edits = editData.edits.map((edit): ISingleEditOperation => { return { range: TypeConverters.Range.from(edit.range), text: edit.text, @@ -564,7 +620,7 @@ export class ExtHostTextEditor implements vscode.TextEditor { let ranges: IRange[]; if (!where || (Array.isArray(where) && where.length === 0)) { - ranges = this._selections.map(TypeConverters.Range.from); + ranges = this._selections.map(range => TypeConverters.Range.from(range)); } else if (where instanceof Position) { const { lineNumber, column } = TypeConverters.Position.from(where); @@ -589,7 +645,7 @@ export class ExtHostTextEditor implements vscode.TextEditor { // ---- util - private _runOnProxy(callback: () => Promise): Promise { + private _runOnProxy(callback: () => Promise): Promise { if (this._disposed) { console.warn('TextEditor is closed/disposed'); return Promise.resolve(undefined); diff --git a/src/vs/workbench/api/node/extHostTextEditors.ts b/src/vs/workbench/api/node/extHostTextEditors.ts index 9435433498..d93a786e1f 100644 --- a/src/vs/workbench/api/node/extHostTextEditors.ts +++ b/src/vs/workbench/api/node/extHostTextEditors.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; +import * as arrays from 'vs/base/common/arrays'; import { ExtHostEditorsShape, IEditorPropertiesChangeData, IMainContext, ITextDocumentShowOptions, ITextEditorPositionData, MainContext, MainThreadTextEditorsShape } from 'vs/workbench/api/node/extHost.protocol'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors'; import { ExtHostTextEditor, TextEditorDecorationType } from 'vs/workbench/api/node/extHostTextEditor'; @@ -42,7 +43,7 @@ export class ExtHostEditors implements ExtHostEditorsShape { this._extHostDocumentsAndEditors.onDidChangeActiveTextEditor(e => this._onDidChangeActiveTextEditor.fire(e)); } - getActiveTextEditor(): ExtHostTextEditor { + getActiveTextEditor(): ExtHostTextEditor | undefined { return this._extHostDocumentsAndEditors.activeEditor(); } @@ -52,8 +53,8 @@ export class ExtHostEditors implements ExtHostEditorsShape { showTextDocument(document: vscode.TextDocument, column: vscode.ViewColumn, preserveFocus: boolean): Promise; showTextDocument(document: vscode.TextDocument, options: { column: vscode.ViewColumn, preserveFocus: boolean, pinned: boolean }): Promise; - showTextDocument(document: vscode.TextDocument, columnOrOptions: vscode.ViewColumn | vscode.TextDocumentShowOptions, preserveFocus?: boolean): Promise; - showTextDocument(document: vscode.TextDocument, columnOrOptions: vscode.ViewColumn | vscode.TextDocumentShowOptions, preserveFocus?: boolean): Promise { + showTextDocument(document: vscode.TextDocument, columnOrOptions: vscode.ViewColumn | vscode.TextDocumentShowOptions | undefined, preserveFocus?: boolean): Promise; + showTextDocument(document: vscode.TextDocument, columnOrOptions: vscode.ViewColumn | vscode.TextDocumentShowOptions | undefined, preserveFocus?: boolean): Promise { let options: ITextDocumentShowOptions; if (typeof columnOrOptions === 'number') { options = { @@ -74,7 +75,7 @@ export class ExtHostEditors implements ExtHostEditorsShape { } return this._proxy.$tryShowTextDocument(document.uri, options).then(id => { - let editor = this._extHostDocumentsAndEditors.getEditor(id); + const editor = id && this._extHostDocumentsAndEditors.getEditor(id); if (editor) { return editor; } else { @@ -96,6 +97,9 @@ export class ExtHostEditors implements ExtHostEditorsShape { $acceptEditorPropertiesChanged(id: string, data: IEditorPropertiesChangeData): void { const textEditor = this._extHostDocumentsAndEditors.getEditor(id); + if (!textEditor) { + throw new Error('unknown text editor'); + } // (1) set all properties if (data.options) { @@ -106,7 +110,7 @@ export class ExtHostEditors implements ExtHostEditorsShape { textEditor._acceptSelections(selections); } if (data.visibleRanges) { - const visibleRanges = data.visibleRanges.map(TypeConverters.Range.to); + const visibleRanges = arrays.coalesce(data.visibleRanges.map(TypeConverters.Range.to)); textEditor._acceptVisibleRanges(visibleRanges); } @@ -127,7 +131,7 @@ export class ExtHostEditors implements ExtHostEditorsShape { }); } if (data.visibleRanges) { - const visibleRanges = data.visibleRanges.map(TypeConverters.Range.to); + const visibleRanges = arrays.coalesce(data.visibleRanges.map(TypeConverters.Range.to)); this._onDidChangeTextEditorVisibleRanges.fire({ textEditor, visibleRanges @@ -136,9 +140,12 @@ export class ExtHostEditors implements ExtHostEditorsShape { } $acceptEditorPositionData(data: ITextEditorPositionData): void { - for (let id in data) { - let textEditor = this._extHostDocumentsAndEditors.getEditor(id); - let viewColumn = TypeConverters.ViewColumn.to(data[id]); + for (const id in data) { + const textEditor = this._extHostDocumentsAndEditors.getEditor(id); + if (!textEditor) { + throw new Error('Unknown text editor'); + } + const viewColumn = TypeConverters.ViewColumn.to(data[id]); if (textEditor.viewColumn !== viewColumn) { textEditor._acceptViewColumn(viewColumn); this._onDidChangeTextEditorViewColumn.fire({ textEditor, viewColumn }); diff --git a/src/vs/workbench/api/node/extHostTreeViews.ts b/src/vs/workbench/api/node/extHostTreeViews.ts index 1212ac8840..2424714286 100644 --- a/src/vs/workbench/api/node/extHostTreeViews.ts +++ b/src/vs/workbench/api/node/extHostTreeViews.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import * as vscode from 'vscode'; -import { basename } from 'vs/base/common/paths'; +import { basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -17,15 +17,15 @@ import { TreeItemCollapsibleState, ThemeIcon, MarkdownString } from 'vs/workbenc import { isUndefinedOrNull, isString } from 'vs/base/common/types'; import { equals, coalesce } from 'vs/base/common/arrays'; import { ILogService } from 'vs/platform/log/common/log'; -import { IExtensionDescription, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; -import * as typeConvert from 'vs/workbench/api/node/extHostTypeConverters'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; // {{SQL CARBON EDIT}} import * as azdata from 'azdata'; import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views'; export type TreeItemHandle = string; -function toTreeItemLabel(label: any, extension: IExtensionDescription): ITreeItemLabel { +function toTreeItemLabel(label: any, extension: IExtensionDescription): ITreeItemLabel | undefined { if (isString(label)) { return { label }; } @@ -34,7 +34,7 @@ function toTreeItemLabel(label: any, extension: IExtensionDescription): ITreeIte && typeof label === 'object' && typeof label.label === 'string') { checkProposedApiEnabled(extension); - let highlights: [number, number][] = undefined; + let highlights: [number, number][] | undefined = undefined; if (Array.isArray(label.highlights)) { highlights = (<[number, number][]>label.highlights).filter((highlight => highlight.length === 2 && typeof highlight[0] === 'number' && typeof highlight[1] === 'number')); highlights = highlights.length ? highlights : undefined; @@ -139,11 +139,14 @@ export class ExtHostTreeViews implements ExtHostTreeViewsShape { } } +type Root = null | undefined; +type TreeData = { message: boolean, element: T | Root | false }; + // {{SQL CARBON EDIT}} export interface TreeNode { item: ITreeItem; - parent: TreeNode; - children: TreeNode[]; + parent: TreeNode | Root; + children?: TreeNode[]; } // {{SQL CARBON EDIT}} @@ -163,7 +166,7 @@ export class ExtHostTreeView extends Disposable { get visible(): boolean { return this._visible; } private _selectedHandles: TreeItemHandle[] = []; - get selectedElements(): T[] { return this._selectedHandles.map(handle => this.getExtensionElement(handle)).filter(element => !isUndefinedOrNull(element)); } + get selectedElements(): T[] { return this._selectedHandles.map(handle => this.getExtensionElement(handle)).filter(element => !isUndefinedOrNull(element)); } private _onDidExpandElement: Emitter> = this._register(new Emitter>()); readonly onDidExpandElement: Event> = this._onDidExpandElement.event; @@ -177,7 +180,9 @@ export class ExtHostTreeView extends Disposable { private _onDidChangeVisibility: Emitter = this._register(new Emitter()); readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; - private refreshPromise: Promise = Promise.resolve(null); + private _onDidChangeData: Emitter> = this._register(new Emitter>()); + + private refreshPromise: Promise = Promise.resolve(); constructor(private viewId: string, options: vscode.TreeViewOptions, private proxy: MainThreadTreeViewsShape, private commands: CommandsConverter, private logService: ILogService, private extension: IExtensionDescription) { super(); @@ -187,23 +192,39 @@ export class ExtHostTreeView extends Disposable { this.proxy.$registerTreeViewDataProvider(viewId, { showCollapseAll: !!options.showCollapseAll }); } if (this.dataProvider.onDidChangeTreeData) { - let refreshingPromise, promiseCallback; - this._register(Event.debounce(this.dataProvider.onDidChangeTreeData, (last, current) => { + this._register(this.dataProvider.onDidChangeTreeData(element => this._onDidChangeData.fire({ message: false, element }))); + } + + let refreshingPromise, promiseCallback; + this._register(Event.debounce, { message: boolean, elements: (T | Root)[] }>(this._onDidChangeData.event, (result, current) => { + if (!result) { + result = { message: false, elements: [] }; + } + if (current.element !== false) { if (!refreshingPromise) { // New refresh has started refreshingPromise = new Promise(c => promiseCallback = c); this.refreshPromise = this.refreshPromise.then(() => refreshingPromise); } - return last ? [...last, current] : [current]; - }, 200)(elements => { + result.elements.push(current.element); + } + if (current.message) { + result.message = true; + } + return result; + }, 200)(({ message, elements }) => { + if (elements.length) { const _promiseCallback = promiseCallback; refreshingPromise = null; this.refresh(elements).then(() => _promiseCallback()); - })); - } + } + if (message) { + this.proxy.$setMessage(this.viewId, this._message); + } + })); } - getChildren(parentHandle?: TreeItemHandle): Promise { + getChildren(parentHandle: TreeItemHandle | Root): Promise { const parentElement = parentHandle ? this.getExtensionElement(parentHandle) : undefined; if (parentHandle && !parentElement) { console.error(`No tree item with id \'${parentHandle}\' found.`); @@ -215,7 +236,7 @@ export class ExtHostTreeView extends Disposable { .then(nodes => nodes.map(n => n.item)); } - getExtensionElement(treeItemHandle: TreeItemHandle): T { + getExtensionElement(treeItemHandle: TreeItemHandle): T | undefined { return this.elements.get(treeItemHandle); } @@ -241,7 +262,7 @@ export class ExtHostTreeView extends Disposable { set message(message: string | MarkdownString) { this._message = message; - this.proxy.$setMessage(this.viewId, typeConvert.MarkdownString.fromStrict(this._message)); + this._onDidChangeData.fire({ message: true, element: false }); } setExpanded(treeItemHandle: TreeItemHandle, expanded: boolean): void { @@ -285,12 +306,12 @@ export class ExtHostTreeView extends Disposable { }); } - private resolveParent(element: T): Promise { + private resolveParent(element: T): Promise { const node = this.nodes.get(element); if (node) { - return Promise.resolve(node.parent ? this.elements.get(node.parent.item.handle) : null); + return Promise.resolve(node.parent ? this.elements.get(node.parent.item.handle) : undefined); } - return asPromise(() => this.dataProvider.getParent(element)); + return asPromise(() => this.dataProvider.getParent!(element)); } // {{SQL CARBON EDIT}} @@ -301,7 +322,7 @@ export class ExtHostTreeView extends Disposable { } return asPromise(() => this.dataProvider.getTreeItem(element)) .then(extTreeItem => this.createHandle(element, extTreeItem, parent, true)) - .then(handle => this.getChildren(parent ? parent.item.handle : null) + .then(handle => this.getChildren(parent ? parent.item.handle : undefined) .then(() => { const cachedElement = this.getExtensionElement(handle); if (cachedElement) { @@ -314,16 +335,16 @@ export class ExtHostTreeView extends Disposable { })); } - private getChildrenNodes(parentNodeOrHandle?: TreeNode | TreeItemHandle): TreeNode[] { + private getChildrenNodes(parentNodeOrHandle: TreeNode | TreeItemHandle | Root): TreeNode[] | null { if (parentNodeOrHandle) { - let parentNode: TreeNode; + let parentNode: TreeNode | undefined; if (typeof parentNodeOrHandle === 'string') { const parentElement = this.getExtensionElement(parentNodeOrHandle); - parentNode = parentElement ? this.nodes.get(parentElement) : null; + parentNode = parentElement ? this.nodes.get(parentElement) : undefined; } else { parentNode = parentNodeOrHandle; } - return parentNode ? parentNode.children : null; + return parentNode ? parentNode.children || null : null; } return this.roots; } @@ -341,13 +362,13 @@ export class ExtHostTreeView extends Disposable { .then(coalesce); } - private refresh(elements: T[]): Promise { + private refresh(elements: (T | Root)[]): Promise { const hasRoot = elements.some(element => !element); if (hasRoot) { this.clearAll(); // clear cache return this.proxy.$refresh(this.viewId); } else { - const handlesToRefresh = this.getHandlesToRefresh(elements); + const handlesToRefresh = this.getHandlesToRefresh(elements); if (handlesToRefresh.length) { return this.refreshHandles(handlesToRefresh); } @@ -359,15 +380,15 @@ export class ExtHostTreeView extends Disposable { protected getHandlesToRefresh(elements: T[]): TreeItemHandle[] { const elementsToUpdate = new Set(); for (const element of elements) { - let elementNode = this.nodes.get(element); + const elementNode = this.nodes.get(element); if (elementNode && !elementsToUpdate.has(elementNode.item.handle)) { // check if an ancestor of extElement is already in the elements to update list - let currentNode = elementNode; + let currentNode: TreeNode | undefined = elementNode; while (currentNode && currentNode.parent && !elementsToUpdate.has(currentNode.parent.item.handle)) { const parentElement = this.elements.get(currentNode.parent.item.handle); - currentNode = this.nodes.get(parentElement); + currentNode = parentElement ? this.nodes.get(parentElement) : undefined; } - if (!currentNode.parent) { + if (currentNode && !currentNode.parent) { elementsToUpdate.add(elementNode.item.handle); } } @@ -377,9 +398,11 @@ export class ExtHostTreeView extends Disposable { // Take only top level elements elementsToUpdate.forEach((handle) => { const element = this.elements.get(handle); - let node = this.nodes.get(element); - if (node && (!node.parent || !elementsToUpdate.has(node.parent.item.handle))) { - handlesToUpdate.push(handle); + if (element) { + const node = this.nodes.get(element); + if (node && (!node.parent || !elementsToUpdate.has(node.parent.item.handle))) { + handlesToUpdate.push(handle); + } } }); @@ -396,26 +419,31 @@ export class ExtHostTreeView extends Disposable { itemsToRefresh[treeItemHandle] = node.item; } }))) - .then(() => Object.keys(itemsToRefresh).length ? this.proxy.$refresh(this.viewId, itemsToRefresh) : null); + .then(() => Object.keys(itemsToRefresh).length ? this.proxy.$refresh(this.viewId, itemsToRefresh) : undefined); } // {{SQL CARBON EDIT}} - protected refreshNode(treeItemHandle: TreeItemHandle): Promise { + protected refreshNode(treeItemHandle: TreeItemHandle): Promise { const extElement = this.getExtensionElement(treeItemHandle); - const existing = this.nodes.get(extElement); - this.clearChildren(extElement); // clear children cache - return asPromise(() => this.dataProvider.getTreeItem(extElement)) - .then(extTreeItem => { - if (extTreeItem) { - const newNode = this.createTreeNode(extElement, extTreeItem, existing.parent); - this.updateNodeCache(extElement, newNode, existing, existing.parent); - return newNode; - } - return null; - }); + if (extElement) { + const existing = this.nodes.get(extElement); + if (existing) { + this.clearChildren(extElement); // clear children cache + return asPromise(() => this.dataProvider.getTreeItem(extElement)) + .then(extTreeItem => { + if (extTreeItem) { + const newNode = this.createTreeNode(extElement, extTreeItem, existing.parent); + this.updateNodeCache(extElement, newNode, existing, existing.parent); + return newNode; + } + return null; + }); + } + } + return Promise.resolve(null); } - private createAndRegisterTreeNode(element: T, extTreeItem: vscode.TreeItem, parentNode: TreeNode): TreeNode { + private createAndRegisterTreeNode(element: T, extTreeItem: vscode.TreeItem, parentNode: TreeNode | Root): TreeNode { const node = this.createTreeNode(element, extTreeItem, parentNode); if (extTreeItem.id && this.elements.has(node.item.handle)) { throw new Error(localize('treeView.duplicateElement', 'Element with id {0} is already registered', extTreeItem.id)); @@ -426,7 +454,7 @@ export class ExtHostTreeView extends Disposable { } // {{SQL CARBON EDIT}} - protected createTreeNode(element: T, extensionTreeItem: vscode.TreeItem, parent: TreeNode): TreeNode { + protected createTreeNode(element: T, extensionTreeItem: vscode.TreeItem, parent: TreeNode | Root): TreeNode { return { item: this.createTreeItem(element, extensionTreeItem, parent), parent, @@ -435,7 +463,7 @@ export class ExtHostTreeView extends Disposable { } // {{SQL CARBON EDIT}} - protected createTreeItem(element: T, extensionTreeItem: azdata.TreeItem, parent?: TreeNode): sqlITreeItem { + protected createTreeItem(element: T, extensionTreeItem: azdata.TreeItem, parent: TreeNode | Root): ITreeItem { const handle = this.createHandle(element, extensionTreeItem, parent); const icon = this.getLightIconPath(extensionTreeItem); @@ -460,16 +488,16 @@ export class ExtHostTreeView extends Disposable { return item; } - private createHandle(element: T, { id, label, resourceUri }: vscode.TreeItem, parent: TreeNode, returnFirst?: boolean): TreeItemHandle { + private createHandle(element: T, { id, label, resourceUri }: vscode.TreeItem, parent: TreeNode | Root, returnFirst?: boolean): TreeItemHandle { if (id) { return `${ExtHostTreeView.ID_HANDLE_PREFIX}/${id}`; } const treeItemLabel = toTreeItemLabel(label, this.extension); const prefix: string = parent ? parent.item.handle : ExtHostTreeView.LABEL_HANDLE_PREFIX; - let elementId = treeItemLabel ? treeItemLabel.label : resourceUri ? basename(resourceUri.path) : ''; + let elementId = treeItemLabel ? treeItemLabel.label : resourceUri ? basename(resourceUri) : ''; elementId = elementId.indexOf('/') !== -1 ? elementId.replace('/', '//') : elementId; - const existingHandle = this.nodes.has(element) ? this.nodes.get(element).item.handle : undefined; + const existingHandle = this.nodes.has(element) ? this.nodes.get(element)!.item.handle : undefined; const childrenNodes = (this.getChildrenNodes(parent) || []); let handle: TreeItemHandle; @@ -488,7 +516,7 @@ export class ExtHostTreeView extends Disposable { return handle; } - private getLightIconPath(extensionTreeItem: vscode.TreeItem): URI { + private getLightIconPath(extensionTreeItem: vscode.TreeItem): URI | undefined { if (extensionTreeItem.iconPath && !(extensionTreeItem.iconPath instanceof ThemeIcon)) { if (typeof extensionTreeItem.iconPath === 'string' || extensionTreeItem.iconPath instanceof URI) { @@ -499,7 +527,7 @@ export class ExtHostTreeView extends Disposable { return undefined; } - private getDarkIconPath(extensionTreeItem: vscode.TreeItem): URI { + private getDarkIconPath(extensionTreeItem: vscode.TreeItem): URI | undefined { if (extensionTreeItem.iconPath && !(extensionTreeItem.iconPath instanceof ThemeIcon) && extensionTreeItem.iconPath['dark']) { return this.getIconPath(extensionTreeItem.iconPath['dark']); } @@ -519,7 +547,7 @@ export class ExtHostTreeView extends Disposable { } // {{SQL CARBON EDIT}} - protected updateNodeCache(element: T, newNode: TreeNode, existing: TreeNode, parentNode: TreeNode): void { + protected updateNodeCache(element: T, newNode: TreeNode, existing: TreeNode, parentNode: TreeNode | Root): void { // Remove from the cache this.elements.delete(newNode.item.handle); this.nodes.delete(element); @@ -538,7 +566,7 @@ export class ExtHostTreeView extends Disposable { } } - private addNodeToParentCache(node: TreeNode, parentNode: TreeNode): void { + private addNodeToParentCache(node: TreeNode, parentNode: TreeNode | Root): void { if (parentNode) { if (!parentNode.children) { parentNode.children = []; @@ -554,7 +582,26 @@ export class ExtHostTreeView extends Disposable { private clearChildren(parentElement?: T): void { if (parentElement) { - let node = this.nodes.get(parentElement); + const node = this.nodes.get(parentElement); + if (node) { + if (node.children) { + for (const child of node.children) { + const childEleement = this.elements.get(child.item.handle); + if (childEleement) { + this.clear(childEleement); + } + } + } + node.children = undefined; + } + } else { + this.clearAll(); + } + } + + private clear(element: T): void { + const node = this.nodes.get(element); + if (node) { if (node.children) { for (const child of node.children) { const childEleement = this.elements.get(child.item.handle); @@ -563,26 +610,11 @@ export class ExtHostTreeView extends Disposable { } } } - node.children = undefined; - } else { - this.clearAll(); + this.nodes.delete(element); + this.elements.delete(node.item.handle); } } - private clear(element: T): void { - let node = this.nodes.get(element); - if (node.children) { - for (const child of node.children) { - const childEleement = this.elements.get(child.item.handle); - if (childEleement) { - this.clear(childEleement); - } - } - } - this.nodes.delete(element); - this.elements.delete(node.item.handle); - } - // {{SQL CARBON EDIT}} protected clearAll(): void { this.roots = null; diff --git a/src/vs/workbench/api/node/extHostTypeConverters.ts b/src/vs/workbench/api/node/extHostTypeConverters.ts index 00bb96073f..a4f7d7dda7 100644 --- a/src/vs/workbench/api/node/extHostTypeConverters.ts +++ b/src/vs/workbench/api/node/extHostTypeConverters.ts @@ -5,7 +5,7 @@ import * as modes from 'vs/editor/common/modes'; import * as types from './extHostTypes'; -import * as search from 'vs/workbench/parts/search/common/search'; +import * as search from 'vs/workbench/contrib/search/common/search'; import { ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { EditorViewColumn } from 'vs/workbench/api/shared/editor'; import { IDecorationOptions, IThemeDecorationRenderOptions, IDecorationRenderOptions, IContentDecorationRenderOptions } from 'vs/editor/common/editorCommon'; @@ -28,6 +28,7 @@ import * as marked from 'vs/base/common/marked/marked'; import { parse } from 'vs/base/common/marshalling'; import { cloneAndChange } from 'vs/base/common/objects'; import { LogLevel as _MainLogLevel } from 'vs/platform/log/common/log'; +import { coalesce } from 'vs/base/common/arrays'; export interface PositionLike { line: number; @@ -64,7 +65,10 @@ export namespace Selection { } export namespace Range { - export function from(range: RangeLike): IRange { + export function from(range: undefined): undefined; + export function from(range: RangeLike): IRange; + export function from(range: RangeLike | undefined): IRange | undefined; + export function from(range: RangeLike | undefined): IRange | undefined { if (!range) { return undefined; } @@ -77,7 +81,10 @@ export namespace Range { }; } - export function to(range: IRange): types.Range { + export function to(range: undefined): types.Range; + export function to(range: IRange): types.Range; + export function to(range: IRange | undefined): types.Range | undefined; + export function to(range: IRange | undefined): types.Range | undefined { if (!range) { return undefined; } @@ -96,7 +103,7 @@ export namespace Position { } export namespace DiagnosticTag { - export function from(value: vscode.DiagnosticTag): MarkerTag { + export function from(value: vscode.DiagnosticTag): MarkerTag | undefined { switch (value) { case types.DiagnosticTag.Unnecessary: return MarkerTag.Unnecessary; @@ -114,7 +121,7 @@ export namespace Diagnostic { code: isString(value.code) || isNumber(value.code) ? String(value.code) : undefined, severity: DiagnosticSeverity.from(value.severity), relatedInformation: value.relatedInformation && value.relatedInformation.map(DiagnosticRelatedInformation.from), - tags: Array.isArray(value.tags) ? value.tags.map(DiagnosticTag.from) : undefined, + tags: Array.isArray(value.tags) ? coalesce(value.tags.map(DiagnosticTag.from)) : undefined, }; } } @@ -175,12 +182,12 @@ export namespace ViewColumn { return ACTIVE_GROUP; // default is always the active group } - export function to(position?: EditorViewColumn): vscode.ViewColumn { + export function to(position: EditorViewColumn): vscode.ViewColumn { if (typeof position === 'number' && position >= 0) { return position + 1; // adjust to index (ViewColumn.ONE => 1) } - return undefined; + throw new Error(`invalid 'EditorViewColumn'`); } } @@ -226,13 +233,15 @@ export namespace MarkdownString { } // extract uris into a separate object - res.uris = Object.create(null); - let renderer = new marked.Renderer(); + const resUris: { [href: string]: UriComponents } = Object.create(null); + res.uris = resUris; + + const renderer = new marked.Renderer(); renderer.image = renderer.link = (href: string): string => { try { let uri = URI.parse(href, true); - uri = uri.with({ query: _uriMassage(uri.query, res.uris) }); - res.uris[href] = uri; + uri = uri.with({ query: _uriMassage(uri.query, resUris) }); + resUris[href] = uri; } catch (e) { // ignore } @@ -258,7 +267,7 @@ export namespace MarkdownString { } data = cloneAndChange(data, value => { if (value instanceof URI) { - let key = `__uri_${Math.random().toString(16).slice(2, 8)}`; + const key = `__uri_${Math.random().toString(16).slice(2, 8)}`; bucket[key] = value; return key; } else { @@ -284,10 +293,12 @@ export namespace MarkdownString { export function fromRangeOrRangeWithMessage(ranges: vscode.Range[] | vscode.DecorationOptions[]): IDecorationOptions[] { if (isDecorationOptionsArr(ranges)) { - return ranges.map(r => { + return ranges.map((r): IDecorationOptions => { return { range: Range.from(r.range), - hoverMessage: Array.isArray(r.hoverMessage) ? MarkdownString.fromMany(r.hoverMessage) : r.hoverMessage && MarkdownString.from(r.hoverMessage), + hoverMessage: Array.isArray(r.hoverMessage) + ? MarkdownString.fromMany(r.hoverMessage) + : (r.hoverMessage ? MarkdownString.from(r.hoverMessage) : undefined), renderOptions: /* URI vs Uri */r.renderOptions }; }); @@ -300,7 +311,7 @@ export function fromRangeOrRangeWithMessage(ranges: vscode.Range[] | vscode.Deco } } -function pathOrURIToURI(value: string | URI): URI { +export function pathOrURIToURI(value: string | URI): URI { if (typeof value === 'undefined') { return value; } @@ -318,7 +329,7 @@ export namespace ThemableDecorationAttachmentRenderOptions { } return { contentText: options.contentText, - contentIconPath: pathOrURIToURI(options.contentIconPath), + contentIconPath: options.contentIconPath ? pathOrURIToURI(options.contentIconPath) : undefined, border: options.border, borderColor: options.borderColor, fontStyle: options.fontStyle, @@ -357,11 +368,11 @@ export namespace ThemableDecorationRenderOptions { color: options.color, opacity: options.opacity, letterSpacing: options.letterSpacing, - gutterIconPath: pathOrURIToURI(options.gutterIconPath), + gutterIconPath: options.gutterIconPath ? pathOrURIToURI(options.gutterIconPath) : undefined, gutterIconSize: options.gutterIconSize, overviewRulerColor: options.overviewRulerColor, - before: ThemableDecorationAttachmentRenderOptions.from(options.before), - after: ThemableDecorationAttachmentRenderOptions.from(options.after), + before: options.before ? ThemableDecorationAttachmentRenderOptions.from(options.before) : undefined, + after: options.after ? ThemableDecorationAttachmentRenderOptions.from(options.after) : undefined, }; } } @@ -388,10 +399,10 @@ export namespace DecorationRenderOptions { export function from(options: vscode.DecorationRenderOptions): IDecorationRenderOptions { return { isWholeLine: options.isWholeLine, - rangeBehavior: DecorationRangeBehavior.from(options.rangeBehavior), + rangeBehavior: options.rangeBehavior ? DecorationRangeBehavior.from(options.rangeBehavior) : undefined, overviewRulerLane: options.overviewRulerLane, - light: ThemableDecorationRenderOptions.from(options.light), - dark: ThemableDecorationRenderOptions.from(options.dark), + light: options.light ? ThemableDecorationRenderOptions.from(options.light) : undefined, + dark: options.dark ? ThemableDecorationRenderOptions.from(options.dark) : undefined, backgroundColor: options.backgroundColor, outline: options.outline, @@ -411,11 +422,11 @@ export namespace DecorationRenderOptions { color: options.color, opacity: options.opacity, letterSpacing: options.letterSpacing, - gutterIconPath: pathOrURIToURI(options.gutterIconPath), + gutterIconPath: options.gutterIconPath ? pathOrURIToURI(options.gutterIconPath) : undefined, gutterIconSize: options.gutterIconSize, overviewRulerColor: options.overviewRulerColor, - before: ThemableDecorationAttachmentRenderOptions.from(options.before), - after: ThemableDecorationAttachmentRenderOptions.from(options.after), + before: options.before ? ThemableDecorationAttachmentRenderOptions.from(options.before) : undefined, + after: options.after ? ThemableDecorationAttachmentRenderOptions.from(options.after) : undefined, }; } } @@ -432,7 +443,7 @@ export namespace TextEdit { export function to(edit: modes.TextEdit): types.TextEdit { const result = new types.TextEdit(Range.to(edit.range), edit.text); - result.newEol = EndOfLine.to(edit.eol); + result.newEol = (typeof edit.eol === 'undefined' ? undefined : EndOfLine.to(edit.eol))!; return result; } } @@ -446,7 +457,7 @@ export namespace WorkspaceEdit { const [uri, uriOrEdits] = entry; if (Array.isArray(uriOrEdits)) { // text edits - const doc = documents ? documents.getDocument(uri.toString()) : undefined; + const doc = documents && uri ? documents.getDocument(uri) : undefined; result.edits.push({ resource: uri, modelVersionId: doc && doc.version, edits: uriOrEdits.map(TextEdit.from) }); } else { // resource edits @@ -648,7 +659,7 @@ export namespace CompletionContext { export namespace CompletionItemKind { - export function from(kind: types.CompletionItemKind): modes.CompletionItemKind { + export function from(kind: types.CompletionItemKind | undefined): modes.CompletionItemKind { switch (kind) { case types.CompletionItemKind.Method: return modes.CompletionItemKind.Method; case types.CompletionItemKind.Function: return modes.CompletionItemKind.Function; @@ -724,9 +735,9 @@ export namespace CompletionItem { result.preselect = suggestion.preselect; result.commitCharacters = suggestion.commitCharacters; result.range = Range.to(suggestion.range); - result.keepWhitespace = Boolean(suggestion.insertTextRules & modes.CompletionItemInsertTextRule.KeepWhitespace); + result.keepWhitespace = typeof suggestion.insertTextRules === 'undefined' ? false : Boolean(suggestion.insertTextRules & modes.CompletionItemInsertTextRule.KeepWhitespace); // 'inserText'-logic - if (suggestion.insertTextRules & modes.CompletionItemInsertTextRule.InsertAsSnippet) { + if (typeof suggestion.insertTextRules !== 'undefined' && suggestion.insertTextRules & modes.CompletionItemInsertTextRule.InsertAsSnippet) { result.insertText = new types.SnippetString(suggestion.insertText); } else { result.insertText = suggestion.insertText; @@ -742,7 +753,7 @@ export namespace ParameterInformation { export function from(info: types.ParameterInformation): modes.ParameterInformation { return { label: info.label, - documentation: MarkdownString.fromStrict(info.documentation) + documentation: info.documentation ? MarkdownString.fromStrict(info.documentation) : undefined }; } export function to(info: modes.ParameterInformation): types.ParameterInformation { @@ -758,7 +769,7 @@ export namespace SignatureInformation { export function from(info: types.SignatureInformation): modes.SignatureInformation { return { label: info.label, - documentation: MarkdownString.fromStrict(info.documentation), + documentation: info.documentation ? MarkdownString.fromStrict(info.documentation) : undefined, parameters: info.parameters && info.parameters.map(ParameterInformation.from) }; } @@ -796,12 +807,20 @@ export namespace DocumentLink { export function from(link: vscode.DocumentLink): modes.ILink { return { range: Range.from(link.range), - url: link.target && link.target.toString() + url: link.target }; } export function to(link: modes.ILink): vscode.DocumentLink { - return new types.DocumentLink(Range.to(link.range), link.url && URI.parse(link.url)); + let target: URI | undefined = undefined; + if (link.url) { + try { + target = typeof link.url === 'string' ? URI.parse(link.url, true) : URI.revive(link.url); + } catch (err) { + // ignore + } + } + return new types.DocumentLink(Range.to(link.range), target); } } @@ -835,27 +854,17 @@ export namespace Color { } } -export namespace SelectionRangeKind { - - export function from(kind: vscode.SelectionRangeKind): string { - return kind.value; - } - - export function to(value: string): vscode.SelectionRangeKind { - return new types.SelectionRangeKind(value); - } -} export namespace SelectionRange { export function from(obj: vscode.SelectionRange): modes.SelectionRange { return { - kind: SelectionRangeKind.from(obj.kind), + kind: '', range: Range.from(obj.range) }; } export function to(obj: modes.SelectionRange): vscode.SelectionRange { - return new types.SelectionRange(Range.to(obj.range), SelectionRangeKind.to(obj.kind)); + return new types.SelectionRange(Range.to(obj.range)); } } @@ -877,7 +886,7 @@ export namespace TextDocumentSaveReason { export namespace EndOfLine { - export function from(eol: vscode.EndOfLine): EndOfLineSequence { + export function from(eol: vscode.EndOfLine): EndOfLineSequence | undefined { if (eol === types.EndOfLine.CRLF) { return EndOfLineSequence.CRLF; } else if (eol === types.EndOfLine.LF) { @@ -886,7 +895,7 @@ export namespace EndOfLine { return undefined; } - export function to(eol: EndOfLineSequence): vscode.EndOfLine { + export function to(eol: EndOfLineSequence): vscode.EndOfLine | undefined { if (eol === EndOfLineSequence.CRLF) { return types.EndOfLine.CRLF; } else if (eol === EndOfLineSequence.LF) { @@ -903,7 +912,7 @@ export namespace ProgressLocation { case types.ProgressLocation.Window: return MainProgressLocation.Window; case types.ProgressLocation.Notification: return MainProgressLocation.Notification; } - return undefined; + throw new Error(`Unknown 'ProgressLocation'`); } } @@ -935,7 +944,7 @@ export namespace FoldingRangeKind { export namespace TextEditorOptions { - export function from(options?: vscode.TextDocumentShowOptions): ITextEditorOptions { + export function from(options?: vscode.TextDocumentShowOptions): ITextEditorOptions | undefined { if (options) { return { pinned: typeof options.preview === 'boolean' ? !options.preview : undefined, @@ -951,7 +960,10 @@ export namespace TextEditorOptions { export namespace GlobPattern { - export function from(pattern: vscode.GlobPattern): string | types.RelativePattern { + export function from(pattern: vscode.GlobPattern): string | types.RelativePattern; + export function from(pattern: undefined | null): undefined | null; + export function from(pattern: vscode.GlobPattern | undefined | null): string | types.RelativePattern | undefined | null; + export function from(pattern: vscode.GlobPattern | undefined | null): string | types.RelativePattern | undefined | null { if (pattern instanceof types.RelativePattern) { return pattern; } @@ -975,7 +987,10 @@ export namespace GlobPattern { export namespace LanguageSelector { - export function from(selector: vscode.DocumentSelector): languageSelector.LanguageSelector { + export function from(selector: undefined): undefined; + export function from(selector: vscode.DocumentSelector): languageSelector.LanguageSelector; + export function from(selector: vscode.DocumentSelector | undefined): languageSelector.LanguageSelector | undefined; + export function from(selector: vscode.DocumentSelector | undefined): languageSelector.LanguageSelector | undefined { if (!selector) { return undefined; } else if (Array.isArray(selector)) { @@ -986,7 +1001,7 @@ export namespace LanguageSelector { return { language: selector.language, scheme: selector.scheme, - pattern: GlobPattern.from(selector.pattern), + pattern: typeof selector.pattern === 'undefined' ? undefined : GlobPattern.from(selector.pattern), exclusive: selector.exclusive }; } diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index bed67834d7..61f8bbaf51 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as crypto from 'crypto'; -import { relative } from 'path'; import { coalesce, equals } from 'vs/base/common/arrays'; import { illegalArgument } from 'vs/base/common/errors'; import { IRelativePattern } from 'vs/base/common/glob'; @@ -16,6 +15,18 @@ import { generateUuid } from 'vs/base/common/uuid'; import * as vscode from 'vscode'; +function es5ClassCompat(target: Function): any { + ///@ts-ignore + function _() { return Reflect.construct(target, arguments, this.constructor); } + Object.defineProperty(_, 'name', Object.getOwnPropertyDescriptor(target, 'name')!); + ///@ts-ignore + Object.setPrototypeOf(_, target); + ///@ts-ignore + Object.setPrototypeOf(_.prototype, target.prototype); + return _; +} + +@es5ClassCompat export class Disposable { static from(...inDisposables: { dispose(): any }[]): Disposable { @@ -46,6 +57,7 @@ export class Disposable { } } +@es5ClassCompat export class Position { static Min(...positions: Position[]): Position { @@ -54,7 +66,7 @@ export class Position { } let result = positions[0]; for (let i = 1; i < positions.length; i++) { - let p = positions[i]; + const p = positions[i]; if (p.isBefore(result!)) { result = p; } @@ -68,7 +80,7 @@ export class Position { } let result = positions[0]; for (let i = 1; i < positions.length; i++) { - let p = positions[i]; + const p = positions[i]; if (p.isAfter(result!)) { result = p; } @@ -217,6 +229,7 @@ export class Position { } } +@es5ClassCompat export class Range { static isRange(thing: any): thing is vscode.Range { @@ -290,8 +303,8 @@ export class Range { } intersection(other: Range): Range | undefined { - let start = Position.Max(other.start, this._start); - let end = Position.Min(other.end, this._end); + const start = Position.Max(other.start, this._start); + const end = Position.Min(other.end, this._end); if (start.isAfter(end)) { // this happens when there is no overlap: // |-----| @@ -307,8 +320,8 @@ export class Range { } else if (other.contains(this)) { return other; } - let start = Position.Min(other.start, this._start); - let end = Position.Max(other.end, this.end); + const start = Position.Min(other.start, this._start); + const end = Position.Max(other.end, this.end); return new Range(start, end); } @@ -351,6 +364,7 @@ export class Range { } } +@es5ClassCompat export class Selection extends Range { static isSelection(thing: any): thing is Selection { @@ -416,11 +430,30 @@ export class Selection extends Range { } } +export class ResolvedAuthority { + readonly host: string; + readonly port: number; + debugListenPort?: number; + debugConnectPort?: number; + + constructor(host: string, port: number) { + if (typeof host !== 'string' || host.length === 0) { + throw illegalArgument('host'); + } + if (typeof port !== 'number' || port === 0 || Math.round(port) !== port) { + throw illegalArgument('port'); + } + this.host = host; + this.port = Math.round(port); + } +} + export enum EndOfLine { LF = 1, CRLF = 2 } +@es5ClassCompat export class TextEdit { static isTextEdit(thing: any): thing is TextEdit { @@ -447,13 +480,13 @@ export class TextEdit { } static setEndOfLine(eol: EndOfLine): TextEdit { - let ret = new TextEdit(new Range(new Position(0, 0), new Position(0, 0)), ''); + const ret = new TextEdit(new Range(new Position(0, 0), new Position(0, 0)), ''); ret.newEol = eol; return ret; } protected _range: Range; - protected _newText: string; + protected _newText: string | null; protected _newEol: EndOfLine; get range(): Range { @@ -489,9 +522,9 @@ export class TextEdit { this._newEol = value; } - constructor(range: Range, newText: string) { + constructor(range: Range, newText: string | null) { this.range = range; - this.newText = newText; + this._newText = newText; } toJSON(): any { @@ -524,6 +557,7 @@ export interface IFileTextEdit { edit: TextEdit; } +@es5ClassCompat export class WorkspaceEdit implements vscode.WorkspaceEdit { private _edits = new Array(); @@ -582,7 +616,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { } get(uri: URI): TextEdit[] { - let res: TextEdit[] = []; + const res: TextEdit[] = []; for (let candidate of this._edits) { if (candidate._type === 2 && candidate.uri.toString() === uri.toString()) { res.push(candidate.edit); @@ -592,7 +626,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { } entries(): [URI, TextEdit[]][] { - let textEdits = new Map(); + const textEdits = new Map(); for (let candidate of this._edits) { if (candidate._type === 2) { let textEdit = textEdits.get(candidate.uri.toString()); @@ -607,7 +641,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { } _allEntries(): ([URI, TextEdit[]] | [URI?, URI?, IFileOperationOptions?])[] { - let res: ([URI, TextEdit[]] | [URI?, URI?, IFileOperationOptions?])[] = []; + const res: ([URI, TextEdit[]] | [URI?, URI?, IFileOperationOptions?])[] = []; for (let edit of this._edits) { if (edit._type === 1) { res.push([edit.from, edit.to, edit.options]); @@ -627,6 +661,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { } } +@es5ClassCompat export class SnippetString { static isSnippetString(thing: any): thing is SnippetString { @@ -720,6 +755,7 @@ export enum DiagnosticSeverity { Error = 0 } +@es5ClassCompat export class Location { static isLocation(thing: any): thing is Location { @@ -758,6 +794,7 @@ export class Location { } } +@es5ClassCompat export class DiagnosticRelatedInformation { static is(thing: any): thing is DiagnosticRelatedInformation { @@ -791,6 +828,7 @@ export class DiagnosticRelatedInformation { } } +@es5ClassCompat export class Diagnostic { range: Range; @@ -835,6 +873,7 @@ export class Diagnostic { } } +@es5ClassCompat export class Hover { public contents: vscode.MarkdownString[] | vscode.MarkedString[]; @@ -864,6 +903,7 @@ export enum DocumentHighlightKind { Write = 2 } +@es5ClassCompat export class DocumentHighlight { range: Range; @@ -911,6 +951,7 @@ export enum SymbolKind { TypeParameter = 25 } +@es5ClassCompat export class SymbolInformation { static validate(candidate: SymbolInformation): void { @@ -924,9 +965,9 @@ export class SymbolInformation { kind: SymbolKind; containerName: string | undefined; - constructor(name: string, kind: SymbolKind, containerName: string, location: Location); + constructor(name: string, kind: SymbolKind, containerName: string | undefined, location: Location); constructor(name: string, kind: SymbolKind, range: Range, uri?: URI, containerName?: string); - constructor(name: string, kind: SymbolKind, rangeOrContainer: string | Range, locationOrUri?: Location | URI, containerName?: string) { + constructor(name: string, kind: SymbolKind, rangeOrContainer: string | undefined | Range, locationOrUri?: Location | URI, containerName?: string) { this.name = name; this.kind = kind; this.containerName = containerName; @@ -954,6 +995,7 @@ export class SymbolInformation { } } +@es5ClassCompat export class DocumentSymbol { static validate(candidate: DocumentSymbol): void { @@ -993,6 +1035,7 @@ export enum CodeActionTrigger { Manual = 2, } +@es5ClassCompat export class CodeAction { title: string; @@ -1011,18 +1054,19 @@ export class CodeAction { } +@es5ClassCompat export class CodeActionKind { private static readonly sep = '.'; - public static readonly Empty = new CodeActionKind(''); - public static readonly QuickFix = CodeActionKind.Empty.append('quickfix'); - public static readonly Refactor = CodeActionKind.Empty.append('refactor'); - public static readonly RefactorExtract = CodeActionKind.Refactor.append('extract'); - public static readonly RefactorInline = CodeActionKind.Refactor.append('inline'); - public static readonly RefactorRewrite = CodeActionKind.Refactor.append('rewrite'); - public static readonly Source = CodeActionKind.Empty.append('source'); - public static readonly SourceOrganizeImports = CodeActionKind.Source.append('organizeImports'); - public static readonly SourceFixAll = CodeActionKind.Source.append('fixAll'); + public static Empty; + public static QuickFix; + public static Refactor; + public static RefactorExtract; + public static RefactorInline; + public static RefactorRewrite; + public static Source; + public static SourceOrganizeImports; + public static SourceFixAll; constructor( public readonly value: string @@ -1040,38 +1084,34 @@ export class CodeActionKind { return this.value === other.value || startsWith(other.value, this.value + CodeActionKind.sep); } } +CodeActionKind.Empty = new CodeActionKind(''); +CodeActionKind.QuickFix = CodeActionKind.Empty.append('quickfix'); +CodeActionKind.Refactor = CodeActionKind.Empty.append('refactor'); +CodeActionKind.RefactorExtract = CodeActionKind.Refactor.append('extract'); +CodeActionKind.RefactorInline = CodeActionKind.Refactor.append('inline'); +CodeActionKind.RefactorRewrite = CodeActionKind.Refactor.append('rewrite'); +CodeActionKind.Source = CodeActionKind.Empty.append('source'); +CodeActionKind.SourceOrganizeImports = CodeActionKind.Source.append('organizeImports'); +CodeActionKind.SourceFixAll = CodeActionKind.Source.append('fixAll'); -export class SelectionRangeKind { - - private static readonly _sep = '.'; - - static readonly Empty = new SelectionRangeKind(''); - static readonly Statement = SelectionRangeKind.Empty.append('statement'); - static readonly Declaration = SelectionRangeKind.Empty.append('declaration'); - - readonly value: string; - - constructor(value: string) { - this.value = value; - } - - append(value: string): SelectionRangeKind { - return new SelectionRangeKind(this.value ? this.value + SelectionRangeKind._sep + value : value); - } -} - +@es5ClassCompat export class SelectionRange { - kind: SelectionRangeKind; range: Range; + parent?: SelectionRange; - constructor(range: Range, kind: SelectionRangeKind, ) { + constructor(range: Range, parent?: SelectionRange) { this.range = range; - this.kind = kind; + this.parent = parent; + + if (parent && !parent.range.contains(this.range)) { + throw new Error('Invalid argument: parent must contain this range'); + } } } +@es5ClassCompat export class CodeLens { range: Range; @@ -1088,6 +1128,20 @@ export class CodeLens { } } + +export class CodeInset { + + range: Range; + height?: number; + + constructor(range: Range, height?: number) { + this.range = range; + this.height = height; + } +} + + +@es5ClassCompat export class MarkdownString { value: string; @@ -1118,6 +1172,7 @@ export class MarkdownString { } } +@es5ClassCompat export class ParameterInformation { label: string | [number, number]; @@ -1129,6 +1184,7 @@ export class ParameterInformation { } } +@es5ClassCompat export class SignatureInformation { label: string; @@ -1142,6 +1198,7 @@ export class SignatureInformation { } } +@es5ClassCompat export class SignatureHelp { signatures: SignatureInformation[]; @@ -1166,8 +1223,8 @@ export enum CompletionTriggerKind { } export interface CompletionContext { - triggerKind: CompletionTriggerKind; - triggerCharacter: string; + readonly triggerKind: CompletionTriggerKind; + readonly triggerCharacter?: string; } export enum CompletionItemKind { @@ -1198,19 +1255,20 @@ export enum CompletionItemKind { TypeParameter = 24 } +@es5ClassCompat export class CompletionItem implements vscode.CompletionItem { label: string; kind: CompletionItemKind | undefined; - detail: string; - documentation: string | MarkdownString; - sortText: string; - filterText: string; - preselect: boolean; + detail?: string; + documentation?: string | MarkdownString; + sortText?: string; + filterText?: string; + preselect?: boolean; insertText: string | SnippetString; keepWhitespace?: boolean; range: Range; - commitCharacters: string[]; + commitCharacters?: string[]; textEdit: TextEdit; additionalTextEdits: TextEdit[]; command: vscode.Command; @@ -1235,6 +1293,7 @@ export class CompletionItem implements vscode.CompletionItem { } } +@es5ClassCompat export class CompletionList { isIncomplete?: boolean; @@ -1314,7 +1373,7 @@ export enum DecorationRangeBehavior { } export namespace TextEditorSelectionChangeKind { - export function fromValue(s: string) { + export function fromValue(s: string | undefined) { switch (s) { case 'keyboard': return TextEditorSelectionChangeKind.Keyboard; case 'mouse': return TextEditorSelectionChangeKind.Mouse; @@ -1324,13 +1383,14 @@ export namespace TextEditorSelectionChangeKind { } } +@es5ClassCompat export class DocumentLink { range: Range; - target: URI; + target?: URI; - constructor(range: Range, target: URI) { + constructor(range: Range, target: URI | undefined) { if (target && !(target instanceof URI)) { throw illegalArgument('target'); } @@ -1342,6 +1402,7 @@ export class DocumentLink { } } +@es5ClassCompat export class Color { readonly red: number; readonly green: number; @@ -1358,6 +1419,7 @@ export class Color { export type IColorFormat = string | { opaque: string, transparent: string }; +@es5ClassCompat export class ColorInformation { range: Range; @@ -1375,6 +1437,7 @@ export class ColorInformation { } } +@es5ClassCompat export class ColorPresentation { label: string; textEdit?: TextEdit; @@ -1416,6 +1479,7 @@ export enum TaskPanelKind { New = 3 } +@es5ClassCompat export class TaskGroup implements vscode.TaskGroup { private _id: string; @@ -1458,6 +1522,7 @@ export class TaskGroup implements vscode.TaskGroup { } } +@es5ClassCompat export class ProcessExecution implements vscode.ProcessExecution { private _process: string; @@ -1530,6 +1595,7 @@ export class ProcessExecution implements vscode.ProcessExecution { } } +@es5ClassCompat export class ShellExecution implements vscode.ShellExecution { private _commandLine: string; @@ -1626,8 +1692,33 @@ export enum TaskScope { Workspace = 2 } -export class Task implements vscode.Task { +export class CustomExecution implements vscode.CustomExecution { + private _callback: (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable; + constructor(callback: (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable) { + this._callback = callback; + } + + public computeId(): string { + const hash = crypto.createHash('md5'); + hash.update('customExecution'); + hash.update(generateUuid()); + return hash.digest('hex'); + } + + public set callback(value: (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable) { + this._callback = value; + } + + public get callback(): (args: vscode.TerminalRenderer, cancellationToken: vscode.CancellationToken) => Thenable { + return this._callback; + } +} + +@es5ClassCompat +export class Task implements vscode.Task2 { + + private static ExtensionCallbackType: string = 'customExecution'; private static ProcessType: string = 'process'; private static ShellType: string = 'shell'; private static EmptyType: string = '$empty'; @@ -1637,7 +1728,7 @@ export class Task implements vscode.Task { private _definition: vscode.TaskDefinition; private _scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder | undefined; private _name: string; - private _execution: ProcessExecution | ShellExecution | undefined; + private _execution: ProcessExecution | ShellExecution | CustomExecution | undefined; private _problemMatchers: string[]; private _hasDefinedMatchers: boolean; private _isBackground: boolean; @@ -1646,8 +1737,8 @@ export class Task implements vscode.Task { private _presentationOptions: vscode.TaskPresentationOptions; private _runOptions: vscode.RunOptions; - constructor(definition: vscode.TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution, problemMatchers?: string | string[]); - constructor(definition: vscode.TaskDefinition, scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder, name: string, source: string, execution?: ProcessExecution | ShellExecution, problemMatchers?: string | string[]); + constructor(definition: vscode.TaskDefinition, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution, problemMatchers?: string | string[]); + constructor(definition: vscode.TaskDefinition, scope: vscode.TaskScope.Global | vscode.TaskScope.Workspace | vscode.WorkspaceFolder, name: string, source: string, execution?: ProcessExecution | ShellExecution | CustomExecution, problemMatchers?: string | string[]); constructor(definition: vscode.TaskDefinition, arg2: string | (vscode.TaskScope.Global | vscode.TaskScope.Workspace) | vscode.WorkspaceFolder, arg3: any, arg4?: any, arg5?: any, arg6?: any) { this.definition = definition; let problemMatchers: string | string[]; @@ -1712,6 +1803,11 @@ export class Task implements vscode.Task { type: Task.ShellType, id: this._execution.computeId() }; + } else if (this._execution instanceof CustomExecution) { + this._definition = { + type: Task.ExtensionCallbackType, + id: this._execution.computeId() + }; } else { this._definition = { type: Task.EmptyType, @@ -1754,17 +1850,25 @@ export class Task implements vscode.Task { } get execution(): ProcessExecution | ShellExecution | undefined { - return this._execution; + return (this._execution instanceof CustomExecution) ? undefined : this._execution; } set execution(value: ProcessExecution | ShellExecution | undefined) { + this.execution2 = value; + } + + get execution2(): ProcessExecution | ShellExecution | CustomExecution | undefined { + return this._execution; + } + + set execution2(value: ProcessExecution | ShellExecution | CustomExecution | undefined) { if (value === null) { value = undefined; } this.clear(); this._execution = value; - let type = this._definition.type; - if (Task.EmptyType === type || Task.ProcessType === type || Task.ShellType === type) { + const type = this._definition.type; + if (Task.EmptyType === type || Task.ProcessType === type || Task.ShellType === type || Task.ExtensionCallbackType === type) { this.computeDefinitionBasedOnExecution(); } } @@ -1858,6 +1962,7 @@ export enum ProgressLocation { Notification = 15 } +@es5ClassCompat export class TreeItem { label?: string | vscode.TreeItemLabel; @@ -1885,18 +1990,23 @@ export enum TreeItemCollapsibleState { Expanded = 2 } +@es5ClassCompat export class ThemeIcon { - static readonly File = new ThemeIcon('file'); - static readonly Folder = new ThemeIcon('folder'); + static File: ThemeIcon; + static Folder: ThemeIcon; readonly id: string; - private constructor(id: string) { + constructor(id: string) { this.id = id; } } +ThemeIcon.File = new ThemeIcon('file'); +ThemeIcon.Folder = new ThemeIcon('folder'); + +@es5ClassCompat export class ThemeColor { id: string; constructor(id: string) { @@ -1912,6 +2022,7 @@ export enum ConfigurationTarget { WorkspaceFolder = 3 } +@es5ClassCompat export class RelativePattern implements IRelativePattern { base: string; baseFolder?: URI; @@ -1938,12 +2049,9 @@ export class RelativePattern implements IRelativePattern { this.pattern = pattern; } - - public pathToRelative(from: string, to: string): string { - return relative(from, to); - } } +@es5ClassCompat export class Breakpoint { private _id: string | undefined; @@ -1974,6 +2082,7 @@ export class Breakpoint { } } +@es5ClassCompat export class SourceBreakpoint extends Breakpoint { readonly location: Location; @@ -1986,6 +2095,7 @@ export class SourceBreakpoint extends Breakpoint { } } +@es5ClassCompat export class FunctionBreakpoint extends Breakpoint { readonly functionName: string; @@ -1998,7 +2108,7 @@ export class FunctionBreakpoint extends Breakpoint { } } -// {{SQL CARBON EDIT}} +@es5ClassCompat export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable { readonly command: string; readonly args: string[]; @@ -2011,6 +2121,7 @@ export class DebugAdapterExecutable implements vscode.DebugAdapterExecutable { } } +@es5ClassCompat export class DebugAdapterServer implements vscode.DebugAdapterServer { readonly port: number; readonly host?: string; @@ -2023,6 +2134,7 @@ export class DebugAdapterServer implements vscode.DebugAdapterServer { // {{SQL CARBON EDIT}} /* +@es5ClassCompat export class DebugAdapterImplementation implements vscode.DebugAdapterImplementation { readonly implementation: any; @@ -2050,6 +2162,7 @@ export enum FileChangeType { Deleted = 3, } +@es5ClassCompat export class FileSystemError extends Error { static FileExists(messageOrUri?: string | URI): FileSystemError { @@ -2092,6 +2205,7 @@ export class FileSystemError extends Error { //#region folding api +@es5ClassCompat export class FoldingRange { start: number; @@ -2127,6 +2241,7 @@ export enum CommentThreadCollapsibleState { Expanded = 1 } +@es5ClassCompat export class QuickInputButtons { static readonly Back: vscode.QuickInputButton = { iconPath: 'back.svg' }; diff --git a/src/vs/workbench/api/node/extHostWebview.ts b/src/vs/workbench/api/node/extHostWebview.ts index 70ddb3b920..9f85e1c0d1 100644 --- a/src/vs/workbench/api/node/extHostWebview.ts +++ b/src/vs/workbench/api/node/extHostWebview.ts @@ -8,14 +8,14 @@ import { URI } from 'vs/base/common/uri'; import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters'; import { EditorViewColumn } from 'vs/workbench/api/shared/editor'; import * as vscode from 'vscode'; -import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewState } from './extHost.protocol'; +import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewPanelHandle, WebviewPanelViewState, WebviewInsetHandle } from './extHost.protocol'; import { Disposable } from './extHostTypes'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; type IconPath = URI | { light: URI, dark: URI }; export class ExtHostWebview implements vscode.Webview { - private readonly _handle: WebviewPanelHandle; + private readonly _handle: WebviewPanelHandle | WebviewInsetHandle; private readonly _proxy: MainThreadWebviewsShape; private _html: string; private _options: vscode.WebviewOptions; @@ -25,7 +25,7 @@ export class ExtHostWebview implements vscode.Webview { public readonly onDidReceiveMessage: Event = this._onMessageEmitter.event; constructor( - handle: WebviewPanelHandle, + handle: WebviewPanelHandle | WebviewInsetHandle, proxy: MainThreadWebviewsShape, options: vscode.WebviewOptions ) { @@ -80,12 +80,12 @@ export class ExtHostWebviewPanel implements vscode.WebviewPanel { private readonly _proxy: MainThreadWebviewsShape; private readonly _viewType: string; private _title: string; - private _iconPath: IconPath; + private _iconPath?: IconPath; private readonly _options: vscode.WebviewPanelOptions; private readonly _webview: ExtHostWebview; private _isDisposed: boolean = false; - private _viewColumn: vscode.ViewColumn; + private _viewColumn: vscode.ViewColumn | undefined; private _visible: boolean = true; private _active: boolean = true; @@ -101,7 +101,7 @@ export class ExtHostWebviewPanel implements vscode.WebviewPanel { proxy: MainThreadWebviewsShape, viewType: string, title: string, - viewColumn: vscode.ViewColumn, + viewColumn: vscode.ViewColumn | undefined, editorOptions: vscode.WebviewPanelOptions, webview: ExtHostWebview ) { @@ -173,7 +173,7 @@ export class ExtHostWebviewPanel implements vscode.WebviewPanel { get viewColumn(): vscode.ViewColumn | undefined { this.assertNotDisposed(); - if (this._viewColumn < 0) { + if (typeof this._viewColumn === 'number' && this._viewColumn < 0) { // We are using a symbolic view column // Return undefined instead to indicate that the real view column is currently unknown but will be resolved. return undefined; @@ -243,7 +243,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { this._proxy = mainContext.getProxy(MainContext.MainThreadWebviews); } - public createWebview( + public createWebviewPanel( extension: IExtensionDescription, viewType: string, title: string, @@ -333,7 +333,7 @@ export class ExtHostWebviews implements ExtHostWebviewsShape { } const webview = new ExtHostWebview(webviewHandle, this._proxy, options); - const revivedPanel = new ExtHostWebviewPanel(webviewHandle, this._proxy, viewType, title, typeConverters.ViewColumn.to(position), options, webview); + const revivedPanel = new ExtHostWebviewPanel(webviewHandle, this._proxy, viewType, title, typeof position === 'number' && position >= 0 ? typeConverters.ViewColumn.to(position) : undefined, options, webview); this._webviewPanels.set(webviewHandle, revivedPanel); return Promise.resolve(serializer.deserializeWebviewPanel(revivedPanel, state)); } diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index c187c25798..23d61a67ff 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -3,28 +3,34 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { join, relative } from 'path'; +import { join } from 'vs/base/common/path'; import { delta as arrayDelta, mapArrayOrNot } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { TernarySearchTree } from 'vs/base/common/map'; import { Counter } from 'vs/base/common/numbers'; -import { normalize } from 'vs/base/common/paths'; import { isLinux } from 'vs/base/common/platform'; -import { basenameOrAuthority, dirname, isEqual } from 'vs/base/common/resources'; +import { basenameOrAuthority, dirname, isEqual, relativePath } from 'vs/base/common/resources'; import { compare } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { ILogService } from 'vs/platform/log/common/log'; import { Severity } from 'vs/platform/notification/common/notification'; -import { IRawFileMatch2, resultIsMatch } from 'vs/platform/search/common/search'; +import { IRawFileMatch2, resultIsMatch } from 'vs/workbench/services/search/common/search'; import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Range, RelativePattern } from 'vs/workbench/api/node/extHostTypes'; -import { ITextQueryBuilderOptions } from 'vs/workbench/parts/search/common/queryBuilder'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; import * as vscode from 'vscode'; -import { ExtHostWorkspaceShape, IMainContext, IWorkspaceData, MainContext, MainThreadMessageServiceShape, MainThreadWorkspaceShape } from './extHost.protocol'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtHostWorkspaceShape, IWorkspaceData, MainThreadMessageServiceShape, MainThreadWorkspaceShape, IMainContext, MainContext, IStaticWorkspaceData } from './extHost.protocol'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { Barrier } from 'vs/base/common/async'; + +export interface IExtHostWorkspaceProvider { + getWorkspaceFolder2(uri: vscode.Uri, resolveParent?: boolean): Promise; + resolveWorkspaceFolder(uri: vscode.Uri): Promise; + getWorkspaceFolders2(): Promise; + resolveProxy(url: string): Promise; +} function isFolderEqual(folderA: URI, folderB: URI): boolean { return isEqual(folderA, folderB, !isLinux); @@ -56,7 +62,7 @@ interface MutableWorkspaceFolder extends vscode.WorkspaceFolder { class ExtHostWorkspaceImpl extends Workspace { - static toExtHostWorkspace(data: IWorkspaceData, previousConfirmedWorkspace?: ExtHostWorkspaceImpl, previousUnconfirmedWorkspace?: ExtHostWorkspaceImpl): { workspace: ExtHostWorkspaceImpl, added: vscode.WorkspaceFolder[], removed: vscode.WorkspaceFolder[] } { + static toExtHostWorkspace(data: IWorkspaceData | null, previousConfirmedWorkspace?: ExtHostWorkspaceImpl, previousUnconfirmedWorkspace?: ExtHostWorkspaceImpl): { workspace: ExtHostWorkspaceImpl | null, added: vscode.WorkspaceFolder[], removed: vscode.WorkspaceFolder[] } { if (!data) { return { workspace: null, added: [], removed: [] }; } @@ -68,7 +74,7 @@ class ExtHostWorkspaceImpl extends Workspace { // data and update their properties. It could be that an extension stored them // for later use and we want to keep them "live" if they are still present. const oldWorkspace = previousConfirmedWorkspace; - if (oldWorkspace) { + if (previousConfirmedWorkspace) { folders.forEach((folderData, index) => { const folderUri = URI.revive(folderData.uri); const existingFolder = ExtHostWorkspaceImpl._findFolder(previousUnconfirmedWorkspace || previousConfirmedWorkspace, folderUri); @@ -95,7 +101,7 @@ class ExtHostWorkspaceImpl extends Workspace { return { workspace, added, removed }; } - private static _findFolder(workspace: ExtHostWorkspaceImpl, folderUriToFind: URI): MutableWorkspaceFolder { + private static _findFolder(workspace: ExtHostWorkspaceImpl, folderUriToFind: URI): MutableWorkspaceFolder | undefined { for (let i = 0; i < workspace.folders.length; i++) { const folder = workspace.workspaceFolders[i]; if (isFolderEqual(folder.uri, folderUriToFind)) { @@ -109,7 +115,7 @@ class ExtHostWorkspaceImpl extends Workspace { private readonly _workspaceFolders: vscode.WorkspaceFolder[] = []; private readonly _structure = TernarySearchTree.forPaths(); - private constructor(id: string, private _name: string, folders: vscode.WorkspaceFolder[]) { + constructor(id: string, private _name: string, folders: vscode.WorkspaceFolder[]) { super(id, folders.map(f => new WorkspaceFolder(f))); // setup the workspace folder data structure @@ -127,7 +133,7 @@ class ExtHostWorkspaceImpl extends Workspace { return this._workspaceFolders.slice(0); } - getWorkspaceFolder(uri: URI, resolveParent?: boolean): vscode.WorkspaceFolder { + getWorkspaceFolder(uri: URI, resolveParent?: boolean): vscode.WorkspaceFolder | undefined { if (resolveParent && this._structure.get(uri.toString())) { // `uri` is a workspace folder so we check for its parent uri = dirname(uri); @@ -135,51 +141,75 @@ class ExtHostWorkspaceImpl extends Workspace { return this._structure.findSubstr(uri.toString()); } - resolveWorkspaceFolder(uri: URI): vscode.WorkspaceFolder { + resolveWorkspaceFolder(uri: URI): vscode.WorkspaceFolder | undefined { return this._structure.get(uri.toString()); } } -export class ExtHostWorkspace implements ExtHostWorkspaceShape { +export class ExtHostWorkspace implements ExtHostWorkspaceShape, IExtHostWorkspaceProvider { private readonly _onDidChangeWorkspace = new Emitter(); - private readonly _proxy: MainThreadWorkspaceShape; - - private _confirmedWorkspace: ExtHostWorkspaceImpl; - private _unconfirmedWorkspace: ExtHostWorkspaceImpl; - - private _messageService: MainThreadMessageServiceShape; - readonly onDidChangeWorkspace: Event = this._onDidChangeWorkspace.event; + private readonly _logService: ILogService; + private readonly _requestIdProvider: Counter; + private readonly _barrier: Barrier; + + private _confirmedWorkspace?: ExtHostWorkspaceImpl; + private _unconfirmedWorkspace?: ExtHostWorkspaceImpl; + + private readonly _proxy: MainThreadWorkspaceShape; + private readonly _messageService: MainThreadMessageServiceShape; + private readonly _activeSearchCallbacks: ((match: IRawFileMatch2) => any)[] = []; constructor( mainContext: IMainContext, - data: IWorkspaceData, - private _logService: ILogService, - private _requestIdProvider: Counter + logService: ILogService, + requestIdProvider: Counter, + data?: IStaticWorkspaceData ) { + this._logService = logService; + this._requestIdProvider = requestIdProvider; + this._barrier = new Barrier(); + this._proxy = mainContext.getProxy(MainContext.MainThreadWorkspace); this._messageService = mainContext.getProxy(MainContext.MainThreadMessageService); - this._confirmedWorkspace = ExtHostWorkspaceImpl.toExtHostWorkspace(data).workspace; + this._confirmedWorkspace = data ? new ExtHostWorkspaceImpl(data.id, data.name, []) : undefined; + } + + $initializeWorkspace(data: IWorkspaceData): void { + this.$acceptWorkspaceData(data); + this._barrier.open(); + } + + waitForInitializeCall(): Promise { + return this._barrier.wait(); } // --- workspace --- - get workspace(): Workspace { + get workspace(): Workspace | undefined { return this._actualWorkspace; } - get name(): string { + get name(): string | undefined { return this._actualWorkspace ? this._actualWorkspace.name : undefined; } - private get _actualWorkspace(): ExtHostWorkspaceImpl { + private get _actualWorkspace(): ExtHostWorkspaceImpl | undefined { return this._unconfirmedWorkspace || this._confirmedWorkspace; } - getWorkspaceFolders(): vscode.WorkspaceFolder[] { + getWorkspaceFolders(): vscode.WorkspaceFolder[] | undefined { + if (!this._actualWorkspace) { + return undefined; + } + return this._actualWorkspace.workspaceFolders.slice(0); + } + + async getWorkspaceFolders2(): Promise { + await this._barrier.wait(); if (!this._actualWorkspace) { return undefined; } @@ -215,7 +245,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { // Simulate the updateWorkspaceFolders method on our data to do more validation const newWorkspaceFolders = currentWorkspaceFolders.slice(0); - newWorkspaceFolders.splice(index, deleteCount, ...validatedDistinctWorkspaceFoldersToAdd.map(f => ({ uri: f.uri, name: f.name || basenameOrAuthority(f.uri), index: undefined }))); + newWorkspaceFolders.splice(index, deleteCount, ...validatedDistinctWorkspaceFoldersToAdd.map(f => ({ uri: f.uri, name: f.name || basenameOrAuthority(f.uri), index: undefined! /* fixed later */ }))); for (let i = 0; i < newWorkspaceFolders.length; i++) { const folder = newWorkspaceFolders[i]; @@ -240,7 +270,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { this._unconfirmedWorkspace = undefined; // show error to user - this._messageService.$showMessage(Severity.Error, localize('updateerror', "Extension '{0}' failed to update workspace folders: {1}", extName, error.toString()), { extension }, []); + this._messageService.$showMessage(Severity.Error, localize('updateerror', "Extension '{0}' failed to update workspace folders: {1}", extName, error), { extension }, []); }); } @@ -250,21 +280,30 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { return true; } - getWorkspaceFolder(uri: vscode.Uri, resolveParent?: boolean): vscode.WorkspaceFolder { + getWorkspaceFolder(uri: vscode.Uri, resolveParent?: boolean): vscode.WorkspaceFolder | undefined { if (!this._actualWorkspace) { return undefined; } return this._actualWorkspace.getWorkspaceFolder(uri, resolveParent); } - resolveWorkspaceFolder(uri: vscode.Uri): vscode.WorkspaceFolder { + async getWorkspaceFolder2(uri: vscode.Uri, resolveParent?: boolean): Promise { + await this._barrier.wait(); + if (!this._actualWorkspace) { + return undefined; + } + return this._actualWorkspace.getWorkspaceFolder(uri, resolveParent); + } + + async resolveWorkspaceFolder(uri: vscode.Uri): Promise { + await this._barrier.wait(); if (!this._actualWorkspace) { return undefined; } return this._actualWorkspace.resolveWorkspaceFolder(uri); } - getPath(): string { + getPath(): string | undefined { // this is legacy from the days before having // multi-root and we keep it only alive if there @@ -283,19 +322,22 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { getRelativePath(pathOrUri: string | vscode.Uri, includeWorkspace?: boolean): string { - let path: string; + let resource: URI | undefined; + let path: string = ''; if (typeof pathOrUri === 'string') { + resource = URI.file(pathOrUri); path = pathOrUri; } else if (typeof pathOrUri !== 'undefined') { + resource = pathOrUri; path = pathOrUri.fsPath; } - if (!path) { + if (!resource) { return path; } const folder = this.getWorkspaceFolder( - typeof pathOrUri === 'string' ? URI.file(pathOrUri) : pathOrUri, + resource, true ); @@ -303,15 +345,15 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { return path; } - if (typeof includeWorkspace === 'undefined') { + if (typeof includeWorkspace === 'undefined' && this._actualWorkspace) { includeWorkspace = this._actualWorkspace.folders.length > 1; } - let result = relative(folder.uri.fsPath, path); - if (includeWorkspace) { + let result = relativePath(folder.uri, resource); + if (includeWorkspace && folder.name) { result = `${folder.name}/${result}`; } - return normalize(result, true); + return result!; } private trySetWorkspaceFolders(folders: vscode.WorkspaceFolder[]): void { @@ -324,7 +366,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { name: this._actualWorkspace.name, configuration: this._actualWorkspace.configuration, folders - } as IWorkspaceData, this._actualWorkspace).workspace; + } as IWorkspaceData, this._actualWorkspace).workspace || undefined; } } @@ -334,7 +376,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { // Update our workspace object. We have a confirmed workspace, so we drop our // unconfirmed workspace. - this._confirmedWorkspace = workspace; + this._confirmedWorkspace = workspace || undefined; this._unconfirmedWorkspace = undefined; // Events @@ -346,11 +388,11 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { // --- search --- - findFiles(include: string | RelativePattern, exclude: vscode.GlobPattern, maxResults: number, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { + findFiles(include: string | RelativePattern | undefined | null, exclude: vscode.GlobPattern | undefined | null, maxResults: number | undefined, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { this._logService.trace(`extHostWorkspace#findFiles: fileSearch, extension: ${extensionId.value}, entryPoint: findFiles`); - let includePattern: string; - let includeFolder: URI; + let includePattern: string | undefined; + let includeFolder: URI | undefined; if (include) { if (typeof include === 'string') { includePattern = include; @@ -362,7 +404,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { } } - let excludePatternOrDisregardExcludes: string | false; + let excludePatternOrDisregardExcludes: string | false | undefined = undefined; if (exclude === null) { excludePatternOrDisregardExcludes = false; } else if (exclude) { @@ -381,7 +423,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { .then(data => Array.isArray(data) ? data.map(URI.revive) : []); } - findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, callback: (result: vscode.TextSearchResult) => void, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { + findTextInFiles(query: vscode.TextSearchQuery, options: vscode.FindTextInFilesOptions, callback: (result: vscode.TextSearchResult) => void, extensionId: ExtensionIdentifier, token: vscode.CancellationToken = CancellationToken.None): Promise { this._logService.trace(`extHostWorkspace#findTextInFiles: textSearch, extension: ${extensionId.value}, entryPoint: findTextInFiles`); const requestId = this._requestIdProvider.getNext(); @@ -413,10 +455,10 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { beforeContext: options.beforeContext, includePattern: options.include && globPatternToString(options.include), - excludePattern: options.exclude && globPatternToString(options.exclude) + excludePattern: options.exclude ? globPatternToString(options.exclude) : undefined }; - let isCanceled = false; + const isCanceled = false; this._activeSearchCallbacks[requestId] = p => { if (isCanceled) { @@ -424,7 +466,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { } const uri = URI.revive(p.resource); - p.results.forEach(result => { + p.results!.forEach(result => { if (resultIsMatch(result)) { callback({ uri, @@ -471,7 +513,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { return this._proxy.$saveAll(includeUntitled); } - resolveProxy(url: string): Promise { + resolveProxy(url: string): Promise { return this._proxy.$resolveProxy(url); } } diff --git a/src/vs/workbench/api/shared/editor.ts b/src/vs/workbench/api/shared/editor.ts index 512fc70011..2278940188 100644 --- a/src/vs/workbench/api/shared/editor.ts +++ b/src/vs/workbench/api/shared/editor.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IEditorGroupsService, IEditorGroup, GroupsOrder } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { GroupIdentifier } from 'vs/workbench/common/editor'; import { ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; @@ -31,6 +31,9 @@ export function viewColumnToEditorGroup(editorGroupService: IEditorGroupsService export function editorGroupToViewColumn(editorGroupService: IEditorGroupsService, editorGroup: IEditorGroup | GroupIdentifier): EditorViewColumn { const group = (typeof editorGroup === 'number') ? editorGroupService.getGroup(editorGroup) : editorGroup; + if (!group) { + throw new Error('Invalid group provided'); + } return editorGroupService.getGroups(GroupsOrder.GRID_APPEARANCE).indexOf(group); } \ No newline at end of file diff --git a/src/vs/workbench/api/shared/tasks.ts b/src/vs/workbench/api/shared/tasks.ts index 6dab3e2f9f..4daf44c98a 100644 --- a/src/vs/workbench/api/shared/tasks.ts +++ b/src/vs/workbench/api/shared/tasks.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { UriComponents } from 'vs/base/common/uri'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export interface TaskDefinitionDTO { type: string; @@ -66,6 +66,10 @@ export interface ShellExecutionDTO { options?: ShellExecutionOptionsDTO; } +export interface CustomExecutionDTO { + customExecution: 'customExecution'; +} + export interface TaskSourceDTO { label: string; extensionId?: string; @@ -79,16 +83,16 @@ export interface TaskHandleDTO { export interface TaskDTO { _id: string; - name: string; - execution: ProcessExecutionDTO | ShellExecutionDTO; + name?: string; + execution: ProcessExecutionDTO | ShellExecutionDTO | CustomExecutionDTO | undefined; definition: TaskDefinitionDTO; - isBackground: boolean; + isBackground?: boolean; source: TaskSourceDTO; group?: string; - presentationOptions: TaskPresentationOptionsDTO; + presentationOptions?: TaskPresentationOptionsDTO; problemMatchers: string[]; hasDefinedMatchers: boolean; - runOptions: RunOptionsDTO; + runOptions?: RunOptionsDTO; } export interface TaskSetDTO { @@ -98,7 +102,7 @@ export interface TaskSetDTO { export interface TaskExecutionDTO { id: string; - task: TaskDTO; + task: TaskDTO | undefined; } export interface TaskProcessStartedDTO { diff --git a/src/vs/workbench/browser/actions.ts b/src/vs/workbench/browser/actions.ts index 856affd353..d9875d539b 100644 --- a/src/vs/workbench/browser/actions.ts +++ b/src/vs/workbench/browser/actions.ts @@ -7,7 +7,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Action, IAction } from 'vs/base/common/actions'; import { BaseActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { ITree, IActionProvider } from 'vs/base/parts/tree/browser/tree'; -import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; /** * The action bar contributor allows to add actions to an actionbar in a given context. @@ -65,11 +65,7 @@ export const Scope = { * The ContributableActionProvider leverages the actionbar contribution model to find actions. */ export class ContributableActionProvider implements IActionProvider { - private registry: IActionBarRegistry; - - constructor() { - this.registry = Registry.as(Extensions.Actionbar); - } + private readonly registry: IActionBarRegistry = Registry.as(Extensions.Actionbar); private toContext(tree: ITree, element: any): any { return { @@ -235,16 +231,19 @@ export interface IActionBarRegistry { */ getActionBarContributors(scope: string): ActionBarContributor[]; - setInstantiationService(service: IInstantiationService): void; + /** + * Starts the registry by providing the required services. + */ + start(accessor: ServicesAccessor): void; } class ActionBarRegistry implements IActionBarRegistry { - private actionBarContributorConstructors: { scope: string; ctor: IConstructorSignature0; }[] = []; - private actionBarContributorInstances: { [scope: string]: ActionBarContributor[] } = Object.create(null); + private readonly actionBarContributorConstructors: { scope: string; ctor: IConstructorSignature0; }[] = []; + private readonly actionBarContributorInstances: { [scope: string]: ActionBarContributor[] } = Object.create(null); private instantiationService: IInstantiationService; - setInstantiationService(service: IInstantiationService): void { - this.instantiationService = service; + start(accessor: ServicesAccessor): void { + this.instantiationService = accessor.get(IInstantiationService); while (this.actionBarContributorConstructors.length > 0) { const entry = this.actionBarContributorConstructors.shift()!; diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 6f05b8b025..bdc2cc7011 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -11,15 +11,15 @@ import { Action } from 'vs/base/common/actions'; import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { IPartService, Parts, Position } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { MenuBarVisibility } from 'vs/platform/windows/common/windows'; import { isWindows, isLinux } from 'vs/base/common/platform'; -import { IsMacContext } from 'vs/platform/workbench/common/contextkeys'; +import { IsMacContext } from 'vs/workbench/common/contextkeys'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { InEditorZenModeContext } from 'vs/workbench/common/editor'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -39,16 +39,16 @@ export class ToggleActivityBarVisibilityAction extends Action { constructor( id: string, label: string, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, label); - this.enabled = !!this.partService; + this.enabled = !!this.layoutService; } run(): Promise { - const visibility = this.partService.isVisible(Parts.ACTIVITYBAR_PART); + const visibility = this.layoutService.isVisible(Parts.ACTIVITYBAR_PART); const newVisibilityValue = !visibility; return this.configurationService.updateValue(ToggleActivityBarVisibilityAction.activityBarVisibleKey, newVisibilityValue, ConfigurationTarget.USER); @@ -76,16 +76,16 @@ class ToggleCenteredLayout extends Action { constructor( id: string, label: string, - @IPartService private readonly partService: IPartService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(id, label); - this.enabled = !!this.partService; + this.enabled = !!this.layoutService; } run(): Promise { - this.partService.centerEditorLayout(!this.partService.isEditorLayoutCentered()); + this.layoutService.centerEditorLayout(!this.layoutService.isEditorLayoutCentered()); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -137,7 +137,7 @@ export class ToggleEditorLayoutAction extends Action { const newOrientation = (this.editorGroupService.orientation === GroupOrientation.VERTICAL) ? GroupOrientation.HORIZONTAL : GroupOrientation.VERTICAL; this.editorGroupService.setGroupOrientation(newOrientation); - return Promise.resolve(null); + return Promise.resolve(); } dispose(): void { @@ -153,7 +153,7 @@ CommandsRegistry.registerCommand('_workbench.editor.setGroupOrientation', functi editorGroupService.setGroupOrientation(orientation); - return Promise.resolve(null); + return Promise.resolve(); }); const group = viewCategory; @@ -180,23 +180,23 @@ export class ToggleSidebarPositionAction extends Action { constructor( id: string, label: string, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, label); - this.enabled = !!this.partService && !!this.configurationService; + this.enabled = !!this.layoutService && !!this.configurationService; } run(): Promise { - const position = this.partService.getSideBarPosition(); + const position = this.layoutService.getSideBarPosition(); const newPositionValue = (position === Position.LEFT) ? 'right' : 'left'; return this.configurationService.updateValue(ToggleSidebarPositionAction.sidebarPositionConfigurationKey, newPositionValue, ConfigurationTarget.USER); } - static getLabel(partService: IPartService): string { - return partService.getSideBarPosition() === Position.LEFT ? nls.localize('moveSidebarRight', "Move Side Bar Right") : nls.localize('moveSidebarLeft', "Move Side Bar Left"); + static getLabel(layoutService: IWorkbenchLayoutService): string { + return layoutService.getSideBarPosition() === Position.LEFT ? nls.localize('moveSidebarRight', "Move Side Bar Right") : nls.localize('moveSidebarLeft', "Move Side Bar Left"); } } @@ -220,18 +220,18 @@ export class ToggleEditorVisibilityAction extends Action { constructor( id: string, label: string, - @IPartService private readonly partService: IPartService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(id, label); - this.enabled = !!this.partService; + this.enabled = !!this.layoutService; } run(): Promise { - const hideEditor = this.partService.isVisible(Parts.EDITOR_PART); - this.partService.setEditorHidden(hideEditor); + const hideEditor = this.layoutService.isVisible(Parts.EDITOR_PART); + this.layoutService.setEditorHidden(hideEditor); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -247,18 +247,18 @@ export class ToggleSidebarVisibilityAction extends Action { constructor( id: string, label: string, - @IPartService private readonly partService: IPartService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(id, label); - this.enabled = !!this.partService; + this.enabled = !!this.layoutService; } run(): Promise { - const hideSidebar = this.partService.isVisible(Parts.SIDEBAR_PART); - this.partService.setSideBarHidden(hideSidebar); + const hideSidebar = this.layoutService.isVisible(Parts.SIDEBAR_PART); + this.layoutService.setSideBarHidden(hideSidebar); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -285,16 +285,16 @@ class ToggleStatusbarVisibilityAction extends Action { constructor( id: string, label: string, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, label); - this.enabled = !!this.partService; + this.enabled = !!this.layoutService; } run(): Promise { - const visibility = this.partService.isVisible(Parts.STATUSBAR_PART); + const visibility = this.layoutService.isVisible(Parts.STATUSBAR_PART); const newVisibilityValue = !visibility; return this.configurationService.updateValue(ToggleStatusbarVisibilityAction.statusbarVisibleKey, newVisibilityValue, ConfigurationTarget.USER); @@ -337,7 +337,11 @@ class ToggleTabsVisibilityAction extends Action { } } -registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleTabsVisibilityAction, ToggleTabsVisibilityAction.ID, ToggleTabsVisibilityAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_W }), 'View: Toggle Tab Visibility', viewCategory); +registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleTabsVisibilityAction, ToggleTabsVisibilityAction.ID, ToggleTabsVisibilityAction.LABEL, { + primary: undefined!, + mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_W, }, + linux: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_W, } +}), 'View: Toggle Tab Visibility', viewCategory); // --- Toggle Zen Mode @@ -349,16 +353,16 @@ class ToggleZenMode extends Action { constructor( id: string, label: string, - @IPartService private readonly partService: IPartService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(id, label); - this.enabled = !!this.partService; + this.enabled = !!this.layoutService; } run(): Promise { - this.partService.toggleZenMode(); + this.layoutService.toggleZenMode(); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -377,8 +381,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'workbench.action.exitZenMode', weight: KeybindingWeight.EditorContrib - 1000, handler(accessor: ServicesAccessor) { - const partService = accessor.get(IPartService); - partService.toggleZenMode(); + const layoutService = accessor.get(IWorkbenchLayoutService); + layoutService.toggleZenMode(); }, when: InEditorZenModeContext, primary: KeyChord(KeyCode.Escape, KeyCode.Escape) @@ -443,15 +447,15 @@ export abstract class BaseResizeViewAction extends Action { constructor( id: string, label: string, - @IPartService protected partService: IPartService + @IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService ) { super(id, label); } protected resizePart(sizeChange: number): void { - const isEditorFocus = this.partService.hasFocus(Parts.EDITOR_PART); - const isSidebarFocus = this.partService.hasFocus(Parts.SIDEBAR_PART); - const isPanelFocus = this.partService.hasFocus(Parts.PANEL_PART); + const isEditorFocus = this.layoutService.hasFocus(Parts.EDITOR_PART); + const isSidebarFocus = this.layoutService.hasFocus(Parts.SIDEBAR_PART); + const isPanelFocus = this.layoutService.hasFocus(Parts.PANEL_PART); let part: Parts | undefined; if (isSidebarFocus) { @@ -463,7 +467,7 @@ export abstract class BaseResizeViewAction extends Action { } if (part) { - this.partService.resizePart(part, sizeChange); + this.layoutService.resizePart(part, sizeChange); } } } @@ -476,9 +480,9 @@ export class IncreaseViewSizeAction extends BaseResizeViewAction { constructor( id: string, label: string, - @IPartService partService: IPartService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(id, label, partService); + super(id, label, layoutService); } run(): Promise { @@ -495,10 +499,10 @@ export class DecreaseViewSizeAction extends BaseResizeViewAction { constructor( id: string, label: string, - @IPartService partService: IPartService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(id, label, partService); + super(id, label, layoutService); } run(): Promise { diff --git a/src/vs/workbench/browser/actions/listCommands.ts b/src/vs/workbench/browser/actions/listCommands.ts index d2f3b0f6b5..6534bfb266 100644 --- a/src/vs/workbench/browser/actions/listCommands.ts +++ b/src/vs/workbench/browser/actions/listCommands.ts @@ -18,7 +18,7 @@ import { DataTree } from 'vs/base/browser/ui/tree/dataTree'; import { ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -function ensureDOMFocus(widget: ListWidget): void { +function ensureDOMFocus(widget: ListWidget | undefined): void { // it can happen that one of the commands is executed while // DOM focus is within another focusable control within the // list/tree item. therefor we should ensure that the @@ -88,10 +88,12 @@ function expandMultiSelection(focused: List | PagedList | ITree | Obje const focus = list.getFocus() ? list.getFocus()[0] : undefined; const selection = list.getSelection(); - if (selection && selection.indexOf(focus) >= 0) { + if (selection && typeof focus === 'number' && selection.indexOf(focus) >= 0) { list.setSelection(selection.filter(s => s !== previousFocus)); } else { - list.setSelection(selection.concat(focus)); + if (typeof focus === 'number') { + list.setSelection(selection.concat(focus)); + } } } @@ -581,7 +583,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const list = focused; const fakeKeyboardEvent = getSelectionKeyboardEvent('keydown', false); list.setSelection(list.getFocus(), fakeKeyboardEvent); - list.open(list.getFocus()); + list.open(list.getFocus(), fakeKeyboardEvent); } // Tree @@ -636,7 +638,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const selectedNode = tree.getNode(start); const parentNode = selectedNode.parent; - if (!parentNode.parent) { // root + if (!parentNode || !parentNode.parent) { // root scope = undefined; } else { scope = parentNode.element; @@ -752,7 +754,8 @@ CommandsRegistry.registerCommand({ // List if (focused instanceof List || focused instanceof PagedList) { - // TODO@joao + const list = focused; + list.toggleKeyboardNavigation(); } // ObjectTree diff --git a/src/vs/workbench/browser/actions/navigationActions.ts b/src/vs/workbench/browser/actions/navigationActions.ts index 0b94ce9420..5b08179a85 100644 --- a/src/vs/workbench/browser/actions/navigationActions.ts +++ b/src/vs/workbench/browser/actions/navigationActions.ts @@ -6,9 +6,9 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; import { Action } from 'vs/base/common/actions'; -import { IEditorGroupsService, GroupDirection, GroupLocation, IFindGroupScope } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService, GroupDirection, GroupLocation, IFindGroupScope } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IPartService, Parts, Position as PartPosition } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService, Parts, Position as PartPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { IPanel } from 'vs/workbench/common/panel'; @@ -22,19 +22,19 @@ abstract class BaseNavigationAction extends Action { label: string, @IEditorGroupsService protected editorGroupService: IEditorGroupsService, @IPanelService protected panelService: IPanelService, - @IPartService protected partService: IPartService, + @IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService, @IViewletService protected viewletService: IViewletService ) { super(id, label); } run(): Promise { - const isEditorFocus = this.partService.hasFocus(Parts.EDITOR_PART); - const isPanelFocus = this.partService.hasFocus(Parts.PANEL_PART); - const isSidebarFocus = this.partService.hasFocus(Parts.SIDEBAR_PART); + const isEditorFocus = this.layoutService.hasFocus(Parts.EDITOR_PART); + const isPanelFocus = this.layoutService.hasFocus(Parts.PANEL_PART); + const isSidebarFocus = this.layoutService.hasFocus(Parts.SIDEBAR_PART); - const isSidebarPositionLeft = this.partService.getSideBarPosition() === PartPosition.LEFT; - const isPanelPositionDown = this.partService.getPanelPosition() === PartPosition.BOTTOM; + const isSidebarPositionLeft = this.layoutService.getSideBarPosition() === PartPosition.LEFT; + const isPanelPositionDown = this.layoutService.getPanelPosition() === PartPosition.BOTTOM; if (isEditorFocus) { return this.navigateOnEditorFocus(isSidebarPositionLeft, isPanelPositionDown); @@ -64,21 +64,25 @@ abstract class BaseNavigationAction extends Action { } protected navigateToPanel(): IPanel | boolean { - if (!this.partService.isVisible(Parts.PANEL_PART)) { + if (!this.layoutService.isVisible(Parts.PANEL_PART)) { return false; } - const activePanelId = this.panelService.getActivePanel().getId(); + const activePanelId = this.panelService.getActivePanel()!.getId(); - return this.panelService.openPanel(activePanelId, true); + return this.panelService.openPanel(activePanelId, true)!; } protected navigateToSidebar(): Promise { - if (!this.partService.isVisible(Parts.SIDEBAR_PART)) { + if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) { return Promise.resolve(false); } - const activeViewletId = this.viewletService.getActiveViewlet().getId(); + const activeViewlet = this.viewletService.getActiveViewlet(); + if (!activeViewlet) { + return Promise.resolve(false); + } + const activeViewletId = activeViewlet.getId(); return this.viewletService.openViewlet(activeViewletId, true) .then(value => value === null ? false : value); @@ -114,10 +118,10 @@ class NavigateLeftAction extends BaseNavigationAction { label: string, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IPanelService panelService: IPanelService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IViewletService viewletService: IViewletService ) { - super(id, label, editorGroupService, panelService, partService, viewletService); + super(id, label, editorGroupService, panelService, layoutService, viewletService); } protected navigateOnEditorFocus(isSidebarPositionLeft: boolean, _isPanelPositionDown: boolean): Promise { @@ -164,10 +168,10 @@ class NavigateRightAction extends BaseNavigationAction { label: string, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IPanelService panelService: IPanelService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IViewletService viewletService: IViewletService ) { - super(id, label, editorGroupService, panelService, partService, viewletService); + super(id, label, editorGroupService, panelService, layoutService, viewletService); } protected navigateOnEditorFocus(isSidebarPositionLeft: boolean, isPanelPositionDown: boolean): Promise { @@ -214,10 +218,10 @@ class NavigateUpAction extends BaseNavigationAction { label: string, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IPanelService panelService: IPanelService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IViewletService viewletService: IViewletService ) { - super(id, label, editorGroupService, panelService, partService, viewletService); + super(id, label, editorGroupService, panelService, layoutService, viewletService); } protected navigateOnEditorFocus(_isSidebarPositionLeft: boolean, _isPanelPositionDown: boolean): Promise { @@ -243,10 +247,10 @@ class NavigateDownAction extends BaseNavigationAction { label: string, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IPanelService panelService: IPanelService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IViewletService viewletService: IViewletService ) { - super(id, label, editorGroupService, panelService, partService, viewletService); + super(id, label, editorGroupService, panelService, layoutService, viewletService); } protected navigateOnEditorFocus(_isSidebarPositionLeft: boolean, isPanelPositionDown: boolean): Promise { diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 7984273658..351b3027cc 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -9,13 +9,10 @@ import { IWindowService } from 'vs/platform/windows/common/windows'; import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { WORKSPACE_FILTER, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { mnemonicButtonLabel } from 'vs/base/common/labels'; +import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL, PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; -import { URI } from 'vs/base/common/uri'; -import { Schemas } from 'vs/base/common/network'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -133,15 +130,14 @@ export class SaveWorkspaceAsAction extends Action { id: string, label: string, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService, - @IFileDialogService private readonly dialogService: IFileDialogService + @IWorkspaceEditingService private readonly workspaceEditingService: IWorkspaceEditingService ) { super(id, label); } run(): Promise { - return this.getNewWorkspaceConfigPath().then((configPathUri): Promise | void => { + return this.workspaceEditingService.pickNewWorkspacePath().then((configPathUri): Promise | void => { if (configPathUri) { switch (this.contextService.getWorkbenchState()) { case WorkbenchState.EMPTY: @@ -155,15 +151,6 @@ export class SaveWorkspaceAsAction extends Action { } }); } - - private getNewWorkspaceConfigPath(): Promise { - return this.dialogService.showSaveDialog({ - saveLabel: mnemonicButtonLabel(nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")), - title: nls.localize('saveWorkspace', "Save Workspace"), - filters: WORKSPACE_FILTER, - defaultUri: this.dialogService.defaultWorkspacePath(Schemas.file) - }); - } } export class OpenWorkspaceAction extends Action { @@ -253,10 +240,11 @@ export class DuplicateWorkspaceInNewWindowAction extends Action { run(): Promise { const folders = this.workspaceContextService.getWorkspace().folders; + const remoteAuthority = this.windowService.getConfiguration().remoteAuthority; - return this.workspacesService.createUntitledWorkspace(folders).then(newWorkspace => { + return this.workspacesService.createUntitledWorkspace(folders, remoteAuthority).then(newWorkspace => { return this.workspaceEditingService.copyWorkspaceSettings(newWorkspace).then(() => { - return this.windowService.openWindow([URI.file(newWorkspace.configPath)], { forceNewWindow: true }); + return this.windowService.openWindow([{ uri: newWorkspace.configPath, typeHint: 'file' }], { forceNewWindow: true }); }); }); } diff --git a/src/vs/workbench/browser/actions/workspaceCommands.ts b/src/vs/workbench/browser/actions/workspaceCommands.ts index dfb7556372..88a06912c2 100644 --- a/src/vs/workbench/browser/actions/workspaceCommands.ts +++ b/src/vs/workbench/browser/actions/workspaceCommands.ts @@ -18,7 +18,6 @@ import { IQuickInputService, IPickOptions, IQuickPickItem } from 'vs/platform/qu import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { Schemas } from 'vs/base/common/network'; import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; export const ADD_ROOT_FOLDER_COMMAND_ID = 'addRootFolder'; @@ -35,7 +34,7 @@ CommandsRegistry.registerCommand({ CommandsRegistry.registerCommand({ id: '_files.pickFolderAndOpen', - handler: (accessor: ServicesAccessor, forceNewWindow: boolean) => accessor.get(IFileDialogService).pickFolderAndOpen({ forceNewWindow }) + handler: (accessor: ServicesAccessor, options: { forceNewWindow: boolean }) => accessor.get(IFileDialogService).pickFolderAndOpen(options) }); CommandsRegistry.registerCommand({ @@ -64,7 +63,7 @@ CommandsRegistry.registerCommand({ title: nls.localize('addFolderToWorkspaceTitle', "Add Folder to Workspace"), canSelectFolders: true, canSelectMany: true, - defaultUri: dialogsService.defaultFolderPath(Schemas.file) + defaultUri: dialogsService.defaultFolderPath() }).then((folders): Promise | null => { if (!folders || !folders.length) { return null; @@ -93,7 +92,7 @@ CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, function (acc const folderPicks = folders.map(folder => { return { label: folder.name, - description: labelService.getUriLabel(resources.dirname(folder.uri)!, { relative: true }), + description: labelService.getUriLabel(resources.dirname(folder.uri), { relative: true }), folder, iconClasses: getIconClasses(modelService, modeService, folder.uri, FileKind.ROOT_FOLDER) } as IQuickPickItem; diff --git a/src/vs/workbench/browser/composite.ts b/src/vs/workbench/browser/composite.ts index d205f58b64..23d55d4291 100644 --- a/src/vs/workbench/browser/composite.ts +++ b/src/vs/workbench/browser/composite.ts @@ -38,7 +38,7 @@ export abstract class Composite extends Component implements IComposite { private _onDidFocus: Emitter; get onDidFocus(): Event { if (!this._onDidFocus) { - this._registerFocusTrackEvents(); + this.registerFocusTrackEvents(); } return this._onDidFocus.event; @@ -47,13 +47,13 @@ export abstract class Composite extends Component implements IComposite { private _onDidBlur: Emitter; get onDidBlur(): Event { if (!this._onDidBlur) { - this._registerFocusTrackEvents(); + this.registerFocusTrackEvents(); } return this._onDidBlur.event; } - private _registerFocusTrackEvents(): void { + private registerFocusTrackEvents(): void { this._onDidFocus = this._register(new Emitter()); this._onDidBlur = this._register(new Emitter()); @@ -173,6 +173,13 @@ export abstract class Composite extends Component implements IComposite { return null; } + /** + * Provide a context to be passed to the toolbar. + */ + getActionsContext(): any { + return null; + } + /** * Returns the instance of IActionRunner to use with this composite for the * composite tool bar. @@ -217,11 +224,11 @@ export abstract class CompositeDescriptor { constructor( private readonly ctor: IConstructorSignature0, - public readonly id: string, - public readonly name: string, - public readonly cssClass?: string, - public readonly order?: number, - public readonly keybindingId?: string, + readonly id: string, + readonly name: string, + readonly cssClass?: string, + readonly order?: number, + readonly keybindingId?: string, ) { } instantiate(instantiationService: IInstantiationService): T { diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts new file mode 100644 index 0000000000..06d88947fc --- /dev/null +++ b/src/vs/workbench/browser/contextkeys.ts @@ -0,0 +1,211 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; +import { IWindowConfiguration, IWindowService } from 'vs/platform/windows/common/windows'; +import { ActiveEditorContext, EditorsVisibleContext, TextCompareEditorVisibleContext, TextCompareEditorActiveContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, TEXT_DIFF_EDITOR_ID, SplitEditorsVertically, InEditorZenModeContext } from 'vs/workbench/common/editor'; +import { IsMacContext, IsLinuxContext, IsWindowsContext, HasMacNativeTabsContext, IsDevelopmentContext, SupportsWorkspacesContext, SupportsOpenFileFolderContext, WorkbenchStateContext, WorkspaceFolderCountContext } from 'vs/workbench/common/contextkeys'; +import { trackFocus, addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { preferredSideBySideGroupDirection, GroupDirection, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { SidebarVisibleContext, SideBarVisibleContext } from 'vs/workbench/common/viewlet'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; + +export class WorkbenchContextKeysHandler extends Disposable { + private inputFocusedContext: IContextKey; + + private activeEditorContext: IContextKey; + private editorsVisibleContext: IContextKey; + private textCompareEditorVisibleContext: IContextKey; + private textCompareEditorActiveContext: IContextKey; + private activeEditorGroupEmpty: IContextKey; + private multipleEditorGroupsContext: IContextKey; + private splitEditorsVerticallyContext: IContextKey; + + private workbenchStateContext: IContextKey; + private workspaceFolderCountContext: IContextKey; + + + private inZenModeContext: IContextKey; + + private sideBarVisibleContext: IContextKey; + //TODO@Isidor remove in May + private sidebarVisibleContext: IContextKey; + + constructor( + @IContextKeyService private contextKeyService: IContextKeyService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IConfigurationService private configurationService: IConfigurationService, + @IEnvironmentService private environmentService: IEnvironmentService, + @IWindowService private windowService: IWindowService, + @IEditorService private editorService: IEditorService, + @IEditorGroupsService private editorGroupService: IEditorGroupsService, + @IWorkbenchLayoutService private layoutService: IWorkbenchLayoutService, + @IViewletService private viewletService: IViewletService + ) { + super(); + + this.initContextKeys(); + this.registerListeners(); + } + + private registerListeners(): void { + this.editorGroupService.whenRestored.then(() => this.updateEditorContextKeys()); + + this._register(this.editorService.onDidActiveEditorChange(() => this.updateEditorContextKeys())); + this._register(this.editorService.onDidVisibleEditorsChange(() => this.updateEditorContextKeys())); + this._register(this.editorGroupService.onDidAddGroup(() => this.updateEditorContextKeys())); + this._register(this.editorGroupService.onDidRemoveGroup(() => this.updateEditorContextKeys())); + + this._register(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(), true)); + + this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateWorkbenchStateContextKey())); + this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.updateWorkspaceFolderCountContextKey())); + + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('workbench.editor.openSideBySideDirection')) { + this.updateSplitEditorsVerticallyContext(); + } + })); + + this._register(this.layoutService.onZenModeChange(enabled => this.inZenModeContext.set(enabled))); + + this._register(this.viewletService.onDidViewletClose(() => this.updateSideBarContextKeys())); + this._register(this.viewletService.onDidViewletOpen(() => this.updateSideBarContextKeys())); + } + + private initContextKeys(): void { + + // Platform + IsMacContext.bindTo(this.contextKeyService); + IsLinuxContext.bindTo(this.contextKeyService); + IsWindowsContext.bindTo(this.contextKeyService); + + // macOS Native Tabs + const windowConfig = this.configurationService.getValue(); + HasMacNativeTabsContext.bindTo(this.contextKeyService).set(windowConfig && windowConfig.window && windowConfig.window.nativeTabs); + + // Development + IsDevelopmentContext.bindTo(this.contextKeyService).set(!this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment); + + // File Pickers + SupportsWorkspacesContext.bindTo(this.contextKeyService); + SupportsOpenFileFolderContext.bindTo(this.contextKeyService).set(!!this.windowService.getConfiguration().remoteAuthority); + + // Editors + this.activeEditorContext = ActiveEditorContext.bindTo(this.contextKeyService); + this.editorsVisibleContext = EditorsVisibleContext.bindTo(this.contextKeyService); + this.textCompareEditorVisibleContext = TextCompareEditorVisibleContext.bindTo(this.contextKeyService); + this.textCompareEditorActiveContext = TextCompareEditorActiveContext.bindTo(this.contextKeyService); + this.activeEditorGroupEmpty = ActiveEditorGroupEmptyContext.bindTo(this.contextKeyService); + this.multipleEditorGroupsContext = MultipleEditorGroupsContext.bindTo(this.contextKeyService); + + // Inputs + this.inputFocusedContext = InputFocusedContext.bindTo(this.contextKeyService); + + // Workbench State + this.workbenchStateContext = WorkbenchStateContext.bindTo(this.contextKeyService); + this.updateWorkbenchStateContextKey(); + + // Workspace Folder Count + this.workspaceFolderCountContext = WorkspaceFolderCountContext.bindTo(this.contextKeyService); + this.updateWorkspaceFolderCountContextKey(); + + // Editor Layout + this.splitEditorsVerticallyContext = SplitEditorsVertically.bindTo(this.contextKeyService); + this.updateSplitEditorsVerticallyContext(); + + // Zen Mode + this.inZenModeContext = InEditorZenModeContext.bindTo(this.contextKeyService); + + // Sidebar + this.sideBarVisibleContext = SideBarVisibleContext.bindTo(this.contextKeyService); + this.sidebarVisibleContext = SidebarVisibleContext.bindTo(this.contextKeyService); + } + + private updateEditorContextKeys(): void { + const activeControl = this.editorService.activeControl; + const visibleEditors = this.editorService.visibleControls; + + this.textCompareEditorActiveContext.set(!!activeControl && activeControl.getId() === TEXT_DIFF_EDITOR_ID); + this.textCompareEditorVisibleContext.set(visibleEditors.some(control => control.getId() === TEXT_DIFF_EDITOR_ID)); + + if (visibleEditors.length > 0) { + this.editorsVisibleContext.set(true); + } else { + this.editorsVisibleContext.reset(); + } + + if (!this.editorService.activeEditor) { + this.activeEditorGroupEmpty.set(true); + } else { + this.activeEditorGroupEmpty.reset(); + } + + if (this.editorGroupService.count > 1) { + this.multipleEditorGroupsContext.set(true); + } else { + this.multipleEditorGroupsContext.reset(); + } + + if (activeControl) { + this.activeEditorContext.set(activeControl.getId()); + } else { + this.activeEditorContext.reset(); + } + } + + private updateInputContextKeys(): void { + + function activeElementIsInput(): boolean { + return !!document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA'); + } + + const isInputFocused = activeElementIsInput(); + this.inputFocusedContext.set(isInputFocused); + + if (isInputFocused) { + const tracker = trackFocus(document.activeElement as HTMLElement); + Event.once(tracker.onDidBlur)(() => { + this.inputFocusedContext.set(activeElementIsInput()); + + tracker.dispose(); + }); + } + } + + private updateWorkbenchStateContextKey(): void { + this.workbenchStateContext.set(this.getWorkbenchStateString()); + } + + private updateWorkspaceFolderCountContextKey(): void { + this.workspaceFolderCountContext.set(this.contextService.getWorkspace().folders.length); + } + + private updateSplitEditorsVerticallyContext(): void { + const direction = preferredSideBySideGroupDirection(this.configurationService); + this.splitEditorsVerticallyContext.set(direction === GroupDirection.DOWN); + } + + private getWorkbenchStateString(): string { + switch (this.contextService.getWorkbenchState()) { + case WorkbenchState.EMPTY: return 'empty'; + case WorkbenchState.FOLDER: return 'folder'; + case WorkbenchState.WORKSPACE: return 'workspace'; + } + } + + private updateSideBarContextKeys(): void { + this.sideBarVisibleContext.set(this.layoutService.isVisible(Parts.SIDEBAR_PART)); + this.sidebarVisibleContext.set(this.layoutService.isVisible(Parts.SIDEBAR_PART)); + } +} diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 65f90fb111..84a07b5d7d 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -3,10 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { WORKSPACE_EXTENSION, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { extname, basename, normalize } from 'vs/base/common/paths'; +import { hasWorkspaceFileExtension, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { normalize } from 'vs/base/common/path'; +import { basename } from 'vs/base/common/resources'; import { IFileService } from 'vs/platform/files/common/files'; -import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; +import { IWindowsService, IWindowService, IURIToOpen } from 'vs/platform/windows/common/windows'; import { URI } from 'vs/base/common/uri'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -15,22 +16,19 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un import { DefaultEndOfLine } from 'vs/editor/common/model'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; -import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; -import { DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults'; +import { DataTransfers } from 'vs/base/browser/dnd'; import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { MIME_BINARY } from 'vs/base/common/mime'; -import { ITree } from 'vs/base/parts/tree/browser/tree'; import { isWindows } from 'vs/base/common/platform'; -import { coalesce } from 'vs/base/common/arrays'; -import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorIdentifier, GroupIdentifier } from 'vs/workbench/common/editor'; -import { basenameOrAuthority } from 'vs/base/common/resources'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { Disposable } from 'vs/base/common/lifecycle'; import { addDisposableListener, EventType } from 'vs/base/browser/dom'; -import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IRecentFile } from 'vs/platform/history/common/history'; export interface IDraggedResource { resource: URI; @@ -60,8 +58,8 @@ export interface IDraggedEditor extends IDraggedResource { export interface ISerializedDraggedEditor { resource: string; - backupResource: string; - viewState: IEditorViewState; + backupResource?: string; + viewState: IEditorViewState | null; } export const CodeDataTransfers = { @@ -71,7 +69,7 @@ export const CodeDataTransfers = { export function extractResources(e: DragEvent, externalOnly?: boolean): Array { const resources: Array = []; - if (e.dataTransfer.types.length > 0) { + if (e.dataTransfer && e.dataTransfer.types.length > 0) { // Check for window-to-window DND if (!externalOnly) { @@ -165,7 +163,7 @@ export class ResourcesDropHandler { ) { } - handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup, afterDrop: (targetGroup: IEditorGroup) => void, targetIndex?: number): void { + handleDrop(event: DragEvent, resolveTargetGroup: () => IEditorGroup | undefined, afterDrop: (targetGroup: IEditorGroup | undefined) => void, targetIndex?: number): void { const untitledOrFileResources = extractResources(event).filter(r => this.fileService.canHandleResource(r.resource) || r.resource.scheme === Schemas.untitled); if (!untitledOrFileResources.length) { return; @@ -181,9 +179,9 @@ export class ResourcesDropHandler { } // Add external ones to recently open list unless dropped resource is a workspace - const filesToAddToHistory = untitledOrFileResources.filter(d => d.isExternal && d.resource.scheme === Schemas.file).map(d => d.resource); - if (filesToAddToHistory.length) { - this.windowsService.addRecentlyOpened(filesToAddToHistory); + const recents: IRecentFile[] = untitledOrFileResources.filter(d => d.isExternal && d.resource.scheme === Schemas.file).map(d => ({ fileUri: d.resource })); + if (recents.length) { + this.windowsService.addRecentlyOpened(recents); } const editors: IResourceEditor[] = untitledOrFileResources.map(untitledOrFileResource => ({ @@ -238,10 +236,10 @@ export class ResourcesDropHandler { } // Resolve the contents of the dropped dirty resource from source - return this.backupFileService.resolveBackupContent(droppedDirtyEditor.backupResource).then(content => { + return this.backupFileService.resolveBackupContent(droppedDirtyEditor.backupResource!).then(content => { // Set the contents of to the resource to the target - return this.backupFileService.backupResource(droppedDirtyEditor.resource, content.create(this.getDefaultEOL()).createSnapshot(true)); + return this.backupFileService.backupResource(droppedDirtyEditor.resource, content!.create(this.getDefaultEOL()).createSnapshot(true)); }).then(() => false, () => false /* ignore any error */); } @@ -255,7 +253,7 @@ export class ResourcesDropHandler { } private handleWorkspaceFileDrop(fileOnDiskResources: URI[]): Promise { - const workspaceResources: { workspaces: URI[], folders: URI[] } = { + const workspaceResources: { workspaces: IURIToOpen[], folders: IURIToOpen[] } = { workspaces: [], folders: [] }; @@ -263,8 +261,8 @@ export class ResourcesDropHandler { return Promise.all(fileOnDiskResources.map(fileOnDiskResource => { // Check for Workspace - if (extname(fileOnDiskResource.fsPath) === `.${WORKSPACE_EXTENSION}`) { - workspaceResources.workspaces.push(fileOnDiskResource); + if (hasWorkspaceFileExtension(fileOnDiskResource.fsPath)) { + workspaceResources.workspaces.push({ uri: fileOnDiskResource, typeHint: 'file' }); return undefined; } @@ -272,7 +270,7 @@ export class ResourcesDropHandler { // Check for Folder return this.fileService.resolveFile(fileOnDiskResource).then(stat => { if (stat.isDirectory) { - workspaceResources.folders.push(stat.resource); + workspaceResources.folders.push({ uri: stat.resource, typeHint: 'folder' }); } }, error => undefined); })).then(_ => { @@ -286,71 +284,32 @@ export class ResourcesDropHandler { // Pass focus to window this.windowService.focusWindow(); - let workspacesToOpen: Promise; + let workspacesToOpen: Promise | undefined; // Open in separate windows if we drop workspaces or just one folder if (workspaces.length > 0 || folders.length === 1) { - workspacesToOpen = Promise.resolve([...workspaces, ...folders].map(resources => resources)); + workspacesToOpen = Promise.resolve([...workspaces, ...folders]); } // Multiple folders: Create new workspace with folders and open else if (folders.length > 1) { - workspacesToOpen = this.workspacesService.createUntitledWorkspace(folders.map(folder => ({ uri: folder }))).then(workspace => [URI.file(workspace.configPath)]); + workspacesToOpen = this.workspacesService.createUntitledWorkspace(folders).then(workspace => [{ uri: workspace.configPath, typeHint: 'file' }]); } // Open - workspacesToOpen.then(workspaces => { - this.windowService.openWindow(workspaces, { forceReuseWindow: true }); - }); + if (workspacesToOpen) { + workspacesToOpen.then(workspaces => { + this.windowService.openWindow(workspaces, { forceReuseWindow: true }); + }); + } return true; }); } } -export class SimpleFileResourceDragAndDrop extends DefaultDragAndDrop { - - constructor( - private toResource: (obj: any) => URI, - @IInstantiationService protected instantiationService: IInstantiationService - ) { - super(); - } - - getDragURI(tree: ITree, obj: any): string { - const resource = this.toResource(obj); - if (resource) { - return resource.toString(); - } - - return undefined; - } - - getDragLabel(tree: ITree, elements: any[]): string { - if (elements.length > 1) { - return String(elements.length); - } - - const resource = this.toResource(elements[0]); - if (resource) { - return basenameOrAuthority(resource); - } - - return undefined; - } - - onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: DragMouseEvent): void { - - // Apply some datatransfer types to allow for dragging the element outside of the application - const resources: URI[] = data.getData().map(source => this.toResource(source)); - if (resources) { - this.instantiationService.invokeFunction(fillResourceDataTransfers, coalesce(resources), originalEvent); - } - } -} - export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: (URI | { resource: URI, isDirectory: boolean })[], event: DragMouseEvent | DragEvent): void { - if (resources.length === 0) { + if (resources.length === 0 || !event.dataTransfer) { return; } @@ -366,11 +325,11 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: // Text: allows to paste into text-capable areas const lineDelimiter = isWindows ? '\r\n' : '\n'; - event.dataTransfer.setData(DataTransfers.TEXT, sources.map(source => source.resource.scheme === Schemas.file ? normalize(normalizeDriveLetter(source.resource.fsPath), true) : source.resource.toString()).join(lineDelimiter)); + event.dataTransfer.setData(DataTransfers.TEXT, sources.map(source => source.resource.scheme === Schemas.file ? normalize(normalizeDriveLetter(source.resource.fsPath)) : source.resource.toString()).join(lineDelimiter)); // Download URL: enables support to drag a tab as file to desktop (only single file supported) if (firstSource.resource.scheme === Schemas.file) { - event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, basename(firstSource.resource.fsPath), firstSource.resource.toString()].join(':')); + event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, basename(firstSource.resource), firstSource.resource.toString()].join(':')); } // Resource URLs: allows to drop multiple resources to a target in VS Code (not directories) @@ -388,7 +347,7 @@ export function fillResourceDataTransfers(accessor: ServicesAccessor, resources: files.forEach(file => { // Try to find editor view state from the visible editors that match given resource - let viewState: IEditorViewState; + let viewState: IEditorViewState | null = null; const textEditorWidgets = editorService.visibleTextEditorWidgets; for (const textEditorWidget of textEditorWidgets) { if (isCodeEditor(textEditorWidget)) { @@ -420,8 +379,8 @@ export class LocalSelectionTransfer { private static readonly INSTANCE = new LocalSelectionTransfer(); - private data: T[]; - private proto: T; + private data?: T[]; + private proto?: T; private constructor() { // protect against external instantiation @@ -442,7 +401,7 @@ export class LocalSelectionTransfer { } } - getData(proto: T): T[] { + getData(proto: T): T[] | undefined { if (this.hasData(proto)) { return this.data; } @@ -489,6 +448,8 @@ export class DragAndDropObserver extends Disposable { })); this._register(addDisposableListener(this.element, EventType.DRAG_OVER, (e: DragEvent) => { + e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome) + if (this.callbacks.onDragOver) { this.callbacks.onDragOver(e); } diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index 7684ba55ee..f07292e1e8 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -172,10 +172,10 @@ class EditorRegistry implements IEditorRegistry { this.editors = editorsToSet; } - getEditorInputs(): any[] { - const inputClasses: any[] = []; + getEditorInputs(): SyncDescriptor[] { + const inputClasses: SyncDescriptor[] = []; for (const editor of this.editors) { - const editorInputDescriptors = []>editor[INPUT_DESCRIPTORS_PROPERTY]; + const editorInputDescriptors = editor[INPUT_DESCRIPTORS_PROPERTY]; inputClasses.push(...editorInputDescriptors.map(descriptor => descriptor.ctor)); } diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 4236a58387..50745fd303 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -24,10 +24,11 @@ import { ILabelService } from 'vs/platform/label/common/label'; import { getIconClasses, getConfiguredLangId } from 'vs/editor/common/services/getIconClasses'; import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { withNullAsUndefined } from 'vs/base/common/types'; export interface IResourceLabelProps { resource?: uri; - name: string; + name?: string; description?: string; } @@ -223,13 +224,13 @@ class ResourceLabelWidget extends IconLabel { private _onDidRender = this._register(new Emitter()); get onDidRender(): Event { return this._onDidRender.event; } - private label: IResourceLabelProps; - private options: IResourceLabelOptions; - private computedIconClasses: string[]; - private lastKnownConfiguredLangId: string; - private computedPathLabel: string; + private label?: IResourceLabelProps; + private options?: IResourceLabelOptions; + private computedIconClasses?: string[]; + private lastKnownConfiguredLangId?: string; + private computedPathLabel?: string; - private needsRedraw: Redraw; + private needsRedraw?: Redraw; private isHidden: boolean = false; constructor( @@ -303,7 +304,7 @@ class ResourceLabelWidget extends IconLabel { this.render(hasResourceChanged); } - private hasResourceChanged(label: IResourceLabelProps, options: IResourceLabelOptions): boolean { + private hasResourceChanged(label: IResourceLabelProps, options?: IResourceLabelOptions): boolean { const newResource = label ? label.resource : undefined; const oldResource = this.label ? this.label.resource : undefined; @@ -331,15 +332,15 @@ class ResourceLabelWidget extends IconLabel { setEditor(editor: IEditorInput, options?: IResourceLabelOptions): void { this.setResource({ - resource: toResource(editor, { supportSideBySide: true }), - name: editor.getName(), - description: editor.getDescription() + resource: withNullAsUndefined(toResource(editor, { supportSideBySide: true })), + name: withNullAsUndefined(editor.getName()), + description: withNullAsUndefined(editor.getDescription()) }, options); } setFile(resource: uri, options?: IFileLabelOptions): void { const hideLabel = options && options.hideLabel; - let name: string; + let name: string | undefined; if (!hideLabel) { if (options && options.fileKind === FileKind.ROOT_FOLDER) { const workspaceFolder = this.contextService.getWorkspaceFolder(resource); @@ -353,7 +354,7 @@ class ResourceLabelWidget extends IconLabel { } } - let description: string; + let description: string | undefined; const hidePath = (options && options.hidePath) || (resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)); if (!hidePath) { description = this.labelService.getUriLabel(resources.dirname(resource), { relative: true }); @@ -386,7 +387,7 @@ class ResourceLabelWidget extends IconLabel { } if (this.label) { - const configuredLangId = getConfiguredLangId(this.modelService, this.label.resource); + const configuredLangId = this.label.resource ? withNullAsUndefined(getConfiguredLangId(this.modelService, this.modeService, this.label.resource)) : undefined; if (this.lastKnownConfiguredLangId !== configuredLangId) { clearIconCache = true; this.lastKnownConfiguredLangId = configuredLangId; @@ -428,7 +429,7 @@ class ResourceLabelWidget extends IconLabel { iconLabelOptions.extraClasses = this.computedIconClasses.slice(0); } if (this.options && this.options.extraClasses) { - iconLabelOptions.extraClasses.push(...this.options.extraClasses); + iconLabelOptions.extraClasses!.push(...this.options.extraClasses); } if (this.options && this.options.fileDecorations && resource) { @@ -444,11 +445,11 @@ class ResourceLabelWidget extends IconLabel { } if (this.options.fileDecorations.colors) { - iconLabelOptions.extraClasses.push(deco.labelClassName); + iconLabelOptions.extraClasses!.push(deco.labelClassName); } if (this.options.fileDecorations.badges) { - iconLabelOptions.extraClasses.push(deco.badgeClassName); + iconLabelOptions.extraClasses!.push(deco.badgeClassName); } } } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 1be4003e3d..5859a90a8b 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -3,759 +3,1139 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { QuickOpenController } from 'vs/workbench/browser/parts/quickopen/quickOpenController'; -import { QuickInputService } from 'vs/workbench/browser/parts/quickinput/quickInput'; -import { Sash, ISashEvent, IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider, Orientation } from 'vs/base/browser/ui/sash/sash'; -import { IPartService, Position, ILayoutOptions, Parts } from 'vs/workbench/services/part/common/partService'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { isMacintosh } from 'vs/base/common/platform'; -import { memoize } from 'vs/base/common/decorators'; -import { NotificationsCenter } from 'vs/workbench/browser/parts/notifications/notificationsCenter'; -import { NotificationsToasts } from 'vs/workbench/browser/parts/notifications/notificationsToasts'; -import { Dimension, getClientArea, size, position, hide, show } from 'vs/base/browser/dom'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; -import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; -import { TitlebarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; -import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; +import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { Event, Emitter } from 'vs/base/common/event'; +import { EventType, addDisposableListener, addClass, removeClass, isAncestor, getClientArea, position, size } from 'vs/base/browser/dom'; +import { onDidChangeFullscreen, isFullscreen, getZoomFactor } from 'vs/base/browser/browser'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; +import { IResourceInput } from 'vs/platform/editor/common/editor'; +import { IUntitledResourceInput, IResourceDiffInput } from 'vs/workbench/common/editor'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; -import { StatusbarPart } from 'vs/workbench/browser/parts/statusbar/statusbarPart'; -import { getZoomFactor } from 'vs/base/browser/browser'; +import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; +import { Position, Parts, IWorkbenchLayoutService, ILayoutOptions } from 'vs/workbench/services/layout/browser/layoutService'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason } from 'vs/platform/storage/common/storage'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { ITitleService } from 'vs/workbench/services/title/common/titleService'; +import { IInstantiationService, ServicesAccessor, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { LifecyclePhase, StartupKind, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { IWindowService, IPath, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { Sizing, Direction, Grid, View } from 'vs/base/browser/ui/grid/grid'; +import { WorkbenchLegacyLayout } from 'vs/workbench/browser/legacyLayout'; +import { IDimension } from 'vs/platform/layout/browser/layoutService'; +import { Part } from 'vs/workbench/browser/part'; +import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; +import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; +import { coalesce } from 'vs/base/common/arrays'; -const TITLE_BAR_HEIGHT = isMacintosh ? 22 : 30; -const STATUS_BAR_HEIGHT = 22; -const ACTIVITY_BAR_WIDTH = 50; +enum Settings { + MENUBAR_VISIBLE = 'window.menuBarVisibility', + ACTIVITYBAR_VISIBLE = 'workbench.activityBar.visible', + STATUSBAR_VISIBLE = 'workbench.statusBar.visible', -const MIN_SIDEBAR_PART_WIDTH = 170; -const DEFAULT_SIDEBAR_PART_WIDTH = 300; -const HIDE_SIDEBAR_WIDTH_THRESHOLD = 50; + SIDEBAR_POSITION = 'workbench.sideBar.location', + PANEL_POSITION = 'workbench.panel.defaultLocation', -const MIN_PANEL_PART_HEIGHT = 77; -const MIN_PANEL_PART_WIDTH = 300; -const DEFAULT_PANEL_PART_SIZE = 350; -const DEFAULT_PANEL_SIZE_COEFFICIENT = 0.4; -const PANEL_SIZE_BEFORE_MAXIMIZED_BOUNDARY = 0.7; -const HIDE_PANEL_HEIGHT_THRESHOLD = 50; -const HIDE_PANEL_WIDTH_THRESHOLD = 100; + ZEN_MODE_RESTORE = 'zenMode.restore' +} -/** - * The workbench layout is responsible to lay out all parts that make the Workbench. - */ -export class WorkbenchLayout extends Disposable implements IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider { +enum Storage { + SIDEBAR_HIDDEN = 'workbench.sidebar.hidden', - private static readonly sashXOneWidthSettingsKey = 'workbench.sidebar.width'; - private static readonly sashXTwoWidthSettingsKey = 'workbench.panel.width'; - private static readonly sashYHeightSettingsKey = 'workbench.panel.height'; - private static readonly panelSizeBeforeMaximizedKey = 'workbench.panel.sizeBeforeMaximized'; + PANEL_HIDDEN = 'workbench.panel.hidden', + PANEL_POSITION = 'workbench.panel.location', - private workbenchSize: Dimension; + ZEN_MODE_ENABLED = 'workbench.zenmode.active', + CENTERED_LAYOUT_ENABLED = 'workbench.centerededitorlayout.active', +} - private sashXOne: Sash; - private sashXTwo: Sash; - private sashY: Sash; +export abstract class Layout extends Disposable implements IWorkbenchLayoutService { - private _sidebarWidth: number; - private sidebarHeight: number; - private titlebarHeight: number; - private statusbarHeight: number; - private panelSizeBeforeMaximized: number; - private panelMaximized: boolean; - private _panelHeight: number; - private _panelWidth: number; + _serviceBrand: ServiceIdentifier; + + private readonly _onTitleBarVisibilityChange: Emitter = this._register(new Emitter()); + get onTitleBarVisibilityChange(): Event { return this._onTitleBarVisibilityChange.event; } + + private readonly _onZenMode: Emitter = this._register(new Emitter()); + get onZenModeChange(): Event { return this._onZenMode.event; } + + private readonly _onLayout = this._register(new Emitter()); + get onLayout(): Event { return this._onLayout.event; } + + private _dimension: IDimension; + get dimension(): IDimension { return this._dimension; } + + private _container: HTMLElement = document.createElement('div'); + get container(): HTMLElement { return this._container; } + + get hasWorkbench(): boolean { return true; } + + private parts: Map = new Map(); + + private workbenchGrid: Grid | WorkbenchLegacyLayout; + + private disposed: boolean; + + private titleBarPartView: View; + private activityBarPartView: View; + private sideBarPartView: View; + private panelPartView: View; + private editorPartView: View; + private statusBarPartView: View; + + private environmentService: IEnvironmentService; + private configurationService: IConfigurationService; + private lifecycleService: ILifecycleService; + private storageService: IStorageService; + private windowService: IWindowService; + private editorService: IEditorService; + private editorGroupService: IEditorGroupsService; + private panelService: IPanelService; + private titleService: ITitleService; + private viewletService: IViewletService; + private contextService: IWorkspaceContextService; + private backupFileService: IBackupFileService; + + protected readonly state = { + fullscreen: false, + + menuBar: { + visibility: 'default' as MenuBarVisibility, + toggled: false + }, + + activityBar: { + hidden: false + }, + + sideBar: { + hidden: false, + position: Position.LEFT, + width: 300, + viewletToRestore: undefined as string | undefined + }, + + editor: { + hidden: false, + centered: false, + restoreCentered: false, + restoreEditors: false, + editorsToOpen: [] as Promise | IResourceEditor[] + }, + + panel: { + hidden: false, + position: Position.BOTTOM, + height: 350, + width: 350, + panelToRestore: undefined as string | undefined + }, + + statusBar: { + hidden: false + }, + + zenMode: { + active: false, + restore: false, + transitionedToFullScreen: false, + transitionedToCenteredEditorLayout: false, + wasSideBarVisible: false, + wasPanelVisible: false, + transitionDisposeables: [] as IDisposable[] + } + }; constructor( - private parent: HTMLElement, - private workbenchContainer: HTMLElement, - private parts: { - titlebar: TitlebarPart, - activitybar: ActivitybarPart, - editor: EditorPart, - sidebar: SidebarPart, - panel: PanelPart, - statusbar: StatusbarPart - }, - private quickopen: QuickOpenController, - private quickInput: QuickInputService, - private notificationsCenter: NotificationsCenter, - private notificationsToasts: NotificationsToasts, - @IStorageService private readonly storageService: IStorageService, - @IContextViewService private readonly contextViewService: IContextViewService, - @IPartService private readonly partService: IPartService, - @IViewletService private readonly viewletService: IViewletService, - @IThemeService private readonly themeService: IThemeService, - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService + protected readonly parent: HTMLElement ) { super(); - - // Restore state - this.restorePreviousState(); - - // Create layout sashes - this.sashXOne = new Sash(this.workbenchContainer, this); - this.sashXTwo = new Sash(this.workbenchContainer, this); - this.sashY = new Sash(this.workbenchContainer, this, { orientation: Orientation.HORIZONTAL }); - - this.registerListeners(); } - private restorePreviousState(): void { - this._sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, this.storageService.getInteger(WorkbenchLayout.sashXOneWidthSettingsKey, StorageScope.GLOBAL, DEFAULT_SIDEBAR_PART_WIDTH)); + protected initLayout(accessor: ServicesAccessor): void { - this._panelWidth = Math.max(this.partLayoutInfo.panel.minWidth, this.storageService.getInteger(WorkbenchLayout.sashXTwoWidthSettingsKey, StorageScope.GLOBAL, DEFAULT_PANEL_PART_SIZE)); - this._panelHeight = Math.max(this.partLayoutInfo.panel.minHeight, this.storageService.getInteger(WorkbenchLayout.sashYHeightSettingsKey, StorageScope.GLOBAL, DEFAULT_PANEL_PART_SIZE)); + // Services + this.environmentService = accessor.get(IEnvironmentService); + this.configurationService = accessor.get(IConfigurationService); + this.lifecycleService = accessor.get(ILifecycleService); + this.windowService = accessor.get(IWindowService); + this.contextService = accessor.get(IWorkspaceContextService); + this.storageService = accessor.get(IStorageService); - this.panelMaximized = false; - this.panelSizeBeforeMaximized = this.storageService.getInteger(WorkbenchLayout.panelSizeBeforeMaximizedKey, StorageScope.GLOBAL, 0); + // Parts + this.editorService = accessor.get(IEditorService); + this.editorGroupService = accessor.get(IEditorGroupsService); + this.panelService = accessor.get(IPanelService); + this.viewletService = accessor.get(IViewletService); + this.titleService = accessor.get(ITitleService); + accessor.get(IStatusbarService); // not used, but called to ensure instantiated + accessor.get(IActivityBarService); // not used, but called to ensure instantiated + + // Listeners + this.registerLayoutListeners(); + + // State + this.initLayoutState(accessor.get(ILifecycleService)); } - private registerListeners(): void { - this._register(this.themeService.onThemeChange(_ => this.layout())); - this._register(this.parts.editor.onDidSizeConstraintsChange(() => this.onDidEditorSizeConstraintsChange())); + private registerLayoutListeners(): void { - this.registerSashListeners(); + // Storage + this._register(this.storageService.onWillSaveState(e => this.saveLayoutState(e))); + + // Restore editor if hidden and it changes + this._register(this.editorService.onDidVisibleEditorsChange(() => this.setEditorHidden(false))); + this._register(this.editorGroupService.onDidActivateGroup(() => this.setEditorHidden(false))); + + // Configuration changes + this._register(this.configurationService.onDidChangeConfiguration(() => this.doUpdateLayoutConfiguration())); + + // Fullscreen changes + this._register(onDidChangeFullscreen(() => this.onFullscreenChanged())); + + // Group changes + this._register(this.editorGroupService.onDidAddGroup(() => this.centerEditorLayout(this.state.editor.centered))); + this._register(this.editorGroupService.onDidRemoveGroup(() => this.centerEditorLayout(this.state.editor.centered))); + + // Prevent workbench from scrolling #55456 + this._register(addDisposableListener(this.container, EventType.SCROLL, () => this.container.scrollTop = 0)); + + // Menubar visibility changes + if ((isWindows || isLinux) && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { + this._register(this.titleService.onMenubarVisibilityChange(visible => this.onMenubarToggled(visible))); + } } - private onDidEditorSizeConstraintsChange(): void { - if (this.workbenchSize && (this.sidebarWidth || this.panelHeight)) { - if (this.editorGroupService.count > 1) { - const minimumEditorPartSize = new Dimension(this.parts.editor.minimumWidth, this.parts.editor.minimumHeight); + private onMenubarToggled(visible: boolean) { + if (visible !== this.state.menuBar.toggled) { + this.state.menuBar.toggled = visible; - const sidebarOverflow = this.workbenchSize.width - this.sidebarWidth < minimumEditorPartSize.width; - - let panelOverflow = false; - if (this.partService.getPanelPosition() === Position.RIGHT) { - panelOverflow = this.workbenchSize.width - this.panelWidth - this.sidebarWidth < minimumEditorPartSize.width; - } else { - panelOverflow = this.workbenchSize.height - this.panelHeight < minimumEditorPartSize.height; - } - - // Trigger a layout if we detect that either sidebar or panel overflow - // as a matter of a new editor group being added to the editor part - if (sidebarOverflow || panelOverflow) { - this.layout(); - } + if (this.state.fullscreen && (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'default')) { + this._onTitleBarVisibilityChange.fire(); + this.layout(); } } } - private get activitybarWidth(): number { - if (this.partService.isVisible(Parts.ACTIVITYBAR_PART)) { - return this.partLayoutInfo.activitybar.width; - } + private onFullscreenChanged(): void { + this.state.fullscreen = isFullscreen(); - return 0; - } - - private get panelHeight(): number { - const panelPosition = this.partService.getPanelPosition(); - if (panelPosition === Position.RIGHT) { - return this.sidebarHeight; - } - - return this._panelHeight; - } - - private set panelHeight(value: number) { - this._panelHeight = Math.min(this.computeMaxPanelHeight(), Math.max(this.partLayoutInfo.panel.minHeight, value)); - } - - private get panelWidth(): number { - const panelPosition = this.partService.getPanelPosition(); - if (panelPosition === Position.BOTTOM) { - return this.workbenchSize.width - this.activitybarWidth - this.sidebarWidth; - } - - return this._panelWidth; - } - - private set panelWidth(value: number) { - this._panelWidth = Math.min(this.computeMaxPanelWidth(), Math.max(this.partLayoutInfo.panel.minWidth, value)); - } - - private computeMaxPanelWidth(): number { - let minSidebarWidth: number; - if (this.partService.isVisible(Parts.SIDEBAR_PART)) { - if (this.partService.getSideBarPosition() === Position.LEFT) { - minSidebarWidth = this.partLayoutInfo.sidebar.minWidth; - } else { - minSidebarWidth = this.sidebarWidth; - } + // Apply as CSS class + if (this.state.fullscreen) { + addClass(this.container, 'fullscreen'); } else { - minSidebarWidth = 0; + removeClass(this.container, 'fullscreen'); + + if (this.state.zenMode.transitionedToFullScreen && this.state.zenMode.active) { + this.toggleZenMode(); + } } - return Math.max(this.partLayoutInfo.panel.minWidth, this.workbenchSize.width - this.parts.editor.minimumWidth - minSidebarWidth - this.activitybarWidth); + // Changing fullscreen state of the window has an impact on custom title bar visibility, so we need to update + if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { + this._onTitleBarVisibilityChange.fire(); + this.layout(); // handle title bar when fullscreen changes + } } - private computeMaxPanelHeight(): number { - return Math.max(this.partLayoutInfo.panel.minHeight, this.sidebarHeight /* simplification for: window.height - status.height - title-height */ - this.parts.editor.minimumHeight); - } + private doUpdateLayoutConfiguration(skipLayout?: boolean): void { - private get sidebarWidth(): number { - if (this.partService.isVisible(Parts.SIDEBAR_PART)) { - return this._sidebarWidth; + // Sidebar position + const newSidebarPositionValue = this.configurationService.getValue(Settings.SIDEBAR_POSITION); + const newSidebarPosition = (newSidebarPositionValue === 'right') ? Position.RIGHT : Position.LEFT; + if (newSidebarPosition !== this.getSideBarPosition()) { + this.setSideBarPosition(newSidebarPosition); } - return 0; + // Panel position + this.updatePanelPosition(); + + if (!this.state.zenMode.active) { + + // Statusbar visibility + const newStatusbarHiddenValue = !this.configurationService.getValue(Settings.STATUSBAR_VISIBLE); + if (newStatusbarHiddenValue !== this.state.statusBar.hidden) { + this.setStatusBarHidden(newStatusbarHiddenValue, skipLayout); + } + + // Activitybar visibility + const newActivityBarHiddenValue = !this.configurationService.getValue(Settings.ACTIVITYBAR_VISIBLE); + if (newActivityBarHiddenValue !== this.state.activityBar.hidden) { + this.setActivityBarHidden(newActivityBarHiddenValue, skipLayout); + } + } + + // Menubar visibility + const newMenubarVisibility = this.configurationService.getValue(Settings.MENUBAR_VISIBLE); + this.setMenubarVisibility(newMenubarVisibility, !!skipLayout); } - private set sidebarWidth(value: number) { - const panelMinWidth = this.partService.getPanelPosition() === Position.RIGHT && this.partService.isVisible(Parts.PANEL_PART) ? this.partLayoutInfo.panel.minWidth : 0; - const maxSidebarWidth = this.workbenchSize.width - this.activitybarWidth - this.parts.editor.minimumWidth - panelMinWidth; + private setSideBarPosition(position: Position): void { + const activityBar = this.getPart(Parts.ACTIVITYBAR_PART); + const sideBar = this.getPart(Parts.SIDEBAR_PART); + const wasHidden = this.state.sideBar.hidden; - this._sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, Math.min(maxSidebarWidth, value)); + if (this.state.sideBar.hidden) { + this.setSideBarHidden(false, true /* Skip Layout */); + } + + const newPositionValue = (position === Position.LEFT) ? 'left' : 'right'; + const oldPositionValue = (this.state.sideBar.position === Position.LEFT) ? 'left' : 'right'; + this.state.sideBar.position = position; + + // Adjust CSS + removeClass(activityBar.getContainer(), oldPositionValue); + removeClass(sideBar.getContainer(), oldPositionValue); + addClass(activityBar.getContainer(), newPositionValue); + addClass(sideBar.getContainer(), newPositionValue); + + // Update Styles + activityBar.updateStyles(); + sideBar.updateStyles(); + + // Layout + if (this.workbenchGrid instanceof Grid) { + if (!wasHidden) { + this.state.sideBar.width = this.workbenchGrid.getViewSize(this.sideBarPartView); + } + + this.workbenchGrid.removeView(this.sideBarPartView); + this.workbenchGrid.removeView(this.activityBarPartView); + + if (!this.state.panel.hidden && this.state.panel.position === Position.BOTTOM) { + this.workbenchGrid.removeView(this.panelPartView); + } + + this.layout(); + } else { + this.workbenchGrid.layout(); + } } - @memoize - public get partLayoutInfo() { - return { - titlebar: { - height: TITLE_BAR_HEIGHT - }, - activitybar: { - width: ACTIVITY_BAR_WIDTH - }, - sidebar: { - minWidth: MIN_SIDEBAR_PART_WIDTH - }, - panel: { - minHeight: MIN_PANEL_PART_HEIGHT, - minWidth: MIN_PANEL_PART_WIDTH - }, - statusbar: { - height: STATUS_BAR_HEIGHT + private initLayoutState(lifecycleService: ILifecycleService): void { + + // Fullscreen + this.state.fullscreen = isFullscreen(); + + // Menubar visibility + this.state.menuBar.visibility = this.configurationService.getValue(Settings.MENUBAR_VISIBLE); + + // Activity bar visibility + this.state.activityBar.hidden = !this.configurationService.getValue(Settings.ACTIVITYBAR_VISIBLE); + + // Sidebar visibility + this.state.sideBar.hidden = this.storageService.getBoolean(Storage.SIDEBAR_HIDDEN, StorageScope.WORKSPACE, this.contextService.getWorkbenchState() === WorkbenchState.EMPTY); + + // Sidebar position + this.state.sideBar.position = (this.configurationService.getValue(Settings.SIDEBAR_POSITION) === 'right') ? Position.RIGHT : Position.LEFT; + + // Sidebar viewlet + if (!this.state.sideBar.hidden) { + + // Only restore last viewlet if window was reloaded or we are in development mode + let viewletToRestore: string; + if (!this.environmentService.isBuilt || lifecycleService.startupKind === StartupKind.ReloadedWindow) { + viewletToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE, this.viewletService.getDefaultViewletId()); + } else { + viewletToRestore = this.viewletService.getDefaultViewletId(); } - }; + + if (viewletToRestore) { + this.state.sideBar.viewletToRestore = viewletToRestore; + } else { + this.state.sideBar.hidden = true; // we hide sidebar if there is no viewlet to restore + } + } + + // Editor centered layout + this.state.editor.restoreCentered = this.storageService.getBoolean(Storage.CENTERED_LAYOUT_ENABLED, StorageScope.WORKSPACE, false); + + // Editors to open + this.state.editor.editorsToOpen = this.resolveEditorsToOpen(); + + // Panel visibility + this.state.panel.hidden = this.storageService.getBoolean(Storage.PANEL_HIDDEN, StorageScope.WORKSPACE, true); + + // Panel position + this.updatePanelPosition(); + + // Panel to restore + if (!this.state.panel.hidden) { + const panelRegistry = Registry.as(PanelExtensions.Panels); + + let panelToRestore = this.storageService.get(PanelPart.activePanelSettingsKey, StorageScope.WORKSPACE, panelRegistry.getDefaultPanelId()); + if (!panelRegistry.hasPanel(panelToRestore)) { + panelToRestore = panelRegistry.getDefaultPanelId(); // fallback to default if panel is unknown + } + + if (panelToRestore) { + this.state.panel.panelToRestore = panelToRestore; + } else { + this.state.panel.hidden = true; // we hide panel if there is no panel to restore + } + } + + // Statusbar visibility + this.state.statusBar.hidden = !this.configurationService.getValue(Settings.STATUSBAR_VISIBLE); + + // Zen mode enablement + this.state.zenMode.restore = this.storageService.getBoolean(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE, false) && this.configurationService.getValue(Settings.ZEN_MODE_RESTORE); } - private registerSashListeners(): void { - let startX: number = 0; - let startY: number = 0; - let startXTwo: number = 0; - let startSidebarWidth: number; - let startPanelHeight: number; - let startPanelWidth: number; + private resolveEditorsToOpen(): Promise | IResourceEditor[] { + const configuration = this.windowService.getConfiguration(); + const hasInitialFilesToOpen = this.hasInitialFilesToOpen(); - this._register(this.sashXOne.onDidStart((e: ISashEvent) => { - startSidebarWidth = this.sidebarWidth; - startX = e.startX; - })); + // Only restore editors if we are not instructed to open files initially + this.state.editor.restoreEditors = !hasInitialFilesToOpen; - this._register(this.sashY.onDidStart((e: ISashEvent) => { - startPanelHeight = this.panelHeight; - startY = e.startY; - })); + // Files to open, diff or create + if (hasInitialFilesToOpen) { - this._register(this.sashXTwo.onDidStart((e: ISashEvent) => { - startPanelWidth = this.panelWidth; - startXTwo = e.startX; - })); - - this._register(this.sashXOne.onDidChange((e: ISashEvent) => { - let doLayout = false; - let sidebarPosition = this.partService.getSideBarPosition(); - let isSidebarVisible = this.partService.isVisible(Parts.SIDEBAR_PART); - let newSashWidth = (sidebarPosition === Position.LEFT) ? startSidebarWidth + e.currentX - startX : startSidebarWidth - e.currentX + startX; - - // Sidebar visible - if (isSidebarVisible) { - - // Automatically hide side bar when a certain threshold is met - if (newSashWidth + HIDE_SIDEBAR_WIDTH_THRESHOLD < this.partLayoutInfo.sidebar.minWidth) { - let dragCompensation = this.partLayoutInfo.sidebar.minWidth - HIDE_SIDEBAR_WIDTH_THRESHOLD; - this.partService.setSideBarHidden(true); - startX = (sidebarPosition === Position.LEFT) ? Math.max(this.activitybarWidth, e.currentX - dragCompensation) : Math.min(e.currentX + dragCompensation, this.workbenchSize.width - this.activitybarWidth); - this.sidebarWidth = startSidebarWidth; // when restoring sidebar, restore to the sidebar width we started from - } - - // Otherwise size the sidebar accordingly - else { - this.sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, newSashWidth); // Sidebar can not become smaller than MIN_PART_WIDTH - doLayout = newSashWidth >= this.partLayoutInfo.sidebar.minWidth; - } + // Files to diff is exclusive + const filesToDiff = this.toInputs(configuration.filesToDiff, false); + if (filesToDiff && filesToDiff.length === 2) { + return [{ + leftResource: filesToDiff[0].resource, + rightResource: filesToDiff[1].resource, + options: { pinned: true }, + forceFile: true + }]; } - // Sidebar hidden - else { - if ((sidebarPosition === Position.LEFT && e.currentX - startX >= this.partLayoutInfo.sidebar.minWidth) || - (sidebarPosition === Position.RIGHT && startX - e.currentX >= this.partLayoutInfo.sidebar.minWidth)) { - startSidebarWidth = this.partLayoutInfo.sidebar.minWidth - (sidebarPosition === Position.LEFT ? e.currentX - startX : startX - e.currentX); - this.sidebarWidth = this.partLayoutInfo.sidebar.minWidth; - this.partService.setSideBarHidden(false); - } + const filesToCreate = this.toInputs(configuration.filesToCreate, true); + const filesToOpen = this.toInputs(configuration.filesToOpen, false); + + // Otherwise: Open/Create files + return [...filesToOpen, ...filesToCreate]; + } + + // Empty workbench + else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && this.configurationService.inspect('workbench.startupEditor').value === 'newUntitledFile') { + const isEmpty = this.editorGroupService.count === 1 && this.editorGroupService.activeGroup.count === 0; + if (!isEmpty) { + return []; // do not open any empty untitled file if we restored editors from previous session } - if (doLayout) { - this.layout({ source: Parts.SIDEBAR_PART }); + return this.backupFileService.hasBackups().then(hasBackups => { + if (hasBackups) { + return []; // do not open any empty untitled file if we have backups to restore + } + + return [{}]; + }); + } + + return []; + } + + private hasInitialFilesToOpen(): boolean { + const configuration = this.windowService.getConfiguration(); + + return !!( + (configuration.filesToCreate && configuration.filesToCreate.length > 0) || + (configuration.filesToOpen && configuration.filesToOpen.length > 0) || + (configuration.filesToDiff && configuration.filesToDiff.length > 0)); + } + + private toInputs(paths: IPath[] | undefined, isNew: boolean): Array { + if (!paths || !paths.length) { + return []; + } + + return coalesce(paths.map(p => { + const resource = p.fileUri; + if (!resource) { + return undefined; } + + let input: IResourceInput | IUntitledResourceInput; + if (isNew) { + input = { filePath: resource.fsPath, options: { pinned: true } } as IUntitledResourceInput; + } else { + input = { resource, options: { pinned: true }, forceFile: true } as IResourceInput; + } + + if (!isNew && typeof p.lineNumber === 'number') { + input.options!.selection = { + startLineNumber: p.lineNumber, + startColumn: p.columnNumber || 1 + }; + } + + return input; })); + } - this._register(this.sashY.onDidChange((e: ISashEvent) => { - let doLayout = false; - let isPanelVisible = this.partService.isVisible(Parts.PANEL_PART); - let newSashHeight = startPanelHeight - (e.currentY - startY); + private updatePanelPosition() { + const defaultPanelPosition = this.configurationService.getValue(Settings.PANEL_POSITION); + const panelPosition = this.storageService.get(Storage.PANEL_POSITION, StorageScope.WORKSPACE, defaultPanelPosition); - // Panel visible - if (isPanelVisible) { + this.state.panel.position = (panelPosition === 'right') ? Position.RIGHT : Position.BOTTOM; + } - // Automatically hide panel when a certain threshold is met - if (newSashHeight + HIDE_PANEL_HEIGHT_THRESHOLD < this.partLayoutInfo.panel.minHeight) { - let dragCompensation = this.partLayoutInfo.panel.minHeight - HIDE_PANEL_HEIGHT_THRESHOLD; - this.partService.setPanelHidden(true); - startY = Math.min(this.sidebarHeight - this.statusbarHeight - this.titlebarHeight, e.currentY + dragCompensation); - this.panelHeight = startPanelHeight; // when restoring panel, restore to the panel height we started from + registerPart(part: Part): void { + this.parts.set(part.getId(), part); + } + + protected getPart(key: Parts): Part { + const part = this.parts.get(key); + if (!part) { + throw new Error(`Unknown part ${key}`); + } + + return part; + } + + isRestored(): boolean { + return this.lifecycleService.phase >= LifecyclePhase.Restored; + } + + hasFocus(part: Parts): boolean { + const activeElement = document.activeElement; + if (!activeElement) { + return false; + } + + const container = this.getContainer(part); + + return isAncestor(activeElement, container); + } + + getContainer(part: Parts): HTMLElement { + switch (part) { + case Parts.TITLEBAR_PART: + return this.getPart(Parts.TITLEBAR_PART).getContainer(); + case Parts.ACTIVITYBAR_PART: + return this.getPart(Parts.ACTIVITYBAR_PART).getContainer(); + case Parts.SIDEBAR_PART: + return this.getPart(Parts.SIDEBAR_PART).getContainer(); + case Parts.PANEL_PART: + return this.getPart(Parts.PANEL_PART).getContainer(); + case Parts.EDITOR_PART: + return this.getPart(Parts.EDITOR_PART).getContainer(); + case Parts.STATUSBAR_PART: + return this.getPart(Parts.STATUSBAR_PART).getContainer(); + } + } + + isVisible(part: Parts): boolean { + switch (part) { + case Parts.TITLEBAR_PART: + if (getTitleBarStyle(this.configurationService, this.environmentService) === 'native') { + return false; + } else if (!this.state.fullscreen) { + return true; + } else if (isMacintosh) { + return false; + } else if (this.state.menuBar.visibility === 'visible') { + return true; + } else if (this.state.menuBar.visibility === 'toggle' || this.state.menuBar.visibility === 'default') { + return this.state.menuBar.toggled; } - // Otherwise size the panel accordingly - else { - this.panelHeight = Math.max(this.partLayoutInfo.panel.minHeight, newSashHeight); // Panel can not become smaller than MIN_PART_HEIGHT - doLayout = newSashHeight >= this.partLayoutInfo.panel.minHeight; + return false; + case Parts.SIDEBAR_PART: + return !this.state.sideBar.hidden; + case Parts.PANEL_PART: + return !this.state.panel.hidden; + case Parts.STATUSBAR_PART: + return !this.state.statusBar.hidden; + case Parts.ACTIVITYBAR_PART: + return !this.state.activityBar.hidden; + case Parts.EDITOR_PART: + return this.workbenchGrid instanceof Grid ? !this.state.editor.hidden : true; + } + + return true; // any other part cannot be hidden + } + + getTitleBarOffset(): number { + let offset = 0; + if (this.isVisible(Parts.TITLEBAR_PART)) { + if (this.workbenchGrid instanceof Grid) { + offset = this.getPart(Parts.TITLEBAR_PART).maximumHeight; + } else { + offset = this.workbenchGrid.partLayoutInfo.titlebar.height; + + if (isMacintosh || this.state.menuBar.visibility === 'hidden') { + offset /= getZoomFactor(); } } + } - // Panel hidden - else { - if (startY - e.currentY >= this.partLayoutInfo.panel.minHeight) { - startPanelHeight = 0; - this.panelHeight = this.partLayoutInfo.panel.minHeight; - this.partService.setPanelHidden(false); - } + return offset; + } + + getWorkbenchElement(): HTMLElement { + return this.container; + } + + toggleZenMode(skipLayout?: boolean, restoring = false): void { + this.state.zenMode.active = !this.state.zenMode.active; + this.state.zenMode.transitionDisposeables = dispose(this.state.zenMode.transitionDisposeables); + + const setLineNumbers = (lineNumbers: any) => this.editorService.visibleTextEditorWidgets.forEach(editor => editor.updateOptions({ lineNumbers })); + + // Check if zen mode transitioned to full screen and if now we are out of zen mode + // -> we need to go out of full screen (same goes for the centered editor layout) + let toggleFullScreen = false; + + // Zen Mode Active + if (this.state.zenMode.active) { + const config: { + fullScreen: boolean; + centerLayout: boolean; + hideTabs: boolean; + hideActivityBar: boolean; + hideStatusBar: boolean; + hideLineNumbers: boolean; + } = this.configurationService.getValue('zenMode'); + + toggleFullScreen = !this.state.fullscreen && config.fullScreen; + + this.state.zenMode.transitionedToFullScreen = restoring ? config.fullScreen : toggleFullScreen; + this.state.zenMode.transitionedToCenteredEditorLayout = !this.isEditorLayoutCentered() && config.centerLayout; + this.state.zenMode.wasSideBarVisible = this.isVisible(Parts.SIDEBAR_PART); + this.state.zenMode.wasPanelVisible = this.isVisible(Parts.PANEL_PART); + + this.setPanelHidden(true, true); + this.setSideBarHidden(true, true); + + if (config.hideActivityBar) { + this.setActivityBarHidden(true, true); } - if (doLayout) { - this.layout({ source: Parts.PANEL_PART }); - } - })); - - this._register(this.sashXTwo.onDidChange((e: ISashEvent) => { - let doLayout = false; - let isPanelVisible = this.partService.isVisible(Parts.PANEL_PART); - let newSashWidth = startPanelWidth - (e.currentX - startXTwo); - - // Panel visible - if (isPanelVisible) { - - // Automatically hide panel when a certain threshold is met - if (newSashWidth + HIDE_PANEL_WIDTH_THRESHOLD < this.partLayoutInfo.panel.minWidth) { - let dragCompensation = this.partLayoutInfo.panel.minWidth - HIDE_PANEL_WIDTH_THRESHOLD; - this.partService.setPanelHidden(true); - startXTwo = Math.min(this.workbenchSize.width - this.activitybarWidth, e.currentX + dragCompensation); - this.panelWidth = startPanelWidth; // when restoring panel, restore to the panel height we started from - } - - // Otherwise size the panel accordingly - else { - this.panelWidth = newSashWidth; - doLayout = newSashWidth >= this.partLayoutInfo.panel.minWidth; - } + if (config.hideStatusBar) { + this.setStatusBarHidden(true, true); } - // Panel hidden - else { - if (startXTwo - e.currentX >= this.partLayoutInfo.panel.minWidth) { - startPanelWidth = 0; - this.panelWidth = this.partLayoutInfo.panel.minWidth; - this.partService.setPanelHidden(false); - } + if (config.hideLineNumbers) { + setLineNumbers('off'); + this.state.zenMode.transitionDisposeables.push(this.editorService.onDidVisibleEditorsChange(() => setLineNumbers('off'))); } - if (doLayout) { - this.layout({ source: Parts.PANEL_PART }); + if (config.hideTabs && this.editorGroupService.partOptions.showTabs) { + this.state.zenMode.transitionDisposeables.push(this.editorGroupService.enforcePartOptions({ showTabs: false })); } - })); - this._register(this.sashXOne.onDidEnd(() => { - this.storageService.store(WorkbenchLayout.sashXOneWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL); - })); + if (config.centerLayout) { + this.centerEditorLayout(true, true); + } + } - this._register(this.sashY.onDidEnd(() => { - this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL); - })); + // Zen Mode Inactive + else { + if (this.state.zenMode.wasPanelVisible) { + this.setPanelHidden(false, true); + } - this._register(this.sashXTwo.onDidEnd(() => { - this.storageService.store(WorkbenchLayout.sashXTwoWidthSettingsKey, this.panelWidth, StorageScope.GLOBAL); - })); + if (this.state.zenMode.wasSideBarVisible) { + this.setSideBarHidden(false, true); + } - this._register(this.sashY.onDidReset(() => { - this.panelHeight = this.sidebarHeight * DEFAULT_PANEL_SIZE_COEFFICIENT; - this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL); + if (this.state.zenMode.transitionedToCenteredEditorLayout) { + this.centerEditorLayout(false, true); + } + setLineNumbers(this.configurationService.getValue('editor.lineNumbers')); + + // Status bar and activity bar visibility come from settings -> update their visibility. + this.doUpdateLayoutConfiguration(true); + + this.editorGroupService.activeGroup.focus(); + + toggleFullScreen = this.state.zenMode.transitionedToFullScreen && this.state.fullscreen; + } + + if (!skipLayout) { this.layout(); - })); + } - this._register(this.sashXOne.onDidReset(() => { - let activeViewlet = this.viewletService.getActiveViewlet(); - let optimalWidth = activeViewlet && activeViewlet.getOptimalWidth(); - this.sidebarWidth = Math.max(optimalWidth, DEFAULT_SIDEBAR_PART_WIDTH); - this.storageService.store(WorkbenchLayout.sashXOneWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL); + if (toggleFullScreen) { + this.windowService.toggleFullScreen(); + } - this.partService.setSideBarHidden(false); - this.layout(); - })); + // Event + this._onZenMode.fire(this.state.zenMode.active); + } - this._register(this.sashXTwo.onDidReset(() => { - this.panelWidth = (this.workbenchSize.width - this.sidebarWidth - this.activitybarWidth) * DEFAULT_PANEL_SIZE_COEFFICIENT; - this.storageService.store(WorkbenchLayout.sashXTwoWidthSettingsKey, this.panelWidth, StorageScope.GLOBAL); + private setStatusBarHidden(hidden: boolean, skipLayout?: boolean): void { + this.state.statusBar.hidden = hidden; - this.layout(); - })); + // Adjust CSS + if (hidden) { + addClass(this.container, 'nostatusbar'); + } else { + removeClass(this.container, 'nostatusbar'); + } + + // Layout + if (!skipLayout) { + if (this.workbenchGrid instanceof Grid) { + this.layout(); + } else { + this.workbenchGrid.layout(); + } + } + } + + protected createWorkbenchLayout(instantiationService: IInstantiationService): void { + const titleBar = this.getPart(Parts.TITLEBAR_PART); + const editorPart = this.getPart(Parts.EDITOR_PART); + const activityBar = this.getPart(Parts.ACTIVITYBAR_PART); + const panelPart = this.getPart(Parts.PANEL_PART); + const sideBar = this.getPart(Parts.SIDEBAR_PART); + const statusBar = this.getPart(Parts.STATUSBAR_PART); + + if (this.configurationService.getValue('workbench.useExperimentalGridLayout')) { + + // Create view wrappers for all parts + this.titleBarPartView = new View(titleBar); + this.sideBarPartView = new View(sideBar); + this.activityBarPartView = new View(activityBar); + this.editorPartView = new View(editorPart); + this.panelPartView = new View(panelPart); + this.statusBarPartView = new View(statusBar); + + this.workbenchGrid = new Grid(this.editorPartView, { proportionalLayout: false }); + + this.container.prepend(this.workbenchGrid.element); + } else { + this.workbenchGrid = instantiationService.createInstance( + WorkbenchLegacyLayout, + this.parent, + this.container, + { + titlebar: titleBar, + activitybar: activityBar, + editor: editorPart, + sidebar: sideBar, + panel: panelPart, + statusbar: statusBar, + } + ); + } } layout(options?: ILayoutOptions): void { - this.workbenchSize = getClientArea(this.parent); + if (!this.disposed) { + this._dimension = getClientArea(this.parent); - const isActivityBarHidden = !this.partService.isVisible(Parts.ACTIVITYBAR_PART); - const isTitlebarHidden = !this.partService.isVisible(Parts.TITLEBAR_PART); - const isPanelHidden = !this.partService.isVisible(Parts.PANEL_PART); - const isStatusbarHidden = !this.partService.isVisible(Parts.STATUSBAR_PART); - const isSidebarHidden = !this.partService.isVisible(Parts.SIDEBAR_PART); - const sidebarPosition = this.partService.getSideBarPosition(); - const panelPosition = this.partService.getPanelPosition(); - const menubarVisibility = this.partService.getMenubarVisibility(); + if (this.workbenchGrid instanceof Grid) { + position(this.container, 0, 0, 0, 0, 'relative'); + size(this.container, this._dimension.width, this._dimension.height); - // Sidebar - if (this.sidebarWidth === -1) { - this.sidebarWidth = this.workbenchSize.width / 5; - } + // Layout the grid widget + this.workbenchGrid.layout(this._dimension.width, this._dimension.height); - this.statusbarHeight = isStatusbarHidden ? 0 : this.partLayoutInfo.statusbar.height; - this.titlebarHeight = isTitlebarHidden ? 0 : this.partLayoutInfo.titlebar.height / (!menubarVisibility || menubarVisibility === 'hidden' ? getZoomFactor() : 1); // adjust for zoom prevention - - this.sidebarHeight = this.workbenchSize.height - this.statusbarHeight - this.titlebarHeight; - let sidebarSize = new Dimension(this.sidebarWidth, this.sidebarHeight); - - // Activity Bar - let activityBarSize = new Dimension(this.activitybarWidth, sidebarSize.height); - - // Panel part - let panelHeight: number; - let panelWidth: number; - const maxPanelHeight = this.computeMaxPanelHeight(); - const maxPanelWidth = this.computeMaxPanelWidth(); - - if (isPanelHidden) { - panelHeight = 0; - panelWidth = 0; - } else if (panelPosition === Position.BOTTOM) { - if (this.panelHeight > 0) { - panelHeight = Math.min(maxPanelHeight, Math.max(this.partLayoutInfo.panel.minHeight, this.panelHeight)); + // Layout grid views + this.layoutGrid(); } else { - panelHeight = sidebarSize.height * DEFAULT_PANEL_SIZE_COEFFICIENT; - } - panelWidth = this.workbenchSize.width - sidebarSize.width - activityBarSize.width; - - if (options && options.toggleMaximizedPanel) { - panelHeight = this.panelMaximized ? Math.max(this.partLayoutInfo.panel.minHeight, Math.min(this.panelSizeBeforeMaximized, maxPanelHeight)) : maxPanelHeight; + this.workbenchGrid.layout(options); } - this.panelMaximized = panelHeight === maxPanelHeight; - if (panelHeight / maxPanelHeight < PANEL_SIZE_BEFORE_MAXIMIZED_BOUNDARY) { - this.panelSizeBeforeMaximized = panelHeight; - } - } else { - panelHeight = sidebarSize.height; - if (this.panelWidth > 0) { - panelWidth = Math.min(maxPanelWidth, Math.max(this.partLayoutInfo.panel.minWidth, this.panelWidth)); - } else { - panelWidth = (this.workbenchSize.width - activityBarSize.width - sidebarSize.width) * DEFAULT_PANEL_SIZE_COEFFICIENT; - } - - if (options && options.toggleMaximizedPanel) { - panelWidth = this.panelMaximized ? Math.max(this.partLayoutInfo.panel.minWidth, Math.min(this.panelSizeBeforeMaximized, maxPanelWidth)) : maxPanelWidth; - } - - this.panelMaximized = panelWidth === maxPanelWidth; - if (panelWidth / maxPanelWidth < PANEL_SIZE_BEFORE_MAXIMIZED_BOUNDARY) { - this.panelSizeBeforeMaximized = panelWidth; - } + // Emit as event + this._onLayout.fire(this._dimension); } - - this.storageService.store(WorkbenchLayout.panelSizeBeforeMaximizedKey, this.panelSizeBeforeMaximized, StorageScope.GLOBAL); - - const panelDimension = new Dimension(panelWidth, panelHeight); - - // Editor - let editorSize = { - width: 0, - height: 0 - }; - - editorSize.width = this.workbenchSize.width - sidebarSize.width - activityBarSize.width - (panelPosition === Position.RIGHT ? panelDimension.width : 0); - editorSize.height = sidebarSize.height - (panelPosition === Position.BOTTOM ? panelDimension.height : 0); - - // Adjust for Editor Part minimum width - const minimumEditorPartSize = new Dimension(this.parts.editor.minimumWidth, this.parts.editor.minimumHeight); - if (editorSize.width < minimumEditorPartSize.width) { - const missingPreferredEditorWidth = minimumEditorPartSize.width - editorSize.width; - let outstandingMissingPreferredEditorWidth = missingPreferredEditorWidth; - - // Take from Panel if Panel Position on the Right and Visible - if (!isPanelHidden && panelPosition === Position.RIGHT && (!options || options.source !== Parts.PANEL_PART)) { - const oldPanelWidth = panelDimension.width; - panelDimension.width = Math.max(this.partLayoutInfo.panel.minWidth, panelDimension.width - outstandingMissingPreferredEditorWidth); - outstandingMissingPreferredEditorWidth -= oldPanelWidth - panelDimension.width; - } - - // Take from Sidebar if Visible - if (!isSidebarHidden && outstandingMissingPreferredEditorWidth > 0) { - const oldSidebarWidth = sidebarSize.width; - sidebarSize.width = Math.max(this.partLayoutInfo.sidebar.minWidth, sidebarSize.width - outstandingMissingPreferredEditorWidth); - outstandingMissingPreferredEditorWidth -= oldSidebarWidth - sidebarSize.width; - } - - editorSize.width += missingPreferredEditorWidth - outstandingMissingPreferredEditorWidth; - if (!isPanelHidden && panelPosition === Position.BOTTOM) { - panelDimension.width = editorSize.width; // ensure panel width is always following editor width - } - } - - // Adjust for Editor Part minimum height - if (editorSize.height < minimumEditorPartSize.height) { - const missingPreferredEditorHeight = minimumEditorPartSize.height - editorSize.height; - let outstandingMissingPreferredEditorHeight = missingPreferredEditorHeight; - - // Take from Panel if Panel Position on the Bottom and Visible - if (!isPanelHidden && panelPosition === Position.BOTTOM) { - const oldPanelHeight = panelDimension.height; - panelDimension.height = Math.max(this.partLayoutInfo.panel.minHeight, panelDimension.height - outstandingMissingPreferredEditorHeight); - outstandingMissingPreferredEditorHeight -= oldPanelHeight - panelDimension.height; - } - - editorSize.height += missingPreferredEditorHeight - outstandingMissingPreferredEditorHeight; - } - - if (!isSidebarHidden) { - this.sidebarWidth = sidebarSize.width; - this.storageService.store(WorkbenchLayout.sashXOneWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL); - } - - if (!isPanelHidden) { - if (panelPosition === Position.BOTTOM) { - this.panelHeight = panelDimension.height; - this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL); - } else { - this.panelWidth = panelDimension.width; - this.storageService.store(WorkbenchLayout.sashXTwoWidthSettingsKey, this.panelWidth, StorageScope.GLOBAL); - } - } - - // Workbench - position(this.workbenchContainer, 0, 0, 0, 0, 'relative'); - size(this.workbenchContainer, this.workbenchSize.width, this.workbenchSize.height); - - // Bug on Chrome: Sometimes Chrome wants to scroll the workbench container on layout changes. The fix is to reset scrolling in this case. - // uses set time to ensure this happens in th next frame (RAF will be at the end of this JS time slice and we don't want that) - setTimeout(() => { - const workbenchContainer = this.workbenchContainer; - if (workbenchContainer.scrollTop > 0) { - workbenchContainer.scrollTop = 0; - } - if (workbenchContainer.scrollLeft > 0) { - workbenchContainer.scrollLeft = 0; - } - }); - - // Title Part - const titleContainer = this.parts.titlebar.getContainer(); - if (isTitlebarHidden) { - hide(titleContainer); - } else { - show(titleContainer); - } - - // Editor Part and Panel part - const editorContainer = this.parts.editor.getContainer(); - const panelContainer = this.parts.panel.getContainer(); - size(editorContainer, editorSize.width, editorSize.height); - size(panelContainer, panelDimension.width, panelDimension.height); - - if (panelPosition === Position.BOTTOM) { - if (sidebarPosition === Position.LEFT) { - position(editorContainer, this.titlebarHeight, 0, this.statusbarHeight + panelDimension.height, sidebarSize.width + activityBarSize.width); - position(panelContainer, editorSize.height + this.titlebarHeight, 0, this.statusbarHeight, sidebarSize.width + activityBarSize.width); - } else { - position(editorContainer, this.titlebarHeight, sidebarSize.width, this.statusbarHeight + panelDimension.height, 0); - position(panelContainer, editorSize.height + this.titlebarHeight, sidebarSize.width, this.statusbarHeight, 0); - } - } else { - if (sidebarPosition === Position.LEFT) { - position(editorContainer, this.titlebarHeight, panelDimension.width, this.statusbarHeight, sidebarSize.width + activityBarSize.width); - position(panelContainer, this.titlebarHeight, 0, this.statusbarHeight, sidebarSize.width + activityBarSize.width + editorSize.width); - } else { - position(editorContainer, this.titlebarHeight, sidebarSize.width + activityBarSize.width + panelWidth, this.statusbarHeight, 0); - position(panelContainer, this.titlebarHeight, sidebarSize.width + activityBarSize.width, this.statusbarHeight, editorSize.width); - } - } - - // Activity Bar Part - const activitybarContainer = this.parts.activitybar.getContainer(); - size(activitybarContainer, null, activityBarSize.height); - if (sidebarPosition === Position.LEFT) { - this.parts.activitybar.getContainer().style.right = ''; - position(activitybarContainer, this.titlebarHeight, null, 0, 0); - } else { - this.parts.activitybar.getContainer().style.left = ''; - position(activitybarContainer, this.titlebarHeight, 0, 0, null); - } - if (isActivityBarHidden) { - hide(activitybarContainer); - } else { - show(activitybarContainer); - } - - // Sidebar Part - const sidebarContainer = this.parts.sidebar.getContainer(); - size(sidebarContainer, sidebarSize.width, sidebarSize.height); - const editorAndPanelWidth = editorSize.width + (panelPosition === Position.RIGHT ? panelWidth : 0); - if (sidebarPosition === Position.LEFT) { - position(sidebarContainer, this.titlebarHeight, editorAndPanelWidth, this.statusbarHeight, activityBarSize.width); - } else { - position(sidebarContainer, this.titlebarHeight, activityBarSize.width, this.statusbarHeight, editorAndPanelWidth); - } - - // Statusbar Part - const statusbarContainer = this.parts.statusbar.getContainer(); - position(statusbarContainer, this.workbenchSize.height - this.statusbarHeight); - if (isStatusbarHidden) { - hide(statusbarContainer); - } else { - show(statusbarContainer); - } - - // Quick open - this.quickopen.layout(this.workbenchSize); - - // Quick input - this.quickInput.layout(this.workbenchSize); - - // Notifications - this.notificationsCenter.layout(this.workbenchSize); - this.notificationsToasts.layout(this.workbenchSize); - - // Sashes - this.sashXOne.layout(); - if (panelPosition === Position.BOTTOM) { - this.sashXTwo.hide(); - this.sashY.layout(); - this.sashY.show(); - } else { - this.sashY.hide(); - this.sashXTwo.layout(); - this.sashXTwo.show(); - } - - // Propagate to Part Layouts - this.parts.titlebar.layout(new Dimension(this.workbenchSize.width, this.titlebarHeight)); - this.parts.editor.layout(new Dimension(editorSize.width, editorSize.height)); - this.parts.sidebar.layout(sidebarSize); - this.parts.panel.layout(panelDimension); - this.parts.activitybar.layout(activityBarSize); - - // Propagate to Context View - this.contextViewService.layout(); } - getVerticalSashTop(sash: Sash): number { - return this.titlebarHeight; + private layoutGrid(): void { + if (!(this.workbenchGrid instanceof Grid)) { + return; + } + + let panelInGrid = this.workbenchGrid.hasView(this.panelPartView); + let sidebarInGrid = this.workbenchGrid.hasView(this.sideBarPartView); + let activityBarInGrid = this.workbenchGrid.hasView(this.activityBarPartView); + let statusBarInGrid = this.workbenchGrid.hasView(this.statusBarPartView); + let titlebarInGrid = this.workbenchGrid.hasView(this.titleBarPartView); + + // Add parts to grid + if (!statusBarInGrid) { + this.workbenchGrid.addView(this.statusBarPartView, Sizing.Split, this.editorPartView, Direction.Down); + statusBarInGrid = true; + } + + if (!titlebarInGrid && getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { + this.workbenchGrid.addView(this.titleBarPartView, Sizing.Split, this.editorPartView, Direction.Up); + titlebarInGrid = true; + } + + if (!activityBarInGrid) { + this.workbenchGrid.addView(this.activityBarPartView, Sizing.Split, panelInGrid && this.state.sideBar.position === this.state.panel.position ? this.panelPartView : this.editorPartView, this.state.sideBar.position === Position.RIGHT ? Direction.Right : Direction.Left); + activityBarInGrid = true; + } + + if (!sidebarInGrid) { + this.workbenchGrid.addView(this.sideBarPartView, this.state.sideBar.width !== undefined ? this.state.sideBar.width : Sizing.Split, this.activityBarPartView, this.state.sideBar.position === Position.LEFT ? Direction.Right : Direction.Left); + sidebarInGrid = true; + } + + if (!panelInGrid) { + this.workbenchGrid.addView(this.panelPartView, this.getPanelDimension(this.state.panel.position) !== undefined ? this.getPanelDimension(this.state.panel.position) : Sizing.Split, this.editorPartView, this.state.panel.position === Position.BOTTOM ? Direction.Down : Direction.Right); + panelInGrid = true; + } + + // Hide parts + if (this.state.panel.hidden) { + this.panelPartView.hide(); + } + + if (this.state.statusBar.hidden) { + this.statusBarPartView.hide(); + } + + if (!this.isVisible(Parts.TITLEBAR_PART)) { + this.titleBarPartView.hide(); + } + + if (this.state.activityBar.hidden) { + this.activityBarPartView.hide(); + } + + if (this.state.sideBar.hidden) { + this.sideBarPartView.hide(); + } + + if (this.state.editor.hidden) { + this.editorPartView.hide(); + } + + // Show visible parts + if (!this.state.editor.hidden) { + this.editorPartView.show(); + } + + if (!this.state.statusBar.hidden) { + this.statusBarPartView.show(); + } + + if (this.isVisible(Parts.TITLEBAR_PART)) { + this.titleBarPartView.show(); + } + + if (!this.state.activityBar.hidden) { + this.activityBarPartView.show(); + } + + if (!this.state.sideBar.hidden) { + this.sideBarPartView.show(); + } + + if (!this.state.panel.hidden) { + this.panelPartView.show(); + } } - getVerticalSashLeft(sash: Sash): number { - let sidebarPosition = this.partService.getSideBarPosition(); - if (sash === this.sashXOne) { + private getPanelDimension(position: Position): number { + return position === Position.BOTTOM ? this.state.panel.height : this.state.panel.width; + } - if (sidebarPosition === Position.LEFT) { - return this.sidebarWidth + this.activitybarWidth; + isEditorLayoutCentered(): boolean { + return this.state.editor.centered; + } + + centerEditorLayout(active: boolean, skipLayout?: boolean): void { + this.state.editor.centered = active; + + this.storageService.store(Storage.CENTERED_LAYOUT_ENABLED, active, StorageScope.WORKSPACE); + + let smartActive = active; + if (this.editorGroupService.groups.length > 1 && this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize')) { + smartActive = false; // Respect the auto resize setting - do not go into centered layout if there is more than 1 group. + } + + // Enter Centered Editor Layout + if (this.editorGroupService.isLayoutCentered() !== smartActive) { + this.editorGroupService.centerLayout(smartActive); + + if (!skipLayout) { + this.layout(); } - - return this.workbenchSize.width - this.sidebarWidth - this.activitybarWidth; } - - return this.workbenchSize.width - this.panelWidth - (sidebarPosition === Position.RIGHT ? this.sidebarWidth + this.activitybarWidth : 0); - } - - getVerticalSashHeight(sash: Sash): number { - if (sash === this.sashXTwo && !this.partService.isVisible(Parts.PANEL_PART)) { - return 0; - } - - return this.sidebarHeight; - } - - getHorizontalSashTop(sash: Sash): number { - const offset = 2; // Horizontal sash should be a bit lower than the editor area, thus add 2px #5524 - return offset + (this.partService.isVisible(Parts.PANEL_PART) ? this.sidebarHeight - this.panelHeight + this.titlebarHeight : this.sidebarHeight + this.titlebarHeight); - } - - getHorizontalSashLeft(sash: Sash): number { - if (this.partService.getSideBarPosition() === Position.RIGHT) { - return 0; - } - - return this.sidebarWidth + this.activitybarWidth; - } - - getHorizontalSashWidth(sash: Sash): number { - return this.panelWidth; - } - - isPanelMaximized(): boolean { - return this.panelMaximized; } resizePart(part: Parts, sizeChange: number): void { - const panelPosition = this.partService.getPanelPosition(); - const sizeChangePxWidth = this.workbenchSize.width * (sizeChange / 100); - const sizeChangePxHeight = this.workbenchSize.height * (sizeChange / 100); - - let doLayout = false; + let view: View; switch (part) { case Parts.SIDEBAR_PART: - this.sidebarWidth = this.sidebarWidth + sizeChangePxWidth; // Sidebar can not become smaller than MIN_PART_WIDTH - - if (this.workbenchSize.width - this.sidebarWidth < this.parts.editor.minimumWidth) { - this.sidebarWidth = this.workbenchSize.width - this.parts.editor.minimumWidth; - } - - doLayout = true; - break; + view = this.sideBarPartView; case Parts.PANEL_PART: - if (panelPosition === Position.BOTTOM) { - this.panelHeight = this.panelHeight + sizeChangePxHeight; - } else if (panelPosition === Position.RIGHT) { - this.panelWidth = this.panelWidth + sizeChangePxWidth; - } - - doLayout = true; - break; + view = this.panelPartView; case Parts.EDITOR_PART: - // If we have one editor we can cheat and resize sidebar with the negative delta - // If the sidebar is not visible and panel is, resize panel main axis with negative Delta - if (this.editorGroupService.count === 1) { - if (this.partService.isVisible(Parts.SIDEBAR_PART)) { - this.sidebarWidth = this.sidebarWidth - sizeChangePxWidth; - doLayout = true; - } else if (this.partService.isVisible(Parts.PANEL_PART)) { - if (panelPosition === Position.BOTTOM) { - this.panelHeight = this.panelHeight - sizeChangePxHeight; - } else if (panelPosition === Position.RIGHT) { - this.panelWidth = this.panelWidth - sizeChangePxWidth; - } - doLayout = true; - } + view = this.editorPartView; + if (this.workbenchGrid instanceof Grid) { + this.workbenchGrid.resizeView(view, this.workbenchGrid.getViewSize(view) + sizeChange); } else { - const activeGroup = this.editorGroupService.activeGroup; - - const activeGroupSize = this.editorGroupService.getSize(activeGroup); - this.editorGroupService.setSize(activeGroup, activeGroupSize + sizeChangePxWidth); + this.workbenchGrid.resizePart(part, sizeChange); } + break; + default: + return; // Cannot resize other parts + } + } + + setActivityBarHidden(hidden: boolean, skipLayout?: boolean): void { + this.state.activityBar.hidden = hidden; + + // Layout + if (!skipLayout) { + if (this.workbenchGrid instanceof Grid) { + this.layout(); + } else { + this.workbenchGrid.layout(); + } + } + } + + setEditorHidden(hidden: boolean, skipLayout?: boolean): void { + if (!(this.workbenchGrid instanceof Grid) || hidden === this.state.editor.hidden) { + return; } - if (doLayout) { + this.state.editor.hidden = hidden; + + // The editor and the panel cannot be hidden at the same time + if (this.state.editor.hidden && this.state.panel.hidden) { + this.setPanelHidden(false, true); + } + + if (!skipLayout) { this.layout(); } } + + setSideBarHidden(hidden: boolean, skipLayout?: boolean): void { + this.state.sideBar.hidden = hidden; + + // Adjust CSS + if (hidden) { + addClass(this.container, 'nosidebar'); + } else { + removeClass(this.container, 'nosidebar'); + } + + // If sidebar becomes hidden, also hide the current active Viewlet if any + if (hidden && this.viewletService.getActiveViewlet()) { + this.viewletService.hideActiveViewlet(); + + // Pass Focus to Editor or Panel if Sidebar is now hidden + const activePanel = this.panelService.getActivePanel(); + if (this.hasFocus(Parts.PANEL_PART) && activePanel) { + activePanel.focus(); + } else { + this.editorGroupService.activeGroup.focus(); + } + } + + // If sidebar becomes visible, show last active Viewlet or default viewlet + else if (!hidden && !this.viewletService.getActiveViewlet()) { + const viewletToOpen = this.viewletService.getLastActiveViewletId(); + if (viewletToOpen) { + const viewlet = this.viewletService.openViewlet(viewletToOpen, true); + if (!viewlet) { + this.viewletService.openViewlet(this.viewletService.getDefaultViewletId(), true); + } + } + } + + // Remember in settings + const defaultHidden = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY; + if (hidden !== defaultHidden) { + this.storageService.store(Storage.SIDEBAR_HIDDEN, hidden ? 'true' : 'false', StorageScope.WORKSPACE); + } else { + this.storageService.remove(Storage.SIDEBAR_HIDDEN, StorageScope.WORKSPACE); + } + + // Layout + if (!skipLayout) { + if (this.workbenchGrid instanceof Grid) { + this.layout(); + } else { + this.workbenchGrid.layout(); + } + } + } + + setPanelHidden(hidden: boolean, skipLayout?: boolean): void { + this.state.panel.hidden = hidden; + + // Adjust CSS + if (hidden) { + addClass(this.container, 'nopanel'); + } else { + removeClass(this.container, 'nopanel'); + } + + // If panel part becomes hidden, also hide the current active panel if any + if (hidden && this.panelService.getActivePanel()) { + this.panelService.hideActivePanel(); + this.editorGroupService.activeGroup.focus(); // Pass focus to editor group if panel part is now hidden + } + + // If panel part becomes visible, show last active panel or default panel + else if (!hidden && !this.panelService.getActivePanel()) { + const panelToOpen = this.panelService.getLastActivePanelId(); + if (panelToOpen) { + const focus = !skipLayout; + this.panelService.openPanel(panelToOpen, focus); + } + } + + // Remember in settings + if (!hidden) { + this.storageService.store(Storage.PANEL_HIDDEN, 'false', StorageScope.WORKSPACE); + } else { + this.storageService.remove(Storage.PANEL_HIDDEN, StorageScope.WORKSPACE); + } + + // The editor and panel cannot be hidden at the same time + if (hidden && this.state.editor.hidden) { + this.setEditorHidden(false, true); + } + + // Layout + if (!skipLayout) { + if (this.workbenchGrid instanceof Grid) { + this.layout(); + } else { + this.workbenchGrid.layout(); + } + } + } + + toggleMaximizedPanel(): void { + if (this.workbenchGrid instanceof Grid) { + this.workbenchGrid.maximizeViewSize(this.panelPartView); + } else { + this.workbenchGrid.layout({ toggleMaximizedPanel: true, source: Parts.PANEL_PART }); + } + } + + isPanelMaximized(): boolean { + if (this.workbenchGrid instanceof Grid) { + try { + return this.workbenchGrid.getViewSize2(this.panelPartView).height === this.getPart(Parts.PANEL_PART).maximumHeight; + } catch (e) { + return false; + } + } else { + return this.workbenchGrid.isPanelMaximized(); + } + } + + getSideBarPosition(): Position { + return this.state.sideBar.position; + } + + setMenubarVisibility(visibility: MenuBarVisibility, skipLayout: boolean): void { + if (this.state.menuBar.visibility !== visibility) { + this.state.menuBar.visibility = visibility; + + // Layout + if (!skipLayout) { + if (this.workbenchGrid instanceof Grid) { + const dimensions = getClientArea(this.parent); + this.workbenchGrid.layout(dimensions.width, dimensions.height); + } else { + this.workbenchGrid.layout(); + } + } + } + } + + getMenubarVisibility(): MenuBarVisibility { + return this.state.menuBar.visibility; + } + + getPanelPosition(): Position { + return this.state.panel.position; + } + + setPanelPosition(position: Position): void { + const panelPart = this.getPart(Parts.PANEL_PART); + const wasHidden = this.state.panel.hidden; + + if (this.state.panel.hidden) { + this.setPanelHidden(false, true /* Skip Layout */); + } else { + this.savePanelDimension(); + } + + const newPositionValue = (position === Position.BOTTOM) ? 'bottom' : 'right'; + const oldPositionValue = (this.state.panel.position === Position.BOTTOM) ? 'bottom' : 'right'; + this.state.panel.position = position; + + function positionToString(position: Position): string { + switch (position) { + case Position.LEFT: return 'left'; + case Position.RIGHT: return 'right'; + case Position.BOTTOM: return 'bottom'; + } + } + + this.storageService.store(Storage.PANEL_POSITION, positionToString(this.state.panel.position), StorageScope.WORKSPACE); + + // Adjust CSS + removeClass(panelPart.getContainer(), oldPositionValue); + addClass(panelPart.getContainer(), newPositionValue); + + // Update Styles + panelPart.updateStyles(); + + // Layout + if (this.workbenchGrid instanceof Grid) { + if (!wasHidden) { + this.savePanelDimension(); + } + + this.workbenchGrid.removeView(this.panelPartView); + this.layout(); + } else { + this.workbenchGrid.layout(); + } + } + + private savePanelDimension(): void { + if (!(this.workbenchGrid instanceof Grid)) { + return; + } + + if (this.state.panel.position === Position.BOTTOM) { + this.state.panel.height = this.workbenchGrid.getViewSize(this.panelPartView); + } else { + this.state.panel.width = this.workbenchGrid.getViewSize(this.panelPartView); + } + } + + private saveLayoutState(e: IWillSaveStateEvent): void { + + // Zen Mode + if (this.state.zenMode.active) { + this.storageService.store(Storage.ZEN_MODE_ENABLED, true, StorageScope.WORKSPACE); + } else { + this.storageService.remove(Storage.ZEN_MODE_ENABLED, StorageScope.WORKSPACE); + } + + if (e.reason === WillSaveStateReason.SHUTDOWN && this.state.zenMode.active) { + if (!this.configurationService.getValue(Settings.ZEN_MODE_RESTORE)) { + this.toggleZenMode(true); // We will not restore zen mode, need to clear all zen mode state changes + } + } + } + + dispose(): void { + super.dispose(); + + this.disposed = true; + } } diff --git a/src/vs/workbench/browser/legacyLayout.ts b/src/vs/workbench/browser/legacyLayout.ts new file mode 100644 index 0000000000..ea2a2ed200 --- /dev/null +++ b/src/vs/workbench/browser/legacyLayout.ts @@ -0,0 +1,738 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Sash, ISashEvent, IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider, Orientation } from 'vs/base/browser/ui/sash/sash'; +import { IWorkbenchLayoutService, Position, ILayoutOptions, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { isMacintosh } from 'vs/base/common/platform'; +import { memoize } from 'vs/base/common/decorators'; +import { Dimension, getClientArea, size, position, hide, show } from 'vs/base/browser/dom'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { getZoomFactor } from 'vs/base/browser/browser'; +import { Part } from 'vs/workbench/browser/part'; + +const TITLE_BAR_HEIGHT = isMacintosh ? 22 : 30; +const STATUS_BAR_HEIGHT = 22; +const ACTIVITY_BAR_WIDTH = 50; + +const MIN_SIDEBAR_PART_WIDTH = 170; +const DEFAULT_SIDEBAR_PART_WIDTH = 300; +const HIDE_SIDEBAR_WIDTH_THRESHOLD = 50; + +const MIN_PANEL_PART_HEIGHT = 77; +const MIN_PANEL_PART_WIDTH = 300; +const DEFAULT_PANEL_PART_SIZE = 350; +const DEFAULT_PANEL_SIZE_COEFFICIENT = 0.4; +const PANEL_SIZE_BEFORE_MAXIMIZED_BOUNDARY = 0.7; +const HIDE_PANEL_HEIGHT_THRESHOLD = 50; +const HIDE_PANEL_WIDTH_THRESHOLD = 100; + +/** + * @deprecated to be replaced by new Grid layout + */ +export class WorkbenchLegacyLayout extends Disposable implements IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider { + + private static readonly sashXOneWidthSettingsKey = 'workbench.sidebar.width'; + private static readonly sashXTwoWidthSettingsKey = 'workbench.panel.width'; + private static readonly sashYHeightSettingsKey = 'workbench.panel.height'; + private static readonly panelSizeBeforeMaximizedKey = 'workbench.panel.sizeBeforeMaximized'; + + private workbenchSize: Dimension; + + private sashXOne: Sash; + private sashXTwo: Sash; + private sashY: Sash; + + private _sidebarWidth: number; + private sidebarHeight: number; + private titlebarHeight: number; + private statusbarHeight: number; + private panelSizeBeforeMaximized: number; + private panelMaximized: boolean; + private _panelHeight: number; + private _panelWidth: number; + + constructor( + private parent: HTMLElement, + private workbenchContainer: HTMLElement, + private parts: { + titlebar: Part, + activitybar: Part, + editor: Part, + sidebar: Part, + panel: Part, + statusbar: Part + }, + @IStorageService private readonly storageService: IStorageService, + @IContextViewService private readonly contextViewService: IContextViewService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IViewletService private readonly viewletService: IViewletService, + @IThemeService private readonly themeService: IThemeService, + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService + ) { + super(); + + // Restore state + this.restorePreviousState(); + + // Create layout sashes + this.sashXOne = new Sash(this.workbenchContainer, this); + this.sashXTwo = new Sash(this.workbenchContainer, this); + this.sashY = new Sash(this.workbenchContainer, this, { orientation: Orientation.HORIZONTAL }); + + this.registerListeners(); + } + + private restorePreviousState(): void { + this._sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, this.storageService.getNumber(WorkbenchLegacyLayout.sashXOneWidthSettingsKey, StorageScope.GLOBAL, DEFAULT_SIDEBAR_PART_WIDTH)); + + this._panelWidth = Math.max(this.partLayoutInfo.panel.minWidth, this.storageService.getNumber(WorkbenchLegacyLayout.sashXTwoWidthSettingsKey, StorageScope.GLOBAL, DEFAULT_PANEL_PART_SIZE)); + this._panelHeight = Math.max(this.partLayoutInfo.panel.minHeight, this.storageService.getNumber(WorkbenchLegacyLayout.sashYHeightSettingsKey, StorageScope.GLOBAL, DEFAULT_PANEL_PART_SIZE)); + + this.panelMaximized = false; + this.panelSizeBeforeMaximized = this.storageService.getNumber(WorkbenchLegacyLayout.panelSizeBeforeMaximizedKey, StorageScope.GLOBAL, 0); + } + + private registerListeners(): void { + this._register(this.themeService.onThemeChange(_ => this.layout())); + this._register((this.parts.editor as any).onDidSizeConstraintsChange(() => this.onDidEditorSizeConstraintsChange())); + + this.registerSashListeners(); + } + + private onDidEditorSizeConstraintsChange(): void { + if (this.workbenchSize && (this.sidebarWidth || this.panelHeight)) { + if (this.editorGroupService.count > 1) { + const minimumEditorPartSize = new Dimension(this.parts.editor.minimumWidth, this.parts.editor.minimumHeight); + + const sidebarOverflow = this.workbenchSize.width - this.sidebarWidth < minimumEditorPartSize.width; + + let panelOverflow = false; + if (this.layoutService.getPanelPosition() === Position.RIGHT) { + panelOverflow = this.workbenchSize.width - this.panelWidth - this.sidebarWidth < minimumEditorPartSize.width; + } else { + panelOverflow = this.workbenchSize.height - this.panelHeight < minimumEditorPartSize.height; + } + + // Trigger a layout if we detect that either sidebar or panel overflow + // as a matter of a new editor group being added to the editor part + if (sidebarOverflow || panelOverflow) { + this.layout(); + } + } + } + } + + private get activitybarWidth(): number { + if (this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) { + return this.partLayoutInfo.activitybar.width; + } + + return 0; + } + + private get panelHeight(): number { + const panelPosition = this.layoutService.getPanelPosition(); + if (panelPosition === Position.RIGHT) { + return this.sidebarHeight; + } + + return this._panelHeight; + } + + private set panelHeight(value: number) { + this._panelHeight = Math.min(this.computeMaxPanelHeight(), Math.max(this.partLayoutInfo.panel.minHeight, value)); + } + + private get panelWidth(): number { + const panelPosition = this.layoutService.getPanelPosition(); + if (panelPosition === Position.BOTTOM) { + return this.workbenchSize.width - this.activitybarWidth - this.sidebarWidth; + } + + return this._panelWidth; + } + + private set panelWidth(value: number) { + this._panelWidth = Math.min(this.computeMaxPanelWidth(), Math.max(this.partLayoutInfo.panel.minWidth, value)); + } + + private computeMaxPanelWidth(): number { + let minSidebarWidth: number; + if (this.layoutService.isVisible(Parts.SIDEBAR_PART)) { + if (this.layoutService.getSideBarPosition() === Position.LEFT) { + minSidebarWidth = this.partLayoutInfo.sidebar.minWidth; + } else { + minSidebarWidth = this.sidebarWidth; + } + } else { + minSidebarWidth = 0; + } + + return Math.max(this.partLayoutInfo.panel.minWidth, this.workbenchSize.width - this.parts.editor.minimumWidth - minSidebarWidth - this.activitybarWidth); + } + + private computeMaxPanelHeight(): number { + return Math.max(this.partLayoutInfo.panel.minHeight, this.sidebarHeight /* simplification for: window.height - status.height - title-height */ - this.parts.editor.minimumHeight); + } + + private get sidebarWidth(): number { + if (this.layoutService.isVisible(Parts.SIDEBAR_PART)) { + return this._sidebarWidth; + } + + return 0; + } + + private set sidebarWidth(value: number) { + const panelMinWidth = this.layoutService.getPanelPosition() === Position.RIGHT && this.layoutService.isVisible(Parts.PANEL_PART) ? this.partLayoutInfo.panel.minWidth : 0; + const maxSidebarWidth = this.workbenchSize.width - this.activitybarWidth - this.parts.editor.minimumWidth - panelMinWidth; + + this._sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, Math.min(maxSidebarWidth, value)); + } + + @memoize + public get partLayoutInfo() { + return { + titlebar: { + height: TITLE_BAR_HEIGHT + }, + activitybar: { + width: ACTIVITY_BAR_WIDTH + }, + sidebar: { + minWidth: MIN_SIDEBAR_PART_WIDTH + }, + panel: { + minHeight: MIN_PANEL_PART_HEIGHT, + minWidth: MIN_PANEL_PART_WIDTH + }, + statusbar: { + height: STATUS_BAR_HEIGHT + } + }; + } + + private registerSashListeners(): void { + let startX: number = 0; + let startY: number = 0; + let startXTwo: number = 0; + let startSidebarWidth: number; + let startPanelHeight: number; + let startPanelWidth: number; + + this._register(this.sashXOne.onDidStart((e: ISashEvent) => { + startSidebarWidth = this.sidebarWidth; + startX = e.startX; + })); + + this._register(this.sashY.onDidStart((e: ISashEvent) => { + startPanelHeight = this.panelHeight; + startY = e.startY; + })); + + this._register(this.sashXTwo.onDidStart((e: ISashEvent) => { + startPanelWidth = this.panelWidth; + startXTwo = e.startX; + })); + + this._register(this.sashXOne.onDidChange((e: ISashEvent) => { + let doLayout = false; + let sidebarPosition = this.layoutService.getSideBarPosition(); + let isSidebarVisible = this.layoutService.isVisible(Parts.SIDEBAR_PART); + let newSashWidth = (sidebarPosition === Position.LEFT) ? startSidebarWidth + e.currentX - startX : startSidebarWidth - e.currentX + startX; + + // Sidebar visible + if (isSidebarVisible) { + + // Automatically hide side bar when a certain threshold is met + if (newSashWidth + HIDE_SIDEBAR_WIDTH_THRESHOLD < this.partLayoutInfo.sidebar.minWidth) { + let dragCompensation = this.partLayoutInfo.sidebar.minWidth - HIDE_SIDEBAR_WIDTH_THRESHOLD; + this.layoutService.setSideBarHidden(true); + startX = (sidebarPosition === Position.LEFT) ? Math.max(this.activitybarWidth, e.currentX - dragCompensation) : Math.min(e.currentX + dragCompensation, this.workbenchSize.width - this.activitybarWidth); + this.sidebarWidth = startSidebarWidth; // when restoring sidebar, restore to the sidebar width we started from + } + + // Otherwise size the sidebar accordingly + else { + this.sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, newSashWidth); // Sidebar can not become smaller than MIN_PART_WIDTH + doLayout = newSashWidth >= this.partLayoutInfo.sidebar.minWidth; + } + } + + // Sidebar hidden + else { + if ((sidebarPosition === Position.LEFT && e.currentX - startX >= this.partLayoutInfo.sidebar.minWidth) || + (sidebarPosition === Position.RIGHT && startX - e.currentX >= this.partLayoutInfo.sidebar.minWidth)) { + startSidebarWidth = this.partLayoutInfo.sidebar.minWidth - (sidebarPosition === Position.LEFT ? e.currentX - startX : startX - e.currentX); + this.sidebarWidth = this.partLayoutInfo.sidebar.minWidth; + this.layoutService.setSideBarHidden(false); + } + } + + if (doLayout) { + this.layout({ source: Parts.SIDEBAR_PART }); + } + })); + + this._register(this.sashY.onDidChange((e: ISashEvent) => { + let doLayout = false; + let isPanelVisible = this.layoutService.isVisible(Parts.PANEL_PART); + let newSashHeight = startPanelHeight - (e.currentY - startY); + + // Panel visible + if (isPanelVisible) { + + // Automatically hide panel when a certain threshold is met + if (newSashHeight + HIDE_PANEL_HEIGHT_THRESHOLD < this.partLayoutInfo.panel.minHeight) { + let dragCompensation = this.partLayoutInfo.panel.minHeight - HIDE_PANEL_HEIGHT_THRESHOLD; + this.layoutService.setPanelHidden(true); + startY = Math.min(this.sidebarHeight - this.statusbarHeight - this.titlebarHeight, e.currentY + dragCompensation); + this.panelHeight = startPanelHeight; // when restoring panel, restore to the panel height we started from + } + + // Otherwise size the panel accordingly + else { + this.panelHeight = Math.max(this.partLayoutInfo.panel.minHeight, newSashHeight); // Panel can not become smaller than MIN_PART_HEIGHT + doLayout = newSashHeight >= this.partLayoutInfo.panel.minHeight; + } + } + + // Panel hidden + else { + if (startY - e.currentY >= this.partLayoutInfo.panel.minHeight) { + startPanelHeight = 0; + this.panelHeight = this.partLayoutInfo.panel.minHeight; + this.layoutService.setPanelHidden(false); + } + } + + if (doLayout) { + this.layout({ source: Parts.PANEL_PART }); + } + })); + + this._register(this.sashXTwo.onDidChange((e: ISashEvent) => { + let doLayout = false; + let isPanelVisible = this.layoutService.isVisible(Parts.PANEL_PART); + let newSashWidth = startPanelWidth - (e.currentX - startXTwo); + + // Panel visible + if (isPanelVisible) { + + // Automatically hide panel when a certain threshold is met + if (newSashWidth + HIDE_PANEL_WIDTH_THRESHOLD < this.partLayoutInfo.panel.minWidth) { + let dragCompensation = this.partLayoutInfo.panel.minWidth - HIDE_PANEL_WIDTH_THRESHOLD; + this.layoutService.setPanelHidden(true); + startXTwo = Math.min(this.workbenchSize.width - this.activitybarWidth, e.currentX + dragCompensation); + this.panelWidth = startPanelWidth; // when restoring panel, restore to the panel height we started from + } + + // Otherwise size the panel accordingly + else { + this.panelWidth = newSashWidth; + doLayout = newSashWidth >= this.partLayoutInfo.panel.minWidth; + } + } + + // Panel hidden + else { + if (startXTwo - e.currentX >= this.partLayoutInfo.panel.minWidth) { + startPanelWidth = 0; + this.panelWidth = this.partLayoutInfo.panel.minWidth; + this.layoutService.setPanelHidden(false); + } + } + + if (doLayout) { + this.layout({ source: Parts.PANEL_PART }); + } + })); + + this._register(this.sashXOne.onDidEnd(() => { + this.storageService.store(WorkbenchLegacyLayout.sashXOneWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL); + })); + + this._register(this.sashY.onDidEnd(() => { + this.storageService.store(WorkbenchLegacyLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL); + })); + + this._register(this.sashXTwo.onDidEnd(() => { + this.storageService.store(WorkbenchLegacyLayout.sashXTwoWidthSettingsKey, this.panelWidth, StorageScope.GLOBAL); + })); + + this._register(this.sashY.onDidReset(() => { + this.panelHeight = this.sidebarHeight * DEFAULT_PANEL_SIZE_COEFFICIENT; + this.storageService.store(WorkbenchLegacyLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL); + + this.layout(); + })); + + this._register(this.sashXOne.onDidReset(() => { + const activeViewlet = this.viewletService.getActiveViewlet(); + const optimalWidth = activeViewlet ? activeViewlet.getOptimalWidth() : null; + this.sidebarWidth = typeof optimalWidth === 'number' ? Math.max(optimalWidth, DEFAULT_SIDEBAR_PART_WIDTH) : DEFAULT_SIDEBAR_PART_WIDTH; + this.storageService.store(WorkbenchLegacyLayout.sashXOneWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL); + + this.layoutService.setSideBarHidden(false); + this.layout(); + })); + + this._register(this.sashXTwo.onDidReset(() => { + this.panelWidth = (this.workbenchSize.width - this.sidebarWidth - this.activitybarWidth) * DEFAULT_PANEL_SIZE_COEFFICIENT; + this.storageService.store(WorkbenchLegacyLayout.sashXTwoWidthSettingsKey, this.panelWidth, StorageScope.GLOBAL); + + this.layout(); + })); + } + + layout(options?: ILayoutOptions): void { + this.workbenchSize = getClientArea(this.parent); + + const isActivityBarHidden = !this.layoutService.isVisible(Parts.ACTIVITYBAR_PART); + const isTitlebarHidden = !this.layoutService.isVisible(Parts.TITLEBAR_PART); + const isPanelHidden = !this.layoutService.isVisible(Parts.PANEL_PART); + const isStatusbarHidden = !this.layoutService.isVisible(Parts.STATUSBAR_PART); + const isSidebarHidden = !this.layoutService.isVisible(Parts.SIDEBAR_PART); + const sidebarPosition = this.layoutService.getSideBarPosition(); + const panelPosition = this.layoutService.getPanelPosition(); + const menubarVisibility = this.layoutService.getMenubarVisibility(); + + // Sidebar + if (this.sidebarWidth === -1) { + this.sidebarWidth = this.workbenchSize.width / 5; + } + + this.statusbarHeight = isStatusbarHidden ? 0 : this.partLayoutInfo.statusbar.height; + this.titlebarHeight = isTitlebarHidden ? 0 : this.partLayoutInfo.titlebar.height / (isMacintosh || !menubarVisibility || menubarVisibility === 'hidden' ? getZoomFactor() : 1); // adjust for zoom prevention + + this.sidebarHeight = this.workbenchSize.height - this.statusbarHeight - this.titlebarHeight; + let sidebarSize = new Dimension(this.sidebarWidth, this.sidebarHeight); + + // Activity Bar + let activityBarSize = new Dimension(this.activitybarWidth, sidebarSize.height); + + // Panel part + let panelHeight: number; + let panelWidth: number; + const maxPanelHeight = this.computeMaxPanelHeight(); + const maxPanelWidth = this.computeMaxPanelWidth(); + + if (isPanelHidden) { + panelHeight = 0; + panelWidth = 0; + } else if (panelPosition === Position.BOTTOM) { + if (this.panelHeight > 0) { + panelHeight = Math.min(maxPanelHeight, Math.max(this.partLayoutInfo.panel.minHeight, this.panelHeight)); + } else { + panelHeight = sidebarSize.height * DEFAULT_PANEL_SIZE_COEFFICIENT; + } + panelWidth = this.workbenchSize.width - sidebarSize.width - activityBarSize.width; + + if (options && options.toggleMaximizedPanel) { + panelHeight = this.panelMaximized ? Math.max(this.partLayoutInfo.panel.minHeight, Math.min(this.panelSizeBeforeMaximized, maxPanelHeight)) : maxPanelHeight; + } + + this.panelMaximized = panelHeight === maxPanelHeight; + if (panelHeight / maxPanelHeight < PANEL_SIZE_BEFORE_MAXIMIZED_BOUNDARY) { + this.panelSizeBeforeMaximized = panelHeight; + } + } else { + panelHeight = sidebarSize.height; + if (this.panelWidth > 0) { + panelWidth = Math.min(maxPanelWidth, Math.max(this.partLayoutInfo.panel.minWidth, this.panelWidth)); + } else { + panelWidth = (this.workbenchSize.width - activityBarSize.width - sidebarSize.width) * DEFAULT_PANEL_SIZE_COEFFICIENT; + } + + if (options && options.toggleMaximizedPanel) { + panelWidth = this.panelMaximized ? Math.max(this.partLayoutInfo.panel.minWidth, Math.min(this.panelSizeBeforeMaximized, maxPanelWidth)) : maxPanelWidth; + } + + this.panelMaximized = panelWidth === maxPanelWidth; + if (panelWidth / maxPanelWidth < PANEL_SIZE_BEFORE_MAXIMIZED_BOUNDARY) { + this.panelSizeBeforeMaximized = panelWidth; + } + } + + this.storageService.store(WorkbenchLegacyLayout.panelSizeBeforeMaximizedKey, this.panelSizeBeforeMaximized, StorageScope.GLOBAL); + + const panelDimension = new Dimension(panelWidth, panelHeight); + + // Editor + let editorSize = { + width: 0, + height: 0 + }; + + editorSize.width = this.workbenchSize.width - sidebarSize.width - activityBarSize.width - (panelPosition === Position.RIGHT ? panelDimension.width : 0); + editorSize.height = sidebarSize.height - (panelPosition === Position.BOTTOM ? panelDimension.height : 0); + + // Adjust for Editor Part minimum width + const minimumEditorPartSize = new Dimension(this.parts.editor.minimumWidth, this.parts.editor.minimumHeight); + if (editorSize.width < minimumEditorPartSize.width) { + const missingPreferredEditorWidth = minimumEditorPartSize.width - editorSize.width; + let outstandingMissingPreferredEditorWidth = missingPreferredEditorWidth; + + // Take from Panel if Panel Position on the Right and Visible + if (!isPanelHidden && panelPosition === Position.RIGHT && (!options || options.source !== Parts.PANEL_PART)) { + const oldPanelWidth = panelDimension.width; + panelDimension.width = Math.max(this.partLayoutInfo.panel.minWidth, panelDimension.width - outstandingMissingPreferredEditorWidth); + outstandingMissingPreferredEditorWidth -= oldPanelWidth - panelDimension.width; + } + + // Take from Sidebar if Visible + if (!isSidebarHidden && outstandingMissingPreferredEditorWidth > 0) { + const oldSidebarWidth = sidebarSize.width; + sidebarSize.width = Math.max(this.partLayoutInfo.sidebar.minWidth, sidebarSize.width - outstandingMissingPreferredEditorWidth); + outstandingMissingPreferredEditorWidth -= oldSidebarWidth - sidebarSize.width; + } + + editorSize.width += missingPreferredEditorWidth - outstandingMissingPreferredEditorWidth; + if (!isPanelHidden && panelPosition === Position.BOTTOM) { + panelDimension.width = editorSize.width; // ensure panel width is always following editor width + } + } + + // Adjust for Editor Part minimum height + if (editorSize.height < minimumEditorPartSize.height) { + const missingPreferredEditorHeight = minimumEditorPartSize.height - editorSize.height; + let outstandingMissingPreferredEditorHeight = missingPreferredEditorHeight; + + // Take from Panel if Panel Position on the Bottom and Visible + if (!isPanelHidden && panelPosition === Position.BOTTOM) { + const oldPanelHeight = panelDimension.height; + panelDimension.height = Math.max(this.partLayoutInfo.panel.minHeight, panelDimension.height - outstandingMissingPreferredEditorHeight); + outstandingMissingPreferredEditorHeight -= oldPanelHeight - panelDimension.height; + } + + editorSize.height += missingPreferredEditorHeight - outstandingMissingPreferredEditorHeight; + } + + if (!isSidebarHidden) { + this.sidebarWidth = sidebarSize.width; + this.storageService.store(WorkbenchLegacyLayout.sashXOneWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL); + } + + if (!isPanelHidden) { + if (panelPosition === Position.BOTTOM) { + this.panelHeight = panelDimension.height; + this.storageService.store(WorkbenchLegacyLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL); + } else { + this.panelWidth = panelDimension.width; + this.storageService.store(WorkbenchLegacyLayout.sashXTwoWidthSettingsKey, this.panelWidth, StorageScope.GLOBAL); + } + } + + // Workbench + position(this.workbenchContainer, 0, 0, 0, 0, 'relative'); + size(this.workbenchContainer, this.workbenchSize.width, this.workbenchSize.height); + + // Bug on Chrome: Sometimes Chrome wants to scroll the workbench container on layout changes. The fix is to reset scrolling in this case. + // uses set time to ensure this happens in th next frame (RAF will be at the end of this JS time slice and we don't want that) + setTimeout(() => { + const workbenchContainer = this.workbenchContainer; + if (workbenchContainer.scrollTop > 0) { + workbenchContainer.scrollTop = 0; + } + if (workbenchContainer.scrollLeft > 0) { + workbenchContainer.scrollLeft = 0; + } + }); + + // Title Part + const titleContainer = this.parts.titlebar.getContainer(); + if (isTitlebarHidden) { + hide(titleContainer); + } else { + show(titleContainer); + } + + // Editor Part and Panel part + const editorContainer = this.parts.editor.getContainer(); + const panelContainer = this.parts.panel.getContainer(); + size(editorContainer, editorSize.width, editorSize.height); + size(panelContainer, panelDimension.width, panelDimension.height); + + if (panelPosition === Position.BOTTOM) { + if (sidebarPosition === Position.LEFT) { + position(editorContainer, this.titlebarHeight, 0, this.statusbarHeight + panelDimension.height, sidebarSize.width + activityBarSize.width); + position(panelContainer, editorSize.height + this.titlebarHeight, 0, this.statusbarHeight, sidebarSize.width + activityBarSize.width); + } else { + position(editorContainer, this.titlebarHeight, sidebarSize.width, this.statusbarHeight + panelDimension.height, 0); + position(panelContainer, editorSize.height + this.titlebarHeight, sidebarSize.width, this.statusbarHeight, 0); + } + } else { + if (sidebarPosition === Position.LEFT) { + position(editorContainer, this.titlebarHeight, panelDimension.width, this.statusbarHeight, sidebarSize.width + activityBarSize.width); + position(panelContainer, this.titlebarHeight, 0, this.statusbarHeight, sidebarSize.width + activityBarSize.width + editorSize.width); + } else { + position(editorContainer, this.titlebarHeight, sidebarSize.width + activityBarSize.width + panelWidth, this.statusbarHeight, 0); + position(panelContainer, this.titlebarHeight, sidebarSize.width + activityBarSize.width, this.statusbarHeight, editorSize.width); + } + } + + // Activity Bar Part + const activitybarContainer = this.parts.activitybar.getContainer(); + size(activitybarContainer, null, activityBarSize.height); + if (sidebarPosition === Position.LEFT) { + this.parts.activitybar.getContainer().style.right = ''; + position(activitybarContainer, this.titlebarHeight, undefined, 0, 0); + } else { + this.parts.activitybar.getContainer().style.left = ''; + position(activitybarContainer, this.titlebarHeight, 0, 0, undefined); + } + if (isActivityBarHidden) { + hide(activitybarContainer); + } else { + show(activitybarContainer); + } + + // Sidebar Part + const sidebarContainer = this.parts.sidebar.getContainer(); + size(sidebarContainer, sidebarSize.width, sidebarSize.height); + const editorAndPanelWidth = editorSize.width + (panelPosition === Position.RIGHT ? panelWidth : 0); + if (sidebarPosition === Position.LEFT) { + position(sidebarContainer, this.titlebarHeight, editorAndPanelWidth, this.statusbarHeight, activityBarSize.width); + } else { + position(sidebarContainer, this.titlebarHeight, activityBarSize.width, this.statusbarHeight, editorAndPanelWidth); + } + + // Statusbar Part + const statusbarContainer = this.parts.statusbar.getContainer(); + position(statusbarContainer, this.workbenchSize.height - this.statusbarHeight); + if (isStatusbarHidden) { + hide(statusbarContainer); + } else { + show(statusbarContainer); + } + + // Sashes + this.sashXOne.layout(); + if (panelPosition === Position.BOTTOM) { + this.sashXTwo.hide(); + this.sashY.layout(); + this.sashY.show(); + } else { + this.sashY.hide(); + this.sashXTwo.layout(); + this.sashXTwo.show(); + } + + // Propagate to Part Layouts + this.parts.titlebar.layout(this.workbenchSize.width, this.titlebarHeight, -1); + this.parts.editor.layout(editorSize.width, editorSize.height, -1); + this.parts.sidebar.layout(sidebarSize.width, sidebarSize.height, -1); + this.parts.panel.layout(panelDimension.width, panelDimension.height, -1); + this.parts.activitybar.layout(activityBarSize.width, activityBarSize.height, -1); + + // Propagate to Context View + this.contextViewService.layout(); + } + + getVerticalSashTop(sash: Sash): number { + return this.titlebarHeight; + } + + getVerticalSashLeft(sash: Sash): number { + let sidebarPosition = this.layoutService.getSideBarPosition(); + if (sash === this.sashXOne) { + + if (sidebarPosition === Position.LEFT) { + return this.sidebarWidth + this.activitybarWidth; + } + + return this.workbenchSize.width - this.sidebarWidth - this.activitybarWidth; + } + + return this.workbenchSize.width - this.panelWidth - (sidebarPosition === Position.RIGHT ? this.sidebarWidth + this.activitybarWidth : 0); + } + + getVerticalSashHeight(sash: Sash): number { + if (sash === this.sashXTwo && !this.layoutService.isVisible(Parts.PANEL_PART)) { + return 0; + } + + return this.sidebarHeight; + } + + getHorizontalSashTop(sash: Sash): number { + const offset = 2; // Horizontal sash should be a bit lower than the editor area, thus add 2px #5524 + return offset + (this.layoutService.isVisible(Parts.PANEL_PART) ? this.sidebarHeight - this.panelHeight + this.titlebarHeight : this.sidebarHeight + this.titlebarHeight); + } + + getHorizontalSashLeft(sash: Sash): number { + if (this.layoutService.getSideBarPosition() === Position.RIGHT) { + return 0; + } + + return this.sidebarWidth + this.activitybarWidth; + } + + getHorizontalSashWidth(sash: Sash): number { + return this.panelWidth; + } + + isPanelMaximized(): boolean { + return this.panelMaximized; + } + + resizePart(part: Parts, sizeChange: number): void { + const panelPosition = this.layoutService.getPanelPosition(); + const sizeChangePxWidth = this.workbenchSize.width * (sizeChange / 100); + const sizeChangePxHeight = this.workbenchSize.height * (sizeChange / 100); + + let doLayout = false; + switch (part) { + case Parts.SIDEBAR_PART: + this.sidebarWidth = this.sidebarWidth + sizeChangePxWidth; // Sidebar can not become smaller than MIN_PART_WIDTH + + if (this.workbenchSize.width - this.sidebarWidth < this.parts.editor.minimumWidth) { + this.sidebarWidth = this.workbenchSize.width - this.parts.editor.minimumWidth; + } + + doLayout = true; + break; + case Parts.PANEL_PART: + if (panelPosition === Position.BOTTOM) { + this.panelHeight = this.panelHeight + sizeChangePxHeight; + } else if (panelPosition === Position.RIGHT) { + this.panelWidth = this.panelWidth + sizeChangePxWidth; + } + + doLayout = true; + break; + case Parts.EDITOR_PART: + // If we have one editor we can cheat and resize sidebar with the negative delta + // If the sidebar is not visible and panel is, resize panel main axis with negative Delta + if (this.editorGroupService.count === 1) { + if (this.layoutService.isVisible(Parts.SIDEBAR_PART)) { + this.sidebarWidth = this.sidebarWidth - sizeChangePxWidth; + doLayout = true; + } else if (this.layoutService.isVisible(Parts.PANEL_PART)) { + if (panelPosition === Position.BOTTOM) { + this.panelHeight = this.panelHeight - sizeChangePxHeight; + } else if (panelPosition === Position.RIGHT) { + this.panelWidth = this.panelWidth - sizeChangePxWidth; + } + doLayout = true; + } + } else { + const activeGroup = this.editorGroupService.activeGroup; + + const activeGroupSize = this.editorGroupService.getSize(activeGroup); + this.editorGroupService.setSize(activeGroup, activeGroupSize + sizeChangePxWidth); + } + } + + if (doLayout) { + this.layout(); + } + } +} diff --git a/src/vs/workbench/browser/media/part.css b/src/vs/workbench/browser/media/part.css index 3be60b92c7..2ecf9c88bf 100644 --- a/src/vs/workbench/browser/media/part.css +++ b/src/vs/workbench/browser/media/part.css @@ -3,6 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.monaco-workbench > .part { + position: absolute; /* only position absolute when grid is not used (TODO@sbatten TODO@ben remove eventually) */ +} + +.monaco-workbench .part { + box-sizing: border-box; +} + .monaco-workbench .part > .title { display: none; /* Parts have to opt in to show title area */ } diff --git a/src/vs/workbench/browser/media/style.css b/src/vs/workbench/browser/media/style.css new file mode 100644 index 0000000000..12873f7111 --- /dev/null +++ b/src/vs/workbench/browser/media/style.css @@ -0,0 +1,222 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* Font Families (with CJK support) */ + +.mac { font-family: -apple-system, BlinkMacSystemFont, sans-serif; } +.mac:lang(zh-Hans) { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; } +.mac:lang(zh-Hant) { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; } +.mac:lang(ja) { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; } +.mac:lang(ko) { font-family: -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; } + +.windows { font-family: "Segoe WPC", "Segoe UI", sans-serif; } +.windows:lang(zh-Hans) { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; } +.windows:lang(zh-Hant) { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; } +.windows:lang(ja) { font-family: "Segoe WPC", "Segoe UI", "Meiryo", sans-serif; } +.windows:lang(ko) { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; } + +.linux { font-family: "Ubuntu", "Droid Sans", sans-serif; } +.linux:lang(zh-Hans) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; } +.linux:lang(zh-Hant) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; } +.linux:lang(ja) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; } +.linux:lang(ko) { font-family: "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } + +.mac { --monaco-monospace-font: Monaco, Menlo, Inconsolata, "Courier New", monospace; } +.windows { --monaco-monospace-font: Consolas, Inconsolata, "Courier New", monospace; } +.linux { --monaco-monospace-font: "Droid Sans Mono", Inconsolata, "Courier New", monospace, "Droid Sans Fallback"; } + +/* Global Styles */ + +body { + height: 100%; + width: 100%; + margin: 0; + padding: 0; + overflow: hidden; + font-size: 11px; + user-select: none; +} + +@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } + +.monaco-workbench { + font-size: 13px; + line-height: 1.4em; + position: relative; + z-index: 1; + overflow: hidden; +} + +.monaco-workbench img { + border: 0; +} + +.monaco-workbench label { + cursor: pointer; +} + +.monaco-workbench a { + text-decoration: none; +} + +.monaco-workbench a:active { + color: inherit; + background-color: inherit; +} + +.monaco-workbench a.plain { + color: inherit; + text-decoration: none; +} + +.monaco-workbench a.plain:hover, +.monaco-workbench a.plain.hover { + color: inherit; + text-decoration: none; +} + +.monaco-workbench input { + color: inherit; + font-family: inherit; + font-size: 100%; +} + +.monaco-workbench select { + font-family: inherit; +} + +.monaco-workbench .pointer { + cursor: pointer; +} + +.monaco-workbench.monaco-font-aliasing-antialiased { + -webkit-font-smoothing: antialiased; +} + +.monaco-workbench.monaco-font-aliasing-none { + -webkit-font-smoothing: none; +} + +@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + .monaco-workbench.monaco-font-aliasing-auto { + -webkit-font-smoothing: antialiased; + } +} + +.monaco-workbench .context-view { + -webkit-app-region: no-drag; +} + +.monaco-workbench .monaco-menu .monaco-action-bar.vertical { + padding: .5em 0; +} + +.monaco-workbench .monaco-menu .monaco-action-bar.vertical .action-menu-item { + height: 1.8em; +} + +.monaco-workbench .monaco-menu .monaco-action-bar.vertical .action-label:not(.separator), +.monaco-workbench .monaco-menu .monaco-action-bar.vertical .keybinding { + font-size: inherit; + padding: 0 2em; +} + +.monaco-workbench .monaco-menu .monaco-action-bar.vertical .menu-item-check { + font-size: inherit; + width: 2em; +} + +.monaco-workbench .monaco-menu .monaco-action-bar.vertical .action-label.separator { + font-size: inherit; + padding: 0.2em 0 0 0; + margin-bottom: 0.2em; +} + +.monaco-workbench.linux .monaco-menu .monaco-action-bar.vertical .action-label.separator { + margin-left: 0; + margin-right: 0; +} + +.monaco-workbench .monaco-menu .monaco-action-bar.vertical .submenu-indicator { + font-size: 60%; + padding: 0 1.8em; +} + +.monaco-workbench.linux .monaco-menu .monaco-action-bar.vertical .submenu-indicator { + height: 100%; + -webkit-mask-size: 10px 10px; + mask-size: 10px 10px; +} + +.monaco-workbench .monaco-menu .action-item { + cursor: default; +} + +/* START Keyboard Focus Indication Styles */ + +.monaco-workbench [tabindex="0"]:focus, +.monaco-workbench .synthetic-focus, +.monaco-workbench select:focus, +.monaco-workbench input[type="button"]:focus, +.monaco-workbench input[type="text"]:focus, +.monaco-workbench textarea:focus, +.monaco-workbench input[type="checkbox"]:focus { + outline-width: 1px; + outline-style: solid; + outline-offset: -1px; + opacity: 1 !important; +} + +.monaco-workbench [tabindex="0"]:active, +.monaco-workbench select:active, +.monaco-workbench input[type="button"]:active, +.monaco-workbench input[type="checkbox"]:active, +.monaco-workbench .monaco-tree .monaco-tree-row +.monaco-workbench .monaco-tree.focused.no-focused-item:active:before { + outline: 0 !important; /* fixes some flashing outlines from showing up when clicking */ +} + +.monaco-workbench.mac select:focus { + border-color: transparent; /* outline is a square, but border has a radius, so we avoid this glitch when focused (https://github.com/Microsoft/vscode/issues/26045) */ +} + +.monaco-workbench .monaco-tree.focused .monaco-tree-row.focused [tabindex="0"]:focus { + outline-width: 1px; /* higher contrast color for focusable elements in a row that shows focus feedback */ + outline-style: solid; +} + +.monaco-workbench .monaco-tree.focused.no-focused-item:focus:before, +.monaco-workbench .monaco-list:not(.element-focused):focus:before { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 5; /* make sure we are on top of the tree items */ + content: ""; + pointer-events: none; /* enable click through */ + outline: 1px solid; /* we still need to handle the empty tree or no focus item case */ + outline-width: 1px; + outline-style: solid; + outline-offset: -1px; +} + +.monaco-workbench .synthetic-focus :focus { + outline: 0 !important; /* elements within widgets that draw synthetic-focus should never show focus */ +} + +.monaco-workbench .monaco-inputbox.info.synthetic-focus, +.monaco-workbench .monaco-inputbox.warning.synthetic-focus, +.monaco-workbench .monaco-inputbox.error.synthetic-focus, +.monaco-workbench .monaco-inputbox.info input[type="text"]:focus, +.monaco-workbench .monaco-inputbox.warning input[type="text"]:focus, +.monaco-workbench .monaco-inputbox.error input[type="text"]:focus { + outline: 0 !important; /* outline is not going well with decoration */ +} + +.monaco-workbench .monaco-tree.focused:focus, +.monaco-workbench .monaco-list:focus { + outline: 0 !important; /* tree indicates focus not via outline but through the focused item */ +} diff --git a/src/vs/workbench/browser/nodeless.main.ts b/src/vs/workbench/browser/nodeless.main.ts new file mode 100644 index 0000000000..af6086861e --- /dev/null +++ b/src/vs/workbench/browser/nodeless.main.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { mark } from 'vs/base/common/performance'; +import { domContentLoaded, addDisposableListener, EventType } from 'vs/base/browser/dom'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { ILogService } from 'vs/platform/log/common/log'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { SimpleLogService } from 'vs/workbench/browser/nodeless.simpleservices'; +import { Workbench } from 'vs/workbench/browser/workbench'; + +class CodeRendererMain extends Disposable { + + private workbench: Workbench; + + open(): Promise { + const services = this.initServices(); + + return domContentLoaded().then(() => { + mark('willStartWorkbench'); + + // Create Workbench + this.workbench = new Workbench( + document.body, + services.serviceCollection, + services.logService + ); + + // Layout + this._register(addDisposableListener(window, EventType.RESIZE, () => this.workbench.layout())); + + // Workbench Lifecycle + this._register(this.workbench.onShutdown(() => this.dispose())); + + // Startup + this.workbench.startup(); + }); + } + + private initServices(): { serviceCollection: ServiceCollection, logService: ILogService } { + const serviceCollection = new ServiceCollection(); + + const logService = new SimpleLogService(); + serviceCollection.set(ILogService, logService); + + return { serviceCollection, logService }; + } +} + +export function main(): Promise { + const renderer = new CodeRendererMain(); + + return renderer.open(); +} diff --git a/src/vs/workbench/browser/nodeless.simpleservices.ts b/src/vs/workbench/browser/nodeless.simpleservices.ts new file mode 100644 index 0000000000..3ae64bad00 --- /dev/null +++ b/src/vs/workbench/browser/nodeless.simpleservices.ts @@ -0,0 +1,1834 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { URI } from 'vs/base/common/uri'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { ITextSnapshot, IFileStat, IContent, IFileService, IResourceEncodings, IResolveFileOptions, IResolveFileResult, IResolveContentOptions, IStreamContent, IUpdateContentOptions, snapshotToString, ICreateFileOptions, IResourceEncoding } from 'vs/platform/files/common/files'; +import { ITextBufferFactory } from 'vs/editor/common/model'; +import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; +import { keys, ResourceMap } from 'vs/base/common/map'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Event } from 'vs/base/common/event'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +// tslint:disable-next-line: import-patterns no-standalone-editor +import { SimpleConfigurationService as StandaloneEditorConfigurationService, SimpleDialogService as StandaloneEditorDialogService, StandaloneKeybindingService, SimpleResourcePropertiesService } from 'vs/editor/standalone/browser/simpleServices'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDownloadService } from 'vs/platform/download/common/download'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IEnvironmentService, IExtensionHostDebugParams, IDebugParams } from 'vs/platform/environment/common/environment'; +import { IExtensionGalleryService, IQueryOptions, IGalleryExtension, InstallOperation, StatisticType, ITranslation, IGalleryExtensionVersion, IExtensionIdentifier, IReportedExtension, IExtensionManagementService, ILocalExtension, IGalleryMetadata, IExtensionTipsService, ExtensionRecommendationReason, IExtensionRecommendation } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IPager } from 'vs/base/common/paging'; +import { IExtensionManifest, ExtensionType, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { NullExtensionService, IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IURLHandler, IURLService } from 'vs/platform/url/common/url'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ITelemetryService, ITelemetryData, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { AbstractLifecycleService } from 'vs/platform/lifecycle/common/lifecycleService'; +import { ILogService, LogLevel, ConsoleLogService } from 'vs/platform/log/common/log'; +import { ShutdownReason, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { IMenubarService, IMenubarData } from 'vs/platform/menubar/common/menubar'; +import { IProductService } from 'vs/platform/product/common/product'; +import { IRemoteAuthorityResolverService, ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { joinPath, isEqualOrParent, isEqual, dirname } from 'vs/base/common/resources'; +import { basename } from 'vs/base/common/path'; +import { ISearchService, ITextQueryProps, ISearchProgressItem, ISearchComplete, IFileQueryProps, SearchProviderType, ISearchResultProvider, ITextQuery, IFileMatch, QueryType, FileMatch, pathIncludedInQuery } from 'vs/workbench/services/search/common/search'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { coalesce } from 'vs/base/common/arrays'; +import { Schemas } from 'vs/base/common/network'; +import { editorMatchesToTextSearchResults, addContextToEditorMatches } from 'vs/workbench/services/search/common/searchHelpers'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { InMemoryStorageService, IStorageService } from 'vs/platform/storage/common/storage'; +import { ITextMateService, IGrammar as ITextMategrammar } from 'vs/workbench/services/textMate/common/textMateService'; +import { LanguageId, TokenizationRegistry } from 'vs/editor/common/modes'; +import { IUpdateService, State } from 'vs/platform/update/common/update'; +import { IWindowConfiguration, IPath, IPathsToWaitFor, IWindowService, INativeOpenDialogOptions, IEnterWorkspaceResult, IURIToOpen, IMessageBoxResult, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IProcessEnvironment, isWindows } from 'vs/base/common/platform'; +import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceFolderCreationData, isSingleFolderWorkspaceIdentifier, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { ExportData } from 'vs/base/common/performance'; +import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; +import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; +import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; +import { IWorkspaceContextService, Workspace, toWorkspaceFolders, IWorkspaceFolder, WorkbenchState, IWorkspace } from 'vs/platform/workspace/common/workspace'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Color, RGBA } from 'vs/base/common/color'; + +export const workspaceResource = URI.file(isWindows ? 'C:\\simpleWorkspace' : '/simpleWorkspace'); + +//#region Backup File + +export class SimpleBackupFileService implements IBackupFileService { + + _serviceBrand: any; + + private backups: Map = new Map(); + + hasBackups(): Promise { + return Promise.resolve(this.backups.size > 0); + } + + loadBackupResource(resource: URI): Promise { + const backupResource = this.toBackupResource(resource); + if (this.backups.has(backupResource.toString())) { + return Promise.resolve(backupResource); + } + + return Promise.resolve(undefined); + } + + backupResource(resource: URI, content: ITextSnapshot, versionId?: number): Promise { + const backupResource = this.toBackupResource(resource); + this.backups.set(backupResource.toString(), content); + + return Promise.resolve(); + } + + resolveBackupContent(backupResource: URI): Promise { + const snapshot = this.backups.get(backupResource.toString()); + if (snapshot) { + return Promise.resolve(createTextBufferFactoryFromSnapshot(snapshot)); + } + + return Promise.resolve(undefined); + } + + getWorkspaceFileBackups(): Promise { + return Promise.resolve(keys(this.backups).map(key => URI.parse(key))); + } + + discardResourceBackup(resource: URI): Promise { + this.backups.delete(this.toBackupResource(resource).toString()); + + return Promise.resolve(); + } + + discardAllWorkspaceBackups(): Promise { + this.backups.clear(); + + return Promise.resolve(); + } + + toBackupResource(resource: URI): URI { + return resource; + } +} + +registerSingleton(IBackupFileService, SimpleBackupFileService, true); + +//#endregion + +//#region Broadcast + +export const IBroadcastService = createDecorator('broadcastService'); + +export interface IBroadcast { + channel: string; + payload: any; +} + +export interface IBroadcastService { + _serviceBrand: any; + + onBroadcast: Event; + + broadcast(b: IBroadcast): void; +} + +export class SimpleBroadcastService implements IBroadcastService { + + _serviceBrand: any; + + readonly onBroadcast: Event = Event.None; + + broadcast(b: IBroadcast): void { } +} + +registerSingleton(IBroadcastService, SimpleBroadcastService, true); + +//#endregion + +//#region Clipboard + +export class SimpleClipboardService implements IClipboardService { + + _serviceBrand: any; + + writeText(text: string, type?: string): void { } + + readText(type?: string): string { + // @ts-ignore + return undefined; + } + + readFindText(): string { + // @ts-ignore + return undefined; + } + + writeFindText(text: string): void { } + + writeResources(resources: URI[]): void { } + + readResources(): URI[] { + return []; + } + + hasResources(): boolean { + return false; + } +} + +registerSingleton(IClipboardService, SimpleClipboardService, true); + +//#endregion + +//#region Configuration + +export class SimpleConfigurationService extends StandaloneEditorConfigurationService { } + +registerSingleton(IConfigurationService, SimpleConfigurationService); + +//#endregion + +//#region Dialog + +export class SimpleDialogService extends StandaloneEditorDialogService { } + +registerSingleton(IDialogService, SimpleDialogService, true); + +//#endregion + +//#region Download + +export class SimpleDownloadService implements IDownloadService { + + _serviceBrand: any; + + download(uri: URI, to?: string, cancellationToken?: CancellationToken): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } +} + +registerSingleton(IDownloadService, SimpleDownloadService, true); + +//#endregion + +//#region Environment + +export class SimpleEnvironmentService implements IEnvironmentService { + untitledWorkspacesHome: URI; + extensionTestsLocationURI?: URI; + _serviceBrand: any; + args = { _: [] }; + execPath: string; + cliPath: string; + appRoot: string = '/nodeless/'; + userHome: string; + userDataPath: string; + appNameLong: string; + appQuality?: string; + appSettingsHome: string = '/nodeless/settings'; + appSettingsPath: string = '/nodeless/settings/settings.json'; + appKeybindingsPath: string = '/nodeless/settings/keybindings.json'; + settingsSearchBuildId?: number; + settingsSearchUrl?: string; + globalStorageHome: string; + workspaceStorageHome: string; + backupHome: string; + backupWorkspacesPath: string; + workspacesHome: string; + isExtensionDevelopment: boolean; + disableExtensions: boolean | string[]; + builtinExtensionsPath: string; + extensionsPath: string; + extensionDevelopmentLocationURI?: URI; + extensionTestsPath?: string; + debugExtensionHost: IExtensionHostDebugParams; + debugSearch: IDebugParams; + logExtensionHostCommunication: boolean; + isBuilt: boolean; + wait: boolean; + status: boolean; + log?: string; + logsPath: string = '/nodeless/logs'; + verbose: boolean; + skipGettingStarted: boolean; + skipReleaseNotes: boolean; + skipAddToRecentlyOpened: boolean; + mainIPCHandle: string; + sharedIPCHandle: string; + nodeCachedDataDir?: string; + installSourcePath: string; + disableUpdates: boolean; + disableCrashReporter: boolean; + driverHandle?: string; + driverVerbose: boolean; +} + +registerSingleton(IEnvironmentService, SimpleEnvironmentService); + +//#endregion + +//#region Extension Gallery + +export class SimpleExtensionGalleryService implements IExtensionGalleryService { + + _serviceBrand: any; + + isEnabled(): boolean { + return false; + } + + query(options?: IQueryOptions): Promise> { + // @ts-ignore + return Promise.resolve(undefined); + } + + download(extension: IGalleryExtension, operation: InstallOperation): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + reportStatistic(publisher: string, name: string, version: string, type: StatisticType): Promise { + return Promise.resolve(undefined); + } + + getReadme(extension: IGalleryExtension, token: CancellationToken): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + getManifest(extension: IGalleryExtension, token: CancellationToken): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + getChangelog(extension: IGalleryExtension, token: CancellationToken): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + getCoreTranslation(extension: IGalleryExtension, languageId: string): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + getAllVersions(extension: IGalleryExtension, compatible: boolean): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + loadAllDependencies(dependencies: IExtensionIdentifier[], token: CancellationToken): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + getExtensionsReport(): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + // @ts-ignore + getCompatibleExtension(extension: IGalleryExtension): Promise; + getCompatibleExtension(id: IExtensionIdentifier, version?: string): Promise; + getCompatibleExtension(id: any, version?: any) { + return Promise.resolve(undefined); + } +} + +registerSingleton(IExtensionGalleryService, SimpleExtensionGalleryService, true); + +//#endregion + +//#region Extension Management + +//#region Extension Tips + +export class SimpleExtensionTipsService implements IExtensionTipsService { + _serviceBrand: any; + + onRecommendationChange = Event.None; + + getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason; reasonText: string; }; } { + return Object.create(null); + } + + getFileBasedRecommendations(): IExtensionRecommendation[] { + return []; + } + + getOtherRecommendations(): Promise { + return Promise.resolve([]); + } + + getWorkspaceRecommendations(): Promise { + return Promise.resolve([]); + } + + getKeymapRecommendations(): IExtensionRecommendation[] { + return []; + } + + toggleIgnoredRecommendation(extensionId: string, shouldIgnore: boolean): void { + } + + getAllIgnoredRecommendations(): { global: string[]; workspace: string[]; } { + return Object.create(null); + } +} + +registerSingleton(IExtensionTipsService, SimpleExtensionTipsService, true); + +//#endregion + +export class SimpleExtensionManagementService implements IExtensionManagementService { + + _serviceBrand: any; + + onInstallExtension = Event.None; + onDidInstallExtension = Event.None; + onUninstallExtension = Event.None; + onDidUninstallExtension = Event.None; + + zip(extension: ILocalExtension): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + unzip(zipLocation: URI, type: ExtensionType): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + install(vsix: URI): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + installFromGallery(extension: IGalleryExtension): Promise { + return Promise.resolve(undefined); + } + + uninstall(extension: ILocalExtension, force?: boolean): Promise { + return Promise.resolve(undefined); + } + + reinstallFromGallery(extension: ILocalExtension): Promise { + return Promise.resolve(undefined); + } + + getInstalled(type?: ExtensionType): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + getExtensionsReport(): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } +} + +registerSingleton(IExtensionManagementService, SimpleExtensionManagementService); + +//#endregion + +//#region Extensions + +export class SimpleExtensionService extends NullExtensionService { } + +registerSingleton(IExtensionService, SimpleExtensionService); + +//#endregion + +//#region Extension URL Handler + +export const IExtensionUrlHandler = createDecorator('inactiveExtensionUrlHandler'); + +export interface IExtensionUrlHandler { + readonly _serviceBrand: any; + registerExtensionHandler(extensionId: ExtensionIdentifier, handler: IURLHandler): void; + unregisterExtensionHandler(extensionId: ExtensionIdentifier): void; +} + +export class SimpleExtensionURLHandler implements IExtensionUrlHandler { + + _serviceBrand: any; + + registerExtensionHandler(extensionId: ExtensionIdentifier, handler: IURLHandler): void { } + + unregisterExtensionHandler(extensionId: ExtensionIdentifier): void { } +} + +registerSingleton(IExtensionUrlHandler, SimpleExtensionURLHandler, true); + +//#endregion + +//#region Keybinding + +export class SimpleKeybindingService extends StandaloneKeybindingService { + constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @ICommandService commandService: ICommandService, + @ITelemetryService telemetryService: ITelemetryService, + @INotificationService notificationService: INotificationService, + ) { + super(contextKeyService, commandService, telemetryService, notificationService, window.document.body); + } +} + +registerSingleton(IKeybindingService, SimpleKeybindingService); + +//#endregion + +//#region Lifecycle + +export class SimpleLifecycleService extends AbstractLifecycleService { + + _serviceBrand: any; + + constructor( + @ILogService readonly logService: ILogService + ) { + super(logService); + + this.registerListeners(); + } + + private registerListeners(): void { + window.onbeforeunload = () => this.beforeUnload(); + } + + private beforeUnload(): string { + + // Before Shutdown + this._onBeforeShutdown.fire({ + veto(value) { + if (value === true) { + console.warn(new Error('Preventing onBeforeUnload currently not supported')); + } else if (value instanceof Promise) { + console.warn(new Error('Long running onBeforeShutdown currently not supported')); + } + }, + reason: ShutdownReason.QUIT + }); + + // Will Shutdown + this._onWillShutdown.fire({ + join() { + console.warn(new Error('Long running onWillShutdown currently not supported')); + }, + reason: ShutdownReason.QUIT + }); + + // @ts-ignore + return null; + } +} + +registerSingleton(ILifecycleService, SimpleLifecycleService); + +//#endregion + +//#region Log + +export class SimpleLogService extends ConsoleLogService { } + +//#endregion + +//#region Menu Bar + +export class SimpleMenubarService implements IMenubarService { + + _serviceBrand: any; + + updateMenubar(windowId: number, menuData: IMenubarData): Promise { + return Promise.resolve(undefined); + } +} + +registerSingleton(IMenubarService, SimpleMenubarService); + +//#endregion + +//#region Multi Extension Management + +export class SimpleMultiExtensionsManagementService implements IExtensionManagementService { + + _serviceBrand: any; + + onInstallExtension = Event.None; + onDidInstallExtension = Event.None; + onUninstallExtension = Event.None; + onDidUninstallExtension = Event.None; + + zip(extension: ILocalExtension): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + unzip(zipLocation: URI, type: ExtensionType): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + install(vsix: URI): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + installFromGallery(extension: IGalleryExtension): Promise { + return Promise.resolve(undefined); + } + + uninstall(extension: ILocalExtension, force?: boolean): Promise { + return Promise.resolve(undefined); + } + + reinstallFromGallery(extension: ILocalExtension): Promise { + return Promise.resolve(undefined); + } + + getInstalled(type?: ExtensionType): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + getExtensionsReport(): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } +} + +//#endregion + +//#region Product + +export class SimpleProductService implements IProductService { + + _serviceBrand: any; + + version?: string; + commit?: string; + + enableTelemetry: boolean = false; +} + +registerSingleton(IProductService, SimpleProductService, true); + +//#endregion + +//#region Remote Agent + +export const IRemoteAgentService = createDecorator('remoteAgentService'); + +export interface IRemoteAgentService { + _serviceBrand: any; + + getConnection(): object; +} + +export class SimpleRemoteAgentService implements IRemoteAgentService { + + _serviceBrand: any; + + getConnection(): object { + // @ts-ignore + return undefined; + } +} + +registerSingleton(IRemoteAgentService, SimpleRemoteAgentService); + +//#endregion + +//#region Remote Authority Resolver + +export class SimpleRemoteAuthorityResolverService implements IRemoteAuthorityResolverService { + + _serviceBrand: any; + + resolveAuthority(authority: string): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + setResolvedAuthority(resolvedAuthority: ResolvedAuthority): void { } + + setResolvedAuthorityError(authority: string, err: any): void { } +} + +registerSingleton(IRemoteAuthorityResolverService, SimpleRemoteAuthorityResolverService, true); + +//#endregion + +//#region File Servie + +const fileMap: ResourceMap = new ResourceMap(); +const contentMap: ResourceMap = new ResourceMap(); +initFakeFileSystem(); + +export class SimpleRemoteFileService implements IFileService { + + _serviceBrand: any; + + encoding: IResourceEncodings; + + readonly onFileChanges = Event.None; + readonly onAfterOperation = Event.None; + readonly onDidChangeFileSystemProviderRegistrations = Event.None; + + resolveFile(resource: URI, options?: IResolveFileOptions): Promise { + // @ts-ignore + return Promise.resolve(fileMap.get(resource)); + } + + resolveFiles(toResolve: { resource: URI, options?: IResolveFileOptions }[]): Promise { + return Promise.all(toResolve.map(resourceAndOption => this.resolveFile(resourceAndOption.resource, resourceAndOption.options))).then(stats => stats.map(stat => ({ stat, success: true }))); + } + + existsFile(resource: URI): Promise { + return Promise.resolve(fileMap.has(resource)); + } + + resolveContent(resource: URI, _options?: IResolveContentOptions): Promise { + // @ts-ignore + return Promise.resolve(contentMap.get(resource)); + } + + resolveStreamContent(resource: URI, _options?: IResolveContentOptions): Promise { + return Promise.resolve(contentMap.get(resource)).then(content => { + return { + // @ts-ignore + resource: content.resource, + value: { + on: (event: string, callback: Function): void => { + if (event === 'data') { + // @ts-ignore + callback(content.value); + } + + if (event === 'end') { + callback(); + } + } + }, + // @ts-ignore + etag: content.etag, + // @ts-ignore + encoding: content.encoding, + // @ts-ignore + mtime: content.mtime, + // @ts-ignore + name: content.name + }; + }); + } + + updateContent(resource: URI, value: string | ITextSnapshot, _options?: IUpdateContentOptions): Promise { + // @ts-ignore + return Promise.resolve(fileMap.get(resource)).then(file => { + const content = contentMap.get(resource); + + if (typeof value === 'string') { + // @ts-ignore + content.value = value; + } else { + // @ts-ignore + content.value = snapshotToString(value); + } + + return file; + }); + } + + moveFile(_source: URI, _target: URI, _overwrite?: boolean): Promise { return Promise.resolve(null!); } + + copyFile(_source: URI, _target: URI, _overwrite?: boolean): Promise { + const parent = fileMap.get(dirname(_target)); + if (!parent) { + return Promise.resolve(undefined); + } + + return this.resolveContent(_source).then(content => { + return Promise.resolve(createFile(parent, basename(_target.path), content.value)); + }); + } + + createFile(_resource: URI, _content?: string, _options?: ICreateFileOptions): Promise { + const parent = fileMap.get(dirname(_resource)); + if (!parent) { + return Promise.reject(new Error(`Unable to create file in ${dirname(_resource).path}`)); + } + + return Promise.resolve(createFile(parent, basename(_resource.path))); + } + + readFolder(_resource: URI) { return Promise.resolve([]); } + + createFolder(_resource: URI): Promise { + const parent = fileMap.get(dirname(_resource)); + if (!parent) { + return Promise.reject(new Error(`Unable to create folder in ${dirname(_resource).path}`)); + } + + return Promise.resolve(createFolder(parent, basename(_resource.path))); + } + + registerProvider(_scheme: string, _provider) { return { dispose() { } }; } + + activateProvider(_scheme: string): Promise { return Promise.resolve(undefined); } + + canHandleResource(resource: URI): boolean { return resource.scheme === 'file'; } + + del(_resource: URI, _options?: { useTrash?: boolean, recursive?: boolean }): Promise { return Promise.resolve(); } + + watchFileChanges(_resource: URI): void { } + + unwatchFileChanges(_resource: URI): void { } + + getWriteEncoding(_resource: URI): IResourceEncoding { return { encoding: 'utf8', hasBOM: false }; } + + dispose(): void { } +} + +function createFile(parent: IFileStat, name: string, content: string = ''): IFileStat { + const file: IFileStat = { + resource: joinPath(parent.resource, name), + etag: Date.now().toString(), + mtime: Date.now(), + isDirectory: false, + name + }; + + // @ts-ignore + parent.children.push(file); + + fileMap.set(file.resource, file); + + contentMap.set(file.resource, { + resource: joinPath(parent.resource, name), + etag: Date.now().toString(), + mtime: Date.now(), + value: content, + encoding: 'utf8', + name + } as IContent); + + return file; +} + +function createFolder(parent: IFileStat, name: string): IFileStat { + const folder: IFileStat = { + resource: joinPath(parent.resource, name), + etag: Date.now().toString(), + mtime: Date.now(), + isDirectory: true, + name, + children: [] + }; + + // @ts-ignore + parent.children.push(folder); + + fileMap.set(folder.resource, folder); + + return folder; +} + +function initFakeFileSystem(): void { + + const root: IFileStat = { + resource: workspaceResource, + etag: Date.now().toString(), + mtime: Date.now(), + isDirectory: true, + name: basename(workspaceResource.fsPath), + children: [] + }; + + fileMap.set(root.resource, root); + + createFile(root, '.gitignore', `out +node_modules +.vscode-test/ +*.vsix +`); + createFile(root, '.vscodeignore', `.vscode/** +.vscode-test/** +out/test/** +src/** +.gitignore +vsc-extension-quickstart.md +**/tsconfig.json +**/tslint.json +**/*.map +**/*.ts`); + createFile(root, 'CHANGELOG.md', `# Change Log +All notable changes to the "test-ts" extension will be documented in this file. + +Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. + +## [Unreleased] +- Initial release`); + createFile(root, 'package.json', `{ + "name": "test-ts", + "displayName": "test-ts", + "description": "", + "version": "0.0.1", + "engines": { + "vscode": "^1.31.0" + }, + "categories": [ + "Other" + ], + "activationEvents": [ + "onCommand:extension.helloWorld" + ], + "main": "./out/extension.js", + "contributes": { + "commands": [ + { + "command": "extension.helloWorld", + "title": "Hello World" + } + ] + }, + "scripts": { + "vscode:prepublish": "npm run compile", + "compile": "tsc -p ./", + "watch": "tsc -watch -p ./", + "postinstall": "node ./node_modules/vscode/bin/install", + "test": "npm run compile && node ./node_modules/vscode/bin/test" + }, + "devDependencies": { + "typescript": "^3.3.1", + "vscode": "^1.1.28", + "tslint": "^5.12.1", + "@types/node": "^8.10.25", + "@types/mocha": "^2.2.42" + } +} +`); + createFile(root, 'tsconfig.json', `{ + "compilerOptions": { + "module": "commonjs", + "target": "es6", + "outDir": "out", + "lib": [ + "es6" + ], + "sourceMap": true, + "rootDir": "src", + "strict": true /* enable all strict type-checking options */ + /* Additional Checks */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + }, + "exclude": [ + "node_modules", + ".vscode-test" + ] +} +`); + createFile(root, 'tslint.json', `{ + "rules": { + "no-string-throw": true, + "no-unused-expression": true, + "no-duplicate-variable": true, + "curly": true, + "class-name": true, + "semicolon": [ + true, + "always" + ], + "triple-equals": true + }, + "defaultSeverity": "warning" +} +`); + + const src = createFolder(root, 'src'); + createFile(src, 'extension.ts', `// The module 'vscode' contains the VS Code extensibility API +// Import the module and reference it with the alias vscode in your code below +import * as vscode from 'vscode'; + +// this method is called when your extension is activated +// your extension is activated the very first time the command is executed +export function activate(context: vscode.ExtensionContext) { + + // Use the console to output diagnostic information (console.log) and errors (console.error) + // This line of code will only be executed once when your extension is activated + console.log('Congratulations, your extension "test-ts" is now active!'); + + // The command has been defined in the package.json file + // Now provide the implementation of the command with registerCommand + // The commandId parameter must match the command field in package.json + let disposable = vscode.commands.registerCommand('extension.helloWorld', () => { + // The code you place here will be executed every time your command is executed + + // Display a message box to the user + vscode.window.showInformationMessage('Hello World!'); + }); + + context.subscriptions.push(disposable); +} + +// this method is called when your extension is deactivated +export function deactivate() {} +`); + + const test = createFolder(src, 'test'); + + createFile(test, 'extension.test.ts', `// +// Note: This example test is leveraging the Mocha test framework. +// Please refer to their documentation on https://mochajs.org/ for help. +// + +// The module 'assert' provides assertion methods from node +import * as assert from 'assert'; + +// You can import and use all API from the 'vscode' module +// as well as import your extension to test it +// import * as vscode from 'vscode'; +// import * as myExtension from '../extension'; + +// Defines a Mocha test suite to group tests of similar kind together +suite("Extension Tests", function () { + + // Defines a Mocha unit test + test("Something 1", function() { + assert.equal(-1, [1, 2, 3].indexOf(5)); + assert.equal(-1, [1, 2, 3].indexOf(0)); + }); +});`); + + createFile(test, 'index.ts', `// +// PLEASE DO NOT MODIFY / DELETE UNLESS YOU KNOW WHAT YOU ARE DOING +// +// This file is providing the test runner to use when running extension tests. +// By default the test runner in use is Mocha based. +// +// You can provide your own test runner if you want to override it by exporting +// a function run(testRoot: string, clb: (error:Error) => void) that the extension +// host can call to run the tests. The test runner is expected to use console.log +// to report the results back to the caller. When the tests are finished, return +// a possible error to the callback or null if none. + +import * as testRunner from 'vscode/lib/testrunner'; + +// You can directly control Mocha options by configuring the test runner below +// See https://github.com/mochajs/mocha/wiki/Using-mocha-programmatically#set-options +// for more info +testRunner.configure({ + ui: 'tdd', // the TDD UI is being used in extension.test.ts (suite, test, etc.) + useColors: true // colored output from test results +}); + +module.exports = testRunner;`); +} + +registerSingleton(IFileService, SimpleRemoteFileService); + +//#endregion + +//#region Request + +export const IRequestService = createDecorator('requestService'); + +export interface IRequestService { + _serviceBrand: any; + + request(options, token: CancellationToken): Promise; +} + +export class SimpleRequestService implements IRequestService { + + _serviceBrand: any; + + request(options, token: CancellationToken): Promise { + return Promise.resolve(Object.create(null)); + } +} + +//#endregion + +//#region Search + +export class SimpleSearchService implements ISearchService { + + _serviceBrand: any; + + constructor( + @IModelService private modelService: IModelService, + @IEditorService private editorService: IEditorService, + @IUntitledEditorService private untitledEditorService: IUntitledEditorService + ) { + + } + + textSearch(query: ITextQueryProps, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): Promise { + // Get local results from dirty/untitled + const localResults = this.getLocalResults(query); + + if (onProgress) { + coalesce(localResults.values()).forEach(onProgress); + } + + // @ts-ignore + return Promise.resolve(undefined); + } + + fileSearch(query: IFileQueryProps, token?: CancellationToken): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + clearCache(cacheKey: string): Promise { + return Promise.resolve(undefined); + } + + registerSearchResultProvider(scheme: string, type: SearchProviderType, provider: ISearchResultProvider): IDisposable { + return Disposable.None; + } + + private getLocalResults(query: ITextQuery): ResourceMap { + const localResults = new ResourceMap(); + + if (query.type === QueryType.Text) { + const models = this.modelService.getModels(); + models.forEach((model) => { + const resource = model.uri; + if (!resource) { + return; + } + + if (!this.editorService.isOpen({ resource })) { + return; + } + + // Support untitled files + if (resource.scheme === Schemas.untitled) { + if (!this.untitledEditorService.exists(resource)) { + return; + } + } + + // Don't support other resource schemes than files for now + // why is that? we should search for resources from other + // schemes + else if (resource.scheme !== Schemas.file) { + return; + } + + if (!this.matches(resource, query)) { + return; // respect user filters + } + + // Use editor API to find matches + // @ts-ignore + const matches = model.findMatches(query.contentPattern.pattern, false, query.contentPattern.isRegExp, query.contentPattern.isCaseSensitive, query.contentPattern.isWordMatch ? query.contentPattern.wordSeparators : null, false, query.maxResults); + if (matches.length) { + const fileMatch = new FileMatch(resource); + localResults.set(resource, fileMatch); + + const textSearchResults = editorMatchesToTextSearchResults(matches, model, query.previewOptions); + fileMatch.results = addContextToEditorMatches(textSearchResults, model, query); + } else { + // @ts-ignore + localResults.set(resource, null); + } + }); + } + + return localResults; + } + + private matches(resource: URI, query: ITextQuery): boolean { + // includes + if (query.includePattern) { + if (resource.scheme !== Schemas.file) { + return false; // if we match on file patterns, we have to ignore non file resources + } + } + + return pathIncludedInQuery(query, resource.fsPath); + } +} + +registerSingleton(ISearchService, SimpleSearchService, true); + +//#endregion + +//#region Storage + +export class SimpleStorageService extends InMemoryStorageService { } + +registerSingleton(IStorageService, SimpleStorageService); + +//#endregion + +//#region Telemetry + +export class SimpleTelemetryService implements ITelemetryService { + + _serviceBrand: undefined; + + isOptedIn: true; + + publicLog(eventName: string, data?: ITelemetryData) { + return Promise.resolve(undefined); + } + + getTelemetryInfo(): Promise { + return Promise.resolve({ + instanceId: 'someValue.instanceId', + sessionId: 'someValue.sessionId', + machineId: 'someValue.machineId' + }); + } +} + +registerSingleton(ITelemetryService, SimpleTelemetryService); + +//#endregion + +//#region Textmate + +TokenizationRegistry.setColorMap([null, new Color(new RGBA(212, 212, 212, 1)), new Color(new RGBA(30, 30, 30, 1))]); + +export class SimpleTextMateService implements ITextMateService { + + _serviceBrand: any; + + readonly onDidEncounterLanguage: Event = Event.None; + + createGrammar(modeId: string): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } +} + +registerSingleton(ITextMateService, SimpleTextMateService, true); + +//#endregion + +//#region Text Resource Properties + +export class SimpleTextResourcePropertiesService extends SimpleResourcePropertiesService { } + +registerSingleton(ITextResourcePropertiesService, SimpleTextResourcePropertiesService); + +//#endregion + +//#region Update + +export class SimpleUpdateService implements IUpdateService { + + _serviceBrand: any; + + onStateChange = Event.None; + state: State; + + checkForUpdates(context: any): Promise { + return Promise.resolve(undefined); + } + + downloadUpdate(): Promise { + return Promise.resolve(undefined); + } + + applyUpdate(): Promise { + return Promise.resolve(undefined); + } + + quitAndInstall(): Promise { + return Promise.resolve(undefined); + } + + isLatestVersion(): Promise { + return Promise.resolve(true); + } +} + +registerSingleton(IUpdateService, SimpleUpdateService); + +//#endregion + +//#region URL + +export class SimpleURLService implements IURLService { + _serviceBrand: any; + + open(url: URI): Promise { + return Promise.resolve(false); + } + + registerHandler(handler: IURLHandler): IDisposable { + return Disposable.None; + } +} + +registerSingleton(IURLService, SimpleURLService); + +//#endregion + +//#region Window + +export class SimpleWindowConfiguration implements IWindowConfiguration { + _: any[]; + machineId: string; + windowId: number; + logLevel: LogLevel; + + mainPid: number; + + appRoot: string; + execPath: string; + isInitialStartup?: boolean; + + userEnv: IProcessEnvironment; + nodeCachedDataDir?: string; + + backupPath?: string; + + workspace?: IWorkspaceIdentifier; + folderUri?: ISingleFolderWorkspaceIdentifier; + + remoteAuthority?: string; + + zoomLevel?: number; + fullscreen?: boolean; + maximized?: boolean; + highContrast?: boolean; + frameless?: boolean; + accessibilitySupport?: boolean; + partsSplashPath?: string; + + perfStartTime?: number; + perfAppReady?: number; + perfWindowLoadTime?: number; + perfEntries: ExportData; + + filesToOpen?: IPath[]; + filesToCreate?: IPath[]; + filesToDiff?: IPath[]; + filesToWait?: IPathsToWaitFor; + termProgram?: string; +} + +export class SimpleWindowService implements IWindowService { + + _serviceBrand: any; + + readonly onDidChangeFocus: Event = Event.None; + readonly onDidChangeMaximize: Event = Event.None; + + hasFocus = true; + + private configuration: IWindowConfiguration = new SimpleWindowConfiguration(); + + isFocused(): Promise { + return Promise.resolve(false); + } + + isMaximized(): Promise { + return Promise.resolve(false); + } + + getConfiguration(): IWindowConfiguration { + return this.configuration; + } + + getCurrentWindowId(): number { + return 0; + } + + pickFileFolderAndOpen(_options: INativeOpenDialogOptions): Promise { + return Promise.resolve(); + } + + pickFileAndOpen(_options: INativeOpenDialogOptions): Promise { + return Promise.resolve(); + } + + pickFolderAndOpen(_options: INativeOpenDialogOptions): Promise { + return Promise.resolve(); + } + + pickWorkspaceAndOpen(_options: INativeOpenDialogOptions): Promise { + return Promise.resolve(); + } + + reloadWindow(): Promise { + return Promise.resolve(); + } + + openDevTools(): Promise { + return Promise.resolve(); + } + + toggleDevTools(): Promise { + return Promise.resolve(); + } + + closeWorkspace(): Promise { + return Promise.resolve(); + } + + enterWorkspace(_path: URI): Promise { + return Promise.resolve(undefined); + } + + toggleFullScreen(): Promise { + return Promise.resolve(); + } + + setRepresentedFilename(_fileName: string): Promise { + return Promise.resolve(); + } + + getRecentlyOpened(): Promise { + return Promise.resolve({ + workspaces: [], + files: [] + }); + } + + focusWindow(): Promise { + return Promise.resolve(); + } + + maximizeWindow(): Promise { + return Promise.resolve(); + } + + unmaximizeWindow(): Promise { + return Promise.resolve(); + } + + minimizeWindow(): Promise { + return Promise.resolve(); + } + + openWindow(_uris: IURIToOpen[], _options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): Promise { + return Promise.resolve(); + } + + closeWindow(): Promise { + return Promise.resolve(); + } + + setDocumentEdited(_flag: boolean): Promise { + return Promise.resolve(); + } + + onWindowTitleDoubleClick(): Promise { + return Promise.resolve(); + } + + show(): Promise { + return Promise.resolve(); + } + + showMessageBox(_options: Electron.MessageBoxOptions): Promise { + return Promise.resolve({ button: 0 }); + } + + showSaveDialog(_options: Electron.SaveDialogOptions): Promise { + throw new Error('not implemented'); + } + + showOpenDialog(_options: Electron.OpenDialogOptions): Promise { + throw new Error('not implemented'); + } + + updateTouchBar(_items: ISerializableCommandAction[][]): Promise { + return Promise.resolve(); + } + + resolveProxy(url: string): Promise { + return Promise.resolve(undefined); + } +} + +registerSingleton(IWindowService, SimpleWindowService); + +//#endregion + +//#region Window + +export class SimpleWindowsService implements IWindowsService { + _serviceBrand: any; + + windowCount = 1; + + readonly onWindowOpen: Event = Event.None; + readonly onWindowFocus: Event = Event.None; + readonly onWindowBlur: Event = Event.None; + readonly onWindowMaximize: Event = Event.None; + readonly onWindowUnmaximize: Event = Event.None; + readonly onRecentlyOpenedChange: Event = Event.None; + + isFocused(_windowId: number): Promise { + return Promise.resolve(false); + } + + pickFileFolderAndOpen(_options: INativeOpenDialogOptions): Promise { + return Promise.resolve(); + } + + pickFileAndOpen(_options: INativeOpenDialogOptions): Promise { + return Promise.resolve(); + } + + pickFolderAndOpen(_options: INativeOpenDialogOptions): Promise { + return Promise.resolve(); + } + + pickWorkspaceAndOpen(_options: INativeOpenDialogOptions): Promise { + return Promise.resolve(); + } + + reloadWindow(_windowId: number): Promise { + return Promise.resolve(); + } + + openDevTools(_windowId: number): Promise { + return Promise.resolve(); + } + + toggleDevTools(_windowId: number): Promise { + return Promise.resolve(); + } + + closeWorkspace(_windowId: number): Promise { + return Promise.resolve(); + } + + enterWorkspace(_windowId: number, _path: URI): Promise { + return Promise.resolve(undefined); + } + + toggleFullScreen(_windowId: number): Promise { + return Promise.resolve(); + } + + setRepresentedFilename(_windowId: number, _fileName: string): Promise { + return Promise.resolve(); + } + + addRecentlyOpened(recents: IRecent[]): Promise { + return Promise.resolve(); + } + + removeFromRecentlyOpened(_paths: URI[]): Promise { + return Promise.resolve(); + } + + clearRecentlyOpened(): Promise { + return Promise.resolve(); + } + + getRecentlyOpened(_windowId: number): Promise { + return Promise.resolve({ + workspaces: [], + files: [] + }); + } + + focusWindow(_windowId: number): Promise { + return Promise.resolve(); + } + + closeWindow(_windowId: number): Promise { + return Promise.resolve(); + } + + isMaximized(_windowId: number): Promise { + return Promise.resolve(false); + } + + maximizeWindow(_windowId: number): Promise { + return Promise.resolve(); + } + + minimizeWindow(_windowId: number): Promise { + return Promise.resolve(); + } + + unmaximizeWindow(_windowId: number): Promise { + return Promise.resolve(); + } + + onWindowTitleDoubleClick(_windowId: number): Promise { + return Promise.resolve(); + } + + setDocumentEdited(_windowId: number, _flag: boolean): Promise { + return Promise.resolve(); + } + + quit(): Promise { + return Promise.resolve(); + } + + relaunch(_options: { addArgs?: string[], removeArgs?: string[] }): Promise { + return Promise.resolve(); + } + + whenSharedProcessReady(): Promise { + return Promise.resolve(); + } + + toggleSharedProcess(): Promise { + return Promise.resolve(); + } + + // Global methods + openWindow(_windowId: number, _uris: IURIToOpen[], _options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): Promise { + return Promise.resolve(); + } + + openNewWindow(): Promise { + return Promise.resolve(); + } + + showWindow(_windowId: number): Promise { + return Promise.resolve(); + } + + getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { + return Promise.resolve([]); + } + + getWindowCount(): Promise { + return Promise.resolve(this.windowCount); + } + + log(_severity: string, ..._messages: string[]): Promise { + return Promise.resolve(); + } + + showItemInFolder(_path: URI): Promise { + return Promise.resolve(); + } + + newWindowTab(): Promise { + return Promise.resolve(); + } + + showPreviousWindowTab(): Promise { + return Promise.resolve(); + } + + showNextWindowTab(): Promise { + return Promise.resolve(); + } + + moveWindowTabToNewWindow(): Promise { + return Promise.resolve(); + } + + mergeAllWindowTabs(): Promise { + return Promise.resolve(); + } + + toggleWindowTabsBar(): Promise { + return Promise.resolve(); + } + + updateTouchBar(_windowId: number, _items: ISerializableCommandAction[][]): Promise { + return Promise.resolve(); + } + + getActiveWindowId(): Promise { + return Promise.resolve(undefined); + } + + // This needs to be handled from browser process to prevent + // foreground ordering issues on Windows + openExternal(_url: string): Promise { + return Promise.resolve(true); + } + + // TODO: this is a bit backwards + startCrashReporter(_config: Electron.CrashReporterStartOptions): Promise { + return Promise.resolve(); + } + + showMessageBox(_windowId: number, _options: Electron.MessageBoxOptions): Promise { + throw new Error('not implemented'); + } + + showSaveDialog(_windowId: number, _options: Electron.SaveDialogOptions): Promise { + throw new Error('not implemented'); + } + + showOpenDialog(_windowId: number, _options: Electron.OpenDialogOptions): Promise { + throw new Error('not implemented'); + } + + openAboutDialog(): Promise { + return Promise.resolve(); + } + + resolveProxy(windowId: number, url: string): Promise { + return Promise.resolve(undefined); + } +} + +registerSingleton(IWindowsService, SimpleWindowsService); + +//#endregion + +//#region Workspace Editing + +export class SimpleWorkspaceEditingService implements IWorkspaceEditingService { + + _serviceBrand: any; + + addFolders(folders: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): Promise { + return Promise.resolve(undefined); + } + + removeFolders(folders: URI[], donotNotifyError?: boolean): Promise { + return Promise.resolve(undefined); + } + + updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): Promise { + return Promise.resolve(undefined); + } + + enterWorkspace(path: URI): Promise { + return Promise.resolve(undefined); + } + + createAndEnterWorkspace(folders: IWorkspaceFolderCreationData[], path?: URI): Promise { + return Promise.resolve(undefined); + } + + saveAndEnterWorkspace(path: URI): Promise { + return Promise.resolve(undefined); + } + + copyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): Promise { + return Promise.resolve(undefined); + } + + pickNewWorkspacePath(): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } +} + +registerSingleton(IWorkspaceEditingService, SimpleWorkspaceEditingService, true); + +//#endregion + +//#region Workspace + +export class SimpleWorkspaceService implements IWorkspaceContextService { + _serviceBrand: any; + + private workspace: Workspace; + + readonly onDidChangeWorkspaceName = Event.None; + readonly onDidChangeWorkspaceFolders = Event.None; + readonly onDidChangeWorkbenchState = Event.None; + + constructor() { + this.workspace = new Workspace( + workspaceResource.toString(), + toWorkspaceFolders([{ path: workspaceResource.fsPath }]) + ); + } + + getFolders(): IWorkspaceFolder[] { + return this.workspace ? this.workspace.folders : []; + } + + getWorkbenchState(): WorkbenchState { + if (this.workspace.configuration) { + return WorkbenchState.WORKSPACE; + } + + if (this.workspace.folders.length) { + return WorkbenchState.FOLDER; + } + + return WorkbenchState.EMPTY; + } + + getCompleteWorkspace(): Promise { + return Promise.resolve(this.getWorkspace()); + } + + getWorkspace(): IWorkspace { + return this.workspace; + } + + getWorkspaceFolder(resource: URI): IWorkspaceFolder | null { + return this.workspace.getFolder(resource); + } + + isInsideWorkspace(resource: URI): boolean { + if (resource && this.workspace) { + return isEqualOrParent(resource, this.workspace.folders[0].uri); + } + + return false; + } + + isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean { + return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && isEqual(this.workspace.folders[0].uri, workspaceIdentifier); + } +} + +registerSingleton(IWorkspaceContextService, SimpleWorkspaceService); + +//#endregion + +//#region Workspaces + +export class SimpleWorkspacesService implements IWorkspacesService { + + _serviceBrand: any; + + createUntitledWorkspace(folders?: IWorkspaceFolderCreationData[]): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } + + deleteUntitledWorkspace(workspace: IWorkspaceIdentifier): Promise { + return Promise.resolve(undefined); + } + + getWorkspaceIdentifier(workspacePath: URI): Promise { + // @ts-ignore + return Promise.resolve(undefined); + } +} + +registerSingleton(IWorkspacesService, SimpleWorkspacesService); + +//#endregion diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index f241fef09e..601bbb4f35 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -8,7 +8,7 @@ import { IPanel } from 'vs/workbench/common/panel'; import { Composite, CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite'; import { Action } from 'vs/base/common/actions'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; import { isAncestor } from 'vs/base/browser/dom'; @@ -61,6 +61,13 @@ export class PanelRegistry extends CompositeRegistry { getDefaultPanelId(): string { return this.defaultPanelId; } + + /** + * Find out if a panel exists with the provided ID. + */ + hasPanel(id: string): boolean { + return this.getPanels().some(panel => panel.id === id); + } } /** @@ -68,40 +75,37 @@ export class PanelRegistry extends CompositeRegistry { */ export abstract class TogglePanelAction extends Action { - private panelId: string; - constructor( id: string, label: string, - panelId: string, + private readonly panelId: string, protected panelService: IPanelService, - private partService: IPartService, + private layoutService: IWorkbenchLayoutService, cssClass?: string ) { super(id, label, cssClass); - this.panelId = panelId; } run(): Promise { if (this.isPanelFocused()) { - this.partService.setPanelHidden(true); + this.layoutService.setPanelHidden(true); } else { this.panelService.openPanel(this.panelId, true); } - return Promise.resolve(null); + return Promise.resolve(); } private isPanelActive(): boolean { const activePanel = this.panelService.getActivePanel(); - return activePanel && activePanel.getId() === this.panelId; + return !!activePanel && activePanel.getId() === this.panelId; } private isPanelFocused(): boolean { const activeElement = document.activeElement; - return !!(this.isPanelActive() && activeElement && isAncestor(activeElement, this.partService.getContainer(Parts.PANEL_PART))); + return !!(this.isPanelActive() && activeElement && isAncestor(activeElement, this.layoutService.getContainer(Parts.PANEL_PART))); } } diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index 281385739b..62e5740c89 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -8,17 +8,26 @@ import { Component } from 'vs/workbench/common/component'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { Dimension, size } from 'vs/base/browser/dom'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IDimension } from 'vs/platform/layout/browser/layoutService'; +import { ISerializableView, Orientation } from 'vs/base/browser/ui/grid/grid'; +import { Event, Emitter } from 'vs/base/common/event'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export interface IPartOptions { hasTitle?: boolean; borderWidth?: () => number; } +export interface ILayoutContentResult { + titleSize: IDimension; + contentSize: IDimension; +} + /** * Parts are layed out in the workbench and have their own layout that * arranges an optional title and mandatory content area to show content. */ -export abstract class Part extends Component { +export abstract class Part extends Component implements ISerializableView { private parent: HTMLElement; private titleArea: HTMLElement | null; private contentArea: HTMLElement | null; @@ -28,9 +37,12 @@ export abstract class Part extends Component { id: string, private options: IPartOptions, themeService: IThemeService, - storageService: IStorageService + storageService: IStorageService, + layoutService: IWorkbenchLayoutService ) { super(id, themeService, storageService); + + layoutService.registerPart(this); } protected onThemeChange(theme: ITheme): void { @@ -41,18 +53,22 @@ export abstract class Part extends Component { } } + updateStyles(): void { + super.updateStyles(); + } + /** * Note: Clients should not call this method, the workbench calls this * method. Calling it otherwise may result in unexpected behavior. * * Called to create title and content area of the part. */ - create(parent: HTMLElement): void { + create(parent: HTMLElement, options?: object): void { this.parent = parent; - this.titleArea = this.createTitleArea(parent); - this.contentArea = this.createContentArea(parent); + this.titleArea = this.createTitleArea(parent, options); + this.contentArea = this.createContentArea(parent, options); - this.partLayout = new PartLayout(this.parent, this.options, this.titleArea, this.contentArea); + this.partLayout = new PartLayout(this.options, this.contentArea); this.updateStyles(); } @@ -67,7 +83,7 @@ export abstract class Part extends Component { /** * Subclasses override to provide a title area implementation. */ - protected createTitleArea(parent: HTMLElement): HTMLElement | null { + protected createTitleArea(parent: HTMLElement, options?: object): HTMLElement | null { return null; } @@ -81,7 +97,7 @@ export abstract class Part extends Component { /** * Subclasses override to provide a content area implementation. */ - protected createContentArea(parent: HTMLElement): HTMLElement | null { + protected createContentArea(parent: HTMLElement, options?: object): HTMLElement | null { return null; } @@ -95,22 +111,35 @@ export abstract class Part extends Component { /** * Layout title and content area in the given dimension. */ - layout(dimension: Dimension): Dimension[] { - return this.partLayout.layout(dimension); + protected layoutContents(width: number, height: number): ILayoutContentResult { + return this.partLayout.layout(width, height); } + + //#region ISerializableView + + private _onDidChange = this._register(new Emitter<{ width: number; height: number; }>()); + get onDidChange(): Event<{ width: number, height: number }> { return this._onDidChange.event; } + + element: HTMLElement; + + abstract minimumWidth: number; + abstract maximumWidth: number; + abstract minimumHeight: number; + abstract maximumHeight: number; + + abstract layout(width: number, height: number, orientation: Orientation): void; + abstract toJSON(): object; + + //#endregion } -export class PartLayout { +class PartLayout { private static readonly TITLE_HEIGHT = 35; - constructor(container: HTMLElement, private options: IPartOptions, titleArea: HTMLElement | null, private contentArea: HTMLElement | null) { } + constructor(private options: IPartOptions, private contentArea: HTMLElement | null) { } - layout(dimension: Dimension): Dimension[] { - const { width, height } = dimension; - - // Return the applied sizes to title and content - const sizes: Dimension[] = []; + layout(width: number, height: number): ILayoutContentResult { // Title Size: Width (Fill), Height (Variable) let titleSize: Dimension; @@ -127,14 +156,11 @@ export class PartLayout { contentSize.width -= this.options.borderWidth(); // adjust for border size } - sizes.push(titleSize); - sizes.push(contentSize); - // Content if (this.contentArea) { size(this.contentArea, contentSize.width, contentSize.height); } - return sizes; + return { titleSize, contentSize }; } } \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 997f741824..40bf14f901 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -7,7 +7,6 @@ import 'vs/css!./media/activityaction'; import * as nls from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { Action } from 'vs/base/common/actions'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -24,8 +23,8 @@ import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { IActivity, IGlobalActivity } from 'vs/workbench/common/activity'; import { ACTIVITY_BAR_FOREGROUND } from 'vs/workbench/common/theme'; -import { IActivityService } from 'vs/workbench/services/activity/common/activity'; -import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; +import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; export class ViewletActivityAction extends ActivityAction { @@ -37,7 +36,7 @@ export class ViewletActivityAction extends ActivityAction { constructor( activity: IActivity, @IViewletService private readonly viewletService: IViewletService, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @ITelemetryService private readonly telemetryService: ITelemetryService ) { super(activity); @@ -55,14 +54,14 @@ export class ViewletActivityAction extends ActivityAction { } this.lastRun = now; - const sideBarVisible = this.partService.isVisible(Parts.SIDEBAR_PART); + const sideBarVisible = this.layoutService.isVisible(Parts.SIDEBAR_PART); const activeViewlet = this.viewletService.getActiveViewlet(); // Hide sidebar if selected viewlet already visible if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.activity.id) { this.logAction('hide'); - this.partService.setSideBarHidden(true); - return Promise.resolve(null); + this.layoutService.setSideBarHidden(true); + return Promise.resolve(); } this.logAction('show'); @@ -84,20 +83,20 @@ export class ToggleViewletAction extends Action { constructor( private _viewlet: ViewletDescriptor, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IViewletService private readonly viewletService: IViewletService ) { super(_viewlet.id, _viewlet.name); } run(): Promise { - const sideBarVisible = this.partService.isVisible(Parts.SIDEBAR_PART); + const sideBarVisible = this.layoutService.isVisible(Parts.SIDEBAR_PART); const activeViewlet = this.viewletService.getActiveViewlet(); // Hide sidebar if selected viewlet already visible if (sideBarVisible && activeViewlet && activeViewlet.getId() === this._viewlet.id) { - this.partService.setSideBarHidden(true); - return Promise.resolve(null); + this.layoutService.setSideBarHidden(true); + return Promise.resolve(); } return this.viewletService.openViewlet(this._viewlet.id, true); @@ -130,32 +129,29 @@ export class GlobalActivityActionItem extends ActivityActionItem { this._register(DOM.addDisposableListener(this.container, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => { DOM.EventHelper.stop(e, true); - - const event = new StandardMouseEvent(e); - this.showContextMenu({ x: event.posx, y: event.posy }); + this.showContextMenu(); })); this._register(DOM.addDisposableListener(this.container, DOM.EventType.KEY_UP, (e: KeyboardEvent) => { let event = new StandardKeyboardEvent(e); if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { DOM.EventHelper.stop(e, true); - - this.showContextMenu(this.container); + this.showContextMenu(); } })); this._register(DOM.addDisposableListener(this.container, TouchEventType.Tap, (e: GestureEvent) => { DOM.EventHelper.stop(e, true); - - const event = new StandardMouseEvent(e); - this.showContextMenu({ x: event.posx, y: event.posy }); + this.showContextMenu(); })); } - private showContextMenu(location: HTMLElement | { x: number, y: number }): void { + private showContextMenu(): void { const globalAction = this._action as GlobalActivityAction; const activity = globalAction.activity as IGlobalActivity; const actions = activity.getActions(); + const containerPosition = DOM.getDomNodePagePosition(this.container); + const location = { x: containerPosition.left + containerPosition.width / 2, y: containerPosition.top }; this.contextMenuService.showContextMenu({ getAnchor: () => location, @@ -170,10 +166,10 @@ export class PlaceHolderViewletActivityAction extends ViewletActivityAction { constructor( id: string, iconUrl: URI, @IViewletService viewletService: IViewletService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService ) { - super({ id, name: id, cssClass: `extensionViewlet-placeholder-${id.replace(/\./g, '-')}` }, viewletService, partService, telemetryService); + super({ id, name: id, cssClass: `extensionViewlet-placeholder-${id.replace(/\./g, '-')}` }, viewletService, layoutService, telemetryService); const iconClass = `.monaco-workbench .activitybar .monaco-action-bar .action-label.${this.class}`; // Generate Placeholder CSS to show the icon in the activity bar DOM.createCSSRule(iconClass, `-webkit-mask: url('${iconUrl || ''}') no-repeat 50% 50%`); @@ -201,19 +197,19 @@ class SwitchSideBarViewAction extends Action { id: string, name: string, @IViewletService private readonly viewletService: IViewletService, - @IActivityService private readonly activityService: IActivityService + @IActivityBarService private readonly activityBarService: IActivityBarService ) { super(id, name); } run(offset: number): Promise { - const pinnedViewletIds = this.activityService.getPinnedViewletIds(); + const pinnedViewletIds = this.activityBarService.getPinnedViewletIds(); const activeViewlet = this.viewletService.getActiveViewlet(); if (!activeViewlet) { - return Promise.resolve(null); + return Promise.resolve(); } - let targetViewletId: string; + let targetViewletId: string | undefined; for (let i = 0; i < pinnedViewletIds.length; i++) { if (pinnedViewletIds[i] === activeViewlet.getId()) { targetViewletId = pinnedViewletIds[(i + pinnedViewletIds.length + offset) % pinnedViewletIds.length]; @@ -233,9 +229,9 @@ export class PreviousSideBarViewAction extends SwitchSideBarViewAction { id: string, name: string, @IViewletService viewletService: IViewletService, - @IActivityService activityService: IActivityService + @IActivityBarService activityBarService: IActivityBarService ) { - super(id, name, viewletService, activityService); + super(id, name, viewletService, activityBarService); } run(): Promise { @@ -252,9 +248,9 @@ export class NextSideBarViewAction extends SwitchSideBarViewAction { id: string, name: string, @IViewletService viewletService: IViewletService, - @IActivityService activityService: IActivityService + @IActivityBarService activityBarService: IActivityBarService ) { - super(id, name, viewletService, activityService); + super(id, name, viewletService, activityBarService); } run(): Promise { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 3e027d1fa0..ee6f825ca5 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -6,7 +6,6 @@ import 'vs/css!./media/activitybarpart'; import * as nls from 'vs/nls'; import { illegalArgument } from 'vs/base/common/errors'; -import { Emitter } from 'vs/base/common/event'; import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { GlobalActivityExtensions, IGlobalActivityRegistry } from 'vs/workbench/common/activity'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -14,8 +13,8 @@ import { Part } from 'vs/workbench/browser/part'; import { GlobalActivityActionItem, GlobalActivityAction, ViewletActivityAction, ToggleViewletAction, PlaceHolderToggleCompositePinnedAction, PlaceHolderViewletActivityAction } from 'vs/workbench/browser/parts/activitybar/activitybarActions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; -import { IPartService, Parts, Position as SideBarPosition } from 'vs/workbench/services/part/common/partService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from 'vs/workbench/services/layout/browser/layoutService'; +import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/layoutActions'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; @@ -32,25 +31,34 @@ import { IViewsService, IViewContainersRegistry, Extensions as ViewContainerExte import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { isUndefinedOrNull } from 'vs/base/common/types'; -import { ISerializableView } from 'vs/base/browser/ui/grid/grid'; - -const SCM_VIEWLET_ID = 'workbench.view.scm'; +import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Schemas } from 'vs/base/common/network'; interface ICachedViewlet { id: string; - iconUrl: UriComponents; + iconUrl?: UriComponents; pinned: boolean; - order: number; + order?: number; visible: boolean; - views?: { when: string }[]; + views?: { when?: string }[]; } -export class ActivitybarPart extends Part implements ISerializableView { +export class ActivitybarPart extends Part implements IActivityBarService { + + _serviceBrand: ServiceIdentifier; private static readonly ACTION_HEIGHT = 50; private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets'; - private dimension: Dimension; + //#region IView + + readonly minimumWidth: number = 50; + readonly maximumWidth: number = 50; + readonly minimumHeight: number = 0; + readonly maximumHeight: number = Number.POSITIVE_INFINITY; + + //#endregion private globalActionBar: ActionBar; private globalActivityIdToActions: { [globalActivityId: string]: GlobalActivityAction; } = Object.create(null); @@ -59,27 +67,17 @@ export class ActivitybarPart extends Part implements ISerializableView { private compositeBar: CompositeBar; private compositeActions: { [compositeId: string]: { activityAction: ViewletActivityAction, pinnedAction: ToggleCompositePinnedAction } } = Object.create(null); - element: HTMLElement; - minimumWidth: number = 50; - maximumWidth: number = 50; - minimumHeight: number = 0; - maximumHeight: number = Number.POSITIVE_INFINITY; - - private _onDidChange = new Emitter<{ width: number; height: number; }>(); - readonly onDidChange = this._onDidChange.event; - constructor( - id: string, @IViewletService private readonly viewletService: IViewletService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IThemeService themeService: IThemeService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @IViewsService private readonly viewsService: IViewsService, - @IContextKeyService private readonly contextKeyService: IContextKeyService + @IContextKeyService private readonly contextKeyService: IContextKeyService, ) { - super(id, { hasTitle: false }, themeService, storageService); + super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); this.cachedViewlets = this.getCachedViewlets(); for (const cachedViewlet of this.cachedViewlets) { @@ -97,9 +95,9 @@ export class ActivitybarPart extends Part implements ISerializableView { getOnCompositeClickAction: (compositeId: string) => this.instantiationService.createInstance(ToggleViewletAction, this.viewletService.getViewlet(compositeId)), getContextMenuActions: () => [this.instantiationService.createInstance(ToggleActivityBarVisibilityAction, ToggleActivityBarVisibilityAction.ID, nls.localize('hideActivitBar', "Hide Activity Bar"))], getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(), - hidePart: () => this.partService.setSideBarHidden(true), + hidePart: () => this.layoutService.setSideBarHidden(true), compositeSize: 50, - colors: theme => this.getActivitybarItemColors(theme), + colors: (theme: ITheme) => this.getActivitybarItemColors(theme), overflowActionSize: ActivitybarPart.ACTION_HEIGHT })); @@ -121,6 +119,7 @@ export class ActivitybarPart extends Part implements ISerializableView { private registerListeners(): void { + // Viewlet registration this._register(this.viewletService.onDidViewletRegister(viewlet => this.onDidRegisterViewlets([viewlet]))); this._register(this.viewletService.onDidViewletDeregister(({ id }) => this.removeComposite(id, true))); @@ -130,6 +129,7 @@ export class ActivitybarPart extends Part implements ISerializableView { // Deactivate viewlet action on close this._register(this.viewletService.onDidViewletClose(viewlet => this.compositeBar.deactivateComposite(viewlet.getId()))); + // Extension registration let disposables: IDisposable[] = []; this._register(this.extensionService.onDidRegisterExtensions(() => { disposables = dispose(disposables); @@ -137,15 +137,17 @@ export class ActivitybarPart extends Part implements ISerializableView { this.compositeBar.onDidChange(() => this.saveCachedViewlets(), this, disposables); this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e), this, disposables); })); + this._register(toDisposable(() => dispose(disposables))); } private onDidRegisterExtensions(): void { this.removeNotExistingComposites(); + for (const viewlet of this.viewletService.getViewlets()) { this.enableCompositeActions(viewlet); const viewContainer = this.getViewContainer(viewlet.id); - if (viewContainer) { + if (viewContainer && viewContainer.hideIfEmpty) { const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); if (viewDescriptors) { this.onDidChangeActiveViews(viewlet, viewDescriptors); @@ -153,6 +155,7 @@ export class ActivitybarPart extends Part implements ISerializableView { } } } + this.saveCachedViewlets(); } @@ -165,16 +168,21 @@ export class ActivitybarPart extends Part implements ISerializableView { } private onDidViewletOpen(viewlet: IViewlet): void { + // Update the composite bar by adding - this.compositeBar.addComposite(this.viewletService.getViewlet(viewlet.getId())); + const foundViewlet = this.viewletService.getViewlet(viewlet.getId()); + if (foundViewlet) { + this.compositeBar.addComposite(foundViewlet); + } this.compositeBar.activateComposite(viewlet.getId()); const viewletDescriptor = this.viewletService.getViewlet(viewlet.getId()); - const viewContainer = this.getViewContainer(viewletDescriptor.id); - if (viewContainer) { - const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); - if (viewDescriptors && viewDescriptors.activeViewDescriptors.length === 0) { - // Update the composite bar by hiding - this.removeComposite(viewletDescriptor.id, true); + if (viewletDescriptor) { + const viewContainer = this.getViewContainer(viewletDescriptor.id); + if (viewContainer && viewContainer.hideIfEmpty) { + const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); + if (viewDescriptors && viewDescriptors.activeViewDescriptors.length === 0) { + this.removeComposite(viewletDescriptor.id, true); // Update the composite bar by hiding + } } } } @@ -230,7 +238,7 @@ export class ActivitybarPart extends Part implements ISerializableView { container.style.backgroundColor = background; const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder); - const isPositionLeft = this.partService.getSideBarPosition() === SideBarPosition.LEFT; + const isPositionLeft = this.layoutService.getSideBarPosition() === SideBarPosition.LEFT; container.style.boxSizing = borderColor && isPositionLeft ? 'border-box' : null; container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : null; container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : null; @@ -247,7 +255,7 @@ export class ActivitybarPart extends Part implements ISerializableView { badgeBackground: theme.getColor(ACTIVITY_BAR_BADGE_BACKGROUND), badgeForeground: theme.getColor(ACTIVITY_BAR_BADGE_FOREGROUND), dragAndDropBackground: theme.getColor(ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND), - activeBackgroundColor: null, inactiveBackgroundColor: null, activeBorderBottomColor: null, + activeBackgroundColor: undefined, inactiveBackgroundColor: undefined, activeBorderBottomColor: undefined, }; } @@ -259,7 +267,7 @@ export class ActivitybarPart extends Part implements ISerializableView { .map(a => new GlobalActivityAction(a)); this.globalActionBar = this._register(new ActionBar(container, { - actionItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionItem, a, theme => this.getActivitybarItemColors(theme)), + actionItemProvider: a => this.instantiationService.createInstance(GlobalActivityActionItem, a, (theme: ITheme) => this.getActivitybarItemColors(theme)), orientation: ActionsOrientation.VERTICAL, ariaLabel: nls.localize('globalActions', "Global Actions"), animated: false @@ -283,7 +291,7 @@ export class ActivitybarPart extends Part implements ISerializableView { } else { const cachedComposite = this.cachedViewlets.filter(c => c.id === compositeId)[0]; compositeActions = { - activityAction: this.instantiationService.createInstance(PlaceHolderViewletActivityAction, compositeId, cachedComposite ? URI.revive(cachedComposite.iconUrl) : undefined), + activityAction: this.instantiationService.createInstance(PlaceHolderViewletActivityAction, compositeId, cachedComposite && cachedComposite.iconUrl ? URI.revive(cachedComposite.iconUrl) : undefined), pinnedAction: new PlaceHolderToggleCompositePinnedAction(compositeId, this.compositeBar) }; } @@ -316,8 +324,12 @@ export class ActivitybarPart extends Part implements ISerializableView { } private shouldBeHidden(viewletId: string, cachedViewlet: ICachedViewlet): boolean { + const viewContainer = this.getViewContainer(viewletId); + if (!viewContainer || !viewContainer.hideIfEmpty) { + return false; + } return cachedViewlet && cachedViewlet.views && cachedViewlet.views.length - ? cachedViewlet.views.every(({ when }) => when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when))) + ? cachedViewlet.views.every(({ when }) => !!when && !this.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(when))) : viewletId === TEST_VIEW_CONTAINER_ID /* Hide Test viewlet for the first time or it had no views registered before */; } @@ -336,6 +348,7 @@ export class ActivitybarPart extends Part implements ISerializableView { } else { this.compositeBar.removeComposite(compositeId); } + const compositeActions = this.compositeActions[compositeId]; if (compositeActions) { compositeActions.activityAction.dispose(); @@ -349,6 +362,7 @@ export class ActivitybarPart extends Part implements ISerializableView { if (activityAction instanceof PlaceHolderViewletActivityAction) { activityAction.setActivity(viewlet); } + if (pinnedAction instanceof PlaceHolderToggleCompositePinnedAction) { pinnedAction.setActivity(viewlet); } @@ -356,38 +370,27 @@ export class ActivitybarPart extends Part implements ISerializableView { getPinnedViewletIds(): string[] { const pinnedCompositeIds = this.compositeBar.getPinnedComposites().map(v => v.id); + return this.viewletService.getViewlets() .filter(v => this.compositeBar.isPinned(v.id)) .sort((v1, v2) => pinnedCompositeIds.indexOf(v1.id) - pinnedCompositeIds.indexOf(v2.id)) .map(v => v.id); } - layout(dimension: Dimension): Dimension[]; - layout(width: number, height: number): void; - layout(dim1: Dimension | number, dim2?: number): Dimension[] | void { - if (!this.partService.isVisible(Parts.ACTIVITYBAR_PART)) { - if (dim1 instanceof Dimension) { - return [dim1]; - } - + layout(width: number, height: number): void { + if (!this.layoutService.isVisible(Parts.ACTIVITYBAR_PART)) { return; } - // Pass to super - const sizes = super.layout(dim1 instanceof Dimension ? dim1 : new Dimension(dim1, dim2)); + // Layout contents + const contentAreaSize = super.layoutContents(width, height).contentSize; - this.dimension = sizes[1]; - - let availableHeight = this.dimension.height; + // Layout composite bar + let availableHeight = contentAreaSize.height; if (this.globalActionBar) { - // adjust height for global actions showing - availableHeight -= (this.globalActionBar.items.length * ActivitybarPart.ACTION_HEIGHT); - } - this.compositeBar.layout(new Dimension(dim1 instanceof Dimension ? dim1.width : dim1, availableHeight)); - - if (dim1 instanceof Dimension) { - return sizes; + availableHeight -= (this.globalActionBar.items.length * ActivitybarPart.ACTION_HEIGHT); // adjust height for global actions showing } + this.compositeBar.layout(new Dimension(width, availableHeight)); } private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void { @@ -425,13 +428,14 @@ export class ActivitybarPart extends Part implements ISerializableView { private saveCachedViewlets(): void { const state: ICachedViewlet[] = []; - const compositeItems = this.compositeBar.getCompositeBarItems(); const allViewlets = this.viewletService.getViewlets(); + + const compositeItems = this.compositeBar.getCompositeBarItems(); for (const compositeItem of compositeItems) { const viewContainer = this.getViewContainer(compositeItem.id); const viewlet = allViewlets.filter(({ id }) => id === compositeItem.id)[0]; if (viewlet) { - const views: { when: string }[] = []; + const views: { when: string | undefined }[] = []; if (viewContainer) { const viewDescriptors = this.viewsService.getViewDescriptors(viewContainer); if (viewDescriptors) { @@ -440,9 +444,10 @@ export class ActivitybarPart extends Part implements ISerializableView { } } } - state.push({ id: compositeItem.id, iconUrl: viewlet.iconUrl, views, pinned: compositeItem && compositeItem.pinned, order: compositeItem ? compositeItem.order : undefined, visible: compositeItem && compositeItem.visible }); + state.push({ id: compositeItem.id, iconUrl: viewlet.iconUrl && viewlet.iconUrl.scheme === Schemas.file ? viewlet.iconUrl : undefined, views, pinned: compositeItem && compositeItem.pinned, order: compositeItem ? compositeItem.order : undefined, visible: compositeItem && compositeItem.visible }); } } + this.cachedViewletsValue = JSON.stringify(state); } @@ -453,6 +458,7 @@ export class ActivitybarPart extends Part implements ISerializableView { serialized.visible = isUndefinedOrNull(serialized.visible) ? true : serialized.visible; return serialized; }); + for (const old of this.loadOldCachedViewlets()) { const cachedViewlet = cachedViewlets.filter(cached => cached.id === old.id)[0]; if (cachedViewlet) { @@ -460,6 +466,7 @@ export class ActivitybarPart extends Part implements ISerializableView { cachedViewlet.views = old.views; } } + return cachedViewlets; } @@ -467,14 +474,16 @@ export class ActivitybarPart extends Part implements ISerializableView { const previousState = this.storageService.get('workbench.activity.placeholderViewlets', StorageScope.GLOBAL, '[]'); const result = (JSON.parse(previousState)); this.storageService.remove('workbench.activity.placeholderViewlets', StorageScope.GLOBAL); + return result; } - private _cachedViewletsValue: string; + private _cachedViewletsValue: string | null; private get cachedViewletsValue(): string { if (!this._cachedViewletsValue) { this._cachedViewletsValue = this.getStoredCachedViewletsValue(); } + return this._cachedViewletsValue; } @@ -494,10 +503,6 @@ export class ActivitybarPart extends Part implements ISerializableView { } private getViewContainer(viewletId: string): ViewContainer | undefined { - // TODO: @Joao Remove this after moving SCM Viewlet to ViewContainerViewlet - https://github.com/Microsoft/vscode/issues/49054 - if (viewletId === SCM_VIEWLET_ID) { - return null; - } const viewContainerRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); return viewContainerRegistry.get(viewletId); } @@ -508,3 +513,5 @@ export class ActivitybarPart extends Part implements ISerializableView { }; } } + +registerSingleton(IActivityBarService, ActivitybarPart); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index 3d0e4d21f6..e278205701 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -23,9 +23,9 @@ import { Emitter, Event } from 'vs/base/common/event'; export interface ICompositeBarItem { id: string; - name: string; + name?: string; pinned: boolean; - order: number; + order?: number; visible: boolean; } @@ -49,8 +49,8 @@ export class CompositeBar extends Widget implements ICompositeBar { private dimension: Dimension; private compositeSwitcherBar: ActionBar; - private compositeOverflowAction: CompositeOverflowActivityAction; - private compositeOverflowActionItem: CompositeOverflowActivityActionItem; + private compositeOverflowAction: CompositeOverflowActivityAction | null; + private compositeOverflowActionItem: CompositeOverflowActivityActionItem | null; private model: CompositeBarModel; private visibleComposites: string[]; @@ -112,7 +112,7 @@ export class CompositeBar extends Widget implements ICompositeBar { if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { EventHelper.stop(e, true); - const draggedCompositeId = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype)[0].id; + const draggedCompositeId = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype)![0].id; this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype); const targetItem = this.model.visibleItems[this.model.visibleItems.length - 1]; @@ -174,7 +174,7 @@ export class CompositeBar extends Widget implements ICompositeBar { if (this.model.activate(id)) { // Update if current composite is neither visible nor pinned // or previous active composite is not pinned - if (this.visibleComposites.indexOf(id) === - 1 || !this.model.activeItem.pinned || (previousActiveItem && !previousActiveItem.pinned)) { + if (this.visibleComposites.indexOf(id) === - 1 || (!!this.model.activeItem && !this.model.activeItem.pinned) || (previousActiveItem && !previousActiveItem.pinned)) { this.updateCompositeSwitcher(); } } @@ -265,7 +265,7 @@ export class CompositeBar extends Widget implements ICompositeBar { } } - getAction(compositeId): ActivityAction { + getAction(compositeId: string): ActivityAction { const item = this.model.findItem(compositeId); return item && item.activityAction; } @@ -304,7 +304,7 @@ export class CompositeBar extends Widget implements ICompositeBar { let size = 0; const limit = this.options.orientation === ActionsOrientation.VERTICAL ? this.dimension.height : this.dimension.width; for (let i = 0; i < compositesToShow.length && size <= limit; i++) { - size += this.compositeSizeInBar.get(compositesToShow[i]); + size += this.compositeSizeInBar.get(compositesToShow[i])!; if (size > limit) { maxVisible = i; } @@ -312,19 +312,19 @@ export class CompositeBar extends Widget implements ICompositeBar { overflows = compositesToShow.length > maxVisible; if (overflows) { - size -= this.compositeSizeInBar.get(compositesToShow[maxVisible]); + size -= this.compositeSizeInBar.get(compositesToShow[maxVisible])!; compositesToShow = compositesToShow.slice(0, maxVisible); size += this.options.overflowActionSize; } // Check if we need to make extra room for the overflow action if (size > limit) { - size -= this.compositeSizeInBar.get(compositesToShow.pop()); + size -= this.compositeSizeInBar.get(compositesToShow.pop()!)!; } // We always try show the active composite - if (this.model.activeItem && compositesToShow.every(compositeId => compositeId !== this.model.activeItem.id)) { - const removedComposite = compositesToShow.pop(); - size = size - this.compositeSizeInBar.get(removedComposite) + this.compositeSizeInBar.get(this.model.activeItem.id); + if (this.model.activeItem && compositesToShow.every(compositeId => !!this.model.activeItem && compositeId !== this.model.activeItem.id)) { + const removedComposite = compositesToShow.pop()!; + size = size - this.compositeSizeInBar.get(removedComposite)! + this.compositeSizeInBar.get(this.model.activeItem.id)!; compositesToShow.push(this.model.activeItem.id); } @@ -342,7 +342,9 @@ export class CompositeBar extends Widget implements ICompositeBar { this.compositeOverflowAction.dispose(); this.compositeOverflowAction = null; - this.compositeOverflowActionItem.dispose(); + if (this.compositeOverflowActionItem) { + this.compositeOverflowActionItem.dispose(); + } this.compositeOverflowActionItem = null; } @@ -378,7 +380,11 @@ export class CompositeBar extends Widget implements ICompositeBar { // Add overflow action as needed if ((visibleCompositesChange && overflows) || this.compositeSwitcherBar.length() === 0) { - this.compositeOverflowAction = this.instantiationService.createInstance(CompositeOverflowActivityAction, () => this.compositeOverflowActionItem.showMenu()); + this.compositeOverflowAction = this.instantiationService.createInstance(CompositeOverflowActivityAction, () => { + if (this.compositeOverflowActionItem) { + this.compositeOverflowActionItem.showMenu(); + } + }); this.compositeOverflowActionItem = this.instantiationService.createInstance( CompositeOverflowActivityActionItem, this.compositeOverflowAction, @@ -398,7 +404,7 @@ export class CompositeBar extends Widget implements ICompositeBar { this._onDidChange.fire(); } - private getOverflowingComposites(): { id: string, name: string }[] { + private getOverflowingComposites(): { id: string, name?: string }[] { let overflowingIds = this.model.visibleItems.filter(item => item.pinned).map(item => item.id); // Show the active composite even if it is not pinned @@ -453,7 +459,7 @@ class CompositeBarModel { private _items: ICompositeBarModelItem[]; private readonly options: ICompositeBarOptions; - activeItem: ICompositeBarModelItem; + activeItem?: ICompositeBarModelItem; constructor( items: ICompositeBarItem[], @@ -507,7 +513,7 @@ class CompositeBarModel { return this.items.filter(item => item.visible && item.pinned); } - private createCompositeBarItem(id: string, name: string, order: number, pinned: boolean, visible: boolean): ICompositeBarModelItem { + private createCompositeBarItem(id: string, name: string | undefined, order: number | undefined, pinned: boolean, visible: boolean): ICompositeBarModelItem { const options = this.options; return { id, name, pinned, order, visible, @@ -521,7 +527,7 @@ class CompositeBarModel { }; } - add(id: string, name: string, order: number): boolean { + add(id: string, name: string, order: number | undefined): boolean { const item = this.findItem(id); if (item) { let changed = false; @@ -541,7 +547,7 @@ class CompositeBarModel { this.items.push(item); } else { let index = 0; - while (index < this.items.length && this.items[index].order < order) { + while (index < this.items.length && typeof this.items[index].order === 'number' && this.items[index].order! < order) { index++; } this.items.splice(index, 0, item); diff --git a/src/vs/workbench/browser/parts/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts index c8fc811738..ee93c8cfb6 100644 --- a/src/vs/workbench/browser/parts/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -23,7 +23,7 @@ import { Color } from 'vs/base/common/color'; export interface ICompositeActivity { badge: IBadge; - clazz: string; + clazz?: string; priority: number; } @@ -57,13 +57,11 @@ export class ActivityAction extends Action { private _onDidChangeBadge = new Emitter(); get onDidChangeBadge(): Event { return this._onDidChangeBadge.event; } - private badge: IBadge; + private badge?: IBadge; private clazz: string | undefined; constructor(private _activity: IActivity) { super(_activity.id, _activity.name, _activity.cssClass); - - this.badge = null; } get activity(): IActivity { @@ -87,7 +85,7 @@ export class ActivityAction extends Action { } } - getBadge(): IBadge { + getBadge(): IBadge | undefined { return this.badge; } @@ -95,7 +93,7 @@ export class ActivityAction extends Action { return this.clazz; } - setBadge(badge: IBadge, clazz?: string): void { + setBadge(badge: IBadge | undefined, clazz?: string): void { this.badge = badge; this.clazz = clazz; this._onDidChangeBadge.fire(this); @@ -110,14 +108,14 @@ export class ActivityAction extends Action { } export interface ICompositeBarColors { - activeBackgroundColor: Color; - inactiveBackgroundColor: Color; - activeBorderBottomColor: Color; - activeForegroundColor: Color; - inactiveForegroundColor: Color; - badgeBackground: Color; - badgeForeground: Color; - dragAndDropBackground: Color; + activeBackgroundColor?: Color; + inactiveBackgroundColor?: Color; + activeBorderBottomColor?: Color; + activeForegroundColor?: Color; + inactiveForegroundColor?: Color; + badgeBackground?: Color; + badgeForeground?: Color; + dragAndDropBackground?: Color; } export interface IActivityActionItemOptions extends IBaseActionItemOptions { @@ -207,10 +205,10 @@ export class ActivityActionItem extends BaseActionItem { })); // Label - this.label = dom.append(this.element, dom.$('a')); + this.label = dom.append(this.element!, dom.$('a')); // Badge - this.badge = dom.append(this.element, dom.$('.badge')); + this.badge = dom.append(this.element!, dom.$('.badge')); this.badgeContent = dom.append(this.badge, dom.$('.badge-content')); dom.hide(this.badge); @@ -254,9 +252,9 @@ export class ActivityActionItem extends BaseActionItem { const noOfThousands = badge.number / 1000; const floor = Math.floor(noOfThousands); if (noOfThousands > floor) { - number = nls.localize('largeNumberBadge1', '{0}k+', floor); + number = `${floor}K+`; } else { - number = nls.localize('largeNumberBadge2', '{0}k', noOfThousands); + number = `${noOfThousands}K`; } } this.badgeContent.textContent = number; @@ -373,7 +371,7 @@ export class CompositeOverflowActivityActionItem extends ActivityActionItem { this.actions = this.getActions(); this.contextMenuService.showContextMenu({ - getAnchor: () => this.element, + getAnchor: () => this.element!, getActions: () => this.actions, onHide: () => dispose(this.actions) }); @@ -385,7 +383,7 @@ export class CompositeOverflowActivityActionItem extends ActivityActionItem { action.radio = this.getActiveCompositeId() === action.id; const badge = this.getBadge(composite.id); - let suffix: string | number; + let suffix: string | number | undefined; if (badge instanceof NumberBadge) { suffix = badge.number; } else if (badge instanceof TextBadge) { @@ -434,7 +432,7 @@ export class CompositeActionItem extends ActivityActionItem { private static manageExtensionAction: ManageExtensionAction; - private compositeActivity: IActivity; + private compositeActivity: IActivity | null; private compositeTransfer: LocalSelectionTransfer; constructor( @@ -463,7 +461,7 @@ export class CompositeActionItem extends ActivityActionItem { protected get activity(): IActivity { if (!this.compositeActivity) { let activityName: string; - const keybinding = this.getKeybindingLabel(this.compositeActivityAction.activity.keybindingId); + const keybinding = typeof this.compositeActivityAction.activity.keybindingId === 'string' ? this.getKeybindingLabel(this.compositeActivityAction.activity.keybindingId) : null; if (keybinding) { activityName = nls.localize('titleKeybinding', "{0} ({1})", this.compositeActivityAction.activity.name, keybinding); } else { @@ -480,7 +478,7 @@ export class CompositeActionItem extends ActivityActionItem { return this.compositeActivity; } - private getKeybindingLabel(id: string): string { + private getKeybindingLabel(id: string): string | null { const kb = this.keybindingService.lookupKeybinding(id); if (kb) { return kb.getLabel(); @@ -503,7 +501,7 @@ export class CompositeActionItem extends ActivityActionItem { // Allow to drag this._register(dom.addDisposableListener(this.container, dom.EventType.DRAG_START, (e: DragEvent) => { - e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer!.effectAllowed = 'move'; // Registe as dragged to local transfer this.compositeTransfer.setData([new DraggedCompositeIdentifier(this.activity.id)], DraggedCompositeIdentifier.prototype); @@ -516,7 +514,7 @@ export class CompositeActionItem extends ActivityActionItem { this._register(new DragAndDropObserver(this.container, { onDragEnter: e => { - if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) && this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype)[0].id !== this.activity.id) { + if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) && this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype)![0].id !== this.activity.id) { this.updateFromDragging(container, true); } }, @@ -539,7 +537,7 @@ export class CompositeActionItem extends ActivityActionItem { dom.EventHelper.stop(e, true); if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { - const draggedCompositeId = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype)[0].id; + const draggedCompositeId = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype)![0].id; if (draggedCompositeId !== this.activity.id) { this.updateFromDragging(container, false); this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype); @@ -617,6 +615,10 @@ export class CompositeActionItem extends ActivityActionItem { } protected updateEnabled(): void { + if (!this.element) { + return; + } + if (this.getAction().enabled) { dom.removeClass(this.element, 'disabled'); } else { @@ -636,12 +638,12 @@ export class CompositeActionItem extends ActivityActionItem { export class ToggleCompositePinnedAction extends Action { constructor( - private activity: IActivity, + private activity: IActivity | undefined, private compositeBar: ICompositeBar ) { super('show.toggleCompositePinned', activity ? activity.name : nls.localize('toggle', "Toggle View Pinned")); - this.checked = this.activity && this.compositeBar.isPinned(this.activity.id); + this.checked = !!this.activity && this.compositeBar.isPinned(this.activity.id); } run(context: string): Promise { diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 69fe248c5f..389d4e87bb 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -19,7 +19,7 @@ import { Part, IPartOptions } from 'vs/workbench/browser/part'; import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite'; import { IComposite } from 'vs/workbench/common/composite'; import { ScopedProgressService } from 'vs/workbench/services/progress/browser/progressService'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -32,6 +32,7 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Dimension, append, $, addClass, hide, show, addClasses } from 'vs/base/browser/dom'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { withNullAsUndefined } from 'vs/base/common/types'; export interface ICompositeTitleLabel { @@ -54,8 +55,8 @@ interface CompositeItem { export abstract class CompositePart extends Part { - protected _onDidCompositeOpen = this._register(new Emitter<{ composite: IComposite, focus: boolean }>()); - protected _onDidCompositeClose = this._register(new Emitter()); + protected readonly onDidCompositeOpen = this._register(new Emitter<{ composite: IComposite, focus: boolean }>()); + protected readonly onDidCompositeClose = this._register(new Emitter()); protected toolBar: ToolBar; @@ -75,7 +76,7 @@ export abstract class CompositePart extends Part { protected storageService: IStorageService, private telemetryService: ITelemetryService, protected contextMenuService: IContextMenuService, - protected partService: IPartService, + protected layoutService: IWorkbenchLayoutService, protected keybindingService: IKeybindingService, protected instantiationService: IInstantiationService, themeService: IThemeService, @@ -84,11 +85,11 @@ export abstract class CompositePart extends Part { private defaultCompositeId: string, private nameForTelemetry: string, private compositeCSSClass: string, - private titleForegroundColor: string, + private titleForegroundColor: string | undefined, id: string, options: IPartOptions ) { - super(id, options, themeService, storageService); + super(id, options, themeService, storageService, layoutService); this.mapCompositeToCompositeContainer = {}; this.mapActionsBindingToComposite = {}; @@ -98,6 +99,7 @@ export abstract class CompositePart extends Part { } protected openComposite(id: string, focus?: boolean): Composite | undefined { + // Check if composite already visible and just focus in that case if (this.activeComposite && this.activeComposite.getId() === id) { if (focus) { @@ -140,7 +142,7 @@ export abstract class CompositePart extends Part { composite.focus(); } - this._onDidCompositeOpen.fire({ composite, focus }); + this.onDidCompositeOpen.fire({ composite, focus }); return composite; } @@ -152,7 +154,7 @@ export abstract class CompositePart extends Part { // Return with the composite that is being opened if (composite) { - this._onDidCompositeOpen.fire({ composite, focus }); + this.onDidCompositeOpen.fire({ composite, focus }); } return composite; @@ -222,7 +224,7 @@ export abstract class CompositePart extends Part { // Report progress for slow loading composites (but only if we did not create the composites before already) const compositeItem = this.instantiatedCompositeItems.get(composite.getId()); if (compositeItem && !compositeContainer) { - compositeItem.progressService.showWhile(Promise.resolve(), this.partService.isRestored() ? 800 : 3200 /* less ugly initial startup */); + compositeItem.progressService.showWhile(Promise.resolve(), this.layoutService.isRestored() ? 800 : 3200 /* less ugly initial startup */); } // Fill Content and Actions @@ -244,7 +246,7 @@ export abstract class CompositePart extends Part { // Update title with composite title if it differs from descriptor const descriptor = this.registry.getComposite(composite.getId()); if (descriptor && descriptor.name !== composite.getTitle()) { - this.updateTitle(composite.getId(), composite.getTitle() || undefined); + this.updateTitle(composite.getId(), withNullAsUndefined(composite.getTitle())); } // Handle Composite Actions @@ -341,6 +343,9 @@ export abstract class CompositePart extends Part { primaryActions.push(...this.getActions()); secondaryActions.push(...this.getSecondaryActions()); + // Update context + this.toolBar.context = this.actionsContextProvider(); + // Return fn to set into toolbar return this.toolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions)); } @@ -375,7 +380,7 @@ export abstract class CompositePart extends Part { // Empty Actions this.toolBar.setActions([])(); - this._onDidCompositeClose.fire(composite); + this.onDidCompositeClose.fire(composite); return composite; } @@ -415,12 +420,12 @@ export abstract class CompositePart extends Part { }, updateStyles: () => { - titleLabel.style.color = $this.getColor($this.titleForegroundColor); + titleLabel.style.color = $this.titleForegroundColor ? $this.getColor($this.titleForegroundColor) : null; } }; } - protected updateStyles(): void { + updateStyles(): void { super.updateStyles(); // Forward to title label @@ -437,6 +442,16 @@ export abstract class CompositePart extends Part { return null; } + protected actionsContextProvider(): any { + + // Check Active Composite + if (this.activeComposite) { + return this.activeComposite.getActionsContext(); + } + + return null; + } + createContentArea(parent: HTMLElement): HTMLElement { const contentContainer = append(parent, $('.content')); @@ -449,6 +464,7 @@ export abstract class CompositePart extends Part { getProgressIndicator(id: string): IProgressService | null { const compositeItem = this.instantiatedCompositeItems.get(id); + return compositeItem ? compositeItem.progressService : null; } @@ -464,27 +480,20 @@ export abstract class CompositePart extends Part { return AnchorAlignment.RIGHT; } - layout(dimension: Dimension): Dimension[]; - layout(width: number, height: number): void; - layout(dim1: Dimension | number, dim2?: number): Dimension[] | void { - // Pass to super - const sizes = super.layout(dim1 instanceof Dimension ? dim1 : new Dimension(dim1, dim2!)); + layout(width: number, height: number): void { - // Pass Contentsize to composite - this.contentAreaSize = sizes[1]; + // Layout contents + this.contentAreaSize = super.layoutContents(width, height).contentSize; + + // Layout composite if (this.activeComposite) { this.activeComposite.layout(this.contentAreaSize); } - - if (dim1 instanceof Dimension) { - return sizes; - } } protected removeComposite(compositeId: string): boolean { if (this.activeComposite && this.activeComposite.getId() === compositeId) { - // do not remove active compoiste - return false; + return false; // do not remove active composite } delete this.mapCompositeToCompositeContainer[compositeId]; @@ -495,6 +504,7 @@ export abstract class CompositePart extends Part { dispose(compositeItem.disposable); this.instantiatedCompositeItems.delete(compositeId); } + return true; } diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index 9d30067bfa..b41f5c3601 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -8,7 +8,7 @@ import { EditorInput, EditorOptions, IEditor, GroupIdentifier, IEditorMemento } import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { LRUCache } from 'vs/base/common/map'; import { URI } from 'vs/base/common/uri'; diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index bf5a75a5cc..0bb6a8cf6c 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -11,7 +11,7 @@ import { localize } from 'vs/nls'; import { IConfigurationOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; import { GroupIdentifier } from 'vs/workbench/common/editor'; @@ -19,7 +19,7 @@ export const IBreadcrumbsService = createDecorator('IEditor export interface IBreadcrumbsService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; register(group: GroupIdentifier, widget: BreadcrumbsWidget): IDisposable; @@ -29,7 +29,7 @@ export interface IBreadcrumbsService { export class BreadcrumbsService implements IBreadcrumbsService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private readonly _map = new Map(); diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 428d886714..4d4281e5a5 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -41,10 +41,12 @@ import { BreadcrumbElement, EditorBreadcrumbsModel, FileElement } from 'vs/workb import { BreadcrumbsPicker, createBreadcrumbsPicker } from 'vs/workbench/browser/parts/editor/breadcrumbsPicker'; import { SideBySideEditorInput } from 'vs/workbench/common/editor'; import { ACTIVE_GROUP, ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; +import { onDidChangeZoomLevel } from 'vs/base/browser/browser'; +import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; class Item extends BreadcrumbsItem { @@ -148,6 +150,7 @@ export class BreadcrumbsControl { private _disposables = new Array(); private _breadcrumbsDisposables = new Array(); private _breadcrumbsPickerShowing = false; + private _breadcrumbsPickerIgnoreOnceItem: BreadcrumbsItem | undefined; constructor( container: HTMLElement, @@ -196,7 +199,7 @@ export class BreadcrumbsControl { this.domNode.remove(); } - layout(dim: dom.Dimension): void { + layout(dim: dom.Dimension | undefined): void { this._widget.layout(dim); } @@ -219,7 +222,7 @@ export class BreadcrumbsControl { input = input.master; } - if (!input || !input.getResource() || (input.getResource().scheme !== Schemas.untitled && !this._fileService.canHandleResource(input.getResource()))) { + if (!input || !input.getResource() || (input.getResource()!.scheme !== Schemas.untitled && !this._fileService.canHandleResource(input.getResource()!))) { // cleanup and return when there is no input or when // we cannot handle this input this._ckBreadcrumbsPossible.set(false); @@ -236,7 +239,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsPossible.set(true); let editor = this._getActiveCodeEditor(); - let model = new EditorBreadcrumbsModel(input.getResource(), editor, this._workspaceService, this._configurationService); + let model = new EditorBreadcrumbsModel(input.getResource()!, editor, this._workspaceService, this._configurationService); dom.toggleClass(this.domNode, 'relative-path', model.isRelative()); let updateBreadcrumbs = () => { @@ -260,9 +263,12 @@ export class BreadcrumbsControl { return true; } - private _getActiveCodeEditor(): ICodeEditor { + private _getActiveCodeEditor(): ICodeEditor | undefined { + if (!this._editorGroup.activeControl) { + return undefined; + } let control = this._editorGroup.activeControl.getControl(); - let editor: ICodeEditor; + let editor: ICodeEditor | undefined; if (isCodeEditor(control)) { editor = control as ICodeEditor; } else if (isDiffEditor(control)) { @@ -282,6 +288,13 @@ export class BreadcrumbsControl { return; } + if (event.item === this._breadcrumbsPickerIgnoreOnceItem) { + this._breadcrumbsPickerIgnoreOnceItem = undefined; + this._widget.setFocused(undefined); + this._widget.setSelection(undefined); + return; + } + const { element } = event.item as Item; this._editorGroup.focus(); @@ -313,7 +326,7 @@ export class BreadcrumbsControl { let picker: BreadcrumbsPicker; let editor = this._getActiveCodeEditor(); let editorDecorations: string[] = []; - let editorViewState: ICodeEditorViewState; + let editorViewState: ICodeEditorViewState | undefined; this._contextViewService.showContextView({ render: (parent: HTMLElement) => { @@ -336,7 +349,7 @@ export class BreadcrumbsControl { return; } if (!editorViewState) { - editorViewState = editor.saveViewState(); + editorViewState = withNullAsUndefined(editor.saveViewState()); } const { symbol } = data.target; editor.revealRangeInCenter(symbol.range, ScrollType.Smooth); @@ -347,12 +360,29 @@ export class BreadcrumbsControl { isWholeLine: true } }]); - }); + + let zoomListener = onDidChangeZoomLevel(() => { + this._contextViewService.hideContextView(this); + }); + + let focusTracker = dom.trackFocus(parent); + let blurListener = focusTracker.onDidBlur(() => { + this._breadcrumbsPickerIgnoreOnceItem = this._widget.isDOMFocused() ? event.item : undefined; + this._contextViewService.hideContextView(this); + }); + this._breadcrumbsPickerShowing = true; this._updateCkBreadcrumbsActive(); - return combinedDisposable([selectListener, focusListener, picker]); + return combinedDisposable([ + picker, + selectListener, + focusListener, + zoomListener, + focusTracker, + blurListener + ]); }, getAnchor: () => { let maxInnerWidth = window.innerWidth - 8 /*a little less the full widget*/; @@ -381,7 +411,7 @@ export class BreadcrumbsControl { } else { pickerArrowOffset = (data.left + (data.width * 0.3)) - x; } - picker.setInput(element, maxHeight, pickerWidth, pickerArrowSize, Math.max(0, pickerArrowOffset)); + picker.show(element, maxHeight, pickerWidth, pickerArrowSize, Math.max(0, pickerArrowOffset)); return { x, y }; }, onHide: (data) => { @@ -406,7 +436,7 @@ export class BreadcrumbsControl { this._ckBreadcrumbsActive.set(value); } - private _revealInEditor(event: IBreadcrumbsItemEvent, element: any, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, pinned: boolean = false): void { + private _revealInEditor(event: IBreadcrumbsItemEvent, element: any, group: SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE | undefined, pinned: boolean = false): void { if (element instanceof FileElement) { if (element.kind === FileKind.FILE) { // open file in any editor @@ -421,14 +451,16 @@ export class BreadcrumbsControl { } else if (element instanceof OutlineElement) { // open symbol in code editor - let model = OutlineModel.get(element); - this._codeEditorService.openCodeEditor({ - resource: model.textModel.uri, - options: { - selection: Range.collapseToStart(element.symbol.selectionRange), - revealInCenterIfOutsideViewport: true - } - }, this._getActiveCodeEditor(), group === SIDE_GROUP); + const model = OutlineModel.get(element); + if (model) { + this._codeEditorService.openCodeEditor({ + resource: model.textModel.uri, + options: { + selection: Range.collapseToStart(element.symbol.selectionRange), + revealInCenterIfOutsideViewport: true + } + }, withUndefinedAsNull(this._getActiveCodeEditor()), group === SIDE_GROUP); + } } } @@ -449,7 +481,7 @@ export class BreadcrumbsControl { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: 'breadcrumbs.toggle', - title: { value: localize('cmd.toggle', "Toggle Breadcrumbs"), original: 'Toggle Breadcrumbs' }, + title: { value: localize('cmd.toggle', "Toggle Breadcrumbs"), original: 'View: Toggle Breadcrumbs' }, category: localize('cmd.category', "View") } }); @@ -540,7 +572,11 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler(accessor) { const groups = accessor.get(IEditorGroupsService); const breadcrumbs = accessor.get(IBreadcrumbsService); - breadcrumbs.getWidget(groups.activeGroup.id).focusNext(); + const widget = breadcrumbs.getWidget(groups.activeGroup.id); + if (!widget) { + return; + } + widget.focusNext(); } }); KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -556,7 +592,11 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler(accessor) { const groups = accessor.get(IEditorGroupsService); const breadcrumbs = accessor.get(IBreadcrumbsService); - breadcrumbs.getWidget(groups.activeGroup.id).focusPrev(); + const widget = breadcrumbs.getWidget(groups.activeGroup.id); + if (!widget) { + return; + } + widget.focusPrev(); } }); KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -569,6 +609,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const groups = accessor.get(IEditorGroupsService); const breadcrumbs = accessor.get(IBreadcrumbsService); const widget = breadcrumbs.getWidget(groups.activeGroup.id); + if (!widget) { + return; + } widget.setSelection(widget.getFocused(), BreadcrumbsControl.Payload_Pick); } }); @@ -582,6 +625,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const groups = accessor.get(IEditorGroupsService); const breadcrumbs = accessor.get(IBreadcrumbsService); const widget = breadcrumbs.getWidget(groups.activeGroup.id); + if (!widget) { + return; + } widget.setSelection(widget.getFocused(), BreadcrumbsControl.Payload_Reveal); } }); @@ -593,9 +639,15 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler(accessor) { const groups = accessor.get(IEditorGroupsService); const breadcrumbs = accessor.get(IBreadcrumbsService); - breadcrumbs.getWidget(groups.activeGroup.id).setFocused(undefined); - breadcrumbs.getWidget(groups.activeGroup.id).setSelection(undefined); - groups.activeGroup.activeControl.focus(); + const widget = breadcrumbs.getWidget(groups.activeGroup.id); + if (!widget) { + return; + } + widget.setFocused(undefined); + widget.setSelection(undefined); + if (groups.activeGroup.activeControl) { + groups.activeGroup.activeControl.focus(); + } } }); KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -606,15 +658,20 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler(accessor) { const editors = accessor.get(IEditorService); const lists = accessor.get(IListService); - const element = lists.lastFocusedList.getFocus(); + const element = lists.lastFocusedList ? lists.lastFocusedList.getFocus() : undefined; if (element instanceof OutlineElement) { + const outlineElement = OutlineModel.get(element); + if (!outlineElement) { + return undefined; + } + // open symbol in editor return editors.openEditor({ - resource: OutlineModel.get(element).textModel.uri, + resource: outlineElement.textModel.uri, options: { selection: Range.collapseToStart(element.symbol.selectionRange) } }, SIDE_GROUP); - } else if (URI.isUri(element.resource)) { + } else if (element && URI.isUri(element.resource)) { // open file in editor return editors.openEditor({ resource: element.resource, diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index 3d0009020f..1045f7ac34 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -21,6 +21,7 @@ import { Schemas } from 'vs/base/common/network'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { FileKind } from 'vs/platform/files/common/files'; +import { withNullAsUndefined } from 'vs/base/common/types'; export class FileElement { constructor( @@ -105,7 +106,7 @@ export class EditorBreadcrumbsModel { } let info: FileInfo = { - folder: workspaceService.getWorkspaceFolder(uri) || undefined, + folder: withNullAsUndefined(workspaceService.getWorkspaceFolder(uri)), path: [] }; @@ -117,7 +118,7 @@ export class EditorBreadcrumbsModel { info.path.unshift(new FileElement(uriPrefix, info.path.length === 0 ? FileKind.FILE : FileKind.FOLDER)); let prevPathLength = uriPrefix.path.length; uriPrefix = dirname(uriPrefix); - if (!uriPrefix || uriPrefix.path.length === prevPathLength) { + if (uriPrefix.path.length === prevPathLength) { break; } } diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 4392297a50..3389e994b5 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -3,35 +3,38 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { onDidChangeZoomLevel } from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { compareFileNames } from 'vs/base/common/comparers'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { createMatches, FuzzyScore, fuzzyScore } from 'vs/base/common/filters'; +import { createMatches, FuzzyScore } from 'vs/base/common/filters'; import * as glob from 'vs/base/common/glob'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { join } from 'vs/base/common/paths'; +import { posix } from 'vs/base/common/path'; import { basename, dirname, isEqual } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { IDataSource, IFilter, IRenderer, ISorter, ITree } from 'vs/base/parts/tree/browser/tree'; import 'vs/css!./media/breadcrumbscontrol'; import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; -import { OutlineDataSource, OutlineItemComparator, OutlineRenderer, OutlineItemCompareType } from 'vs/editor/contrib/documentSymbols/outlineTree'; -import { localize } from 'vs/nls'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { FileKind, IFileService, IFileStat } from 'vs/platform/files/common/files'; import { IConstructorSignature1, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { HighlightingWorkbenchTree, IHighlighter, IHighlightingTreeConfiguration, IHighlightingTreeOptions } from 'vs/platform/list/browser/listService'; +import { WorkbenchDataTree, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { breadcrumbsPickerBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ResourceLabels, IResourceLabel, DEFAULT_LABELS_CONTAINER } from 'vs/workbench/browser/labels'; import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { BreadcrumbElement, FileElement } from 'vs/workbench/browser/parts/editor/breadcrumbsModel'; import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IAsyncDataSource, ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, ITreeSorter, IDataSource } from 'vs/base/browser/ui/tree/tree'; +import { OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItemComparator, OutlineIdentityProvider, OutlineNavigationLabelProvider, OutlineDataSource, OutlineSortOrder, OutlineItem } from 'vs/editor/contrib/documentSymbols/outlineTree'; +import { IIdentityProvider, IListVirtualDelegate, IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list'; +import { IDataTreeOptions } from 'vs/base/browser/ui/tree/dataTree'; export function createBreadcrumbsPicker(instantiationService: IInstantiationService, parent: HTMLElement, element: BreadcrumbElement): BreadcrumbsPicker { - let ctor: IConstructorSignature1 = element instanceof FileElement ? BreadcrumbsFilePicker : BreadcrumbsOutlinePicker; + const ctor: IConstructorSignature1 = element instanceof FileElement + ? BreadcrumbsFilePicker + : BreadcrumbsOutlinePicker; + return instantiationService.createInstance(ctor, parent); } @@ -43,16 +46,17 @@ interface ILayoutInfo { inputHeight: number; } +type Tree = WorkbenchDataTree | WorkbenchAsyncDataTree; + export abstract class BreadcrumbsPicker { protected readonly _disposables = new Array(); protected readonly _domNode: HTMLDivElement; - protected readonly _arrow: HTMLDivElement; - protected readonly _treeContainer: HTMLDivElement; - protected readonly _tree: HighlightingWorkbenchTree; - protected readonly _focus: dom.IFocusTracker; - protected readonly _symbolSortOrder: BreadcrumbsConfig<'position' | 'name' | 'type'>; - private _layoutInfo: ILayoutInfo; + protected _arrow: HTMLDivElement; + protected _treeContainer: HTMLDivElement; + protected _tree: Tree; + protected _fakeEvent = new UIEvent('fakeEvent'); + protected _layoutInfo: ILayoutInfo; private readonly _onDidPickElement = new Emitter<{ target: any, payload: any }>(); readonly onDidPickElement: Event<{ target: any, payload: any }> = this._onDidPickElement.event; @@ -64,154 +68,111 @@ export abstract class BreadcrumbsPicker { parent: HTMLElement, @IInstantiationService protected readonly _instantiationService: IInstantiationService, @IWorkbenchThemeService protected readonly _themeService: IWorkbenchThemeService, - @IConfigurationService private readonly _configurationService: IConfigurationService, + @IConfigurationService protected readonly _configurationService: IConfigurationService, ) { this._domNode = document.createElement('div'); this._domNode.className = 'monaco-breadcrumbs-picker show-file-icons'; parent.appendChild(this._domNode); - - this._focus = dom.trackFocus(this._domNode); - this._focus.onDidBlur(_ => this._onDidPickElement.fire({ target: undefined, payload: undefined }), undefined, this._disposables); - this._disposables.push(onDidChangeZoomLevel(_ => this._onDidPickElement.fire({ target: undefined, payload: undefined }))); - - const theme = this._themeService.getTheme(); - const color = theme.getColor(breadcrumbsPickerBackground); - - this._arrow = document.createElement('div'); - this._arrow.className = 'arrow'; - this._arrow.style.borderColor = `transparent transparent ${color.toString()}`; - this._domNode.appendChild(this._arrow); - - this._treeContainer = document.createElement('div'); - this._treeContainer.style.background = color.toString(); - this._treeContainer.style.paddingTop = '2px'; - this._treeContainer.style.boxShadow = `0px 5px 8px ${this._themeService.getTheme().getColor(widgetShadow)}`; - this._domNode.appendChild(this._treeContainer); - - this._symbolSortOrder = BreadcrumbsConfig.SymbolSortOrder.bindTo(this._configurationService); - - const filterConfig = BreadcrumbsConfig.FilterOnType.bindTo(this._configurationService); - this._disposables.push(filterConfig); - - const treeConfig = this._completeTreeConfiguration({ dataSource: undefined, renderer: undefined, highlighter: undefined }); - this._tree = this._instantiationService.createInstance( - HighlightingWorkbenchTree, - this._treeContainer, - treeConfig, - { useShadows: false, filterOnType: filterConfig.getValue(), showTwistie: false, twistiePixels: 12 }, - { placeholder: localize('placeholder', "Find") } - ); - this._disposables.push(this._tree.onDidChangeSelection(e => { - if (e.payload !== this._tree) { - const target = this._getTargetFromEvent(e.selection[0], e.payload); - if (target) { - setTimeout(_ => {// need to debounce here because this disposes the tree and the tree doesn't like to be disposed on click - this._onDidPickElement.fire({ target, payload: e.payload }); - }, 0); - } - } - })); - this._disposables.push(this._tree.onDidChangeFocus(e => { - const target = this._getTargetFromEvent(e.focus, e.payload); - if (target) { - this._onDidFocusElement.fire({ target, payload: e.payload }); - } - })); - this._disposables.push(this._tree.onDidStartFiltering(() => { - this._layoutInfo.inputHeight = 36; - this._layout(); - })); - this._disposables.push(this._tree.onDidExpandItem(() => { - this._layout(); - })); - this._disposables.push(this._tree.onDidCollapseItem(() => { - this._layout(); - })); - - // tree icon theme specials - dom.addClass(this._treeContainer, 'file-icon-themable-tree'); - dom.addClass(this._treeContainer, 'show-file-icons'); - const onFileIconThemeChange = (fileIconTheme: IFileIconTheme) => { - dom.toggleClass(this._treeContainer, 'align-icons-and-twisties', fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons); - dom.toggleClass(this._treeContainer, 'hide-arrows', fileIconTheme.hidesExplorerArrows === true); - }; - this._disposables.push(_themeService.onDidFileIconThemeChange(onFileIconThemeChange)); - onFileIconThemeChange(_themeService.getFileIconTheme()); - - this._domNode.focus(); } dispose(): void { dispose(this._disposables); this._onDidPickElement.dispose(); this._tree.dispose(); - this._focus.dispose(); - this._symbolSortOrder.dispose(); } - setInput(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): void { - let actualInput = this._getInput(input); - this._tree.setInput(actualInput).then(() => { + show(input: any, maxHeight: number, width: number, arrowSize: number, arrowOffset: number): void { - this._layoutInfo = { maxHeight, width, arrowSize, arrowOffset, inputHeight: 0 }; - this._layout(); + const theme = this._themeService.getTheme(); + const color = theme.getColor(breadcrumbsPickerBackground); - // use proper selection, reveal - let selection = this._getInitialSelection(this._tree, input); - if (selection) { - return this._tree.reveal(selection, 0.5).then(() => { - this._tree.setSelection([selection], this._tree); - this._tree.setFocus(selection); - this._tree.domFocus(); - }); - } else { - this._tree.focusFirst(); - this._tree.setSelection([this._tree.getFocus()], this._tree); - this._tree.domFocus(); - return Promise.resolve(null); + this._arrow = document.createElement('div'); + this._arrow.className = 'arrow'; + this._arrow.style.borderColor = `transparent transparent ${color ? color.toString() : ''}`; + this._domNode.appendChild(this._arrow); + + this._treeContainer = document.createElement('div'); + this._treeContainer.style.background = color ? color.toString() : ''; + this._treeContainer.style.paddingTop = '2px'; + this._treeContainer.style.boxShadow = `0px 5px 8px ${this._themeService.getTheme().getColor(widgetShadow)}`; + this._domNode.appendChild(this._treeContainer); + + + const filterConfig = BreadcrumbsConfig.FilterOnType.bindTo(this._configurationService); + this._disposables.push(filterConfig); + + this._layoutInfo = { maxHeight, width, arrowSize, arrowOffset, inputHeight: 0 }; + this._tree = this._createTree(this._treeContainer); + + this._disposables.push(this._tree.onDidChangeSelection(e => { + if (e.browserEvent !== this._fakeEvent) { + const target = this._getTargetFromEvent(e.elements[0], e.browserEvent); + if (target) { + setTimeout(_ => {// need to debounce here because this disposes the tree and the tree doesn't like to be disposed on click + this._onDidPickElement.fire({ target, payload: undefined }); + }, 0); + } } - }, onUnexpectedError); + })); + this._disposables.push(this._tree.onDidChangeFocus(e => { + const target = this._getTargetFromEvent(e.elements[0], e.browserEvent); + if (target) { + this._onDidFocusElement.fire({ target, payload: undefined }); + } + })); + this._disposables.push(this._tree.onDidChangeContentHeight(() => { + this._layout(); + })); + + // filter on type: state + const cfgFilterOnType = BreadcrumbsConfig.FilterOnType.bindTo(this._configurationService); + this._tree.updateOptions({ filterOnType: cfgFilterOnType.getValue() }); + this._disposables.push(this._tree.onDidUpdateOptions(e => { + this._configurationService.updateValue(cfgFilterOnType.name, e.filterOnType, ConfigurationTarget.MEMORY); + })); + + this._domNode.focus(); + + this._setInput(input).then(() => { + this._layout(); + }).catch(onUnexpectedError); } - private _layout(info: ILayoutInfo = this._layoutInfo): void { + protected _layout(): void { - let count = 0; - let nav = this._tree.getNavigator(undefined, false); - while (nav.next() && count < 13) { count += 1; } - - let headerHeight = 2 * info.arrowSize; - let treeHeight = Math.min(info.maxHeight - headerHeight, count * 22); - let totalHeight = treeHeight + headerHeight; + const headerHeight = 2 * this._layoutInfo.arrowSize; + const treeHeight = Math.min(this._layoutInfo.maxHeight - headerHeight, this._tree.contentHeight); + const totalHeight = treeHeight + headerHeight; this._domNode.style.height = `${totalHeight}px`; - this._domNode.style.width = `${info.width}px`; - this._arrow.style.top = `-${2 * info.arrowSize}px`; - this._arrow.style.borderWidth = `${info.arrowSize}px`; - this._arrow.style.marginLeft = `${info.arrowOffset}px`; + this._domNode.style.width = `${this._layoutInfo.width}px`; + this._arrow.style.top = `-${2 * this._layoutInfo.arrowSize}px`; + this._arrow.style.borderWidth = `${this._layoutInfo.arrowSize}px`; + this._arrow.style.marginLeft = `${this._layoutInfo.arrowOffset}px`; this._treeContainer.style.height = `${treeHeight}px`; - this._treeContainer.style.width = `${info.width}px`; - this._tree.layout(); - this._layoutInfo = info; + this._treeContainer.style.width = `${this._layoutInfo.width}px`; + this._tree.layout(treeHeight, this._layoutInfo.width); } - protected abstract _getInput(input: BreadcrumbElement): any; - protected abstract _getInitialSelection(tree: ITree, input: BreadcrumbElement): any; - protected abstract _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration; - protected abstract _getTargetFromEvent(element: any, payload: any): any | undefined; + protected abstract _setInput(element: BreadcrumbElement): Promise; + protected abstract _createTree(container: HTMLElement): Tree; + protected abstract _getTargetFromEvent(element: any, payload: UIEvent | undefined): any | undefined; } //#region - Files -export class FileDataSource implements IDataSource { +class FileVirtualDelegate implements IListVirtualDelegate { + getHeight(_element: IFileStat | IWorkspaceFolder) { + return 22; + } + getTemplateId(_element: IFileStat | IWorkspaceFolder): string { + return 'FileStat'; + } +} - private readonly _parents = new WeakMap(); - - constructor( - @IFileService private readonly _fileService: IFileService, - ) { } - - getId(tree: ITree, element: IWorkspace | IWorkspaceFolder | IFileStat | URI): string { +class FileIdentityProvider implements IIdentityProvider { + getId(element: IWorkspace | IWorkspaceFolder | IFileStat | URI): { toString(): string; } { if (URI.isUri(element)) { return element.toString(); } else if (IWorkspace.isIWorkspace(element)) { @@ -222,12 +183,26 @@ export class FileDataSource implements IDataSource { return element.resource.toString(); } } +} - hasChildren(tree: ITree, element: IWorkspace | IWorkspaceFolder | IFileStat | URI): boolean { - return URI.isUri(element) || IWorkspace.isIWorkspace(element) || IWorkspaceFolder.isIWorkspaceFolder(element) || element.isDirectory; + +class FileDataSource implements IAsyncDataSource { + + private readonly _parents = new WeakMap(); + + constructor( + @IFileService private readonly _fileService: IFileService, + ) { } + + hasChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): boolean { + return URI.isUri(element) + || IWorkspace.isIWorkspace(element) + || IWorkspaceFolder.isIWorkspaceFolder(element) + || element.isDirectory; } - getChildren(tree: ITree, element: IWorkspace | IWorkspaceFolder | IFileStat | URI): Promise { + getChildren(element: IWorkspace | URI | IWorkspaceFolder | IFileStat): Promise<(IWorkspaceFolder | IFileStat)[]> { + if (IWorkspace.isIWorkspace(element)) { return Promise.resolve(element.folders).then(folders => { for (let child of folders) { @@ -245,19 +220,62 @@ export class FileDataSource implements IDataSource { uri = element.resource; } return this._fileService.resolveFile(uri).then(stat => { - for (let child of stat.children) { + for (const child of stat.children || []) { this._parents.set(stat, child); } - return stat.children; + return stat.children || []; }); } - - getParent(tree: ITree, element: IWorkspace | URI | IWorkspaceFolder | IFileStat): Promise { - return Promise.resolve(this._parents.get(element)); - } } -export class FileFilter implements IFilter { +class FileRenderer implements ITreeRenderer { + + readonly templateId: string = 'FileStat'; + + constructor( + private readonly _labels: ResourceLabels, + @IConfigurationService private readonly _configService: IConfigurationService, + ) { } + + + renderTemplate(container: HTMLElement): IResourceLabel { + return this._labels.create(container, { supportHighlights: true }); + } + + renderElement(node: ITreeNode, index: number, templateData: IResourceLabel): void { + const fileDecorations = this._configService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations'); + const { element } = node; + let resource: URI; + let fileKind: FileKind; + if (IWorkspaceFolder.isIWorkspaceFolder(element)) { + resource = element.uri; + fileKind = FileKind.ROOT_FOLDER; + } else { + resource = element.resource; + fileKind = element.isDirectory ? FileKind.FOLDER : FileKind.FILE; + } + templateData.setFile(resource, { + fileKind, + hidePath: true, + fileDecorations: fileDecorations, + matches: createMatches(node.filterData), + extraClasses: ['picker-item'] + }); + } + + disposeTemplate(templateData: IResourceLabel): void { + templateData.dispose(); + } +} + +class FileNavigationLabelProvider implements IKeyboardNavigationLabelProvider { + + getKeyboardNavigationLabel(element: IWorkspaceFolder | IFileStat): { toString(): string; } { + return element.name; + } +} + +class FileFilter implements ITreeFilter { private readonly _cachedExpressions = new Map(); private readonly _disposables: IDisposable[] = []; @@ -281,7 +299,7 @@ export class FileFilter implements IFilter { continue; } let patternAbs = pattern.indexOf('**/') !== 0 - ? join(folder.uri.path, pattern) + ? posix.join(folder.uri.path, pattern) : pattern; adjustedConfig[patternAbs] = excludesConfig[pattern]; @@ -301,7 +319,7 @@ export class FileFilter implements IFilter { dispose(this._disposables); } - isVisible(tree: ITree, element: IWorkspaceFolder | IFileStat): boolean { + filter(element: IWorkspaceFolder | IFileStat, _parentVisibility: TreeVisibility): boolean { if (IWorkspaceFolder.isIWorkspaceFolder(element)) { // not a file return true; @@ -312,77 +330,24 @@ export class FileFilter implements IFilter { return true; } - const expression = this._cachedExpressions.get(folder.uri.toString()); + const expression = this._cachedExpressions.get(folder.uri.toString())!; return !expression(element.resource.path, basename(element.resource)); } } -export class FileHighlighter implements IHighlighter { - getHighlightsStorageKey(element: IFileStat | IWorkspaceFolder): string { - return IWorkspaceFolder.isIWorkspaceFolder(element) ? element.uri.toString() : element.resource.toString(); - } - getHighlights(tree: ITree, element: IFileStat | IWorkspaceFolder, pattern: string): FuzzyScore { - return fuzzyScore(pattern, pattern.toLowerCase(), 0, element.name, element.name.toLowerCase(), 0, true); - } -} -export class FileRenderer implements IRenderer { - - constructor( - private readonly _labels: ResourceLabels, - @IConfigurationService private readonly _configService: IConfigurationService, - ) { } - - getHeight(tree: ITree, element: any): number { - return 22; - } - - getTemplateId(tree: ITree, element: any): string { - return 'FileStat'; - } - - renderTemplate(tree: ITree, templateId: string, container: HTMLElement) { - return this._labels.create(container, { supportHighlights: true }); - } - - renderElement(tree: ITree, element: IFileStat | IWorkspaceFolder, templateId: string, templateData: IResourceLabel): void { - let fileDecorations = this._configService.getValue<{ colors: boolean, badges: boolean }>('explorer.decorations'); - let resource: URI; - let fileKind: FileKind; - if (IWorkspaceFolder.isIWorkspaceFolder(element)) { - resource = element.uri; - fileKind = FileKind.ROOT_FOLDER; - } else { - resource = element.resource; - fileKind = element.isDirectory ? FileKind.FOLDER : FileKind.FILE; - } - templateData.setFile(resource, { - fileKind, - hidePath: true, - fileDecorations: fileDecorations, - matches: createMatches((tree as HighlightingWorkbenchTree).getHighlighterScore(element)), - extraClasses: ['picker-item'] - }); - } - - disposeTemplate(tree: ITree, templateId: string, templateData: IResourceLabel): void { - templateData.dispose(); - } -} - -export class FileSorter implements ISorter { - compare(tree: ITree, a: IFileStat | IWorkspaceFolder, b: IFileStat | IWorkspaceFolder): number { +export class FileSorter implements ITreeSorter { + compare(a: IFileStat | IWorkspaceFolder, b: IFileStat | IWorkspaceFolder): number { if (IWorkspaceFolder.isIWorkspaceFolder(a) && IWorkspaceFolder.isIWorkspaceFolder(b)) { return a.index - b.index; + } + if ((a as IFileStat).isDirectory === (b as IFileStat).isDirectory) { + // same type -> compare on names + return compareFileNames(a.name, b.name); + } else if ((a as IFileStat).isDirectory) { + return -1; } else { - if ((a as IFileStat).isDirectory === (b as IFileStat).isDirectory) { - // same type -> compare on names - return compareFileNames(a.name, b.name); - } else if ((a as IFileStat).isDirectory) { - return -1; - } else { - return 1; - } + return 1; } } } @@ -399,44 +364,69 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { super(parent, instantiationService, themeService, configService); } - protected _getInput(input: BreadcrumbElement): any { - let { uri, kind } = (input as FileElement); - if (kind === FileKind.ROOT_FOLDER) { - return this._workspaceService.getWorkspace(); - } else { - return dirname(uri); - } - } + _createTree(container: HTMLElement) { - protected _getInitialSelection(tree: ITree, input: BreadcrumbElement): any { - let { uri } = (input as FileElement); - let nav = tree.getNavigator(); - while (nav.next()) { - let cur = nav.current(); - let candidate = IWorkspaceFolder.isIWorkspaceFolder(cur) ? cur.uri : (cur as IFileStat).resource; - if (isEqual(uri, candidate)) { - return cur; - } - } - return undefined; - } + // tree icon theme specials + dom.addClass(this._treeContainer, 'file-icon-themable-tree'); + dom.addClass(this._treeContainer, 'show-file-icons'); + const onFileIconThemeChange = (fileIconTheme: IFileIconTheme) => { + dom.toggleClass(this._treeContainer, 'align-icons-and-twisties', fileIconTheme.hasFileIcons && !fileIconTheme.hasFolderIcons); + dom.toggleClass(this._treeContainer, 'hide-arrows', fileIconTheme.hidesExplorerArrows === true); + }; + this._disposables.push(this._themeService.onDidFileIconThemeChange(onFileIconThemeChange)); + onFileIconThemeChange(this._themeService.getFileIconTheme()); - protected _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration { - // todo@joh reuse explorer implementations? - const filter = this._instantiationService.createInstance(FileFilter); - this._disposables.push(filter); - - config.dataSource = this._instantiationService.createInstance(FileDataSource); const labels = this._instantiationService.createInstance(ResourceLabels, DEFAULT_LABELS_CONTAINER /* TODO@Jo visibility propagation */); this._disposables.push(labels); - config.renderer = this._instantiationService.createInstance(FileRenderer, labels); - config.sorter = new FileSorter(); - config.highlighter = new FileHighlighter(); - config.filter = filter; - return config; + + return this._instantiationService.createInstance( + WorkbenchAsyncDataTree, + container, + new FileVirtualDelegate(), + [this._instantiationService.createInstance(FileRenderer, labels)], + this._instantiationService.createInstance(FileDataSource), + { + filterOnType: true, + multipleSelectionSupport: false, + sorter: new FileSorter(), + filter: this._instantiationService.createInstance(FileFilter), + identityProvider: new FileIdentityProvider(), + keyboardNavigationLabelProvider: new FileNavigationLabelProvider() + } + ) as WorkbenchAsyncDataTree; + } + + _setInput(element: BreadcrumbElement): Promise { + const { uri, kind } = (element as FileElement); + let input: IWorkspace | URI; + if (kind === FileKind.ROOT_FOLDER) { + input = this._workspaceService.getWorkspace(); + } else { + input = dirname(uri); + } + + const tree = this._tree as WorkbenchAsyncDataTree; + return tree.setInput(input).then(() => { + let focusElement: IWorkspaceFolder | IFileStat | undefined; + for (const { element } of tree.getNode().children) { + if (IWorkspaceFolder.isIWorkspaceFolder(element) && isEqual(element.uri, uri)) { + focusElement = element; + break; + } else if (isEqual((element as IFileStat).resource, uri)) { + focusElement = element as IFileStat; + break; + } + } + if (focusElement) { + tree.reveal(focusElement, 0.5); + tree.setFocus([focusElement], this._fakeEvent); + } + tree.domFocus(); + }); } protected _getTargetFromEvent(element: any, _payload: any): any | undefined { + // todo@joh if (element && !IWorkspaceFolder.isIWorkspaceFolder(element) && !(element as IFileStat).isDirectory) { return new FileElement((element as IFileStat).resource, FileKind.FILE); } @@ -446,52 +436,84 @@ export class BreadcrumbsFilePicker extends BreadcrumbsPicker { //#region - Symbols -class OutlineHighlighter implements IHighlighter { - getHighlights(tree: ITree, element: OutlineElement, pattern: string): FuzzyScore { - OutlineModel.get(element).updateMatches(pattern); - return element.score; - } -} - export class BreadcrumbsOutlinePicker extends BreadcrumbsPicker { - protected _getInput(input: BreadcrumbElement): any { - let element = input as TreeElement; - let model = OutlineModel.get(element); - model.updateMatches(''); - return model; + protected readonly _symbolSortOrder: BreadcrumbsConfig<'position' | 'name' | 'type'>; + + constructor( + parent: HTMLElement, + @IInstantiationService instantiationService: IInstantiationService, + @IWorkbenchThemeService themeService: IWorkbenchThemeService, + @IConfigurationService configurationService: IConfigurationService, + ) { + super(parent, instantiationService, themeService, configurationService); + this._symbolSortOrder = BreadcrumbsConfig.SymbolSortOrder.bindTo(this._configurationService); } - protected _getInitialSelection(_tree: ITree, input: BreadcrumbElement): any { - return input instanceof OutlineModel ? undefined : input; + protected _createTree(container: HTMLElement) { + return this._instantiationService.createInstance< + HTMLElement, + IListVirtualDelegate, + ITreeRenderer[], + IDataSource, + IDataTreeOptions, + WorkbenchDataTree + >( + WorkbenchDataTree, + container, + new OutlineVirtualDelegate(), + [new OutlineGroupRenderer(), this._instantiationService.createInstance(OutlineElementRenderer)], + new OutlineDataSource(), + { + filterOnType: true, + expandOnlyOnTwistieClick: true, + multipleSelectionSupport: false, + sorter: new OutlineItemComparator(this._getOutlineItemCompareType()), + identityProvider: new OutlineIdentityProvider(), + keyboardNavigationLabelProvider: this._instantiationService.createInstance(OutlineNavigationLabelProvider) + } + ) as WorkbenchDataTree; } - protected _completeTreeConfiguration(config: IHighlightingTreeConfiguration): IHighlightingTreeConfiguration { - config.dataSource = this._instantiationService.createInstance(OutlineDataSource); - config.renderer = this._instantiationService.createInstance(OutlineRenderer); - config.sorter = new OutlineItemComparator(this._getOutlineItemComparator()); - config.highlighter = new OutlineHighlighter(); - return config; + dispose(): void { + this._symbolSortOrder.dispose(); + super.dispose(); } - protected _getTargetFromEvent(element: any, payload: any): any | undefined { - if (payload && payload.didClickOnTwistie) { - return; + protected _setInput(input: BreadcrumbElement): Promise { + const element = input as TreeElement; + const model = OutlineModel.get(element)!; + const tree = this._tree as WorkbenchDataTree; + tree.setInput(model); + + let focusElement: TreeElement; + if (element === model) { + focusElement = tree.navigate().first(); + } else { + focusElement = element; } + tree.reveal(focusElement, 0.5); + tree.setFocus([focusElement], this._fakeEvent); + tree.domFocus(); + + return Promise.resolve(); + } + + protected _getTargetFromEvent(element: any): any | undefined { if (element instanceof OutlineElement) { return element; } } - private _getOutlineItemComparator(): OutlineItemCompareType { + private _getOutlineItemCompareType(): OutlineSortOrder { switch (this._symbolSortOrder.getValue()) { case 'name': - return OutlineItemCompareType.ByName; + return OutlineSortOrder.ByName; case 'type': - return OutlineItemCompareType.ByKind; + return OutlineSortOrder.ByKind; case 'position': default: - return OutlineItemCompareType.ByPosition; + return OutlineSortOrder.ByPosition; } } } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 900ebb55c0..ded8b64f08 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -103,7 +103,7 @@ Registry.as(EditorExtensions.Editors).registerEditor( interface ISerializedUntitledEditorInput { resource: string; resourceJSON: object; - modeId: string; + modeId: string | null; encoding: string; } @@ -114,7 +114,7 @@ class UntitledEditorInputFactory implements IEditorInputFactory { @ITextFileService private readonly textFileService: ITextFileService ) { } - serialize(editorInput: EditorInput): string { + serialize(editorInput: EditorInput): string | null { if (!this.textFileService.isHotExitEnabled) { return null; // never restore untitled unless hot exit is enabled } @@ -170,7 +170,7 @@ interface ISerializedSideBySideEditorInput { // Register Side by Side Editor Input Factory class SideBySideEditorInputFactory implements IEditorInputFactory { - serialize(editorInput: EditorInput): string { + serialize(editorInput: EditorInput): string | null { const input = editorInput; if (input.details && input.master) { @@ -198,7 +198,7 @@ class SideBySideEditorInputFactory implements IEditorInputFactory { return null; } - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput { + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | null { const deserialized: ISerializedSideBySideEditorInput = JSON.parse(serializedEditorInput); const registry = Registry.as(EditorInputExtensions.EditorInputFactories); @@ -266,7 +266,7 @@ export class QuickOpenActionContributor extends ActionBarContributor { return actions; } - private getEntry(context: any): IEditorQuickOpenEntry { + private getEntry(context: any): IEditorQuickOpenEntry | null { if (!context || !context.element) { return null; } diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index fd35efd75a..4f8343ec67 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { GroupIdentifier, IWorkbenchEditorConfiguration, IWorkbenchEditorPartConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditor } from 'vs/workbench/common/editor'; +import { GroupIdentifier, IWorkbenchEditorConfiguration, EditorOptions, TextEditorOptions, IEditorInput, IEditorIdentifier, IEditorCloseEvent, IEditor, IEditorPartOptions } from 'vs/workbench/common/editor'; import { EditorGroup } from 'vs/workbench/common/editor/editorGroup'; -import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroup, GroupDirection, IAddGroupOptions, IMergeGroupOptions, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Dimension } from 'vs/base/browser/dom'; import { Event } from 'vs/base/common/event'; @@ -18,13 +18,13 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic export const EDITOR_TITLE_HEIGHT = 35; +export interface IEditorPartCreationOptions { + restorePreviousState: boolean; +} + export const DEFAULT_EDITOR_MIN_DIMENSIONS = new Dimension(220, 70); export const DEFAULT_EDITOR_MAX_DIMENSIONS = new Dimension(Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY); -export interface IEditorPartOptions extends IWorkbenchEditorPartConfiguration { - iconTheme?: string; -} - export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { showTabs: true, highlightModifiedTabs: false, @@ -77,7 +77,7 @@ export interface IEditorOpeningEvent extends IEditorIdentifier { * to return a promise that resolves to NULL to prevent the opening * alltogether. */ - prevent(callback: () => Promise): void; + prevent(callback: () => undefined | Promise): void; } export interface IEditorGroupsAccessor { @@ -87,7 +87,7 @@ export interface IEditorGroupsAccessor { readonly partOptions: IEditorPartOptions; readonly onDidEditorPartOptionsChange: Event; - getGroup(identifier: GroupIdentifier): IEditorGroupView; + getGroup(identifier: GroupIdentifier): IEditorGroupView | undefined; getGroups(order: GroupsOrder): IEditorGroupView[]; activateGroup(identifier: IEditorGroupView | GroupIdentifier): IEditorGroupView; @@ -146,15 +146,3 @@ export interface EditorServiceImpl extends IEditorService { */ readonly onDidOpenEditorFail: Event; } - -/** - * A sub-interface of IEditorGroupsService to hide some workbench-core specific - * methods from clients. - */ -export interface EditorGroupsServiceImpl extends IEditorGroupsService { - - /** - * A promise that resolves when groups have been restored. - */ - readonly whenRestored: Promise; -} diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index bc1a3262e6..b7d328935e 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -10,7 +10,7 @@ import { IEditorInput, EditorInput, IEditorIdentifier, ConfirmResult, IEditorCom import { QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { EditorQuickOpenEntry, EditorQuickOpenEntryGroup, IEditorQuickOpenEntry, QuickOpenAction } from 'vs/workbench/browser/quickopen'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -18,7 +18,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { CLOSE_EDITOR_COMMAND_ID, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, MOVE_ACTIVE_EDITOR_COMMAND_ID, NAVIGATE_IN_ACTIVE_GROUP_PREFIX, ActiveEditorMoveArguments, SPLIT_EDITOR_LEFT, SPLIT_EDITOR_RIGHT, SPLIT_EDITOR_UP, SPLIT_EDITOR_DOWN, splitEditor, LAYOUT_EDITOR_GROUPS_COMMAND_ID, mergeAllGroups } from 'vs/workbench/browser/parts/editor/editorCommands'; -import { IEditorGroupsService, IEditorGroup, GroupsArrangement, EditorsOrder, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupsArrangement, EditorsOrder, GroupLocation, GroupDirection, preferredSideBySideGroupDirection, IFindGroupScope, GroupOrientation, EditorGroupLayout, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -188,20 +188,22 @@ export class JoinTwoGroupsAction extends Action { } run(context?: IEditorIdentifier): Promise { - let sourceGroup: IEditorGroup; + let sourceGroup: IEditorGroup | undefined; if (context && typeof context.groupId === 'number') { sourceGroup = this.editorGroupService.getGroup(context.groupId); } else { sourceGroup = this.editorGroupService.activeGroup; } - const targetGroupDirections = [GroupDirection.RIGHT, GroupDirection.DOWN, GroupDirection.LEFT, GroupDirection.UP]; - for (const targetGroupDirection of targetGroupDirections) { - const targetGroup = this.editorGroupService.findGroup({ direction: targetGroupDirection }, sourceGroup); - if (targetGroup && sourceGroup !== targetGroup) { - this.editorGroupService.mergeGroup(sourceGroup, targetGroup); + if (sourceGroup) { + const targetGroupDirections = [GroupDirection.RIGHT, GroupDirection.DOWN, GroupDirection.LEFT, GroupDirection.UP]; + for (const targetGroupDirection of targetGroupDirections) { + const targetGroup = this.editorGroupService.findGroup({ direction: targetGroupDirection }, sourceGroup); + if (targetGroup && sourceGroup !== targetGroup) { + this.editorGroupService.mergeGroup(sourceGroup, targetGroup); - return Promise.resolve(true); + return Promise.resolve(true); + } } } @@ -428,7 +430,7 @@ export class OpenToSideFromQuickOpenAction extends Action { if (entry) { const input = entry.getInput(); if (input instanceof EditorInput) { - return this.editorService.openEditor(input, entry.getOptions(), SIDE_GROUP); + return this.editorService.openEditor(input, entry.getOptions() || undefined, SIDE_GROUP); } const resourceInput = input as IResourceInput; @@ -441,7 +443,7 @@ export class OpenToSideFromQuickOpenAction extends Action { } } -export function toEditorQuickOpenEntry(element: any): IEditorQuickOpenEntry { +export function toEditorQuickOpenEntry(element: any): IEditorQuickOpenEntry | null { // QuickOpenEntryGroup if (element instanceof QuickOpenEntryGroup) { @@ -491,13 +493,13 @@ export class CloseOneEditorAction extends Action { } run(context?: IEditorCommandsContext): Promise { - let group: IEditorGroup; - let editorIndex: number; + let group: IEditorGroup | undefined; + let editorIndex: number | undefined; if (context) { group = this.editorGroupService.getGroup(context.groupId); if (group) { - editorIndex = context.editorIndex; // only allow editor at index if group is valid + editorIndex = context.editorIndex!; // only allow editor at index if group is valid } } @@ -579,7 +581,7 @@ export class CloseLeftEditorsInGroupAction extends Action { } } -function getTarget(editorService: IEditorService, editorGroupService: IEditorGroupsService, context?: IEditorIdentifier): { editor: IEditorInput, group: IEditorGroup } { +function getTarget(editorService: IEditorService, editorGroupService: IEditorGroupsService, context?: IEditorIdentifier): { editor: IEditorInput | null, group: IEditorGroup | undefined } { if (context) { return { editor: context.editor, group: editorGroupService.getGroup(context.groupId) }; } @@ -593,7 +595,7 @@ export abstract class BaseCloseAllAction extends Action { constructor( id: string, label: string, - clazz: string, + clazz: string | undefined, private textFileService: ITextFileService, protected editorGroupService: IEditorGroupsService ) { @@ -629,9 +631,9 @@ export abstract class BaseCloseAllAction extends Action { let saveOrRevertPromise: Promise; if (confirm === ConfirmResult.DONT_SAVE) { - saveOrRevertPromise = this.textFileService.revertAll(null, { soft: true }).then(() => true); + saveOrRevertPromise = this.textFileService.revertAll(undefined, { soft: true }).then(() => true); } else { - saveOrRevertPromise = this.textFileService.saveAll(true).then(res => res.results.every(r => r.success)); + saveOrRevertPromise = this.textFileService.saveAll(true).then(res => res.results.every(r => !!r.success)); } return saveOrRevertPromise.then(success => { @@ -703,8 +705,8 @@ export class CloseEditorsInOtherGroupsAction extends Action { run(context?: IEditorIdentifier): Promise { const groupToSkip = context ? this.editorGroupService.getGroup(context.groupId) : this.editorGroupService.activeGroup; return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => { - if (g.id === groupToSkip.id) { - return Promise.resolve(null); + if (groupToSkip && g.id === groupToSkip.id) { + return Promise.resolve(); } return g.closeAllEditors(); @@ -732,7 +734,7 @@ export class CloseEditorInAllGroupsAction extends Action { return Promise.all(this.editorGroupService.getGroups(GroupsOrder.MOST_RECENTLY_ACTIVE).map(g => g.closeEditor(activeEditor))); } - return Promise.resolve(null); + return Promise.resolve(); } } @@ -748,22 +750,24 @@ export class BaseMoveGroupAction extends Action { } run(context?: IEditorIdentifier): Promise { - let sourceGroup: IEditorGroup; + let sourceGroup: IEditorGroup | undefined; if (context && typeof context.groupId === 'number') { sourceGroup = this.editorGroupService.getGroup(context.groupId); } else { sourceGroup = this.editorGroupService.activeGroup; } - const targetGroup = this.findTargetGroup(sourceGroup); - if (targetGroup) { - this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction); + if (sourceGroup) { + const targetGroup = this.findTargetGroup(sourceGroup); + if (targetGroup) { + this.editorGroupService.moveGroup(sourceGroup, targetGroup, this.direction); + } } return Promise.resolve(true); } - private findTargetGroup(sourceGroup: IEditorGroup): IEditorGroup { + private findTargetGroup(sourceGroup: IEditorGroup): IEditorGroup | undefined { const targetNeighbours: GroupDirection[] = [this.direction]; // Allow the target group to be in alternative locations to support more @@ -882,14 +886,14 @@ export class ResetGroupSizesAction extends Action { export class MaximizeGroupAction extends Action { static readonly ID = 'workbench.action.maximizeEditor'; - static readonly LABEL = nls.localize('maximizeEditor', "Maximize Editor Group and Hide Sidebar"); + static readonly LABEL = nls.localize('maximizeEditor', "Maximize Editor Group and Hide Side Bar"); constructor( id: string, label: string, @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IPartService private readonly partService: IPartService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(id, label); } @@ -897,7 +901,7 @@ export class MaximizeGroupAction extends Action { run(): Promise { if (this.editorService.activeEditor) { this.editorGroupService.arrangeGroups(GroupsArrangement.MINIMIZE_OTHERS); - this.partService.setSideBarHidden(true); + this.layoutService.setSideBarHidden(true); } return Promise.resolve(false); @@ -927,10 +931,14 @@ export abstract class BaseNavigateEditorAction extends Action { } const group = this.editorGroupService.getGroup(groupId); - return group.openEditor(editor); + if (group) { + return group.openEditor(editor); + } + + return Promise.resolve(); } - protected abstract navigate(): IEditorIdentifier; + protected abstract navigate(): IEditorIdentifier | undefined; } export class OpenNextEditor extends BaseNavigateEditorAction { @@ -947,12 +955,12 @@ export class OpenNextEditor extends BaseNavigateEditorAction { super(id, label, editorGroupService, editorService); } - protected navigate(): IEditorIdentifier { + protected navigate(): IEditorIdentifier | undefined { // Navigate in active group if possible const activeGroup = this.editorGroupService.activeGroup; const activeGroupEditors = activeGroup.getEditors(EditorsOrder.SEQUENTIAL); - const activeEditorIndex = activeGroupEditors.indexOf(activeGroup.activeEditor); + const activeEditorIndex = activeGroup.activeEditor ? activeGroupEditors.indexOf(activeGroup.activeEditor) : -1; if (activeEditorIndex + 1 < activeGroupEditors.length) { return { editor: activeGroupEditors[activeEditorIndex + 1], groupId: activeGroup.id }; } @@ -982,12 +990,12 @@ export class OpenPreviousEditor extends BaseNavigateEditorAction { super(id, label, editorGroupService, editorService); } - protected navigate(): IEditorIdentifier { + protected navigate(): IEditorIdentifier | undefined { // Navigate in active group if possible const activeGroup = this.editorGroupService.activeGroup; const activeGroupEditors = activeGroup.getEditors(EditorsOrder.SEQUENTIAL); - const activeEditorIndex = activeGroupEditors.indexOf(activeGroup.activeEditor); + const activeEditorIndex = activeGroup.activeEditor ? activeGroupEditors.indexOf(activeGroup.activeEditor) : -1; if (activeEditorIndex > 0) { return { editor: activeGroupEditors[activeEditorIndex - 1], groupId: activeGroup.id }; } @@ -1020,7 +1028,7 @@ export class OpenNextEditorInGroup extends BaseNavigateEditorAction { protected navigate(): IEditorIdentifier { const group = this.editorGroupService.activeGroup; const editors = group.getEditors(EditorsOrder.SEQUENTIAL); - const index = editors.indexOf(group.activeEditor); + const index = group.activeEditor ? editors.indexOf(group.activeEditor) : -1; return { editor: index + 1 < editors.length ? editors[index + 1] : editors[0], groupId: group.id }; } @@ -1043,7 +1051,7 @@ export class OpenPreviousEditorInGroup extends BaseNavigateEditorAction { protected navigate(): IEditorIdentifier { const group = this.editorGroupService.activeGroup; const editors = group.getEditors(EditorsOrder.SEQUENTIAL); - const index = editors.indexOf(group.activeEditor); + const index = group.activeEditor ? editors.indexOf(group.activeEditor) : -1; return { editor: index > 0 ? editors[index - 1] : editors[editors.length - 1], groupId: group.id }; } @@ -1105,7 +1113,7 @@ export class NavigateForwardAction extends Action { run(): Promise { this.historyService.forward(); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -1121,7 +1129,7 @@ export class NavigateBackwardsAction extends Action { run(): Promise { this.historyService.back(); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -1137,7 +1145,7 @@ export class NavigateToLastEditLocationAction extends Action { run(): Promise { this.historyService.openLastEditLocation(); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -1153,7 +1161,7 @@ export class NavigateLastAction extends Action { run(): Promise { this.historyService.last(); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -1296,7 +1304,7 @@ export class OpenPreviousEditorFromHistoryAction extends Action { run(): Promise { const keys = this.keybindingService.lookupKeybindings(this.id); - this.quickOpenService.show(null, { quickNavigateConfiguration: { keybindings: keys } }); + this.quickOpenService.show(undefined, { quickNavigateConfiguration: { keybindings: keys } }); return Promise.resolve(true); } @@ -1314,7 +1322,7 @@ export class OpenNextRecentlyUsedEditorAction extends Action { run(): Promise { this.historyService.forward(true); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -1330,7 +1338,7 @@ export class OpenPreviousRecentlyUsedEditorAction extends Action { run(): Promise { this.historyService.back(true); - return Promise.resolve(null); + return Promise.resolve(); } } diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 6c9bbce9af..a7167bb7bb 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -8,7 +8,7 @@ import * as types from 'vs/base/common/types'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { TextCompareEditorVisibleContext, EditorInput, IEditorIdentifier, IEditorCommandsContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, CloseDirection, IEditor, IEditorInput } from 'vs/workbench/common/editor'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorService, IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { TextDiffEditor } from 'vs/workbench/browser/parts/editor/textDiffEditor'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; @@ -16,8 +16,8 @@ import { URI } from 'vs/base/common/uri'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IListService } from 'vs/platform/list/browser/listService'; import { List } from 'vs/base/browser/ui/list/listWidget'; -import { distinct } from 'vs/base/common/arrays'; -import { IEditorGroupsService, IEditorGroup, GroupDirection, GroupLocation, GroupsOrder, preferredSideBySideGroupDirection, EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService'; +import { distinct, coalesce } from 'vs/base/common/arrays'; +import { IEditorGroupsService, IEditorGroup, GroupDirection, GroupLocation, GroupsOrder, preferredSideBySideGroupDirection, EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; @@ -52,9 +52,9 @@ export const NAVIGATE_IN_ACTIVE_GROUP_PREFIX = 'edt active '; export const OPEN_EDITOR_AT_INDEX_COMMAND_ID = 'workbench.action.openEditorAtIndex'; export interface ActiveEditorMoveArguments { - to?: 'first' | 'last' | 'left' | 'right' | 'up' | 'down' | 'center' | 'position' | 'previous' | 'next'; - by?: 'tab' | 'group'; - value?: number; + to: 'first' | 'last' | 'left' | 'right' | 'up' | 'down' | 'center' | 'position' | 'previous' | 'next'; + by: 'tab' | 'group'; + value: number; } const isActiveEditorMoveArg = function (arg: ActiveEditorMoveArguments): boolean { @@ -90,7 +90,24 @@ function registerActiveEditorMoveCommand(): void { { name: nls.localize('editorCommand.activeEditorMove.arg.name', "Active editor move argument"), description: nls.localize('editorCommand.activeEditorMove.arg.description', "Argument Properties:\n\t* 'to': String value providing where to move.\n\t* 'by': String value providing the unit for move (by tab or by group).\n\t* 'value': Number value providing how many positions or an absolute position to move."), - constraint: isActiveEditorMoveArg + constraint: isActiveEditorMoveArg, + schema: { + 'type': 'object', + 'required': ['to'], + 'properties': { + 'to': { + 'type': 'string', + 'enum': ['left', 'right'] + }, + 'by': { + 'type': 'string', + 'enum': ['tab', 'group'] + }, + 'value': { + 'type': 'number' + } + }, + } } ] } @@ -113,7 +130,7 @@ function moveActiveEditor(args: ActiveEditorMoveArguments = Object.create(null), } } -function moveActiveTab(args: ActiveEditorMoveArguments, control: IEditor, accessor: ServicesAccessor): void { +function moveActiveTab(args: ActiveEditorMoveArguments, control: IVisibleEditor, accessor: ServicesAccessor): void { const group = control.group; let index = group.getIndexOfEditor(control.input); switch (args.to) { @@ -141,12 +158,12 @@ function moveActiveTab(args: ActiveEditorMoveArguments, control: IEditor, access group.moveEditor(control.input, group, { index }); } -function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, control: IEditor, accessor: ServicesAccessor): void { +function moveActiveEditorToGroup(args: ActiveEditorMoveArguments, control: IVisibleEditor, accessor: ServicesAccessor): void { const editorGroupService = accessor.get(IEditorGroupsService); const configurationService = accessor.get(IConfigurationService); const sourceGroup = control.group; - let targetGroup: IEditorGroup; + let targetGroup: IEditorGroup | undefined; switch (args.to) { case 'left': @@ -340,7 +357,7 @@ function registerOpenEditorAtIndexCommands(): void { case 9: return KeyCode.KEY_9; } - return undefined; + throw new Error('invalid index'); } } @@ -392,7 +409,7 @@ function registerFocusEditorGroupAtIndexCommands(): void { case 7: return 'workbench.action.focusEighthEditorGroup'; } - return undefined; + throw new Error('Invalid index'); } function toKeyCode(index: number): KeyCode { @@ -406,23 +423,27 @@ function registerFocusEditorGroupAtIndexCommands(): void { case 7: return KeyCode.KEY_8; } - return undefined; + throw new Error('Invalid index'); } } export function splitEditor(editorGroupService: IEditorGroupsService, direction: GroupDirection, context?: IEditorCommandsContext): void { - let sourceGroup: IEditorGroup; + let sourceGroup: IEditorGroup | undefined; if (context && typeof context.groupId === 'number') { sourceGroup = editorGroupService.getGroup(context.groupId); } else { sourceGroup = editorGroupService.activeGroup; } + if (!sourceGroup) { + return; + } + // Add group const newGroup = editorGroupService.addGroup(sourceGroup, direction); // Split editor (if it can be split) - let editorToCopy: IEditorInput; + let editorToCopy: IEditorInput | null; if (context && typeof context.editorIndex === 'number') { editorToCopy = sourceGroup.getEditor(context.editorIndex); } else { @@ -466,9 +487,14 @@ function registerCloseEditorCommands() { contexts.push({ groupId: activeGroup.id }); // active group as fallback } - return Promise.all(distinct(contexts.map(c => c.groupId)).map(groupId => - editorGroupService.getGroup(groupId).closeEditors({ savedOnly: true }) - )); + return Promise.all(distinct(contexts.map(c => c.groupId)).map(groupId => { + const group = editorGroupService.getGroup(groupId); + if (group) { + return group.closeEditors({ savedOnly: true }); + } + + return Promise.resolve(); + })); } }); @@ -486,9 +512,14 @@ function registerCloseEditorCommands() { distinctGroupIds.push(editorGroupService.activeGroup.id); } - return Promise.all(distinctGroupIds.map(groupId => - editorGroupService.getGroup(groupId).closeAllEditors() - )); + return Promise.all(distinctGroupIds.map(groupId => { + const group = editorGroupService.getGroup(groupId); + if (group) { + return group.closeAllEditors(); + } + + return Promise.resolve(); + })); } }); @@ -511,11 +542,15 @@ function registerCloseEditorCommands() { return Promise.all(groupIds.map(groupId => { const group = editorGroupService.getGroup(groupId); - const editors = contexts - .filter(context => context.groupId === groupId) - .map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor); + if (group) { + const editors = coalesce(contexts + .filter(context => context.groupId === groupId) + .map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor)); - return group.closeEditors(editors); + return group.closeEditors(editors); + } + + return Promise.resolve(); })); } }); @@ -530,14 +565,16 @@ function registerCloseEditorCommands() { const editorGroupService = accessor.get(IEditorGroupsService); const commandsContext = getCommandsContext(resourceOrContext, context); - let group: IEditorGroup; + let group: IEditorGroup | undefined; if (commandsContext && typeof commandsContext.groupId === 'number') { group = editorGroupService.getGroup(commandsContext.groupId); } else { group = editorGroupService.activeGroup; } - editorGroupService.removeGroup(group); + if (group) { + editorGroupService.removeGroup(group); + } } }); @@ -560,12 +597,16 @@ function registerCloseEditorCommands() { return Promise.all(groupIds.map(groupId => { const group = editorGroupService.getGroup(groupId); - const editors = contexts - .filter(context => context.groupId === groupId) - .map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor); - const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1); + if (group) { + const editors = contexts + .filter(context => context.groupId === groupId) + .map(context => typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : group.activeEditor); + const editorsToClose = group.editors.filter(e => editors.indexOf(e) === -1); - return group.closeEditors(editorsToClose); + return group.closeEditors(editorsToClose); + } + + return Promise.resolve(); })); } }); @@ -619,7 +660,10 @@ function registerCloseEditorCommands() { const commandsContext = getCommandsContext(resourceOrContext, context); if (commandsContext && typeof commandsContext.groupId === 'number') { - editorGroupService.activateGroup(editorGroupService.getGroup(commandsContext.groupId)); // we need the group to be active + const group = editorGroupService.getGroup(commandsContext.groupId); + if (group) { + editorGroupService.activateGroup(group); // we need the group to be active + } } return quickOpenService.show(NAVIGATE_IN_ACTIVE_GROUP_PREFIX); @@ -642,7 +686,7 @@ function registerCloseEditorCommands() { }); } -function getCommandsContext(resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext): IEditorCommandsContext { +function getCommandsContext(resourceOrContext: URI | IEditorCommandsContext, context?: IEditorCommandsContext): IEditorCommandsContext | undefined { if (URI.isUri(resourceOrContext)) { return context; } @@ -658,11 +702,11 @@ function getCommandsContext(resourceOrContext: URI | IEditorCommandsContext, con return undefined; } -function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor: IEditorInput, control: IEditor } { +function resolveCommandsContext(editorGroupService: IEditorGroupsService, context?: IEditorCommandsContext): { group: IEditorGroup, editor?: IEditorInput, control?: IEditor } { // Resolve from context let group = context && typeof context.groupId === 'number' ? editorGroupService.getGroup(context.groupId) : undefined; - let editor = group && typeof context.editorIndex === 'number' ? group.getEditor(context.editorIndex) : undefined; + let editor = group && context && typeof context.editorIndex === 'number' ? types.withNullAsUndefined(group.getEditor(context.editorIndex)) : undefined; let control = group ? group.activeControl : undefined; // Fallback to active group as needed @@ -675,7 +719,7 @@ function resolveCommandsContext(editorGroupService: IEditorGroupsService, contex return { group, editor, control }; } -export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsContext, listService: IListService, editorGroupService: IEditorGroupsService): IEditorCommandsContext[] { +export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsContext | undefined, listService: IListService, editorGroupService: IEditorGroupsService): IEditorCommandsContext[] { // First check for a focused list to return the selected items from const list = listService.lastFocusedList; @@ -685,7 +729,9 @@ export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsCon return { groupId: element.id, editorIndex: undefined }; } - return { groupId: element.groupId, editorIndex: editorGroupService.getGroup(element.groupId).getIndexOfEditor(element.editor) }; + const group = editorGroupService.getGroup(element.groupId); + + return { groupId: element.groupId, editorIndex: group ? group.getIndexOfEditor(element.editor) : -1 }; }; const onlyEditorGroupAndEditor = (e: IEditorIdentifier | IEditorGroup) => isEditorGroup(e) || isEditorIdentifier(e); @@ -697,7 +743,14 @@ export function getMultiSelectedEditorContexts(editorContext: IEditorCommandsCon const selection: Array = list.getSelectedElements().filter(onlyEditorGroupAndEditor); // Only respect selection if it contains focused element - if (selection && selection.some(s => isEditorGroup(s) ? s.id === focus.groupId : s.groupId === focus.groupId && editorGroupService.getGroup(s.groupId).getIndexOfEditor(s.editor) === focus.editorIndex)) { + if (selection && selection.some(s => { + if (isEditorGroup(s)) { + return s.id === focus.groupId; + } + + const group = editorGroupService.getGroup(s.groupId); + return s.groupId === focus.groupId && (group ? group.getIndexOfEditor(s.editor) : -1) === focus.editorIndex; + })) { return selection.map(elementToContext); } diff --git a/src/vs/workbench/browser/parts/editor/editorControl.ts b/src/vs/workbench/browser/parts/editor/editorControl.ts index 17291de900..2c51b30d49 100644 --- a/src/vs/workbench/browser/parts/editor/editorControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorControl.ts @@ -8,12 +8,14 @@ import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { Dimension, show, hide, addClass } from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; import { IEditorRegistry, Extensions as EditorExtensions, IEditorDescriptor } from 'vs/workbench/browser/editor'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressService, LongRunningOperation } from 'vs/platform/progress/common/progress'; import { IEditorGroupView, DEFAULT_EDITOR_MIN_DIMENSIONS, DEFAULT_EDITOR_MAX_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; +import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; +import { withUndefinedAsNull } from 'vs/base/common/types'; export interface IOpenEditorResult { readonly control: BaseEditor; @@ -27,7 +29,7 @@ export class EditorControl extends Disposable { get maximumWidth() { return this._activeControl ? this._activeControl.maximumWidth : DEFAULT_EDITOR_MAX_DIMENSIONS.width; } get maximumHeight() { return this._activeControl ? this._activeControl.maximumHeight : DEFAULT_EDITOR_MAX_DIMENSIONS.height; } - private _onDidFocus: Emitter = this._register(new Emitter()); + private readonly _onDidFocus: Emitter = this._register(new Emitter()); get onDidFocus(): Event { return this._onDidFocus.event; } private _onDidSizeConstraintsChange = this._register(new Emitter<{ width: number; height: number; } | undefined>()); @@ -43,7 +45,7 @@ export class EditorControl extends Disposable { constructor( private parent: HTMLElement, private groupView: IEditorGroupView, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IProgressService progressService: IProgressService ) { @@ -52,8 +54,8 @@ export class EditorControl extends Disposable { this.editorOperation = this._register(new LongRunningOperation(progressService)); } - get activeControl() { - return this._activeControl; + get activeControl(): IVisibleEditor | null { + return this._activeControl as IVisibleEditor | null; } openEditor(editor: EditorInput, options?: EditorOptions): Promise { @@ -66,7 +68,7 @@ export class EditorControl extends Disposable { const control = this.doShowEditorControl(descriptor); // Set input - return this.doSetInput(control, editor, options || null).then((editorChanged => (({ control, editorChanged } as IOpenEditorResult)))); + return this.doSetInput(control, editor, withUndefinedAsNull(options)).then((editorChanged => (({ control, editorChanged } as IOpenEditorResult)))); } private doShowEditorControl(descriptor: IEditorDescriptor): BaseEditor { @@ -170,7 +172,7 @@ export class EditorControl extends Disposable { // Show progress while setting input after a certain timeout. If the workbench is opening // be more relaxed about progress showing by increasing the delay a little bit to reduce flicker. - const operation = this.editorOperation.start(this.partService.isRestored() ? 800 : 3200); + const operation = this.editorOperation.start(this.layoutService.isRestored() ? 800 : 3200); // Call into editor control const editorWillChange = !inputMatches; diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 6144fdbb39..5e76301fa2 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -12,7 +12,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IEditorIdentifier, EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { isMacintosh } from 'vs/base/common/platform'; -import { GroupDirection, MergeGroupMode } from 'vs/workbench/services/group/common/editorGroupsService'; +import { GroupDirection, MergeGroupMode } from 'vs/workbench/services/editor/common/editorGroupsService'; import { toDisposable } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -28,7 +28,7 @@ class DropOverlay extends Themable { private container: HTMLElement; private overlay: HTMLElement; - private currentDropOperation: IDropOperation; + private currentDropOperation?: IDropOperation; private _disposed: boolean; private cleanupOverlayScheduler: RunOnceScheduler; @@ -103,12 +103,12 @@ class DropOverlay extends Themable { // Update the dropEffect to "copy" if there is no local data to be dragged because // in that case we can only copy the data into and not move it from its source - if (!isDraggingEditor && !isDraggingGroup) { + if (!isDraggingEditor && !isDraggingGroup && e.dataTransfer) { e.dataTransfer.dropEffect = 'copy'; } // Find out if operation is valid - const isCopy = isDraggingGroup ? this.isCopyOperation(e) : isDraggingEditor ? this.isCopyOperation(e, this.editorTransfer.getData(DraggedEditorIdentifier.prototype)[0].identifier) : true; + const isCopy = isDraggingGroup ? this.isCopyOperation(e) : isDraggingEditor ? this.isCopyOperation(e, this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier) : true; if (!isCopy) { const sourceGroupView = this.findSourceGroupView(); if (sourceGroupView === this.groupView) { @@ -158,16 +158,16 @@ class DropOverlay extends Themable { })); } - private findSourceGroupView(): IEditorGroupView { + private findSourceGroupView(): IEditorGroupView | undefined { // Check for group transfer if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) { - return this.accessor.getGroup(this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)[0].identifier); + return this.accessor.getGroup(this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)![0].identifier); } // Check for editor transfer else if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { - return this.accessor.getGroup(this.editorTransfer.getData(DraggedEditorIdentifier.prototype)[0].identifier.groupId); + return this.accessor.getGroup(this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier.groupId); } return undefined; @@ -189,59 +189,66 @@ class DropOverlay extends Themable { // Check for group transfer if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) { - const draggedEditorGroup = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)[0].identifier; + const draggedEditorGroup = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)![0].identifier; // Return if the drop is a no-op const sourceGroup = this.accessor.getGroup(draggedEditorGroup); - if (typeof splitDirection !== 'number' && sourceGroup === this.groupView) { - return; - } + if (sourceGroup) { + if (typeof splitDirection !== 'number' && sourceGroup === this.groupView) { + return; + } - // Split to new group - let targetGroup: IEditorGroupView; - if (typeof splitDirection === 'number') { - if (this.isCopyOperation(event)) { - targetGroup = this.accessor.copyGroup(sourceGroup, this.groupView, splitDirection); - } else { - targetGroup = this.accessor.moveGroup(sourceGroup, this.groupView, splitDirection); + // Split to new group + let targetGroup: IEditorGroupView | undefined; + if (typeof splitDirection === 'number') { + if (this.isCopyOperation(event)) { + targetGroup = this.accessor.copyGroup(sourceGroup, this.groupView, splitDirection); + } else { + targetGroup = this.accessor.moveGroup(sourceGroup, this.groupView, splitDirection); + } + } + + // Merge into existing group + else { + if (this.isCopyOperation(event)) { + targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView, { mode: MergeGroupMode.COPY_EDITORS }); + } else { + targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView); + } + } + + if (targetGroup) { + this.accessor.activateGroup(targetGroup); } } - // Merge into existing group - else { - if (this.isCopyOperation(event)) { - targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView, { mode: MergeGroupMode.COPY_EDITORS }); - } else { - targetGroup = this.accessor.mergeGroup(sourceGroup, this.groupView); - } - } - - this.accessor.activateGroup(targetGroup); this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype); } // Check for editor transfer else if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { - const draggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)[0].identifier; + const draggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier; const targetGroup = ensureTargetGroup(); // Return if the drop is a no-op const sourceGroup = this.accessor.getGroup(draggedEditor.groupId); - if (sourceGroup === targetGroup) { - return; - } + if (sourceGroup) { + if (sourceGroup === targetGroup) { + return; + } - // Open in target group - const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({ pinned: true })); - targetGroup.openEditor(draggedEditor.editor, options); + // Open in target group + const options = getActiveTextEditorOptions(sourceGroup, draggedEditor.editor, EditorOptions.create({ pinned: true })); + targetGroup.openEditor(draggedEditor.editor, options); - // Ensure target has focus - targetGroup.focus(); + // Ensure target has focus + targetGroup.focus(); - // Close in source group unless we copy - const copyEditor = this.isCopyOperation(event, draggedEditor); - if (!copyEditor) { - sourceGroup.closeEditor(draggedEditor.editor); + // Close in source group unless we copy + const copyEditor = this.isCopyOperation(event, draggedEditor); + if (!copyEditor) { + sourceGroup.closeEditor(draggedEditor.editor); + } } this.editorTransfer.clearData(DraggedEditorIdentifier.prototype); @@ -250,7 +257,7 @@ class DropOverlay extends Themable { // Check for URI transfer else { const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true /* open workspace instead of file if dropped */ }); - dropHandler.handleDrop(event, () => ensureTargetGroup(), targetGroup => targetGroup.focus()); + dropHandler.handleDrop(event, () => ensureTargetGroup(), targetGroup => targetGroup!.focus()); } } @@ -298,7 +305,7 @@ class DropOverlay extends Themable { // child.style.top = edgeHeightThreshold + 'px'; // No split if mouse is above certain threshold in the center of the view - let splitDirection: GroupDirection; + let splitDirection: GroupDirection | undefined; if ( mousePosX > edgeWidthThreshold && mousePosX < editorControlWidth - edgeWidthThreshold && mousePosY > edgeHeightThreshold && mousePosY < editorControlHeight - edgeHeightThreshold @@ -429,7 +436,7 @@ class DropOverlay extends Themable { export class EditorDropTarget extends Themable { - private _overlay: DropOverlay; + private _overlay?: DropOverlay; private counter = 0; @@ -447,7 +454,7 @@ export class EditorDropTarget extends Themable { this.registerListeners(); } - private get overlay(): DropOverlay { + private get overlay(): DropOverlay | undefined { if (this._overlay && !this._overlay.disposed) { return this._overlay; } @@ -468,7 +475,7 @@ export class EditorDropTarget extends Themable { if ( !this.editorTransfer.hasData(DraggedEditorIdentifier.prototype) && !this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype) && - !event.dataTransfer.types.length // see https://github.com/Microsoft/vscode/issues/25789 + event.dataTransfer && !event.dataTransfer.types.length // see https://github.com/Microsoft/vscode/issues/25789 ) { event.dataTransfer.dropEffect = 'none'; return; // unsupported transfer @@ -510,7 +517,7 @@ export class EditorDropTarget extends Themable { this.disposeOverlay(); } - private findTargetGroupView(child: HTMLElement): IEditorGroupView { + private findTargetGroupView(child: HTMLElement): IEditorGroupView | undefined { const groups = this.accessor.groups; for (const groupView of groups) { if (isAncestor(child, groupView.element)) { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 7d8d29799d..baef4f031e 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/editorgroupview'; + import { EditorGroup, IEditorOpenOptions, EditorCloseEvent, ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; import { EditorInput, EditorOptions, GroupIdentifier, ConfirmResult, SideBySideEditorInput, CloseDirection, IEditorCloseEvent, EditorGroupActiveEditorDirtyContext, IEditor } from 'vs/workbench/common/editor'; import { Event, Emitter, Relay } from 'vs/base/common/event'; @@ -16,12 +17,11 @@ import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { Themable, EDITOR_GROUP_HEADER_TABS_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_EMPTY_BACKGROUND, EDITOR_GROUP_FOCUSED_EMPTY_BORDER } from 'vs/workbench/common/theme'; -import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, EditorsOrder, GroupsOrder } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IMoveEditorOptions, ICopyEditorOptions, ICloseEditorsFilter, IGroupChangeEvent, GroupChangeKind, EditorsOrder, GroupsOrder, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl'; import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { ProgressService } from 'vs/workbench/services/progress/browser/progressService'; -import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; @@ -33,7 +33,6 @@ import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptionsChangeEvent, getActiveTextEditorOptions, IEditorOpeningEvent } from 'vs/workbench/browser/parts/editor/editor'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { join } from 'vs/base/common/paths'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ActionRunner, IAction, Action } from 'vs/base/common/actions'; @@ -45,10 +44,14 @@ import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemAc import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; // {{SQL CARBON EDIT}} import { ICommandService } from 'vs/platform/commands/common/commands'; -import { GlobalNewUntitledFileAction } from 'vs/workbench/parts/files/electron-browser/fileActions'; +import { GlobalNewUntitledFileAction } from 'vs/workbench/contrib/files/browser/fileActions'; // {{SQL CARBON EDIT}} - End import { isErrorWithActions, IErrorWithActions } from 'vs/base/common/errorsWithActions'; -import { URI } from 'vs/base/common/uri'; +import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { IHashService } from 'vs/workbench/services/hash/common/hashService'; +import { guessMimeTypes } from 'vs/base/common/mime'; +import { extname } from 'vs/base/common/path'; export class EditorGroupView extends Themable implements IEditorGroupView { @@ -70,25 +73,25 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region events - private _onDidFocus: Emitter = this._register(new Emitter()); + private readonly _onDidFocus: Emitter = this._register(new Emitter()); get onDidFocus(): Event { return this._onDidFocus.event; } - private _onWillDispose: Emitter = this._register(new Emitter()); + private readonly _onWillDispose: Emitter = this._register(new Emitter()); get onWillDispose(): Event { return this._onWillDispose.event; } - private _onDidGroupChange: Emitter = this._register(new Emitter()); + private readonly _onDidGroupChange: Emitter = this._register(new Emitter()); get onDidGroupChange(): Event { return this._onDidGroupChange.event; } - private _onWillOpenEditor: Emitter = this._register(new Emitter()); + private readonly _onWillOpenEditor: Emitter = this._register(new Emitter()); get onWillOpenEditor(): Event { return this._onWillOpenEditor.event; } - private _onDidOpenEditorFail: Emitter = this._register(new Emitter()); + private readonly _onDidOpenEditorFail: Emitter = this._register(new Emitter()); get onDidOpenEditorFail(): Event { return this._onDidOpenEditorFail.event; } - private _onWillCloseEditor: Emitter = this._register(new Emitter()); + private readonly _onWillCloseEditor: Emitter = this._register(new Emitter()); get onWillCloseEditor(): Event { return this._onWillCloseEditor.event; } - private _onDidCloseEditor: Emitter = this._register(new Emitter()); + private readonly _onDidCloseEditor: Emitter = this._register(new Emitter()); get onDidCloseEditor(): Event { return this._onDidCloseEditor.event; } //#endregion @@ -112,7 +115,6 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private editorContainer: HTMLElement; private editorControl: EditorControl; - private ignoreOpenEditorErrors: boolean; private disposedEditorsWorker: RunOnceWorker; private mapEditorToPendingConfirmation: Map> = new Map>(); @@ -130,6 +132,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { @IKeybindingService private readonly keybindingService: IKeybindingService, @IMenuService private readonly menuService: IMenuService, @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IHashService private readonly hashService: IHashService, // {{SQL CARBON EDIT}} @ICommandService private commandService: ICommandService ) { @@ -417,6 +420,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } const activeEditor = this._group.activeEditor; + if (!activeEditor) { + return Promise.resolve(); + } + options.pinned = this._group.isPinned(activeEditor); // preserve pinned state options.preserveFocus = true; // handle focus after editor is opened @@ -458,14 +465,18 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } private onDidEditorOpen(editor: EditorInput): void { - /* __GDPR__ - "editorOpened" : { - "${include}": [ - "${EditorTelemetryDescriptor}" - ] - } - */ - this.telemetryService.publicLog('editorOpened', editor.getTelemetryDescriptor()); + + // Telemetry + this.toEditorTelemetryDescriptor(editor).then(descriptor => { + /* __GDPR__ + "editorOpened" : { + "${include}": [ + "${EditorTelemetryDescriptor}" + ] + } + */ + this.telemetryService.publicLog('editorOpened', descriptor); + }); // Update container this.updateContainer(); @@ -496,14 +507,17 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } }); - /* __GDPR__ - "editorClosed" : { - "${include}": [ - "${EditorTelemetryDescriptor}" - ] - } - */ - this.telemetryService.publicLog('editorClosed', event.editor.getTelemetryDescriptor()); + // Telemetry + this.toEditorTelemetryDescriptor(event.editor).then(descriptor => { + /* __GDPR__ + "editorClosed" : { + "${include}": [ + "${EditorTelemetryDescriptor}" + ] + } + */ + this.telemetryService.publicLog('editorClosed', descriptor); + }); // Update container this.updateContainer(); @@ -513,6 +527,26 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onDidGroupChange.fire({ kind: GroupChangeKind.EDITOR_CLOSE, editor, editorIndex: event.index }); } + private toEditorTelemetryDescriptor(editor: EditorInput): Thenable { + const descriptor = editor.getTelemetryDescriptor(); + + const resource = editor.getResource(); + if (resource && resource.fsPath) { + return this.hashService.createSHA1(resource.fsPath).then(hashedPath => { + descriptor['resource'] = { mimeType: guessMimeTypes(resource.fsPath).join(', '), scheme: resource.scheme, ext: extname(resource.fsPath), path: hashedPath }; + + /* __GDPR__FRAGMENT__ + "EditorTelemetryDescriptor" : { + "resource": { "${inline}": [ "${URIDescriptor}" ] } + } + */ + return descriptor; + }); + } + + return Promise.resolve(descriptor); + } + private onDidEditorDispose(editor: EditorInput): void { // To prevent race conditions, we handle disposed editors in our worker with a timeout @@ -524,7 +558,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { private handleDisposedEditors(editors: EditorInput[]): void { // Split between visible and hidden editors - let activeEditor: EditorInput; + let activeEditor: EditorInput | undefined; const inactiveEditors: EditorInput[] = []; editors.forEach(editor => { if (this._group.isActive(editor)) { @@ -567,7 +601,9 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Pin preview editor once user disables preview if (event.oldPartOptions.enablePreview && !event.newPartOptions.enablePreview) { - this.pinEditor(this._group.previewEditor); + if (this._group.previewEditor) { + this.pinEditor(this._group.previewEditor); + } } } @@ -658,15 +694,15 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this._group.count; } - get activeControl(): BaseEditor { - return this.editorControl ? this.editorControl.activeControl : undefined; + get activeControl(): IVisibleEditor | undefined { + return this.editorControl ? withNullAsUndefined(this.editorControl.activeControl) : undefined; } - get activeEditor(): EditorInput { + get activeEditor(): EditorInput | null { return this._group.activeEditor; } - get previewEditor(): EditorInput { + get previewEditor(): EditorInput | null { return this._group.previewEditor; } @@ -686,7 +722,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this.editors; } - getEditor(index: number): EditorInput { + getEditor(index: number): EditorInput | null { return this._group.getEditor(index); } @@ -711,7 +747,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onDidFocus.fire(); } - pinEditor(editor: EditorInput = this.activeEditor): void { + pinEditor(editor: EditorInput | undefined = this.activeEditor || undefined): void { if (editor && !this._group.isPinned(editor)) { // Update model @@ -749,7 +785,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return this.doOpenEditor(editor, options); } - private doOpenEditor(editor: EditorInput, options?: EditorOptions): Promise { + private doOpenEditor(editor: EditorInput, options?: EditorOptions): Promise { // Determine options const openEditorOptions: IEditorOpenOptions = { @@ -758,7 +794,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { active: this._group.count === 0 || !options || !options.inactive }; - if (!openEditorOptions.active && !openEditorOptions.pinned && this._group.isPreview(this._group.activeEditor)) { + if (!openEditorOptions.active && !openEditorOptions.pinned && this._group.activeEditor && this._group.isPreview(this._group.activeEditor)) { // Special case: we are to open an editor inactive and not pinned, but the current active // editor is also not pinned, which means it will get replaced with this one. As such, // the editor can only be active. @@ -787,13 +823,13 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._group.openEditor(editor, openEditorOptions); // Show editor - return this.doShowEditor(editor, openEditorOptions.active, options); + return this.doShowEditor(editor, !!openEditorOptions.active, options); } - private doShowEditor(editor: EditorInput, active: boolean, options?: EditorOptions): Promise { + private doShowEditor(editor: EditorInput, active: boolean, options?: EditorOptions): Promise { // Show in editor control if the active editor changed - let openEditorPromise: Promise; + let openEditorPromise: Promise; if (active) { openEditorPromise = this.editorControl.openEditor(editor, options).then(result => { @@ -824,7 +860,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Report error only if this was not us restoring previous error state or // we are told to ignore errors that occur from opening an editor - if (this.isRestored && !isPromiseCanceledError(error) && !this.ignoreOpenEditorErrors) { + if (this.isRestored && !isPromiseCanceledError(error) && (!options || !options.ignoreError)) { const actions: INotificationActions = { primary: [] }; if (isErrorWithActions(error)) { actions.primary = (error as IErrorWithActions).actions; @@ -836,7 +872,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { actions }); - Event.once(handle.onDidClose)(() => dispose(actions.primary)); + Event.once(handle.onDidClose)(() => actions.primary && dispose(actions.primary)); } // Event @@ -861,10 +897,10 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Do not modify original array editors = editors.slice(0); - let result: IEditor; + let result: IEditor | null; // Use the first editor as active editor - const { editor, options } = editors.shift(); + const { editor, options } = editors.shift()!; return this.openEditor(editor, options).then(activeEditor => { result = activeEditor; // this can be NULL if the opening failed @@ -964,7 +1000,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region closeEditor() - closeEditor(editor: EditorInput = this.activeEditor): Promise { + closeEditor(editor: EditorInput | undefined = this.activeEditor || undefined, options?: ICloseEditorOptions): Promise { if (!editor) { return Promise.resolve(); } @@ -976,11 +1012,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } // Do close - this.doCloseEditor(editor); + this.doCloseEditor(editor, options && options.preserveFocus ? false : undefined); }); } - private doCloseEditor(editor: EditorInput, focusNext = this.accessor.activeGroup === this, fromError?: boolean): void { + private doCloseEditor(editor: EditorInput, focusNext = (this.accessor.activeGroup === this), fromError?: boolean): void { // Closing the active editor of the group is a bit more work if (this._group.isActive(editor)) { @@ -996,7 +1032,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.titleAreaControl.closeEditor(editor); } - private doCloseActiveEditor(focusNext = this.accessor.activeGroup === this, fromError?: boolean): void { + private doCloseActiveEditor(focusNext = (this.accessor.activeGroup === this), fromError?: boolean): void { const editorToClose = this.activeEditor; const restoreFocus = this.shouldRestoreFocus(this.element); @@ -1021,32 +1057,34 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } // Update model - this._group.closeEditor(editorToClose); + if (editorToClose) { + this._group.closeEditor(editorToClose); + } // Open next active if there are more to show const nextActiveEditor = this._group.activeEditor; if (nextActiveEditor) { + const options = EditorOptions.create({ preserveFocus: !focusNext }); // When closing an editor due to an error we can end up in a loop where we continue closing // editors that fail to open (e.g. when the file no longer exists). We do not want to show // repeated errors in this case to the user. As such, if we open the next editor and we are // in a scope of a previous editor failing, we silence the input errors until the editor is - // opened. + // opened by setting ignoreError: true. if (fromError) { - this.ignoreOpenEditorErrors = true; + options.ignoreError = true; } - const options = !focusNext ? EditorOptions.create({ preserveFocus: true }) : undefined; - this.openEditor(nextActiveEditor, options).then(() => { - this.ignoreOpenEditorErrors = false; - }); + this.openEditor(nextActiveEditor, options); } // Otherwise we are empty, so clear from editor control and send event else { // Forward to editor control - this.editorControl.closeEditor(editorToClose); + if (editorToClose) { + this.editorControl.closeEditor(editorToClose); + } // Restore focus to group container as needed unless group gets closed if (restoreFocus && !closeEmptyGroup) { @@ -1085,7 +1123,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return Promise.resolve(false); // no veto } - const editor = editors.shift(); + const editor = editors.shift()!; // To prevent multiple confirmation dialogs from showing up one after the other // we check if a pending confirmation is currently showing and if so, join that @@ -1157,7 +1195,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { //#region closeEditors() - closeEditors(args: EditorInput[] | ICloseEditorsFilter): Promise { + closeEditors(args: EditorInput[] | ICloseEditorsFilter, options?: ICloseEditorOptions): Promise { if (this.isEmpty()) { return Promise.resolve(); } @@ -1171,7 +1209,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { } // Do close - this.doCloseEditors(editors); + this.doCloseEditors(editors, options); }); } @@ -1205,7 +1243,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { return editorsToClose; } - private doCloseEditors(editors: EditorInput[]): void { + private doCloseEditors(editors: EditorInput[], options?: ICloseEditorOptions): void { // Close all inactive editors first let closeActiveEditor = false; @@ -1219,7 +1257,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Close active editor last if contained in editors list to close if (closeActiveEditor) { - this.doCloseActiveEditor(); + this.doCloseActiveEditor(options && options.preserveFocus ? false : undefined); } // Forward to title control @@ -1278,7 +1316,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { replaceEditors(editors: EditorReplacement[]): Promise { // Extract active vs. inactive replacements - let activeReplacement: EditorReplacement; + let activeReplacement: EditorReplacement | undefined; const inactiveReplacements: EditorReplacement[] = []; editors.forEach(({ editor, replacement, options }) => { if (editor.isDirty()) { @@ -1314,11 +1352,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Open inactive editor this.doOpenEditor(replacement, options); - // Close replaced inactive edior - this.doCloseInactiveEditor(editor); - - // Forward to title control - this.titleAreaControl.closeEditor(editor); + // Close replaced inactive editor unless they match + if (!editor.matches(replacement)) { + this.doCloseInactiveEditor(editor); + this.titleAreaControl.closeEditor(editor); + } }); // Handle active last @@ -1327,11 +1365,11 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Open replacement as active editor const openEditorResult = this.doOpenEditor(activeReplacement.replacement, activeReplacement.options); - // Close previous active editor - this.doCloseInactiveEditor(activeReplacement.editor); - - // Forward to title control - this.titleAreaControl.closeEditor(activeReplacement.editor); + // Close replaced active editor unless they match + if (!activeReplacement.editor.matches(activeReplacement.replacement)) { + this.doCloseInactiveEditor(activeReplacement.editor); + this.titleAreaControl.closeEditor(activeReplacement.editor); + } return openEditorResult.then(() => undefined); } @@ -1384,8 +1422,8 @@ export class EditorGroupView extends Themable implements IEditorGroupView { get maximumWidth(): number { return this.editorControl.maximumWidth; } get maximumHeight(): number { return this.editorControl.maximumHeight; } - private _onDidChange = this._register(new Relay<{ width: number; height: number; }>()); - readonly onDidChange: Event<{ width: number; height: number; }> = this._onDidChange.event; + private _onDidChange = this._register(new Relay<{ width: number; height: number; } | undefined>()); + readonly onDidChange: Event<{ width: number; height: number; } | undefined> = this._onDidChange.event; layout(width: number, height: number): void { this.dimension = new Dimension(width, height); @@ -1430,7 +1468,7 @@ class EditorOpeningEvent implements IEditorOpeningEvent { constructor( private _group: GroupIdentifier, private _editor: EditorInput, - private _options: EditorOptions + private _options: EditorOptions | undefined ) { } @@ -1442,7 +1480,7 @@ class EditorOpeningEvent implements IEditorOpeningEvent { return this._editor; } - get options(): EditorOptions { + get options(): EditorOptions | undefined { return this._options; } @@ -1464,10 +1502,10 @@ export interface EditorReplacement { registerThemingParticipant((theme, collector, environment) => { // Letterpress - const letterpress = `resources/letterpress${theme.type === 'dark' ? '-dark' : theme.type === 'hc' ? '-hc' : ''}.svg`; + const letterpress = `./media/letterpress${theme.type === 'dark' ? '-dark' : theme.type === 'hc' ? '-hc' : ''}.svg`; collector.addRule(` .monaco-workbench .part.editor > .content .editor-group-container.empty .editor-group-letterpress { - background-image: url('${URI.file(join(environment.appRoot, letterpress)).toString()}') + background-image: url('${require.toUrl(letterpress)}') } `); diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 243a0d0103..54f3090ac9 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -9,14 +9,14 @@ import { Part } from 'vs/workbench/browser/part'; import { Dimension, isAncestor, toggleClass, addClass, $ } from 'vs/base/browser/dom'; import { Event, Emitter, Relay } from 'vs/base/common/event'; import { contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry'; -import { GroupDirection, IAddGroupOptions, GroupsArrangement, GroupOrientation, IMergeGroupOptions, MergeGroupMode, ICopyEditorOptions, GroupsOrder, GroupChangeKind, GroupLocation, IFindGroupScope, EditorGroupLayout, GroupLayoutArgument } from 'vs/workbench/services/group/common/editorGroupsService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid, ISerializableView } from 'vs/base/browser/ui/grid/grid'; -import { GroupIdentifier, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; +import { GroupDirection, IAddGroupOptions, GroupsArrangement, GroupOrientation, IMergeGroupOptions, MergeGroupMode, ICopyEditorOptions, GroupsOrder, GroupChangeKind, GroupLocation, IFindGroupScope, EditorGroupLayout, GroupLayoutArgument, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid } from 'vs/base/browser/ui/grid/grid'; +import { GroupIdentifier, IWorkbenchEditorConfiguration, IEditorPartOptions } from 'vs/workbench/common/editor'; import { values } from 'vs/base/common/map'; import { EDITOR_GROUP_BORDER, EDITOR_PANE_BACKGROUND } from 'vs/workbench/common/theme'; -import { distinct } from 'vs/base/common/arrays'; -import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptions, getEditorPartOptions, impactsEditorPartOptions, IEditorPartOptionsChangeEvent, EditorGroupsServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; +import { distinct, coalesce } from 'vs/base/common/arrays'; +import { IEditorGroupsAccessor, IEditorGroupView, getEditorPartOptions, impactsEditorPartOptions, IEditorPartOptionsChangeEvent, IEditorPartCreationOptions } from 'vs/workbench/browser/parts/editor/editor'; import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; @@ -29,7 +29,8 @@ import { Color } from 'vs/base/common/color'; import { CenteredViewLayout } from 'vs/base/browser/ui/centered/centeredViewLayout'; import { IView, orthogonal, LayoutPriority } from 'vs/base/browser/ui/grid/gridview'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { Parts } from 'vs/workbench/services/part/common/partService'; +import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; // {{SQL CARBON EDIT}} import { convertEditorInput } from 'sql/parts/common/customInputConverter'; @@ -50,8 +51,8 @@ class GridWidgetView implements IView { get minimumHeight(): number { return this.gridWidget ? this.gridWidget.minimumHeight : 0; } get maximumHeight(): number { return this.gridWidget ? this.gridWidget.maximumHeight : Number.POSITIVE_INFINITY; } - private _onDidChange = new Relay<{ width: number; height: number; }>(); - readonly onDidChange: Event<{ width: number; height: number; }> = this._onDidChange.event; + private _onDidChange = new Relay<{ width: number; height: number; } | undefined>(); + readonly onDidChange: Event<{ width: number; height: number; } | undefined> = this._onDidChange.event; private _gridWidget: Grid; @@ -83,44 +84,43 @@ class GridWidgetView implements IView { } } -export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditorGroupsAccessor, ISerializableView { +export class EditorPart extends Part implements IEditorGroupsService, IEditorGroupsAccessor { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private static readonly EDITOR_PART_UI_STATE_STORAGE_KEY = 'editorpart.state'; private static readonly EDITOR_PART_CENTERED_VIEW_STORAGE_KEY = 'editorpart.centeredview'; //#region Events - private _onDidLayout: Emitter = this._register(new Emitter()); + private readonly _onDidLayout: Emitter = this._register(new Emitter()); get onDidLayout(): Event { return this._onDidLayout.event; } - private _onDidActiveGroupChange: Emitter = this._register(new Emitter()); + private readonly _onDidActiveGroupChange: Emitter = this._register(new Emitter()); get onDidActiveGroupChange(): Event { return this._onDidActiveGroupChange.event; } - private _onDidAddGroup: Emitter = this._register(new Emitter()); + private readonly _onDidActivateGroup: Emitter = this._register(new Emitter()); + get onDidActivateGroup(): Event { return this._onDidActivateGroup.event; } + + private readonly _onDidAddGroup: Emitter = this._register(new Emitter()); get onDidAddGroup(): Event { return this._onDidAddGroup.event; } - private _onDidRemoveGroup: Emitter = this._register(new Emitter()); + private readonly _onDidRemoveGroup: Emitter = this._register(new Emitter()); get onDidRemoveGroup(): Event { return this._onDidRemoveGroup.event; } - private _onDidMoveGroup: Emitter = this._register(new Emitter()); + private readonly _onDidMoveGroup: Emitter = this._register(new Emitter()); get onDidMoveGroup(): Event { return this._onDidMoveGroup.event; } - private onDidSetGridWidget = this._register(new Emitter<{ width: number; height: number; }>()); - private _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; }>()); - get onDidSizeConstraintsChange(): Event<{ width: number; height: number; }> { return Event.any(this.onDidSetGridWidget.event, this._onDidSizeConstraintsChange.event); } + private onDidSetGridWidget = this._register(new Emitter<{ width: number; height: number; } | undefined>()); + private _onDidSizeConstraintsChange = this._register(new Relay<{ width: number; height: number; } | undefined>()); + get onDidSizeConstraintsChange(): Event<{ width: number; height: number; } | undefined> { return Event.any(this.onDidSetGridWidget.event, this._onDidSizeConstraintsChange.event); } - private _onDidPreferredSizeChange: Emitter = this._register(new Emitter()); + private readonly _onDidPreferredSizeChange: Emitter = this._register(new Emitter()); get onDidPreferredSizeChange(): Event { return this._onDidPreferredSizeChange.event; } - private _onDidActivateGroup: Emitter = this._register(new Emitter()); - get onDidActivateGroup(): Event { return this._onDidActivateGroup.event; } - //#endregion - private dimension: Dimension; - private _preferredSize: Dimension; + private _preferredSize: Dimension | undefined; private workspaceMemento: object; private globalMemento: object; @@ -139,22 +139,14 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor private _whenRestored: Promise; private whenRestoredResolve: () => void; - element: HTMLElement; - - private _onDidChange = new Emitter<{ width: number; height: number; }>(); - readonly onDidChange = this._onDidChange.event; - - priority: LayoutPriority = LayoutPriority.High; - constructor( - id: string, - private restorePreviousState: boolean, @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IStorageService storageService: IStorageService + @IStorageService storageService: IStorageService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(id, { hasTitle: false }, themeService, storageService); + super(Parts.EDITOR_PART, { hasTitle: false }, themeService, storageService, layoutService); this.gridWidgetView = new GridWidgetView(); @@ -172,7 +164,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor private enforcedPartOptions: IEditorPartOptions[] = []; - private _onDidEditorPartOptionsChange: Emitter = this._register(new Emitter()); + private readonly _onDidEditorPartOptionsChange: Emitter = this._register(new Emitter()); get onDidEditorPartOptionsChange(): Event { return this._onDidEditorPartOptionsChange.event; } private registerListeners(): void { @@ -216,6 +208,9 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor //#region IEditorGroupsService + private _dimension: Dimension; + get dimension(): Dimension { return this._dimension; } + get activeGroup(): IEditorGroupView { return this._activeGroup; } @@ -229,11 +224,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor } get orientation(): GroupOrientation { - if (!this.gridWidget) { - return undefined; // we have not been created yet - } - - return this.gridWidget.orientation === Orientation.VERTICAL ? GroupOrientation.VERTICAL : GroupOrientation.HORIZONTAL; + return (this.gridWidget && this.gridWidget.orientation === Orientation.VERTICAL) ? GroupOrientation.VERTICAL : GroupOrientation.HORIZONTAL; } get whenRestored(): Promise { @@ -246,7 +237,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor return this.groups; case GroupsOrder.MOST_RECENTLY_ACTIVE: - const mostRecentActive = this.mostRecentActiveGroups.map(groupId => this.getGroup(groupId)); + const mostRecentActive = coalesce(this.mostRecentActiveGroups.map(groupId => this.getGroup(groupId))); // there can be groups that got never active, even though they exist. in this case // make sure to ust append them at the end so that all groups are returned properly @@ -270,7 +261,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor } } - getGroup(identifier: GroupIdentifier): IEditorGroupView { + getGroup(identifier: GroupIdentifier): IEditorGroupView | undefined { return this.groupViews.get(identifier); } @@ -282,7 +273,11 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor } // by location - return this.doFindGroupByLocation(scope.location, source, wrap); + if (typeof scope.location === 'number') { + return this.doFindGroupByLocation(scope.location, source, wrap); + } + + throw new Error('invalid arguments'); } private doFindGroupByDirection(direction: GroupDirection, source: IEditorGroupView | GroupIdentifier, wrap?: boolean): IEditorGroupView { @@ -422,7 +417,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor this.doCreateGridControlWithState(gridDescriptor, activeGroup.id, currentGroupViews); // Layout - this.doLayout(this.dimension); + this.doLayout(this._dimension); // Update container this.updateContainer(); @@ -506,7 +501,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor return newGroupView; } - private doCreateGroupView(from?: IEditorGroupView | ISerializedEditorGroup): IEditorGroupView { + private doCreateGroupView(from?: IEditorGroupView | ISerializedEditorGroup | null): IEditorGroupView { // Label: just use the number of existing groups as label const label = this.getGroupLabel(this.count + 1); @@ -601,7 +596,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor } } - private toGridViewOrientation(orientation: GroupOrientation, fallback?: Orientation): Orientation { + private toGridViewOrientation(orientation: GroupOrientation, fallback: Orientation): Orientation { if (typeof orientation === 'number') { return orientation === GroupOrientation.HORIZONTAL ? Orientation.HORIZONTAL : Orientation.VERTICAL; } @@ -742,21 +737,26 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor } private assertGroupView(group: IEditorGroupView | GroupIdentifier): IEditorGroupView { + let groupView: IEditorGroupView | undefined; if (typeof group === 'number') { - group = this.getGroup(group); + groupView = this.getGroup(group); + } else { + groupView = group; } - if (!group) { + if (!groupView) { throw new Error('Invalid editor group provided!'); } - return group; + return groupView; } //#endregion //#region Part + readonly priority: LayoutPriority = LayoutPriority.High; + get minimumWidth(): number { return this.centeredLayoutWidget.minimumWidth; } get maximumWidth(): number { return this.centeredLayoutWidget.maximumWidth; } get minimumHeight(): number { return this.centeredLayoutWidget.minimumHeight; } @@ -783,7 +783,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor return this.theme.getColor(EDITOR_GROUP_BORDER) || this.theme.getColor(contrastBorder) || Color.transparent; } - protected updateStyles(): void { + updateStyles(): void { this.container.style.backgroundColor = this.getColor(editorBackground); const separatorBorderStyle = { separatorBorder: this.gridSeparatorBorder, background: this.theme.getColor(EDITOR_PANE_BACKGROUND) || Color.transparent }; @@ -791,7 +791,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor this.centeredLayoutWidget.styles(separatorBorderStyle); } - createContentArea(parent: HTMLElement): HTMLElement { + createContentArea(parent: HTMLElement, options?: IEditorPartCreationOptions): HTMLElement { // Container this.element = parent; @@ -800,7 +800,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor parent.appendChild(this.container); // Grid control with center layout - this.doCreateGridControl(); + this.doCreateGridControl(options); this.centeredLayoutWidget = this._register(new CenteredViewLayout(this.container, this.gridWidgetView, this.globalMemento[EditorPart.EDITOR_PART_CENTERED_VIEW_STORAGE_KEY])); @@ -819,15 +819,16 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor return this.centeredLayoutWidget.isActive(); } - private doCreateGridControl(): void { + private doCreateGridControl(options?: IEditorPartCreationOptions): void { // Grid Widget (with previous UI state) - if (this.restorePreviousState) { - this.doCreateGridControlWithPreviousState(); + let restoreError = false; + if (!options || options.restorePreviousState) { + restoreError = !this.doCreateGridControlWithPreviousState(); } // Grid Widget (no previous UI state or failed to restore) - if (!this.gridWidget) { + if (!this.gridWidget || restoreError) { const initialGroup = this.doCreateGroupView(); this.doSetGridWidget(new SerializableGrid(initialGroup)); @@ -842,7 +843,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor this.updateContainer(); } - private doCreateGridControlWithPreviousState(): void { + private doCreateGridControlWithPreviousState(): boolean { const uiState = this.workspaceMemento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] as IEditorPartUIState; if (uiState && uiState.serializedGrid) { try { @@ -856,25 +857,20 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor // Ensure last active group has focus this._activeGroup.focus(); } catch (error) { - this.handleGridRestoreError(error, uiState); + + // Log error + onUnexpectedError(new Error(`Error restoring editor grid widget: ${error} (with state: ${JSON.stringify(uiState)})`)); + + // Clear any state we have from the failing restore + this.groupViews.forEach(group => group.dispose()); + this.groupViews.clear(); + this.mostRecentActiveGroups = []; + + return false; // failure } } - } - private handleGridRestoreError(error: Error, state: IEditorPartUIState): void { - - // Log error - onUnexpectedError(new Error(`Error restoring editor grid widget: ${error} (with state: ${JSON.stringify(state)})`)); - - // Clear any state we have from the failing restore - if (this.gridWidget) { - this.doSetGridWidget(); - } - - this.groupViews.forEach(group => group.dispose()); - this.groupViews.clear(); - this._activeGroup = undefined; - this.mostRecentActiveGroups = []; + return true; // success } private doCreateGridControlWithState(serializedGrid: ISerializedGrid, activeGroupId: GroupIdentifier, editorGroupViewsToReuse?: IEditorGroupView[]): void { @@ -893,7 +889,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor fromJSON: (serializedEditorGroup: ISerializedEditorGroup | null) => { let groupView: IEditorGroupView; if (reuseGroupViews.length > 0) { - groupView = reuseGroupViews.shift(); + groupView = reuseGroupViews.shift()!; } else { groupView = this.doCreateGroupView(serializedEditorGroup); } @@ -924,7 +920,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor this.doSetGridWidget(gridWidget); } - private doSetGridWidget(gridWidget?: SerializableGrid): void { + private doSetGridWidget(gridWidget: SerializableGrid): void { if (this.gridWidget) { this.gridWidget.dispose(); } @@ -932,9 +928,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor this.gridWidget = gridWidget; this.gridWidgetView.gridWidget = gridWidget; - if (gridWidget) { - this._onDidSizeConstraintsChange.input = gridWidget.onDidChange; - } + this._onDidSizeConstraintsChange.input = gridWidget.onDidChange; this.onDidSetGridWidget.fire(undefined); } @@ -962,23 +956,20 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor return this.groupViews.size === 1 && this._activeGroup.isEmpty(); } - layout(dimension: Dimension): Dimension[]; - layout(width: number, height: number): void; - layout(dim1: Dimension | number, dim2?: number): Dimension[] | void { - const sizes = super.layout(dim1 instanceof Dimension ? dim1 : new Dimension(dim1, dim2)); + layout(width: number, height: number): void { - this.doLayout(sizes[1]); + // Layout contents + const contentAreaSize = super.layoutContents(width, height).contentSize; - if (dim1 instanceof Dimension) { - return sizes; - } + // Layout editor container + this.doLayout(contentAreaSize); } private doLayout(dimension: Dimension): void { - this.dimension = dimension; + this._dimension = dimension; // Layout Grid - this.centeredLayoutWidget.layout(this.dimension.width, this.dimension.height); + this.centeredLayoutWidget.layout(this._dimension.width, this._dimension.height); // Event this._onDidLayout.fire(dimension); @@ -1039,3 +1030,5 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor }; } } + +registerSingleton(IEditorGroupsService, EditorPart); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/editorPicker.ts b/src/vs/workbench/browser/parts/editor/editorPicker.ts index f2f8083478..850d7bb2cb 100644 --- a/src/vs/workbench/browser/parts/editor/editorPicker.ts +++ b/src/vs/workbench/browser/parts/editor/editorPicker.ts @@ -5,7 +5,6 @@ import 'vs/css!./media/editorpicker'; import * as nls from 'vs/nls'; -import { URI } from 'vs/base/common/uri'; import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IAutoFocus, Mode, IEntryRunContext, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen'; import { QuickOpenModel, QuickOpenEntry, QuickOpenEntryGroup, QuickOpenItemAccessor } from 'vs/base/parts/quickopen/browser/quickOpenModel'; @@ -14,7 +13,7 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { IModelService } from 'vs/editor/common/services/modelService'; import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupsOrder } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, EditorsOrder, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EditorInput, toResource } from 'vs/workbench/common/editor'; import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; @@ -33,12 +32,12 @@ export class EditorPickerEntry extends QuickOpenEntryGroup { getLabelOptions(): IIconLabelValueOptions { return { - extraClasses: getIconClasses(this.modelService, this.modeService, this.getResource()), + extraClasses: getIconClasses(this.modelService, this.modeService, this.getResource() || undefined), italic: !this._group.isPinned(this.editor) }; } - getLabel(): string { + getLabel() { return this.editor.getName(); } @@ -50,7 +49,7 @@ export class EditorPickerEntry extends QuickOpenEntryGroup { return this._group; } - getResource(): URI { + getResource() { return toResource(this.editor, { supportSideBySide: true }); } @@ -58,7 +57,7 @@ export class EditorPickerEntry extends QuickOpenEntryGroup { return nls.localize('entryAriaLabel', "{0}, editor group picker", this.getLabel()); } - getDescription(): string { + getDescription() { return this.editor.getDescription(); } @@ -109,7 +108,7 @@ export abstract class BaseEditorPicker extends QuickOpenHandler { return false; } - e.setHighlights(itemScore.labelMatch, itemScore.descriptionMatch); + e.setHighlights(itemScore.labelMatch || [], itemScore.descriptionMatch); return true; }); diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 86da68df0e..c07d110874 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -7,16 +7,15 @@ import 'vs/css!./media/editorstatus'; import * as nls from 'vs/nls'; import { $, append, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import * as strings from 'vs/base/common/strings'; -import * as paths from 'vs/base/common/paths'; +import { extname, basename } from 'vs/base/common/resources'; import * as types from 'vs/base/common/types'; import { URI as uri } from 'vs/base/common/uri'; import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { Action } from 'vs/base/common/actions'; -import { language, LANGUAGE_DEFAULT, AccessibilitySupport } from 'vs/base/common/platform'; -import * as browser from 'vs/base/browser/browser'; +import { Language } from 'vs/base/common/platform'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import { IFileEditorInput, EncodingMode, IEncodingSupport, toResource, SideBySideEditorInput, IEditor as IBaseEditor, IEditorInput } from 'vs/workbench/common/editor'; -import { IDisposable, combinedDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IEditorAction } from 'vs/editor/common/editorCommon'; import { EndOfLineSequence, ITextModel } from 'vs/editor/common/model'; @@ -27,7 +26,6 @@ import { BaseBinaryResourceEditor } from 'vs/workbench/browser/parts/editor/bina import { BinaryResourceDiffEditor } from 'vs/workbench/browser/parts/editor/binaryDiffEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { SUPPORTED_ENCODINGS, IFileService, FILES_ASSOCIATIONS_CONFIG } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; @@ -41,7 +39,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { IConfigurationChangedEvent, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; -import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { deepClone } from 'vs/base/common/objects'; import { ICodeEditor, isCodeEditor, isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { Schemas } from 'vs/base/common/network'; @@ -51,6 +49,10 @@ import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; import { timeout } from 'vs/base/common/async'; import { INotificationHandle, INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { Event } from 'vs/base/common/event'; +import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; + +// {{SQL CARBON EDIT}} +import { QueryEditorService } from 'sql/workbench/services/queryEditor/browser/queryEditorService'; class SideBySideEditorEncodingSupport implements IEncodingSupport { constructor(private master: IEncodingSupport, private details: IEncodingSupport) { } @@ -64,10 +66,7 @@ class SideBySideEditorEncodingSupport implements IEncodingSupport { } } -// {{SQL CARBON EDIT}} -import { QueryEditorService } from 'sql/workbench/services/queryEditor/browser/queryEditorService'; - -function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport { +function toEditorWithEncodingSupport(input: IEditorInput): IEncodingSupport | null { // Untitled Editor if (input instanceof UntitledEditorInput) { @@ -104,25 +103,14 @@ interface IEditorSelectionStatus { class StateChange { _stateChangeBrand: void; - indentation: boolean; - selectionStatus: boolean; - mode: boolean; - encoding: boolean; - EOL: boolean; - tabFocusMode: boolean; - screenReaderMode: boolean; - metadata: boolean; - - constructor() { - this.indentation = false; - this.selectionStatus = false; - this.mode = false; - this.encoding = false; - this.EOL = false; - this.tabFocusMode = false; - this.screenReaderMode = false; - this.metadata = false; - } + indentation: boolean = false; + selectionStatus: boolean = false; + mode: boolean = false; + encoding: boolean = false; + EOL: boolean = false; + tabFocusMode: boolean = false; + screenReaderMode: boolean = false; + metadata: boolean = false; combine(other: StateChange) { this.indentation = this.indentation || other.indentation; @@ -134,6 +122,17 @@ class StateChange { this.screenReaderMode = this.screenReaderMode || other.screenReaderMode; this.metadata = this.metadata || other.metadata; } + + public hasChanges(): boolean { + return this.indentation + || this.selectionStatus + || this.mode + || this.encoding + || this.EOL + || this.tabFocusMode + || this.screenReaderMode + || this.metadata; + } } interface StateDelta { @@ -144,33 +143,33 @@ interface StateDelta { indentation?: string; tabFocusMode?: boolean; screenReaderMode?: boolean; - metadata?: string; + metadata?: string | null; } class State { - private _selectionStatus: string; - get selectionStatus(): string { return this._selectionStatus; } + private _selectionStatus: string | null | undefined; + get selectionStatus(): string | null | undefined { return this._selectionStatus; } - private _mode: string; - get mode(): string { return this._mode; } + private _mode: string | null | undefined; + get mode(): string | null | undefined { return this._mode; } - private _encoding: string; - get encoding(): string { return this._encoding; } + private _encoding: string | null | undefined; + get encoding(): string | null | undefined { return this._encoding; } - private _EOL: string; - get EOL(): string { return this._EOL; } + private _EOL: string | null | undefined; + get EOL(): string | null | undefined { return this._EOL; } - private _indentation: string; - get indentation(): string { return this._indentation; } + private _indentation: string | null | undefined; + get indentation(): string | null | undefined { return this._indentation; } - private _tabFocusMode: boolean; - get tabFocusMode(): boolean { return this._tabFocusMode; } + private _tabFocusMode: boolean | null | undefined; + get tabFocusMode(): boolean | null | undefined { return this._tabFocusMode; } - private _screenReaderMode: boolean; - get screenReaderMode(): boolean { return this._screenReaderMode; } + private _screenReaderMode: boolean | null | undefined; + get screenReaderMode(): boolean | null | undefined { return this._screenReaderMode; } - private _metadata: string; - get metadata(): string { return this._metadata; } + private _metadata: string | null | undefined; + get metadata(): string | null | undefined { return this._metadata; } constructor() { this._selectionStatus = null; @@ -183,70 +182,58 @@ class State { } update(update: StateDelta): StateChange { - const e = new StateChange(); - let somethingChanged = false; + const change = new StateChange(); - if (typeof update.selectionStatus !== 'undefined') { + if ('selectionStatus' in update) { if (this._selectionStatus !== update.selectionStatus) { this._selectionStatus = update.selectionStatus; - somethingChanged = true; - e.selectionStatus = true; + change.selectionStatus = true; } } - if (typeof update.indentation !== 'undefined') { + if ('indentation' in update) { if (this._indentation !== update.indentation) { this._indentation = update.indentation; - somethingChanged = true; - e.indentation = true; + change.indentation = true; } } - if (typeof update.mode !== 'undefined') { + if ('mode' in update) { if (this._mode !== update.mode) { this._mode = update.mode; - somethingChanged = true; - e.mode = true; + change.mode = true; } } - if (typeof update.encoding !== 'undefined') { + if ('encoding' in update) { if (this._encoding !== update.encoding) { this._encoding = update.encoding; - somethingChanged = true; - e.encoding = true; + change.encoding = true; } } - if (typeof update.EOL !== 'undefined') { + if ('EOL' in update) { if (this._EOL !== update.EOL) { this._EOL = update.EOL; - somethingChanged = true; - e.EOL = true; + change.EOL = true; } } - if (typeof update.tabFocusMode !== 'undefined') { + if ('tabFocusMode' in update) { if (this._tabFocusMode !== update.tabFocusMode) { this._tabFocusMode = update.tabFocusMode; - somethingChanged = true; - e.tabFocusMode = true; + change.tabFocusMode = true; } } - if (typeof update.screenReaderMode !== 'undefined') { + if ('screenReaderMode' in update) { if (this._screenReaderMode !== update.screenReaderMode) { this._screenReaderMode = update.screenReaderMode; - somethingChanged = true; - e.screenReaderMode = true; + change.screenReaderMode = true; } } - if (typeof update.metadata !== 'undefined') { + if ('metadata' in update) { if (this._metadata !== update.metadata) { this._metadata = update.metadata; - somethingChanged = true; - e.metadata = true; + change.metadata = true; } } - if (somethingChanged) { - return e; - } - return null; + return change; } } @@ -260,34 +247,51 @@ const nlsTabFocusMode = nls.localize('tabFocusModeEnabled', "Tab Moves Focus"); const nlsScreenReaderDetected = nls.localize('screenReaderDetected', "Screen Reader Optimized"); const nlsScreenReaderDetectedTitle = nls.localize('screenReaderDetectedExtra', "If you are not using a Screen Reader, please change the setting `editor.accessibilitySupport` to \"off\"."); -function setDisplay(el: HTMLElement, desiredValue: string): void { - if (el.style.display !== desiredValue) { - el.style.display = desiredValue; + +class StatusBarItem { + private _showing = true; + + constructor( + private readonly element: HTMLElement, + title: string, + ) { + this.setVisible(false); + this.element.title = title; + } + + public set textContent(value: string) { + this.element.textContent = value; + } + + public set onclick(value: () => void) { + this.element.onclick = value; + } + + public setVisible(shouldShow: boolean): void { + if (shouldShow !== this._showing) { + this._showing = shouldShow; + this.element.style.display = shouldShow ? '' : 'none'; + } } } -function show(el: HTMLElement): void { - setDisplay(el, ''); -} -function hide(el: HTMLElement): void { - setDisplay(el, 'none'); -} + export class EditorStatus implements IStatusbarItem { private state: State; private element: HTMLElement; - private tabFocusModeElement: HTMLElement; - private screenRedearModeElement: HTMLElement; - private indentationElement: HTMLElement; - private selectionElement: HTMLElement; - private encodingElement: HTMLElement; - private eolElement: HTMLElement; - private modeElement: HTMLElement; - private metadataElement: HTMLElement; + private tabFocusModeElement: StatusBarItem; + private screenRedearModeElement: StatusBarItem; + private indentationElement: StatusBarItem; + private selectionElement: StatusBarItem; + private encodingElement: StatusBarItem; + private eolElement: StatusBarItem; + private modeElement: StatusBarItem; + private metadataElement: StatusBarItem; private toDispose: IDisposable[]; private activeEditorListeners: IDisposable[]; - private delayedRender: IDisposable; - private toRender: StateChange; - private screenReaderNotification: INotificationHandle; + private delayedRender: IDisposable | null; + private toRender: StateChange | null; + private screenReaderNotification: INotificationHandle | null; constructor( @IEditorService private readonly editorService: IEditorService, @@ -296,8 +300,9 @@ export class EditorStatus implements IStatusbarItem { @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, @IModeService private readonly modeService: IModeService, @ITextFileService private readonly textFileService: ITextFileService, - @IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService, - @INotificationService private readonly notificationService: INotificationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @INotificationService private readonly notificationService: INotificationService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService ) { this.toDispose = []; this.activeEditorListeners = []; @@ -307,59 +312,57 @@ export class EditorStatus implements IStatusbarItem { render(container: HTMLElement): IDisposable { this.element = append(container, $('.editor-statusbar-item')); - this.tabFocusModeElement = append(this.element, $('a.editor-status-tabfocusmode.status-bar-info')); - this.tabFocusModeElement.title = nls.localize('disableTabMode', "Disable Accessibility Mode"); + this.tabFocusModeElement = new StatusBarItem( + append(this.element, $('a.editor-status-tabfocusmode.status-bar-info')), + nls.localize('disableTabMode', "Disable Accessibility Mode")); this.tabFocusModeElement.onclick = () => this.onTabFocusModeClick(); this.tabFocusModeElement.textContent = nlsTabFocusMode; - hide(this.tabFocusModeElement); - this.screenRedearModeElement = append(this.element, $('a.editor-status-screenreadermode.status-bar-info')); + this.screenRedearModeElement = new StatusBarItem( + append(this.element, $('a.editor-status-screenreadermode.status-bar-info')), + nlsScreenReaderDetectedTitle); this.screenRedearModeElement.textContent = nlsScreenReaderDetected; - this.screenRedearModeElement.title = nlsScreenReaderDetectedTitle; this.screenRedearModeElement.onclick = () => this.onScreenReaderModeClick(); - hide(this.screenRedearModeElement); - this.selectionElement = append(this.element, $('a.editor-status-selection')); - this.selectionElement.title = nls.localize('gotoLine', "Go to Line"); + this.selectionElement = new StatusBarItem( + append(this.element, $('a.editor-status-selection')), + nls.localize('gotoLine', "Go to Line")); this.selectionElement.onclick = () => this.onSelectionClick(); - hide(this.selectionElement); - this.indentationElement = append(this.element, $('a.editor-status-indentation')); - this.indentationElement.title = nls.localize('selectIndentation', "Select Indentation"); + this.indentationElement = new StatusBarItem( + append(this.element, $('a.editor-status-indentation')), + nls.localize('selectIndentation', "Select Indentation")); this.indentationElement.onclick = () => this.onIndentationClick(); - hide(this.indentationElement); - this.encodingElement = append(this.element, $('a.editor-status-encoding')); - this.encodingElement.title = nls.localize('selectEncoding', "Select Encoding"); + this.encodingElement = new StatusBarItem( + append(this.element, $('a.editor-status-encoding')), + nls.localize('selectEncoding', "Select Encoding")); this.encodingElement.onclick = () => this.onEncodingClick(); - hide(this.encodingElement); - this.eolElement = append(this.element, $('a.editor-status-eol')); - this.eolElement.title = nls.localize('selectEOL', "Select End of Line Sequence"); + this.eolElement = new StatusBarItem( + append(this.element, $('a.editor-status-eol')), + nls.localize('selectEOL', "Select End of Line Sequence")); this.eolElement.onclick = () => this.onEOLClick(); - hide(this.eolElement); - this.modeElement = append(this.element, $('a.editor-status-mode')); - this.modeElement.title = nls.localize('selectLanguageMode', "Select Language Mode"); + this.modeElement = new StatusBarItem( + append(this.element, $('a.editor-status-mode')), + nls.localize('selectLanguageMode', "Select Language Mode")); this.modeElement.onclick = () => this.onModeClick(); - hide(this.modeElement); - this.metadataElement = append(this.element, $('span.editor-status-metadata')); - this.metadataElement.title = nls.localize('fileInfo', "File Information"); - hide(this.metadataElement); + this.metadataElement = new StatusBarItem( + append(this.element, $('span.editor-status-metadata')), + nls.localize('fileInfo', "File Information")); this.delayedRender = null; this.toRender = null; this.toDispose.push( - { - dispose: () => { - if (this.delayedRender) { - this.delayedRender.dispose(); - this.delayedRender = null; - } + toDisposable(() => { + if (this.delayedRender) { + this.delayedRender.dispose(); + this.delayedRender = null; } - }, + }), this.editorService.onDidActiveEditorChange(() => this.updateStatusBar()), this.untitledEditorService.onDidChangeEncoding(r => this.onResourceEncodingChange(r)), this.textFileService.models.onModelEncodingChanged(e => this.onResourceEncodingChange(e.resource)), @@ -371,7 +374,7 @@ export class EditorStatus implements IStatusbarItem { private updateState(update: StateDelta): void { const changed = this.state.update(update); - if (!changed) { + if (!changed.hasChanges()) { // Nothing really changed return; } @@ -382,7 +385,9 @@ export class EditorStatus implements IStatusbarItem { this.delayedRender = null; const toRender = this.toRender; this.toRender = null; - this._renderNow(toRender); + if (toRender) { + this._renderNow(toRender); + } }); } else { this.toRender.combine(changed); @@ -391,79 +396,71 @@ export class EditorStatus implements IStatusbarItem { private _renderNow(changed: StateChange): void { if (changed.tabFocusMode) { - if (this.state.tabFocusMode && this.state.tabFocusMode === true) { - show(this.tabFocusModeElement); - } else { - hide(this.tabFocusModeElement); - } + this.tabFocusModeElement.setVisible(!!this.state.tabFocusMode); } if (changed.screenReaderMode) { - if (this.state.screenReaderMode && this.state.screenReaderMode === true) { - show(this.screenRedearModeElement); - } else { - hide(this.screenRedearModeElement); - } + this.screenRedearModeElement.setVisible(!!this.state.screenReaderMode); } if (changed.indentation) { if (this.state.indentation) { this.indentationElement.textContent = this.state.indentation; - show(this.indentationElement); + this.indentationElement.setVisible(true); } else { - hide(this.indentationElement); + this.indentationElement.setVisible(false); } } if (changed.selectionStatus) { if (this.state.selectionStatus && !this.state.screenReaderMode) { this.selectionElement.textContent = this.state.selectionStatus; - show(this.selectionElement); + this.selectionElement.setVisible(true); } else { - hide(this.selectionElement); + this.selectionElement.setVisible(false); } } if (changed.encoding) { if (this.state.encoding) { this.encodingElement.textContent = this.state.encoding; - show(this.encodingElement); + this.encodingElement.setVisible(true); } else { - hide(this.encodingElement); + this.encodingElement.setVisible(false); } } if (changed.EOL) { if (this.state.EOL) { this.eolElement.textContent = this.state.EOL === '\r\n' ? nlsEOLCRLF : nlsEOLLF; - show(this.eolElement); + this.eolElement.setVisible(true); } else { - hide(this.eolElement); + this.eolElement.setVisible(false); } } if (changed.mode) { if (this.state.mode) { this.modeElement.textContent = this.state.mode; - show(this.modeElement); + this.modeElement.setVisible(true); } else { - hide(this.modeElement); + this.modeElement.setVisible(false); } } if (changed.metadata) { if (this.state.metadata) { this.metadataElement.textContent = this.state.metadata; - show(this.metadataElement); + this.metadataElement.setVisible(true); } else { - hide(this.metadataElement); + this.metadataElement.setVisible(false); } } } - private getSelectionLabel(info: IEditorSelectionStatus): string { + private getSelectionLabel(info: IEditorSelectionStatus): string | undefined { if (!info || !info.selections) { - return null; + return undefined; } if (info.selections.length === 1) { @@ -482,7 +479,7 @@ export class EditorStatus implements IStatusbarItem { return strings.format(nlsMultiSelection, info.selections.length); } - return null; + return undefined; } private onModeClick(): void { @@ -502,8 +499,7 @@ export class EditorStatus implements IStatusbarItem { if (!this.screenReaderNotification) { this.screenReaderNotification = this.notificationService.prompt( Severity.Info, - // {{SQL CARBON EDIT}} - nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate Azure Data Studio?"), + nls.localize('screenReaderDetectedExplanation.question', "Are you using a screen reader to operate Azure Data Studio? (Certain features like folding, minimap or word wrap are disabled when using a screen reader)"), [{ label: nls.localize('screenReaderDetectedExplanation.answerYes', "Yes"), run: () => { @@ -548,7 +544,7 @@ export class EditorStatus implements IStatusbarItem { private updateStatusBar(): void { const activeControl = this.editorService.activeControl; - const activeCodeEditor = activeControl ? getCodeEditor(activeControl.getControl()) : undefined; + const activeCodeEditor = activeControl ? types.withNullAsUndefined(getCodeEditor(activeControl.getControl())) : undefined; // Update all states this.onScreenReaderModeChange(activeCodeEditor); @@ -586,11 +582,13 @@ export class EditorStatus implements IStatusbarItem { this.activeEditorListeners.push(activeCodeEditor.onDidChangeModelContent((e) => { this.onEOLChange(activeCodeEditor); - let selections = activeCodeEditor.getSelections(); - for (const change of e.changes) { - if (selections.some(selection => Range.areIntersecting(selection, change.range))) { - this.onSelectionChange(activeCodeEditor); - break; + const selections = activeCodeEditor.getSelections(); + if (selections) { + for (const change of e.changes) { + if (selections.some(selection => Range.areIntersecting(selection, change.range))) { + this.onSelectionChange(activeCodeEditor); + break; + } } } })); @@ -630,8 +628,8 @@ export class EditorStatus implements IStatusbarItem { } } - private onModeChange(editorWidget: ICodeEditor): void { - let info: StateDelta = { mode: null }; + private onModeChange(editorWidget: ICodeEditor | undefined): void { + let info: StateDelta = { mode: undefined }; // We only support text based editors if (editorWidget) { @@ -639,15 +637,15 @@ export class EditorStatus implements IStatusbarItem { if (textModel) { // Compute mode const modeId = textModel.getLanguageIdentifier().language; - info = { mode: this.modeService.getLanguageName(modeId) }; + info = { mode: this.modeService.getLanguageName(modeId) || undefined }; } } this.updateState(info); } - private onIndentationChange(editorWidget: ICodeEditor): void { - const update: StateDelta = { indentation: null }; + private onIndentationChange(editorWidget: ICodeEditor | undefined): void { + const update: StateDelta = { indentation: undefined }; if (editorWidget) { const model = editorWidget.getModel(); @@ -655,7 +653,7 @@ export class EditorStatus implements IStatusbarItem { const modelOpts = model.getOptions(); update.indentation = ( modelOpts.insertSpaces - ? nls.localize('spacesSize', "Spaces: {0}", modelOpts.tabSize) + ? nls.localize('spacesSize', "Spaces: {0}", modelOpts.indentSize) : nls.localize({ key: 'tabSize', comment: ['Tab corresponds to the tab key'] }, "Tab Size: {0}", modelOpts.tabSize) ); } @@ -664,8 +662,8 @@ export class EditorStatus implements IStatusbarItem { this.updateState(update); } - private onMetadataChange(editor: IBaseEditor): void { - const update: StateDelta = { metadata: null }; + private onMetadataChange(editor: IBaseEditor | undefined): void { + const update: StateDelta = { metadata: undefined }; if (editor instanceof BaseBinaryResourceEditor || editor instanceof BinaryResourceDiffEditor) { update.metadata = editor.getMetadata(); @@ -676,12 +674,12 @@ export class EditorStatus implements IStatusbarItem { private _promptedScreenReader: boolean = false; - private onScreenReaderModeChange(editorWidget: ICodeEditor): void { + private onScreenReaderModeChange(editorWidget: ICodeEditor | undefined): void { let screenReaderMode = false; // We only support text based editors if (editorWidget) { - const screenReaderDetected = (browser.getAccessibilitySupport() === AccessibilitySupport.Enabled); + const screenReaderDetected = (this.accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled); if (screenReaderDetected) { const screenReaderConfiguration = this.configurationService.getValue('editor').accessibilitySupport; if (screenReaderConfiguration === 'auto') { @@ -705,7 +703,7 @@ export class EditorStatus implements IStatusbarItem { this.updateState({ screenReaderMode: screenReaderMode }); } - private onSelectionChange(editorWidget: ICodeEditor): void { + private onSelectionChange(editorWidget: ICodeEditor | undefined): void { const info: IEditorSelectionStatus = {}; // We only support text based editors @@ -719,13 +717,13 @@ export class EditorStatus implements IStatusbarItem { const textModel = editorWidget.getModel(); if (textModel) { info.selections.forEach(selection => { - info.charactersSelected += textModel.getValueLengthInRange(selection); + info.charactersSelected! += textModel.getValueLengthInRange(selection); }); } // Compute the visible column for one selection. This will properly handle tabs and their configured widths if (info.selections.length === 1) { - const visibleColumn = editorWidget.getVisibleColumnFromPosition(editorWidget.getPosition()); + const visibleColumn = editorWidget.getVisibleColumnFromPosition(editorWidget.getPosition()!); let selectionClone = info.selections[0].clone(); // do not modify the original position we got from the editor selectionClone = new Selection( @@ -742,8 +740,8 @@ export class EditorStatus implements IStatusbarItem { this.updateState({ selectionStatus: this.getSelectionLabel(info) }); } - private onEOLChange(editorWidget: ICodeEditor): void { - const info: StateDelta = { EOL: null }; + private onEOLChange(editorWidget: ICodeEditor | undefined): void { + const info: StateDelta = { EOL: undefined }; if (editorWidget && !editorWidget.getConfiguration().readOnly) { const codeEditorModel = editorWidget.getModel(); @@ -760,11 +758,11 @@ export class EditorStatus implements IStatusbarItem { return; } - const info: StateDelta = { encoding: null }; + const info: StateDelta = { encoding: undefined }; // We only support text based editors if (e && (isCodeEditor(e.getControl()) || isDiffEditor(e.getControl()))) { - const encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(e.input); + const encodingSupport: IEncodingSupport | null = e.input ? toEditorWithEncodingSupport(e.input) : null; if (encodingSupport) { const rawEncoding = encodingSupport.getEncoding(); const encodingInfo = SUPPORTED_ENCODINGS[rawEncoding]; @@ -798,11 +796,11 @@ export class EditorStatus implements IStatusbarItem { private isActiveEditor(control: IBaseEditor): boolean { const activeControl = this.editorService.activeControl; - return activeControl && activeControl === control; + return !!activeControl && activeControl === control; } } -function isWritableCodeEditor(codeEditor: ICodeEditor): boolean { +function isWritableCodeEditor(codeEditor: ICodeEditor | undefined): boolean { if (!codeEditor) { return false; } @@ -811,7 +809,7 @@ function isWritableCodeEditor(codeEditor: ICodeEditor): boolean { } function isWritableBaseEditor(e: IBaseEditor): boolean { - return e && isWritableCodeEditor(getCodeEditor(e.getControl())); + return e && isWritableCodeEditor(getCodeEditor(e.getControl()) || undefined); } export class ShowLanguageExtensionsAction extends Action { @@ -844,7 +842,7 @@ export class ChangeModeAction extends Action { @IModeService private readonly modeService: IModeService, @IModelService private readonly modelService: IModelService, @IEditorService private readonly editorService: IEditorService, - @IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IQuickInputService private readonly quickInputService: IQuickInputService, @IPreferencesService private readonly preferencesService: IPreferencesService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -860,19 +858,19 @@ export class ChangeModeAction extends Action { } const textModel = activeTextEditorWidget.getModel(); - const resource = toResource(this.editorService.activeEditor, { supportSideBySide: true }); + const resource = this.editorService.activeEditor ? toResource(this.editorService.activeEditor, { supportSideBySide: true }) : null; let hasLanguageSupport = !!resource; - if (resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) { + if (resource && resource.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(resource)) { hasLanguageSupport = false; // no configuration for untitled resources (e.g. "Untitled-1") } // Compute mode - let currentModeId: string; + let currentModeId: string | undefined; let modeId: string; if (textModel) { modeId = textModel.getLanguageIdentifier().language; - currentModeId = this.modeService.getLanguageName(modeId); + currentModeId = this.modeService.getLanguageName(modeId) || undefined; } // All languages are valid picks @@ -886,7 +884,7 @@ export class ChangeModeAction extends Action { } // construct a fake resource to be able to show nice icons if any - let fakeResource: uri; + let fakeResource: uri | undefined; const extensions = this.modeService.getExtensions(lang); if (extensions && extensions.length) { fakeResource = uri.file(extensions[0]); @@ -912,8 +910,8 @@ export class ChangeModeAction extends Action { let configureModeAssociations: IQuickPickItem; let configureModeSettings: IQuickPickItem; let galleryAction: Action; - if (hasLanguageSupport) { - const ext = paths.extname(resource.fsPath) || paths.basename(resource.fsPath); + if (hasLanguageSupport && resource) { + const ext = extname(resource) || basename(resource); galleryAction = this.instantiationService.createInstance(ShowLanguageExtensionsAction, ext); if (galleryAction.enabled) { @@ -947,7 +945,9 @@ export class ChangeModeAction extends Action { // User decided to permanently configure associations, return right after if (pick === configureModeAssociations) { - this.configureFileAssociation(resource); + if (resource) { + this.configureFileAssociation(resource); + } return; } @@ -980,10 +980,15 @@ export class ChangeModeAction extends Action { } // Find mode - let languageSelection: ILanguageSelection; + let languageSelection: ILanguageSelection | undefined; if (pick === autoDetectMode) { - // {{SQL CARBON EDIT}} - use activeEditor.input instead of activeEditor - languageSelection = this.modeService.createByFilepathOrFirstLine(toResource(activeEditor.input, { supportSideBySide: true }).fsPath, textModel.getLineContent(1)); + if (textModel) { + // {{SQL CARBON EDIT}} - use activeEditor.input instead of activeEditor + const resource = toResource(activeEditor.input, { supportSideBySide: true }); + if (resource) { + languageSelection = this.modeService.createByFilepathOrFirstLine(resource.fsPath, textModel.getLineContent(1)); + } + } } else { languageSelection = this.modeService.createByLanguageName(pick.label); } @@ -991,10 +996,9 @@ export class ChangeModeAction extends Action { // {{SQL CARBON EDIT}} // Change mode models.forEach(textModel => { - let self = this; QueryEditorService.sqlLanguageModeCheck(textModel, languageSelection, activeEditor).then((newTextModel) => { if (newTextModel) { - self.modelService.setMode(newTextModel, languageSelection); + this.modelService.setMode(newTextModel, languageSelection); } }); }); @@ -1002,9 +1006,9 @@ export class ChangeModeAction extends Action { } private configureFileAssociation(resource: uri): void { - const extension = paths.extname(resource.fsPath); - const basename = paths.basename(resource.fsPath); - const currentAssociation = this.modeService.getModeIdByFilepathOrFirstLine(basename); + const extension = extname(resource); + const base = basename(resource); + const currentAssociation = this.modeService.getModeIdByFilepathOrFirstLine(base); const languages = this.modeService.getRegisteredLanguageNames(); const picks: IQuickPickItem[] = languages.sort().map((lang, index) => { @@ -1018,15 +1022,15 @@ export class ChangeModeAction extends Action { }); setTimeout(() => { - this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || basename) }).then(language => { + this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || base) }).then(language => { if (language) { const fileAssociationsConfig = this.configurationService.inspect(FILES_ASSOCIATIONS_CONFIG); let associationKey: string; - if (extension && basename[0] !== '.') { + if (extension && base[0] !== '.') { associationKey = `*${extension}`; // only use "*.ext" if the file path is in the form of . } else { - associationKey = basename; // otherwise use the basename (e.g. .gitignore, Dockerfile) + associationKey = base; // otherwise use the basename (e.g. .gitignore, Dockerfile) } // If the association is already being made in the workspace, make sure to target workspace settings @@ -1036,11 +1040,7 @@ export class ChangeModeAction extends Action { } // Make sure to write into the value of the target and not the merged value from USER and WORKSPACE config - let currentAssociations = deepClone((target === ConfigurationTarget.WORKSPACE) ? fileAssociationsConfig.workspace : fileAssociationsConfig.user); - if (!currentAssociations) { - currentAssociations = Object.create(null); - } - + const currentAssociations = deepClone((target === ConfigurationTarget.WORKSPACE) ? fileAssociationsConfig.workspace : fileAssociationsConfig.user) || Object.create(null); currentAssociations[associationKey] = language.id; this.configurationService.updateValue(FILES_ASSOCIATIONS_CONFIG, currentAssociations, target); @@ -1089,7 +1089,7 @@ class ChangeIndentationAction extends Action { return { id: a.id, label: a.label, - detail: (language === LANGUAGE_DEFAULT) ? null : a.alias, + detail: Language.isDefaultVariant() ? undefined : a.alias, run: () => { activeTextEditorWidget.focus(); a.run(); @@ -1140,7 +1140,7 @@ export class ChangeEOLAction extends Action { return this.quickInputService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] }).then(eol => { if (eol) { const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorWidget); - if (activeCodeEditor && isWritableCodeEditor(activeCodeEditor)) { + if (activeCodeEditor && activeCodeEditor.hasModel() && isWritableCodeEditor(activeCodeEditor)) { const textModel = activeCodeEditor.getModel(); textModel.pushEOL(eol.eol); } @@ -1170,15 +1170,18 @@ export class ChangeEncodingAction extends Action { return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); } - let activeControl = this.editorService.activeControl; - let encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(activeControl.input); + const activeControl = this.editorService.activeControl; + if (!activeControl) { + return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + } + const encodingSupport: IEncodingSupport | null = toEditorWithEncodingSupport(activeControl.input); if (!encodingSupport) { return this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]); } let saveWithEncodingPick: IQuickPickItem; let reopenWithEncodingPick: IQuickPickItem; - if (language === LANGUAGE_DEFAULT) { + if (Language.isDefaultVariant()) { saveWithEncodingPick = { label: nls.localize('saveWithEncoding', "Save with Encoding") }; reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") }; } else { @@ -1200,7 +1203,7 @@ export class ChangeEncodingAction extends Action { return undefined; } - const resource = toResource(activeControl.input, { supportSideBySide: true }); + const resource = toResource(activeControl!.input, { supportSideBySide: true }); return timeout(50 /* quick open is sensitive to being opened so soon after another */) .then(() => { @@ -1213,10 +1216,10 @@ export class ChangeEncodingAction extends Action { .then((guessedEncoding: string) => { const isReopenWithEncoding = (action === reopenWithEncodingPick); - const configuredEncoding = this.textResourceConfigurationService.getValue(resource, 'files.encoding'); + const configuredEncoding = this.textResourceConfigurationService.getValue(types.withNullAsUndefined(resource), 'files.encoding'); - let directMatchIndex: number; - let aliasMatchIndex: number; + let directMatchIndex: number | undefined; + let aliasMatchIndex: number | undefined; // All encodings are valid picks const picks: QuickPickInput[] = Object.keys(SUPPORTED_ENCODINGS) @@ -1258,12 +1261,16 @@ export class ChangeEncodingAction extends Action { placeHolder: isReopenWithEncoding ? nls.localize('pickEncodingForReopen', "Select File Encoding to Reopen File") : nls.localize('pickEncodingForSave', "Select File Encoding to Save with"), activeItem: items[typeof directMatchIndex === 'number' ? directMatchIndex : typeof aliasMatchIndex === 'number' ? aliasMatchIndex : -1] }).then(encoding => { - if (encoding) { - activeControl = this.editorService.activeControl; - encodingSupport = toEditorWithEncodingSupport(activeControl.input); - if (encodingSupport && encodingSupport.getEncoding() !== encoding.id) { - encodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding - } + if (!encoding) { + return; + } + const activeControl = this.editorService.activeControl; + if (!activeControl) { + return; + } + const encodingSupport = toEditorWithEncodingSupport(activeControl.input); + if (typeof encoding.id !== 'undefined' && encodingSupport && encodingSupport.getEncoding() !== encoding.id) { + encodingSupport.setEncoding(encoding.id, isReopenWithEncoding ? EncodingMode.Decode : EncodingMode.Encode); // Set new encoding } }); }); diff --git a/src/vs/workbench/browser/parts/editor/editorWidgets.ts b/src/vs/workbench/browser/parts/editor/editorWidgets.ts index 4c3a261fbc..9c6e26ddd7 100644 --- a/src/vs/workbench/browser/parts/editor/editorWidgets.ts +++ b/src/vs/workbench/browser/parts/editor/editorWidgets.ts @@ -14,17 +14,16 @@ import { buttonBackground, buttonForeground, editorBackground, editorForeground, import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { Schemas } from 'vs/base/common/network'; -import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; -import { extname } from 'vs/base/common/paths'; +import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { Disposable, dispose } from 'vs/base/common/lifecycle'; import { localize } from 'vs/nls'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { isEqual } from 'vs/base/common/resources'; +import { IFileService } from 'vs/platform/files/common/files'; export class FloatingClickWidget extends Widget implements IOverlayWidget { - private _onClick: Emitter = this._register(new Emitter()); + private readonly _onClick: Emitter = this._register(new Emitter()); get onClick(): Event { return this._onClick.event; } private _domNode: HTMLElement; @@ -108,7 +107,8 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit private editor: ICodeEditor, @IInstantiationService private readonly instantiationService: IInstantiationService, @IWindowService private readonly windowService: IWindowService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IFileService private readonly fileService: IFileService ) { super(); @@ -139,8 +139,12 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit return false; // we need a model } - if (model.uri.scheme !== Schemas.file || extname(model.uri.fsPath) !== `.${WORKSPACE_EXTENSION}`) { - return false; // we need a local workspace file + if (!hasWorkspaceFileExtension(model.uri.fsPath)) { + return false; // we need a workspace file + } + + if (!this.fileService.canHandleResource(model.uri)) { + return false; // needs to be backed by a file service } if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { @@ -159,7 +163,7 @@ export class OpenWorkspaceButtonContribution extends Disposable implements IEdit this._register(this.openWorkspaceButton.onClick(() => { const model = this.editor.getModel(); if (model) { - this.windowService.openWindow([model.uri]); + this.windowService.openWindow([{ uri: model.uri, typeHint: 'file' }]); } })); diff --git a/resources/letterpress-dark.svg b/src/vs/workbench/browser/parts/editor/media/letterpress-dark.svg similarity index 100% rename from resources/letterpress-dark.svg rename to src/vs/workbench/browser/parts/editor/media/letterpress-dark.svg diff --git a/resources/letterpress-hc.svg b/src/vs/workbench/browser/parts/editor/media/letterpress-hc.svg similarity index 100% rename from resources/letterpress-hc.svg rename to src/vs/workbench/browser/parts/editor/media/letterpress-hc.svg diff --git a/resources/letterpress.svg b/src/vs/workbench/browser/parts/editor/media/letterpress.svg similarity index 100% rename from resources/letterpress.svg rename to src/vs/workbench/browser/parts/editor/media/letterpress.svg diff --git a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css index 32d4b6a704..34176cb720 100644 --- a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css @@ -54,15 +54,14 @@ background-image: none; } -/* {{SQL CARBON EDIT}} */ -.windows > .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before { - content: '/'; +.monaco-workbench.windows .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item::before { + content: '\\'; } .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder::before, .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item.root_folder + .monaco-breadcrumb-item::before, .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control.relative-path .monaco-breadcrumb-item:nth-child(2)::before, -.windows > .monaco-workbench .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:nth-child(2)::before { +.monaco-workbench.windows .part.editor > .content .editor-group-container > .title.breadcrumbs .breadcrumbs-control .monaco-breadcrumb-item:nth-child(2)::before { /* workspace folder, item following workspace folder, or relative path -> hide first seperator */ display: none; } diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index 1c7b61d584..70eaf650d3 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -4,19 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/notabstitlecontrol'; -import { toResource, Verbosity, IEditorInput } from 'vs/workbench/common/editor'; +import { toResource, Verbosity, IEditorInput, IEditorPartOptions } from 'vs/workbench/common/editor'; import { TitleControl, IToolbarActions } from 'vs/workbench/browser/parts/editor/titleControl'; import { ResourceLabel, IResourceLabel } from 'vs/workbench/browser/labels'; import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; import { addDisposableListener, EventType, addClass, EventHelper, removeClass, toggleClass } from 'vs/base/browser/dom'; -import { IEditorPartOptions, EDITOR_TITLE_HEIGHT } from 'vs/workbench/browser/parts/editor/editor'; +import { EDITOR_TITLE_HEIGHT } from 'vs/workbench/browser/parts/editor/editor'; import { IAction } from 'vs/base/common/actions'; import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { Color } from 'vs/base/common/color'; +import { withNullAsUndefined } from 'vs/base/common/types'; interface IRenderedEditorLabel { - editor: IEditorInput; + editor?: IEditorInput; pinned: boolean; } @@ -72,8 +73,16 @@ export class NoTabsTitleControl extends TitleControl { this._register(addDisposableListener(this.titleContainer, TouchEventType.Tap, (e: GestureEvent) => this.onTitleClick(e))); // Context Menu - this._register(addDisposableListener(this.titleContainer, EventType.CONTEXT_MENU, (e: Event) => this.onContextMenu(this.group.activeEditor, e, this.titleContainer))); - this._register(addDisposableListener(this.titleContainer, TouchEventType.Contextmenu, (e: Event) => this.onContextMenu(this.group.activeEditor, e, this.titleContainer))); + this._register(addDisposableListener(this.titleContainer, EventType.CONTEXT_MENU, (e: Event) => { + if (this.group.activeEditor) { + this.onContextMenu(this.group.activeEditor, e, this.titleContainer); + } + })); + this._register(addDisposableListener(this.titleContainer, TouchEventType.Contextmenu, (e: Event) => { + if (this.group.activeEditor) { + this.onContextMenu(this.group.activeEditor, e, this.titleContainer); + } + })); } private onTitleLabelClick(e: MouseEvent): void { @@ -95,7 +104,9 @@ export class NoTabsTitleControl extends TitleControl { if (e instanceof MouseEvent && e.button === 1 /* Middle Button */) { EventHelper.stop(e, true /* for https://github.com/Microsoft/vscode/issues/56715 */); - this.group.closeEditor(this.group.activeEditor); + if (this.group.activeEditor) { + this.group.closeEditor(this.group.activeEditor); + } } } @@ -167,7 +178,7 @@ export class NoTabsTitleControl extends TitleControl { if ( !this.activeLabel.editor && this.group.activeEditor || // active editor changed from null => editor this.activeLabel.editor && !this.group.activeEditor || // active editor changed from editor => null - !this.group.isActive(this.activeLabel.editor) // active editor changed from editorA => editorB + (!this.activeLabel.editor || !this.group.isActive(this.activeLabel.editor)) // active editor changed from editorA => editorB ) { fn(); @@ -195,9 +206,9 @@ export class NoTabsTitleControl extends TitleControl { } private redraw(): void { - const editor = this.group.activeEditor; + const editor = withNullAsUndefined(this.group.activeEditor); - const isEditorPinned = this.group.isPinned(this.group.activeEditor); + const isEditorPinned = this.group.activeEditor ? this.group.isPinned(this.group.activeEditor) : false; const isGroupActive = this.accessor.activeGroup === this.group; this.activeLabel = { editor, pinned: isEditorPinned }; @@ -244,7 +255,7 @@ export class NoTabsTitleControl extends TitleControl { title = ''; // dont repeat what is already shown } - this.editorLabel.setResource({ name, description, resource }, { title, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] }); + this.editorLabel.setResource({ name, description, resource: resource || undefined }, { title: typeof title === 'string' ? title : undefined, italic: !isEditorPinned, extraClasses: ['no-tabs', 'title-label'] }); if (isGroupActive) { this.editorLabel.element.style.color = this.getColor(TAB_ACTIVE_FOREGROUND); } else { @@ -256,7 +267,7 @@ export class NoTabsTitleControl extends TitleControl { } } - private getVerbosity(style: string): Verbosity { + private getVerbosity(style: string | undefined): Verbosity { switch (style) { case 'short': return Verbosity.SHORT; case 'long': return Verbosity.LONG; diff --git a/src/vs/workbench/browser/parts/editor/resourceViewer.ts b/src/vs/workbench/browser/parts/editor/resourceViewer.ts index b4fd760344..c24363743f 100644 --- a/src/vs/workbench/browser/parts/editor/resourceViewer.ts +++ b/src/vs/workbench/browser/parts/editor/resourceViewer.ts @@ -27,7 +27,7 @@ export interface IResourceDescriptor { readonly resource: URI; readonly name: string; readonly size: number; - readonly etag: string; + readonly etag?: string; readonly mime: string; } @@ -236,7 +236,7 @@ export class ZoomStatusbarItem extends Themable implements IStatusbarItem { static instance: ZoomStatusbarItem; - showTimeout: any; + private showTimeout: any; private statusBarItem: HTMLElement; private onSelectScale?: (scale: Scale) => void; diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index 488770947b..f89c835b0e 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -13,7 +13,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { scrollbarShadow } from 'vs/platform/theme/common/colorRegistry'; import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { SplitView, Sizing, Orientation } from 'vs/base/browser/ui/splitview/splitview'; import { Event, Relay, Emitter } from 'vs/base/common/event'; import { IStorageService } from 'vs/platform/storage/common/storage'; diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index db6d9c6e96..e4b3e2a28f 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/tabstitlecontrol'; import { isMacintosh } from 'vs/base/common/platform'; import { shorten } from 'vs/base/common/labels'; -import { toResource, GroupIdentifier, IEditorInput, Verbosity, EditorCommandsContextActionRunner } from 'vs/workbench/common/editor'; +import { toResource, GroupIdentifier, IEditorInput, Verbosity, EditorCommandsContextActionRunner, IEditorPartOptions } from 'vs/workbench/common/editor'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -32,25 +32,22 @@ import { ResourcesDropHandler, fillResourceDataTransfers, DraggedEditorIdentifie import { Color } from 'vs/base/common/color'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { MergeGroupMode, IMergeGroupOptions } from 'vs/workbench/services/group/common/editorGroupsService'; +import { MergeGroupMode, IMergeGroupOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { addClass, addDisposableListener, hasClass, EventType, EventHelper, removeClass, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; -import { IEditorGroupsAccessor, IEditorPartOptions, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { CloseOneEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { BreadcrumbsControl } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; import { IFileService } from 'vs/platform/files/common/files'; +import { withNullAsUndefined } from 'vs/base/common/types'; // {{SQL CARBON EDIT}} -- Display the editor's tab color -import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; -import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService'; -import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { GlobalNewUntitledFileAction } from 'vs/workbench/parts/files/electron-browser/fileActions'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import * as QueryConstants from 'sql/parts/query/common/constants'; import * as WorkbenchUtils from 'sql/workbench/common/sqlWorkbenchUtils'; +import { GlobalNewUntitledFileAction } from 'vs/workbench/contrib/files/browser/fileActions'; // {{SQL CARBON EDIT}} -- End interface IEditorInputLabel { @@ -74,7 +71,7 @@ export class TabsTitleControl extends TitleControl { private tabDisposeables: IDisposable[] = []; private dimension: Dimension; - private layoutScheduled: IDisposable; + private layoutScheduled?: IDisposable; private blockRevealActiveTab: boolean; constructor( @@ -95,11 +92,7 @@ export class TabsTitleControl extends TitleControl { @IConfigurationService configurationService: IConfigurationService, @IFileService fileService: IFileService, // {{SQL CARBON EDIT}} -- Display the editor's tab color - @IWorkspaceConfigurationService private workspaceConfigurationService: IWorkspaceConfigurationService, @ICommandService private commandService: ICommandService, - @IConnectionManagementService private connectionService: IConnectionManagementService, - @IQueryEditorService private queryEditorService: IQueryEditorService, - @IObjectExplorerService private objectExplorerService: IObjectExplorerService, // {{SQL CARBON EDIT}} -- End ) { super(parent, accessor, group, contextMenuService, instantiationService, contextKeyService, keybindingService, telemetryService, notificationService, menuService, quickOpenService, themeService, extensionService, configurationService, fileService); @@ -222,7 +215,7 @@ export class TabsTitleControl extends TitleControl { // Return if transfer is unsupported if (!this.isSupportedDropTransfer(e)) { - e.dataTransfer.dropEffect = 'none'; + e.dataTransfer!.dropEffect = 'none'; return; } @@ -231,9 +224,9 @@ export class TabsTitleControl extends TitleControl { if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { isLocalDragAndDrop = true; - const localDraggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)[0].identifier; + const localDraggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier; if (this.group.id === localDraggedEditor.groupId && this.group.getIndexOfEditor(localDraggedEditor.editor) === this.group.count - 1) { - e.dataTransfer.dropEffect = 'none'; + e.dataTransfer!.dropEffect = 'none'; return; } } @@ -241,7 +234,7 @@ export class TabsTitleControl extends TitleControl { // Update the dropEffect to "copy" if there is no local data to be dragged because // in that case we can only copy the data into and not move it from its source if (!isLocalDragAndDrop) { - e.dataTransfer.dropEffect = 'copy'; + e.dataTransfer!.dropEffect = 'copy'; } this.updateDropFeedback(this.tabsContainer, true); @@ -317,7 +310,7 @@ export class TabsTitleControl extends TitleControl { (this.tabsContainer.lastChild as HTMLElement).remove(); // Remove associated tab label and widget - this.tabDisposeables.pop().dispose(); + this.tabDisposeables.pop()!.dispose(); } // A removal of a label requires to recompute all labels @@ -488,7 +481,10 @@ export class TabsTitleControl extends TitleControl { } // Open tabs editor - this.group.openEditor(this.group.getEditor(index)); + const input = this.group.getEditor(index); + if (input) { + this.group.openEditor(input); + } return undefined; }; @@ -496,7 +492,10 @@ export class TabsTitleControl extends TitleControl { const showContextMenu = (e: Event) => { EventHelper.stop(e); - this.onContextMenu(this.group.getEditor(index), e, tab); + const input = this.group.getEditor(index); + if (input) { + this.onContextMenu(input, e, tab); + } }; // Open on Click / Touch @@ -543,7 +542,10 @@ export class TabsTitleControl extends TitleControl { // Run action on Enter/Space if (event.equals(KeyCode.Enter) || event.equals(KeyCode.Space)) { handled = true; - this.group.openEditor(this.group.getEditor(index)); + const input = this.group.getEditor(index); + if (input) { + this.group.openEditor(input); + } } // Navigate in editors @@ -581,22 +583,29 @@ export class TabsTitleControl extends TitleControl { disposables.push(addDisposableListener(tab, EventType.DBLCLICK, (e: MouseEvent) => { EventHelper.stop(e); - this.group.pinEditor(this.group.getEditor(index)); + this.group.pinEditor(this.group.getEditor(index) || undefined); })); // Context menu disposables.push(addDisposableListener(tab, EventType.CONTEXT_MENU, (e: Event) => { EventHelper.stop(e, true); - this.onContextMenu(this.group.getEditor(index), e, tab); + const input = this.group.getEditor(index); + if (input) { + this.onContextMenu(input, e, tab); + } }, true /* use capture to fix https://github.com/Microsoft/vscode/issues/19145 */)); // Drag support disposables.push(addDisposableListener(tab, EventType.DRAG_START, (e: DragEvent) => { const editor = this.group.getEditor(index); + if (!editor) { + return; + } + this.editorTransfer.setData([new DraggedEditorIdentifier({ editor, groupId: this.group.id })], DraggedEditorIdentifier.prototype); - e.dataTransfer.effectAllowed = 'copyMove'; + e.dataTransfer!.effectAllowed = 'copyMove'; // Apply some datatransfer types to allow for dragging the element outside of the application const resource = toResource(editor, { supportSideBySide: true }); @@ -618,7 +627,7 @@ export class TabsTitleControl extends TitleControl { // Return if transfer is unsupported if (!this.isSupportedDropTransfer(e)) { - e.dataTransfer.dropEffect = 'none'; + e.dataTransfer!.dropEffect = 'none'; return; } @@ -627,9 +636,9 @@ export class TabsTitleControl extends TitleControl { if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { isLocalDragAndDrop = true; - const localDraggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)[0].identifier; + const localDraggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier; if (localDraggedEditor.editor === this.group.getEditor(index) && localDraggedEditor.groupId === this.group.id) { - e.dataTransfer.dropEffect = 'none'; + e.dataTransfer!.dropEffect = 'none'; return; } } @@ -637,7 +646,7 @@ export class TabsTitleControl extends TitleControl { // Update the dropEffect to "copy" if there is no local data to be dragged because // in that case we can only copy the data into and not move it from its source if (!isLocalDragAndDrop) { - e.dataTransfer.dropEffect = 'copy'; + e.dataTransfer!.dropEffect = 'copy'; } this.updateDropFeedback(tab, true, index); @@ -668,7 +677,7 @@ export class TabsTitleControl extends TitleControl { private isSupportedDropTransfer(e: DragEvent): boolean { if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) { - const group = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)[0]; + const group = this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)![0]; if (group.identifier === this.group.id) { return false; // groups cannot be dropped on title area it originates from } @@ -680,7 +689,7 @@ export class TabsTitleControl extends TitleControl { return true; // (local) editors can always be dropped } - if (e.dataTransfer.types.length > 0) { + if (e.dataTransfer && e.dataTransfer.types.length > 0) { return true; // optimistically allow external data (// see https://github.com/Microsoft/vscode/issues/25789) } @@ -689,7 +698,8 @@ export class TabsTitleControl extends TitleControl { private updateDropFeedback(element: HTMLElement, isDND: boolean, index?: number): void { const isTab = (typeof index === 'number'); - const isActiveTab = isTab && this.group.isActive(this.group.getEditor(index)); + const editor = typeof index === 'number' ? this.group.getEditor(index) : null; + const isActiveTab = isTab && !!editor && this.group.isActive(editor); // Background const noDNDBackgroundColor = isTab ? this.getColor(isActiveTab ? TAB_ACTIVE_BACKGROUND : TAB_INACTIVE_BACKGROUND) : null; @@ -728,9 +738,9 @@ export class TabsTitleControl extends TitleControl { // Build labels and descriptions for each editor const labels = this.group.editors.map(editor => ({ editor, - name: editor.getName(), - description: editor.getDescription(verbosity), - title: editor.getTitle(Verbosity.LONG) + name: editor.getName()!, + description: withNullAsUndefined(editor.getDescription(verbosity)), + title: withNullAsUndefined(editor.getTitle(Verbosity.LONG)) })); // Shorten labels as needed @@ -782,7 +792,7 @@ export class TabsTitleControl extends TitleControl { if (useLongDescriptions) { mapDescriptionToDuplicates.clear(); duplicateTitles.forEach(label => { - label.description = label.editor.getDescription(Verbosity.LONG); + label.description = withNullAsUndefined(label.editor.getDescription(Verbosity.LONG)); getOrSet(mapDescriptionToDuplicates, label.description, []).push(label); }); } @@ -793,7 +803,7 @@ export class TabsTitleControl extends TitleControl { // Remove description if all descriptions are identical if (descriptions.length === 1) { - for (const label of mapDescriptionToDuplicates.get(descriptions[0])) { + for (const label of mapDescriptionToDuplicates.get(descriptions[0]) || []) { label.description = ''; } @@ -803,14 +813,14 @@ export class TabsTitleControl extends TitleControl { // Shorten descriptions const shortenedDescriptions = shorten(descriptions); descriptions.forEach((description, i) => { - for (const label of mapDescriptionToDuplicates.get(description)) { + for (const label of mapDescriptionToDuplicates.get(description) || []) { label.description = shortenedDescriptions[i]; } }); }); } - private getLabelConfigFlags(value: string) { + private getLabelConfigFlags(value: string | undefined) { switch (value) { case 'short': return { verbosity: Verbosity.SHORT, shortenDuplicates: false }; @@ -891,7 +901,7 @@ export class TabsTitleControl extends TitleControl { tabContainer.title = title; // Label - tabLabelWidget.setResource({ name, description, resource: toResource(editor, { supportSideBySide: true }) }, { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) }); + tabLabelWidget.setResource({ name, description, resource: toResource(editor, { supportSideBySide: true }) || undefined }, { title, extraClasses: ['tab-label'], italic: !this.group.isPinned(editor) }); // {{SQL CARBON EDIT}} -- Display the editor's tab color const isTabActive = this.group.isActive(editor); @@ -961,7 +971,7 @@ export class TabsTitleControl extends TitleControl { // Highlight modified tabs with a border if configured if (this.accessor.partOptions.highlightModifiedTabs) { - let modifiedBorderColor: string; + let modifiedBorderColor: string | null; if (isGroupActive && isTabActive) { modifiedBorderColor = this.getColor(TAB_ACTIVE_MODIFIED_BORDER); } else if (isGroupActive && !isTabActive) { @@ -998,7 +1008,7 @@ export class TabsTitleControl extends TitleControl { layout(dimension: Dimension): void { this.dimension = dimension; - const activeTab = this.getTab(this.group.activeEditor); + const activeTab = this.group.activeEditor ? this.getTab(this.group.activeEditor) : undefined; if (!activeTab || !this.dimension) { return; } @@ -1015,7 +1025,7 @@ export class TabsTitleControl extends TitleControl { } private doLayout(dimension: Dimension): void { - const activeTab = this.getTab(this.group.activeEditor); + const activeTab = this.group.activeEditor ? this.getTab(this.group.activeEditor) : undefined; if (!activeTab) { return; } @@ -1050,25 +1060,25 @@ export class TabsTitleControl extends TitleControl { // Reveal the active one const containerScrollPosX = this.tabsScrollbar.getScrollPosition().scrollLeft; - const activeTabFits = activeTabWidth <= visibleContainerWidth; + const activeTabFits = activeTabWidth! <= visibleContainerWidth; // Tab is overflowing to the right: Scroll minimally until the element is fully visible to the right // Note: only try to do this if we actually have enough width to give to show the tab fully! - if (activeTabFits && containerScrollPosX + visibleContainerWidth < activeTabPosX + activeTabWidth) { + if (activeTabFits && containerScrollPosX + visibleContainerWidth < activeTabPosX! + activeTabWidth!) { this.tabsScrollbar.setScrollPosition({ - scrollLeft: containerScrollPosX + ((activeTabPosX + activeTabWidth) /* right corner of tab */ - (containerScrollPosX + visibleContainerWidth) /* right corner of view port */) + scrollLeft: containerScrollPosX + ((activeTabPosX! + activeTabWidth!) /* right corner of tab */ - (containerScrollPosX + visibleContainerWidth) /* right corner of view port */) }); } // Tab is overlflowng to the left or does not fit: Scroll it into view to the left - else if (containerScrollPosX > activeTabPosX || !activeTabFits) { + else if (containerScrollPosX > activeTabPosX! || !activeTabFits) { this.tabsScrollbar.setScrollPosition({ - scrollLeft: activeTabPosX + scrollLeft: activeTabPosX! }); } } - private getTab(editor: IEditorInput): HTMLElement { + private getTab(editor: IEditorInput): HTMLElement | undefined { const editorIndex = this.group.getIndexOfEditor(editor); if (editorIndex >= 0) { return this.tabsContainer.children[editorIndex] as HTMLElement; @@ -1106,17 +1116,20 @@ export class TabsTitleControl extends TitleControl { // Local Editor DND if (this.editorTransfer.hasData(DraggedEditorIdentifier.prototype)) { - const draggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)[0].identifier; + const draggedEditor = this.editorTransfer.getData(DraggedEditorIdentifier.prototype)![0].identifier; const sourceGroup = this.accessor.getGroup(draggedEditor.groupId); - // Move editor to target position and index - if (this.isMoveOperation(e, draggedEditor.groupId)) { - sourceGroup.moveEditor(draggedEditor.editor, this.group, { index: targetIndex }); - } + if (sourceGroup) { - // Copy editor to target position and index - else { - sourceGroup.copyEditor(draggedEditor.editor, this.group, { index: targetIndex }); + // Move editor to target position and index + if (this.isMoveOperation(e, draggedEditor.groupId)) { + sourceGroup.moveEditor(draggedEditor.editor, this.group, { index: targetIndex }); + } + + // Copy editor to target position and index + else { + sourceGroup.copyEditor(draggedEditor.editor, this.group, { index: targetIndex }); + } } this.group.focus(); @@ -1125,15 +1138,17 @@ export class TabsTitleControl extends TitleControl { // Local Editor Group DND else if (this.groupTransfer.hasData(DraggedEditorGroupIdentifier.prototype)) { - const sourceGroup = this.accessor.getGroup(this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)[0].identifier); + const sourceGroup = this.accessor.getGroup(this.groupTransfer.getData(DraggedEditorGroupIdentifier.prototype)![0].identifier); - const mergeGroupOptions: IMergeGroupOptions = { index: targetIndex }; - if (!this.isMoveOperation(e, sourceGroup.id)) { - mergeGroupOptions.mode = MergeGroupMode.COPY_EDITORS; + if (sourceGroup) { + const mergeGroupOptions: IMergeGroupOptions = { index: targetIndex }; + if (!this.isMoveOperation(e, sourceGroup.id)) { + mergeGroupOptions.mode = MergeGroupMode.COPY_EDITORS; + } + + this.accessor.mergeGroup(sourceGroup, this.group, mergeGroupOptions); } - this.accessor.mergeGroup(sourceGroup, this.group, mergeGroupOptions); - this.group.focus(); this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype); } @@ -1154,7 +1169,7 @@ export class TabsTitleControl extends TitleControl { // {{SQL CARBON EDIT}} -- Display the editor's tab color private setEditorTabColor(editor: IEditorInput, tabContainer: HTMLElement, isTabActive: boolean) { let sqlEditor = editor as any; - let tabColorMode = WorkbenchUtils.getSqlConfigValue(this.workspaceConfigurationService, 'tabColorMode'); + let tabColorMode = WorkbenchUtils.getSqlConfigValue(this.configurationService, 'tabColorMode'); if (tabColorMode === QueryConstants.tabColorModeOff || (tabColorMode !== QueryConstants.tabColorModeBorder && tabColorMode !== QueryConstants.tabColorModeFill) || this.themeService.getTheme().type === HIGH_CONTRAST || !sqlEditor.tabColor) { tabContainer.style.borderTopColor = ''; @@ -1176,7 +1191,8 @@ export class TabsTitleControl extends TitleControl { dispose(): void { super.dispose(); - this.layoutScheduled = dispose(this.layoutScheduled); + dispose(this.layoutScheduled); + this.layoutScheduled = undefined; } } @@ -1206,6 +1222,16 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } + // High Contrast Border Color for Editor Actions + const contrastBorderColor = theme.getColor(contrastBorder); + if (contrastBorder) { + collector.addRule(` + .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions { + outline: 1px solid ${contrastBorderColor} + } + `); + } + // Hover Background const tabHoverBackground = theme.getColor(TAB_HOVER_BACKGROUND); if (tabHoverBackground) { @@ -1251,12 +1277,12 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const editorGroupHeaderTabsBackground = theme.getColor(EDITOR_GROUP_HEADER_TABS_BACKGROUND); const editorDragAndDropBackground = theme.getColor(EDITOR_DRAG_AND_DROP_BACKGROUND); - let adjustedTabBackground: Color; + let adjustedTabBackground: Color | undefined; if (editorGroupHeaderTabsBackground && editorBackgroundColor) { adjustedTabBackground = editorGroupHeaderTabsBackground.flatten(editorBackgroundColor, editorBackgroundColor, workbenchBackground); } - let adjustedTabDragBackground: Color; + let adjustedTabDragBackground: Color | undefined; if (editorGroupHeaderTabsBackground && editorBackgroundColor && editorDragAndDropBackground && editorBackgroundColor) { adjustedTabDragBackground = editorGroupHeaderTabsBackground.flatten(editorBackgroundColor, editorDragAndDropBackground, editorBackgroundColor, workbenchBackground); } diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index c432dd1bb7..b43c137b1c 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -27,7 +27,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { URI } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { EditorMemento } from 'vs/workbench/browser/parts/editor/baseEditor'; @@ -61,7 +61,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { return new EditorMemento(this.getId(), key, Object.create(null), limit, editorGroupService); // do not persist in storage as diff editors are never persisted } - getTitle(): string { + getTitle(): string | null { if (this.input) { return this.input.getName(); } @@ -120,6 +120,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { // Readonly flag diffEditor.updateOptions({ readOnly: resolvedDiffEditorModel.isReadonly() }); + return undefined; }, error => { // In case we tried to open a file and the response indicates that this is not a text file, fallback to binary diff. @@ -262,7 +263,7 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { return super.loadTextEditorViewState(resource) as IDiffEditorViewState; // overridden for text diff editor support } - private saveTextDiffEditorViewState(input: EditorInput): void { + private saveTextDiffEditorViewState(input: EditorInput | null): void { if (!(input instanceof DiffEditorInput)) { return; // only supported for diff editor inputs } @@ -288,11 +289,11 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { } } - protected retrieveTextEditorViewState(resource: URI): IDiffEditorViewState { + protected retrieveTextEditorViewState(resource: URI): IDiffEditorViewState | null { return this.retrieveTextDiffEditorViewState(resource); // overridden for text diff editor support } - private retrieveTextDiffEditorViewState(resource: URI): IDiffEditorViewState { + private retrieveTextDiffEditorViewState(resource: URI): IDiffEditorViewState | null { const control = this.getControl(); const model = control.getModel(); if (!model || !model.modified || !model.original) { @@ -311,9 +312,9 @@ export class TextDiffEditor extends BaseTextEditor implements ITextDiffEditor { return control.saveViewState(); } - private toDiffEditorViewStateResource(modelOrInput: IDiffEditorModel | DiffEditorInput): URI { - let original: URI; - let modified: URI; + private toDiffEditorViewStateResource(modelOrInput: IDiffEditorModel | DiffEditorInput): URI | null { + let original: URI | null; + let modified: URI | null; if (modelOrInput instanceof DiffEditorInput) { original = modelOrInput.originalInput.getResource(); diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 7629a54966..ddba49574f 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -20,7 +20,7 @@ import { ITextFileService, SaveReason, AutoSaveMode } from 'vs/workbench/service import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { isDiffEditor, isCodeEditor, ICodeEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWindowService } from 'vs/platform/windows/common/windows'; diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 18249da4c6..1b79a8eef0 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -20,7 +20,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { Event } from 'vs/base/common/event'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWindowService } from 'vs/platform/windows/common/windows'; @@ -46,7 +46,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorService, editorGroupService, windowService); } - getTitle(): string { + getTitle(): string | null { if (this.input) { return this.input.getName(); } @@ -168,7 +168,7 @@ export class AbstractTextResourceEditor extends BaseTextEditor { super.saveState(); } - private saveTextResourceEditorViewState(input: EditorInput): void { + private saveTextResourceEditorViewState(input: EditorInput | null): void { if (!(input instanceof UntitledEditorInput) && !(input instanceof ResourceEditorInput)) { return; // only enabled for untitled and resource inputs } diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index b5df4d5005..c9274c1253 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -32,13 +32,14 @@ import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillResourceData import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { BreadcrumbsConfig } from 'vs/workbench/browser/parts/editor/breadcrumbs'; import { BreadcrumbsControl, IBreadcrumbsControlOptions } from 'vs/workbench/browser/parts/editor/breadcrumbsControl'; -import { EDITOR_TITLE_HEIGHT, IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptions } from 'vs/workbench/browser/parts/editor/editor'; -import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource } from 'vs/workbench/common/editor'; +import { EDITOR_TITLE_HEIGHT, IEditorGroupsAccessor, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; +import { EditorCommandsContextActionRunner, IEditorCommandsContext, IEditorInput, toResource, IEditorPartOptions } from 'vs/workbench/common/editor'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { Themable } from 'vs/workbench/common/theme'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IFileService } from 'vs/platform/files/common/files'; +import { withUndefinedAsNull } from 'vs/base/common/types'; export interface IToolbarActions { primary: IAction[]; @@ -50,7 +51,7 @@ export abstract class TitleControl extends Themable { protected readonly groupTransfer = LocalSelectionTransfer.getInstance(); protected readonly editorTransfer = LocalSelectionTransfer.getInstance(); - protected breadcrumbsControl: BreadcrumbsControl; + protected breadcrumbsControl?: BreadcrumbsControl; private currentPrimaryEditorActionIds: string[] = []; private currentSecondaryEditorActionIds: string[] = []; @@ -155,18 +156,18 @@ export abstract class TitleControl extends Themable { })); } - private actionItemProvider(action: Action): IActionItem { + private actionItemProvider(action: Action): IActionItem | null { const activeControl = this.group.activeControl; // Check Active Editor - let actionItem: IActionItem; + let actionItem: IActionItem | null = null; if (activeControl instanceof BaseEditor) { actionItem = activeControl.getActionItem(action); } // Check extensions if (!actionItem) { - actionItem = createActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + actionItem = withUndefinedAsNull(createActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService)); } return actionItem; @@ -218,7 +219,7 @@ export abstract class TitleControl extends Themable { this.editorToolBarMenuDisposables = dispose(this.editorToolBarMenuDisposables); // Update the resource context - this.resourceContext.set(toResource(this.group.activeEditor, { supportSideBySide: true })); + this.resourceContext.set(this.group.activeEditor ? toResource(this.group.activeEditor, { supportSideBySide: true }) : null); // Editor actions require the editor control to be there, so we retrieve it via service const activeControl = this.group.activeControl; @@ -254,23 +255,25 @@ export abstract class TitleControl extends Themable { // Set editor group as transfer this.groupTransfer.setData([new DraggedEditorGroupIdentifier(this.group.id)], DraggedEditorGroupIdentifier.prototype); - e.dataTransfer.effectAllowed = 'copyMove'; + e.dataTransfer!.effectAllowed = 'copyMove'; // If tabs are disabled, treat dragging as if an editor tab was dragged if (!this.accessor.partOptions.showTabs) { - const resource = toResource(this.group.activeEditor, { supportSideBySide: true }); + const resource = this.group.activeEditor ? toResource(this.group.activeEditor, { supportSideBySide: true }) : null; if (resource) { this.instantiationService.invokeFunction(fillResourceDataTransfers, [resource], e); } } // Drag Image - let label = this.group.activeEditor.getName(); - if (this.accessor.partOptions.showTabs && this.group.count > 1) { - label = localize('draggedEditorGroup', "{0} (+{1})", label, this.group.count - 1); - } + if (this.group.activeEditor) { + let label = this.group.activeEditor.getName(); + if (this.accessor.partOptions.showTabs && this.group.count > 1) { + label = localize('draggedEditorGroup', "{0} (+{1})", label, this.group.count - 1); + } - applyDragImage(e, label, 'monaco-editor-group-drag-image'); + applyDragImage(e, label, 'monaco-editor-group-drag-image'); + } })); // Drag end @@ -305,7 +308,7 @@ export abstract class TitleControl extends Themable { onHide: () => { // restore previous context - this.resourceContext.set(currentContext); + this.resourceContext.set(currentContext || null); // restore focus to active group this.accessor.activeGroup.focus(); @@ -313,14 +316,14 @@ export abstract class TitleControl extends Themable { }); } - private getKeybinding(action: IAction): ResolvedKeybinding { + private getKeybinding(action: IAction): ResolvedKeybinding | undefined { return this.keybindingService.lookupKeybinding(action.id); } - protected getKeybindingLabel(action: IAction): string { + protected getKeybindingLabel(action: IAction): string | undefined { const keybinding = this.getKeybinding(action); - return keybinding ? keybinding.getLabel() : undefined; + return keybinding ? keybinding.getLabel() || undefined : undefined; } abstract openEditor(editor: IEditorInput): void; @@ -356,7 +359,8 @@ export abstract class TitleControl extends Themable { } dispose(): void { - this.breadcrumbsControl = dispose(this.breadcrumbsControl); + dispose(this.breadcrumbsControl); + this.breadcrumbsControl = undefined; this.editorToolBarMenuDisposables = dispose(this.editorToolBarMenuDisposables); super.dispose(); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts index 0af9e140a1..d2649a7a13 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCenter.ts @@ -8,7 +8,7 @@ import 'vs/css!./media/notificationsActions'; import { Themable, NOTIFICATIONS_BORDER, NOTIFICATIONS_CENTER_HEADER_FOREGROUND, NOTIFICATIONS_CENTER_HEADER_BACKGROUND, NOTIFICATIONS_CENTER_BORDER } from 'vs/workbench/common/theme'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { INotificationsModel, INotificationChangeEvent, NotificationChangeType } from 'vs/workbench/common/notifications'; -import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { Event, Emitter } from 'vs/base/common/event'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { NotificationsCenterVisibleContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; @@ -16,7 +16,7 @@ import { NotificationsList } from 'vs/workbench/browser/parts/notifications/noti import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { addClass, removeClass, isAncestor, Dimension } from 'vs/base/browser/dom'; import { widgetShadow } from 'vs/platform/theme/common/colorRegistry'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { localize } from 'vs/nls'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { ClearAllNotificationsAction, HideNotificationsCenterAction, NotificationActionRunner } from 'vs/workbench/browser/parts/notifications/notificationsActions'; @@ -43,7 +43,7 @@ export class NotificationsCenter extends Themable { private model: INotificationsModel, @IThemeService themeService: IThemeService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IContextKeyService contextKeyService: IContextKeyService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IKeybindingService private readonly keybindingService: IKeybindingService @@ -57,6 +57,7 @@ export class NotificationsCenter extends Themable { private registerListeners(): void { this._register(this.model.onDidNotificationChange(e => this.onDidNotificationChange(e))); + this._register(this.layoutService.onLayout(dimension => this.layout(dimension))); } get isVisible(): boolean { @@ -251,11 +252,11 @@ export class NotificationsCenter extends Themable { // Make sure notifications are not exceeding available height availableHeight = this.workbenchDimensions.height - 35 /* header */; - if (this.partService.isVisible(Parts.STATUSBAR_PART)) { + if (this.layoutService.isVisible(Parts.STATUSBAR_PART)) { availableHeight -= 22; // adjust for status bar } - if (this.partService.isVisible(Parts.TITLEBAR_PART)) { + if (this.layoutService.isVisible(Parts.TITLEBAR_PART)) { availableHeight -= 22; // adjust for title bar } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index 1322ead15d..f2e54681ee 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -32,14 +32,9 @@ export const TOGGLE_NOTIFICATION = 'notification.toggle'; export const CLEAR_NOTIFICATION = 'notification.clear'; export const CLEAR_ALL_NOTIFICATIONS = 'notifications.clearAll'; -const notificationFocusedId = 'notificationFocus'; -export const NotificationFocusedContext = new RawContextKey(notificationFocusedId, true); - -const notificationsCenterVisibleId = 'notificationCenterVisible'; -export const NotificationsCenterVisibleContext = new RawContextKey(notificationsCenterVisibleId, false); - -const notificationsToastsVisibleId = 'notificationToastsVisible'; -export const NotificationsToastsVisibleContext = new RawContextKey(notificationsToastsVisibleId, false); +export const NotificationFocusedContext = new RawContextKey('notificationFocus', true); +export const NotificationsCenterVisibleContext = new RawContextKey('notificationCenterVisible', false); +export const NotificationsToastsVisibleContext = new RawContextKey('notificationToastsVisible', false); export interface INotificationsCenterController { readonly isVisible: boolean; diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index 74fda05482..8344199e67 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -10,11 +10,11 @@ import { addClass, removeClass, isAncestor, addDisposableListener, EventType, Di import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { NotificationsList } from 'vs/workbench/browser/parts/notifications/notificationsList'; import { Event } from 'vs/base/common/event'; -import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { Themable, NOTIFICATIONS_TOAST_BORDER } from 'vs/workbench/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { widgetShadow } from 'vs/platform/theme/common/colorRegistry'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { NotificationsToastsVisibleContext } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { localize } from 'vs/nls'; @@ -62,7 +62,7 @@ export class NotificationsToasts extends Themable { private container: HTMLElement, private model: INotificationsModel, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IThemeService themeService: IThemeService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IContextKeyService contextKeyService: IContextKeyService, @@ -79,6 +79,9 @@ export class NotificationsToasts extends Themable { private registerListeners(): void { + // Layout + this._register(this.layoutService.onLayout(dimension => this.layout(dimension))); + // Delay some tasks until after we can show notifications this.onCanShowNotifications().then(() => { @@ -466,11 +469,11 @@ export class NotificationsToasts extends Themable { // Make sure notifications are not exceeding available height availableHeight = this.workbenchDimensions.height; - if (this.partService.isVisible(Parts.STATUSBAR_PART)) { + if (this.layoutService.isVisible(Parts.STATUSBAR_PART)) { availableHeight -= 22; // adjust for status bar } - if (this.partService.isVisible(Parts.TITLEBAR_PART)) { + if (this.layoutService.isVisible(Parts.TITLEBAR_PART)) { availableHeight -= 22; // adjust for title bar } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts index 57ab5fd7dd..1fc8a800a4 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsViewer.ts @@ -417,7 +417,7 @@ export class NotificationTemplateRenderer { actions.forEach(action => this.template.toolbar.push(action, { icon: true, label: false, keybinding: this.getKeybindingLabel(action) })); } - private renderSource(notification): void { + private renderSource(notification: INotificationViewItem): void { if (notification.expanded && notification.source) { this.template.source.textContent = localize('notificationSource', "Source: {0}", notification.source); this.template.source.title = notification.source; diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index a7c284802e..95f418625c 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -12,9 +12,10 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actions'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IPartService, Parts, Position } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { ActivityAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { IActivity } from 'vs/workbench/common/activity'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; export class ClosePanelAction extends Action { @@ -24,14 +25,14 @@ export class ClosePanelAction extends Action { constructor( id: string, name: string, - @IPartService private readonly partService: IPartService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(id, name, 'hide-panel-action'); } run(): Promise { - this.partService.setPanelHidden(true); - return Promise.resolve(null); + this.layoutService.setPanelHidden(true); + return Promise.resolve(); } } @@ -43,14 +44,14 @@ export class TogglePanelAction extends Action { constructor( id: string, name: string, - @IPartService private readonly partService: IPartService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { - super(id, name, partService.isVisible(Parts.PANEL_PART) ? 'panel expanded' : 'panel'); + super(id, name, layoutService.isVisible(Parts.PANEL_PART) ? 'panel expanded' : 'panel'); } run(): Promise { - this.partService.setPanelHidden(this.partService.isVisible(Parts.PANEL_PART)); - return Promise.resolve(null); + this.layoutService.setPanelHidden(this.layoutService.isVisible(Parts.PANEL_PART)); + return Promise.resolve(); } } @@ -63,7 +64,7 @@ class FocusPanelAction extends Action { id: string, label: string, @IPanelService private readonly panelService: IPanelService, - @IPartService private readonly partService: IPartService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(id, label); } @@ -71,9 +72,9 @@ class FocusPanelAction extends Action { run(): Promise { // Show panel - if (!this.partService.isVisible(Parts.PANEL_PART)) { - this.partService.setPanelHidden(false); - return Promise.resolve(null); + if (!this.layoutService.isVisible(Parts.PANEL_PART)) { + this.layoutService.setPanelHidden(false); + return Promise.resolve(); } // Focus into active panel @@ -82,7 +83,7 @@ class FocusPanelAction extends Action { panel.focus(); } - return Promise.resolve(null); + return Promise.resolve(); } } @@ -99,28 +100,29 @@ export class TogglePanelPositionAction extends Action { constructor( id: string, label: string, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IEditorGroupsService editorGroupsService: IEditorGroupsService ) { - super(id, label, partService.getPanelPosition() === Position.RIGHT ? 'move-panel-to-bottom' : 'move-panel-to-right'); + super(id, label, layoutService.getPanelPosition() === Position.RIGHT ? 'move-panel-to-bottom' : 'move-panel-to-right'); this.toDispose = []; const setClassAndLabel = () => { - const positionRight = this.partService.getPanelPosition() === Position.RIGHT; + const positionRight = this.layoutService.getPanelPosition() === Position.RIGHT; this.class = positionRight ? 'move-panel-to-bottom' : 'move-panel-to-right'; this.label = positionRight ? TogglePanelPositionAction.MOVE_TO_BOTTOM_LABEL : TogglePanelPositionAction.MOVE_TO_RIGHT_LABEL; }; - this.toDispose.push(partService.onEditorLayout(() => setClassAndLabel())); + this.toDispose.push(editorGroupsService.onDidLayout(() => setClassAndLabel())); setClassAndLabel(); } run(): Promise { - const position = this.partService.getPanelPosition(); + const position = this.layoutService.getPanelPosition(); - this.partService.setPanelPosition(position === Position.BOTTOM ? Position.RIGHT : Position.BOTTOM); - return Promise.resolve(null); + this.layoutService.setPanelPosition(position === Position.BOTTOM ? Position.RIGHT : Position.BOTTOM); + return Promise.resolve(); } dispose(): void { @@ -143,26 +145,27 @@ export class ToggleMaximizedPanelAction extends Action { constructor( id: string, label: string, - @IPartService private readonly partService: IPartService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, + @IEditorGroupsService editorGroupsService: IEditorGroupsService ) { - super(id, label, partService.isPanelMaximized() ? 'minimize-panel-action' : 'maximize-panel-action'); + super(id, label, layoutService.isPanelMaximized() ? 'minimize-panel-action' : 'maximize-panel-action'); this.toDispose = []; - this.toDispose.push(partService.onEditorLayout(() => { - const maximized = this.partService.isPanelMaximized(); + this.toDispose.push(editorGroupsService.onDidLayout(() => { + const maximized = this.layoutService.isPanelMaximized(); this.class = maximized ? 'minimize-panel-action' : 'maximize-panel-action'; this.label = maximized ? ToggleMaximizedPanelAction.RESTORE_LABEL : ToggleMaximizedPanelAction.MAXIMIZE_LABEL; })); } run(): Promise { - if (!this.partService.isVisible(Parts.PANEL_PART)) { - this.partService.setPanelHidden(false); + if (!this.layoutService.isVisible(Parts.PANEL_PART)) { + this.layoutService.setPanelHidden(false); } - this.partService.toggleMaximizedPanel(); - return Promise.resolve(null); + this.layoutService.toggleMaximizedPanel(); + return Promise.resolve(); } dispose(): void { @@ -184,7 +187,7 @@ export class PanelActivityAction extends ActivityAction { run(event: any): Promise { this.panelService.openPanel(this.activity.id, true); this.activate(); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -202,17 +205,19 @@ export class SwitchPanelViewAction extends Action { const pinnedPanels = this.panelService.getPinnedPanels(); const activePanel = this.panelService.getActivePanel(); if (!activePanel) { - return Promise.resolve(null); + return Promise.resolve(); } - let targetPanelId: string; + let targetPanelId: string | undefined; for (let i = 0; i < pinnedPanels.length; i++) { if (pinnedPanels[i].id === activePanel.getId()) { targetPanelId = pinnedPanels[(i + pinnedPanels.length + offset) % pinnedPanels.length].id; break; } } - this.panelService.openPanel(targetPanelId, true); - return Promise.resolve(null); + if (typeof targetPanelId === 'string') { + this.panelService.openPanel(targetPanelId, true); + } + return Promise.resolve(); } } @@ -247,7 +252,7 @@ export class NextPanelViewAction extends SwitchPanelViewAction { super(id, name, panelService); } - public run(): Promise { + run(): Promise { return super.run(1); } } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index e581eabed9..152d3b996e 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -5,19 +5,19 @@ import 'vs/css!./media/panelpart'; import { IAction } from 'vs/base/common/actions'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Event } from 'vs/base/common/event'; import { Registry } from 'vs/platform/registry/common/platform'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IPanel } from 'vs/workbench/common/panel'; +import { IPanel, ActivePanelContext, PanelFocusContext } from 'vs/workbench/common/panel'; import { CompositePart, ICompositeTitleLabel } from 'vs/workbench/browser/parts/compositePart'; import { Panel, PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel'; import { IPanelService, IPanelIdentifier } from 'vs/workbench/services/panel/common/panelService'; -import { IPartService, Parts, Position } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { ClosePanelAction, TogglePanelPositionAction, PanelActivityAction, ToggleMaximizedPanelAction, TogglePanelAction } from 'vs/workbench/browser/parts/panel/panelActions'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; @@ -29,56 +29,58 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { Dimension, trackFocus } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { ISerializableView } from 'vs/base/browser/ui/grid/grid'; import { LayoutPriority } from 'vs/base/browser/ui/grid/gridview'; - -export const ActivePanelContext = new RawContextKey('activePanel', ''); -export const PanelFocusContext = new RawContextKey('panelFocus', false); +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; interface ICachedPanel { id: string; pinned: boolean; - order: number; + order?: number; visible: boolean; } -export class PanelPart extends CompositePart implements IPanelService, ISerializableView { +export class PanelPart extends CompositePart implements IPanelService { static readonly activePanelSettingsKey = 'workbench.panelpart.activepanelid'; private static readonly PINNED_PANELS = 'workbench.panel.pinnedPanels'; private static readonly MIN_COMPOSITE_BAR_WIDTH = 50; - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; + + //#region IView + + readonly minimumWidth: number = 300; + readonly maximumWidth: number = Number.POSITIVE_INFINITY; + readonly minimumHeight: number = 77; + readonly maximumHeight: number = Number.POSITIVE_INFINITY; + + readonly snapSize: number = 50; + readonly priority: LayoutPriority = LayoutPriority.Low; + + //#endregion + + get onDidPanelOpen(): Event<{ panel: IPanel, focus: boolean }> { return Event.map(this.onDidCompositeOpen.event, compositeOpen => ({ panel: compositeOpen.composite, focus: compositeOpen.focus })); } + get onDidPanelClose(): Event { return this.onDidCompositeClose.event; } private activePanelContextKey: IContextKey; private panelFocusContextKey: IContextKey; - private blockOpeningPanel: boolean; + private compositeBar: CompositeBar; private compositeActions: { [compositeId: string]: { activityAction: PanelActivityAction, pinnedAction: ToggleCompositePinnedAction } } = Object.create(null); + + private blockOpeningPanel: boolean; private dimension: Dimension; - element: HTMLElement; - minimumWidth: number = 300; - maximumWidth: number = Number.POSITIVE_INFINITY; - minimumHeight: number = 77; - maximumHeight: number = Number.POSITIVE_INFINITY; - snapSize: number = 50; - priority: LayoutPriority = LayoutPriority.Low; - - private _onDidChange = new Emitter<{ width: number; height: number; }>(); - readonly onDidChange = this._onDidChange.event; - constructor( - id: string, @INotificationService notificationService: INotificationService, @IStorageService storageService: IStorageService, @ITelemetryService telemetryService: ITelemetryService, @IContextMenuService contextMenuService: IContextMenuService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @@ -90,7 +92,7 @@ export class PanelPart extends CompositePart implements IPanelService, IS storageService, telemetryService, contextMenuService, - partService, + layoutService, keybindingService, instantiationService, themeService, @@ -99,8 +101,8 @@ export class PanelPart extends CompositePart implements IPanelService, IS Registry.as(PanelExtensions.Panels).getDefaultPanelId(), 'panel', 'panel', - null, - id, + undefined, + Parts.PANEL_PART, { hasTitle: true } ); @@ -116,10 +118,10 @@ export class PanelPart extends CompositePart implements IPanelService, IS this.instantiationService.createInstance(TogglePanelAction, TogglePanelAction.ID, localize('hidePanel', "Hide Panel")) ], getDefaultCompositeId: () => Registry.as(PanelExtensions.Panels).getDefaultPanelId(), - hidePart: () => this.partService.setPanelHidden(true), + hidePart: () => this.layoutService.setPanelHidden(true), compositeSize: 0, overflowActionSize: 44, - colors: theme => ({ + colors: (theme: ITheme) => ({ activeBackgroundColor: theme.getColor(PANEL_BACKGROUND), // Background color for overflow action inactiveBackgroundColor: theme.getColor(PANEL_BACKGROUND), // Background color for overflow action activeBorderBottomColor: theme.getColor(PANEL_ACTIVE_TITLE_BORDER), @@ -141,25 +143,13 @@ export class PanelPart extends CompositePart implements IPanelService, IS this.registerListeners(); } - create(parent: HTMLElement): void { - this.element = parent; - - super.create(parent); - - const focusTracker = trackFocus(parent); - - focusTracker.onDidFocus(() => { - this.panelFocusContextKey.set(true); - }); - focusTracker.onDidBlur(() => { - this.panelFocusContextKey.set(false); - }); - } - private registerListeners(): void { - this._register(this.onDidPanelOpen(({ panel }) => this._onDidPanelOpen(panel))); - this._register(this.onDidPanelClose(this._onDidPanelClose, this)); + // Panel open/close + this._register(this.onDidPanelOpen(({ panel }) => this.onPanelOpen(panel))); + this._register(this.onDidPanelClose(this.onPanelClose, this)); + + // Panel register/deregister this._register(this.registry.onDidRegister(panelDescriptor => this.compositeBar.addComposite(panelDescriptor))); this._register(this.registry.onDidDeregister(panelDescriptor => { this.compositeBar.hideComposite(panelDescriptor.id); @@ -175,17 +165,18 @@ export class PanelPart extends CompositePart implements IPanelService, IS // Deactivate panel action on close this._register(this.onDidPanelClose(panel => this.compositeBar.deactivateComposite(panel.getId()))); + // State this.lifecycleService.when(LifecyclePhase.Eventually).then(() => { this._register(this.compositeBar.onDidChange(() => this.saveCachedPanels())); this._register(this.storageService.onDidChangeStorage(e => this.onDidStorageChange(e))); }); } - private _onDidPanelOpen(panel: IPanel): void { + private onPanelOpen(panel: IPanel): void { this.activePanelContextKey.set(panel.getId()); } - private _onDidPanelClose(panel: IPanel): void { + private onPanelClose(panel: IPanel): void { const id = panel.getId(); if (this.activePanelContextKey.get() === id) { @@ -193,12 +184,14 @@ export class PanelPart extends CompositePart implements IPanelService, IS } } - get onDidPanelOpen(): Event<{ panel: IPanel, focus: boolean }> { - return Event.map(this._onDidCompositeOpen.event, compositeOpen => ({ panel: compositeOpen.composite, focus: compositeOpen.focus })); - } + create(parent: HTMLElement): void { + this.element = parent; - get onDidPanelClose(): Event { - return this._onDidCompositeClose.event; + super.create(parent); + + const focusTracker = this._register(trackFocus(parent)); + this._register(focusTracker.onDidFocus(() => this.panelFocusContextKey.set(true))); + this._register(focusTracker.onDidBlur(() => this.panelFocusContextKey.set(false))); } updateStyles(): void { @@ -209,38 +202,40 @@ export class PanelPart extends CompositePart implements IPanelService, IS container.style.borderLeftColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder); const title = this.getTitleArea(); - title.style.borderTopColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder); + if (title) { + title.style.borderTopColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder); + } } - openPanel(id: string, focus?: boolean): Panel { + openPanel(id: string, focus?: boolean): Panel | null { if (this.blockOpeningPanel) { return null; // Workaround against a potential race condition } // First check if panel is hidden and show if so - if (!this.partService.isVisible(Parts.PANEL_PART)) { + if (!this.layoutService.isVisible(Parts.PANEL_PART)) { try { this.blockOpeningPanel = true; - this.partService.setPanelHidden(false); + this.layoutService.setPanelHidden(false); } finally { this.blockOpeningPanel = false; } } - return this.openComposite(id, focus); + return this.openComposite(id, focus) || null; } showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable { return this.compositeBar.showActivity(panelId, badge, clazz); } - private getPanel(panelId: string): IPanelIdentifier { + private getPanel(panelId: string): IPanelIdentifier | undefined { return this.getPanels().filter(p => p.id === panelId).pop(); } getPanels(): PanelDescriptor[] { return Registry.as(PanelExtensions.Panels).getPanels() - .sort((v1, v2) => v1.order - v2.order); + .sort((v1, v2) => typeof v1.order === 'number' && typeof v2.order === 'number' ? v1.order - v2.order : NaN); } getPinnedPanels(): PanelDescriptor[] { @@ -257,7 +252,7 @@ export class PanelPart extends CompositePart implements IPanelService, IS ]; } - getActivePanel(): IPanel { + getActivePanel(): IPanel | null { return this.getActiveComposite(); } @@ -286,41 +281,31 @@ export class PanelPart extends CompositePart implements IPanelService, IS }; } - layout(dimension: Dimension): Dimension[]; - layout(width: number, height: number): void; - layout(dim1: Dimension | number, dim2?: number): Dimension[] | void { - if (!this.partService.isVisible(Parts.PANEL_PART)) { - if (dim1 instanceof Dimension) { - return [dim1]; - } - + layout(width: number, height: number): void { + if (!this.layoutService.isVisible(Parts.PANEL_PART)) { return; } - const { width, height } = dim1 instanceof Dimension ? dim1 : { width: dim1, height: dim2 }; - - if (this.partService.getPanelPosition() === Position.RIGHT) { - // Take into account the 1px border when layouting - this.dimension = new Dimension(width - 1, height); + if (this.layoutService.getPanelPosition() === Position.RIGHT) { + this.dimension = new Dimension(width - 1, height!); // Take into account the 1px border when layouting } else { - this.dimension = new Dimension(width, height); + this.dimension = new Dimension(width, height!); } - const sizes = super.layout(this.dimension.width, this.dimension.height); + // Layout contents + super.layout(this.dimension.width, this.dimension.height); + + // Layout composite bar this.layoutCompositeBar(); - - if (dim1 instanceof Dimension) { - return sizes; - } } private layoutCompositeBar(): void { if (this.dimension) { let availableWidth = this.dimension.width - 40; // take padding into account if (this.toolBar) { - // adjust height for global actions showing - availableWidth = Math.max(PanelPart.MIN_COMPOSITE_BAR_WIDTH, availableWidth - this.getToolbarWidth()); + availableWidth = Math.max(PanelPart.MIN_COMPOSITE_BAR_WIDTH, availableWidth - this.getToolbarWidth()); // adjust height for global actions showing } + this.compositeBar.layout(new Dimension(availableWidth, this.dimension.height)); } } @@ -334,6 +319,7 @@ export class PanelPart extends CompositePart implements IPanelService, IS }; this.compositeActions[compositeId] = compositeActions; } + return compositeActions; } @@ -345,8 +331,10 @@ export class PanelPart extends CompositePart implements IPanelService, IS compositeActions.pinnedAction.dispose(); delete this.compositeActions[compositeId]; } + return true; } + return false; } @@ -355,6 +343,7 @@ export class PanelPart extends CompositePart implements IPanelService, IS if (!activePanel) { return 0; } + return this.toolBar.getItemsWidth(); } @@ -393,30 +382,35 @@ export class PanelPart extends CompositePart implements IPanelService, IS private saveCachedPanels(): void { const state: ICachedPanel[] = []; + const compositeItems = this.compositeBar.getCompositeBarItems(); for (const compositeItem of compositeItems) { state.push({ id: compositeItem.id, pinned: compositeItem.pinned, order: compositeItem.order, visible: compositeItem.visible }); } + this.cachedPanelsValue = JSON.stringify(state); } private getCachedPanels(): ICachedPanel[] { - const storedStates = >JSON.parse(this.cachedPanelsValue); const registeredPanels = this.getPanels(); + + const storedStates = >JSON.parse(this.cachedPanelsValue); const cachedPanels = storedStates.map(c => { const serialized: ICachedPanel = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: undefined, visible: true } : c; const registered = registeredPanels.some(p => p.id === serialized.id); serialized.visible = registered ? isUndefinedOrNull(serialized.visible) ? true : serialized.visible : false; return serialized; }); + return cachedPanels; } - private _cachedPanelsValue: string; + private _cachedPanelsValue: string | null; private get cachedPanelsValue(): string { if (!this._cachedPanelsValue) { this._cachedPanelsValue = this.getStoredCachedPanelsValue(); } + return this._cachedPanelsValue; } @@ -509,3 +503,5 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } }); + +registerSingleton(IPanelService, PanelPart); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index 6139ce3768..dd625d4fdb 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -6,9 +6,9 @@ import 'vs/css!./quickInput'; import { Component } from 'vs/workbench/common/component'; import { IQuickInputService, IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods } from 'vs/platform/quickinput/common/quickInput'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import * as dom from 'vs/base/browser/dom'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND } from 'vs/workbench/common/theme'; @@ -29,10 +29,10 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Button } from 'vs/base/browser/ui/button/button'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import Severity from 'vs/base/common/severity'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ICommandAndKeybindingRule, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickopen'; +import { inQuickOpenContext, InQuickOpenContextKey } from 'vs/workbench/browser/parts/quickopen/quickopen'; import { ActionBar, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import { URI } from 'vs/base/common/uri'; @@ -40,10 +40,10 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { equals } from 'vs/base/common/arrays'; import { TimeoutTimer } from 'vs/base/common/async'; import { getIconClass } from 'vs/workbench/browser/parts/quickinput/quickInputUtils'; -import { AccessibilitySupport } from 'vs/base/common/platform'; -import * as browser from 'vs/base/browser/browser'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; const $ = dom.$; @@ -115,7 +115,7 @@ class QuickInput implements IQuickInput { this.onDidHideEmitter, ]; - private busyDelay: TimeoutTimer; + private busyDelay: TimeoutTimer | null; constructor(protected ui: QuickInputUI) { } @@ -252,21 +252,21 @@ class QuickInput implements IQuickInput { this.ui.leftActionBar.clear(); const leftButtons = this.buttons.filter(button => button === backButton); this.ui.leftActionBar.push(leftButtons.map((button, index) => { - const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => { + const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath!), true, () => { this.onDidTriggerButtonEmitter.fire(button); return Promise.resolve(null); }); - action.tooltip = button.tooltip; + action.tooltip = button.tooltip || ''; return action; }), { icon: true, label: false }); this.ui.rightActionBar.clear(); const rightButtons = this.buttons.filter(button => button !== backButton); this.ui.rightActionBar.push(rightButtons.map((button, index) => { - const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => { + const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath!), true, () => { this.onDidTriggerButtonEmitter.fire(button); return Promise.resolve(null); }); - action.tooltip = button.tooltip; + action.tooltip = button.tooltip || ''; return action; }), { icon: true, label: false }); } @@ -311,21 +311,26 @@ class QuickPick extends QuickInput implements IQuickPi private _value = ''; private _placeholder; private onDidChangeValueEmitter = new Emitter(); - private onDidAcceptEmitter = new Emitter(); + private onDidAcceptEmitter = new Emitter(); private _items: Array = []; private itemsUpdated = false; private _canSelectMany = false; private _matchOnDescription = false; private _matchOnDetail = false; + private _matchOnLabel = true; + private _autoFocusOnList = true; private _activeItems: T[] = []; private activeItemsUpdated = false; - private activeItemsToConfirm: T[] = []; + private activeItemsToConfirm: T[] | null = []; private onDidChangeActiveEmitter = new Emitter(); private _selectedItems: T[] = []; private selectedItemsUpdated = false; - private selectedItemsToConfirm: T[] = []; + private selectedItemsToConfirm: T[] | null = []; private onDidChangeSelectionEmitter = new Emitter(); private onDidTriggerItemButtonEmitter = new Emitter>(); + private _valueSelection: Readonly<[number, number]>; + private valueSelectionUpdated = true; + private _validationMessage: string; quickNavigate: IQuickNavigateConfiguration; @@ -399,6 +404,24 @@ class QuickPick extends QuickInput implements IQuickPi this.update(); } + get matchOnLabel() { + return this._matchOnLabel; + } + + set matchOnLabel(matchOnLabel: boolean) { + this._matchOnLabel = matchOnLabel; + this.update(); + } + + get autoFocusOnList() { + return this._autoFocusOnList; + } + + set autoFocusOnList(autoFocusOnList: boolean) { + this._autoFocusOnList = autoFocusOnList; + this.update(); + } + get activeItems() { return this._activeItems; } @@ -425,10 +448,33 @@ class QuickPick extends QuickInput implements IQuickPi return this.ui.keyMods; } + set valueSelection(valueSelection: Readonly<[number, number]>) { + this._valueSelection = valueSelection; + this.valueSelectionUpdated = true; + this.update(); + } + + get validationMessage() { + return this._validationMessage; + } + + set validationMessage(validationMessage: string) { + this._validationMessage = validationMessage; + this.update(); + } + onDidChangeSelection = this.onDidChangeSelectionEmitter.event; onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event; + private trySelectFirst() { + if (this.autoFocusOnList) { + if (!this.ui.isScreenReaderOptimized() && !this.canSelectMany) { + this.ui.list.focus('First'); + } + } + } + show() { if (!this.visible) { this.visibleDisposables.push( @@ -438,11 +484,14 @@ class QuickPick extends QuickInput implements IQuickPi } this._value = value; this.ui.list.filter(this.ui.inputBox.value); - if (!this.ui.isScreenReaderOptimized() && !this.canSelectMany) { - this.ui.list.focus('First'); - } + this.trySelectFirst(); this.onDidChangeValueEmitter.fire(value); }), + this.ui.inputBox.onMouseDown(event => { + if (!this.autoFocusOnList) { + this.ui.list.clearFocus(); + } + }), this.ui.inputBox.onKeyDown(event => { switch (event.keyCode) { case KeyCode.DownArrow: @@ -529,6 +578,7 @@ class QuickPick extends QuickInput implements IQuickPi this.ui.list.onButtonTriggered(event => this.onDidTriggerItemButtonEmitter.fire(event as IQuickPickItemButtonEvent)), this.registerQuickNavigation() ); + this.valueSelectionUpdated = true; } super.show(); // TODO: Why have show() bubble up while update() trickles down? (Could move setComboboxAccessibility() here.) } @@ -589,6 +639,10 @@ class QuickPick extends QuickInput implements IQuickPi if (this.ui.inputBox.value !== this.value) { this.ui.inputBox.value = this.value; } + if (this.valueSelectionUpdated) { + this.valueSelectionUpdated = false; + this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] }); + } if (this.ui.inputBox.placeholder !== (this.placeholder || '')) { this.ui.inputBox.placeholder = (this.placeholder || ''); } @@ -599,15 +653,13 @@ class QuickPick extends QuickInput implements IQuickPi this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked(); this.ui.visibleCount.setCount(this.ui.list.getVisibleCount()); this.ui.count.setCount(this.ui.list.getCheckedCount()); - if (!this.ui.isScreenReaderOptimized() && !this.canSelectMany) { - this.ui.list.focus('First'); - } + this.trySelectFirst(); } if (this.ui.container.classList.contains('show-checkboxes') !== !!this.canSelectMany) { if (this.canSelectMany) { this.ui.list.clearFocus(); - } else if (!this.ui.isScreenReaderOptimized()) { - this.ui.list.focus('First'); + } else { + this.trySelectFirst(); } } if (this.activeItemsUpdated) { @@ -630,11 +682,19 @@ class QuickPick extends QuickInput implements IQuickPi this.selectedItemsToConfirm = null; } } + if (this.validationMessage) { + this.ui.message.textContent = this.validationMessage; + this.ui.inputBox.showDecoration(Severity.Error); + } else { + this.ui.message.textContent = null; + this.ui.inputBox.showDecoration(Severity.Ignore); + } this.ui.list.matchOnDescription = this.matchOnDescription; this.ui.list.matchOnDetail = this.matchOnDetail; + this.ui.list.matchOnLabel = this.matchOnLabel; this.ui.setComboboxAccessibility(true); this.ui.inputBox.setAttribute('aria-label', QuickPick.INPUT_BOX_ARIA_LABEL); - this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true }); + this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true, message: !!this.validationMessage } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true, message: !!this.validationMessage }); } } @@ -651,7 +711,7 @@ class InputBox extends QuickInput implements IInputBox { private noValidationMessage = InputBox.noPromptMessage; private _validationMessage: string; private onDidValueChangeEmitter = new Emitter(); - private onDidAcceptEmitter = new Emitter(); + private onDidAcceptEmitter = new Emitter(); constructor(ui: QuickInputUI) { super(ui); @@ -768,13 +828,12 @@ class InputBox extends QuickInput implements IInputBox { export class QuickInputService extends Component implements IQuickInputService { - public _serviceBrand: any; + public _serviceBrand: ServiceIdentifier; private static readonly ID = 'workbench.component.quickinput'; private static readonly MAX_WIDTH = 600; // Max total width of quick open widget private idPrefix = 'quickInput_'; // Constant since there is still only one. - private layoutDimensions: dom.Dimension; private titleBar: HTMLElement; private filterContainer: HTMLElement; private visibleCountContainer: HTMLElement; @@ -791,24 +850,26 @@ export class QuickInputService extends Component implements IQuickInputService { private onDidTriggerButtonEmitter = this._register(new Emitter()); private keyMods: Writeable = { ctrlCmd: false, alt: false }; - private controller: QuickInput; + private controller: QuickInput | null = null; constructor( @IEnvironmentService private readonly environmentService: IEnvironmentService, @IConfigurationService private readonly configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IPartService private readonly partService: IPartService, @IQuickOpenService private readonly quickOpenService: IQuickOpenService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, - @IStorageService storageService: IStorageService + @IStorageService storageService: IStorageService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(QuickInputService.ID, themeService, storageService); - this.inQuickOpenContext = new RawContextKey('inQuickOpen', false).bindTo(contextKeyService); + this.inQuickOpenContext = InQuickOpenContextKey.bindTo(contextKeyService); this._register(this.quickOpenService.onShow(() => this.inQuickOpen('quickOpen', true))); this._register(this.quickOpenService.onHide(() => this.inQuickOpen('quickOpen', false))); + this._register(this.layoutService.onLayout(dimension => this.layout(dimension))); this.registerKeyModsListeners(); } @@ -830,7 +891,7 @@ export class QuickInputService extends Component implements IQuickInputService { } private setContextKey(id?: string) { - let key: IContextKey; + let key: IContextKey | undefined; if (id) { key = this.contexts[id]; if (!key) { @@ -860,7 +921,7 @@ export class QuickInputService extends Component implements IQuickInputService { } private registerKeyModsListeners() { - const workbench = this.partService.getWorkbenchElement(); + const workbench = this.layoutService.getWorkbenchElement(); this._register(dom.addDisposableListener(workbench, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { const event = new StandardKeyboardEvent(e); switch (event.keyCode) { @@ -892,7 +953,7 @@ export class QuickInputService extends Component implements IQuickInputService { return; } - const workbench = this.partService.getWorkbenchElement(); + const workbench = this.layoutService.getWorkbenchElement(); const container = dom.append(workbench, $('.quick-input-widget.show-file-icons')); container.tabIndex = -1; container.style.display = 'none'; @@ -971,7 +1032,7 @@ export class QuickInputService extends Component implements IQuickInputService { })); this._register(list.onDidChangeFocus(() => { if (this.comboboxAccessibility) { - this.ui.inputBox.setAttribute('aria-activedescendant', this.ui.list.getActiveDescendant()); + this.ui.inputBox.setAttribute('aria-activedescendant', this.ui.list.getActiveDescendant() || ''); } })); @@ -1060,7 +1121,7 @@ export class QuickInputService extends Component implements IQuickInputService { return; } const input = this.createQuickPick(); - let activeItem: T; + let activeItem: T | undefined; const disposables = [ input, input.onDidAccept(() => { @@ -1114,15 +1175,17 @@ export class QuickInputService extends Component implements IQuickInputService { resolve(undefined); }), ]; - input.canSelectMany = options.canPickMany; + input.canSelectMany = !!options.canPickMany; input.placeholder = options.placeHolder; - input.ignoreFocusOut = options.ignoreFocusLost; - input.matchOnDescription = options.matchOnDescription; - input.matchOnDetail = options.matchOnDetail; + input.ignoreFocusOut = !!options.ignoreFocusLost; + input.matchOnDescription = !!options.matchOnDescription; + input.matchOnDetail = !!options.matchOnDetail; + input.matchOnLabel = (options.matchOnLabel === undefined) || options.matchOnLabel; // default to true + input.autoFocusOnList = (options.autoFocusOnList === undefined) || options.autoFocusOnList; // default to true input.quickNavigate = options.quickNavigate; input.contextKey = options.contextKey; input.busy = true; - Promise.all([picks, options.activeItem]) + Promise.all[], T | undefined>([picks, options.activeItem]) .then(([items, _activeItem]) => { activeItem = _activeItem; input.busy = false; @@ -1162,7 +1225,7 @@ export class QuickInputService extends Component implements IQuickInputService { } validation.then(result => { if (value === validationValue) { - input.validationMessage = result; + input.validationMessage = result || undefined; } }); }), @@ -1189,12 +1252,12 @@ export class QuickInputService extends Component implements IQuickInputService { resolve(undefined); }), ]; - input.value = options.value; + input.value = options.value || ''; input.valueSelection = options.valueSelection; input.prompt = options.prompt; input.placeholder = options.placeHolder; - input.password = options.password; - input.ignoreFocusOut = options.ignoreFocusLost; + input.password = !!options.password; + input.ignoreFocusOut = !!options.ignoreFocusLost; input.show(); }); } @@ -1236,6 +1299,7 @@ export class QuickInputService extends Component implements IQuickInputService { this.ui.list.setElements([]); this.ui.list.matchOnDescription = false; this.ui.list.matchOnDetail = false; + this.ui.list.matchOnLabel = true; this.ui.ignoreFocusOut = false; this.setComboboxAccessibility(false); this.ui.inputBox.removeAttribute('aria-label'); @@ -1259,7 +1323,7 @@ export class QuickInputService extends Component implements IQuickInputService { this.countContainer.style.display = visibilities.count ? '' : 'none'; this.okContainer.style.display = visibilities.ok ? '' : 'none'; this.ui.message.style.display = visibilities.message ? '' : 'none'; - this.ui.list.display(visibilities.list); + this.ui.list.display(!!visibilities.list); this.ui.container.classList[visibilities.checkAll ? 'add' : 'remove']('show-checkboxes'); this.updateLayout(); // TODO } @@ -1271,7 +1335,7 @@ export class QuickInputService extends Component implements IQuickInputService { this.ui.inputBox.setAttribute('role', 'combobox'); this.ui.inputBox.setAttribute('aria-haspopup', 'true'); this.ui.inputBox.setAttribute('aria-autocomplete', 'list'); - this.ui.inputBox.setAttribute('aria-activedescendant', this.ui.list.getActiveDescendant()); + this.ui.inputBox.setAttribute('aria-activedescendant', this.ui.list.getActiveDescendant() || ''); } else { this.ui.inputBox.removeAttribute('role'); this.ui.inputBox.removeAttribute('aria-haspopup'); @@ -1282,7 +1346,7 @@ export class QuickInputService extends Component implements IQuickInputService { } private isScreenReaderOptimized() { - const detected = browser.getAccessibilitySupport() === AccessibilitySupport.Enabled; + const detected = this.accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; const config = this.configurationService.getValue('editor').accessibilitySupport; return config === 'on' || (config === 'auto' && detected); } @@ -1354,17 +1418,16 @@ export class QuickInputService extends Component implements IQuickInputService { } layout(dimension: dom.Dimension): void { - this.layoutDimensions = dimension; this.updateLayout(); } private updateLayout() { - if (this.layoutDimensions && this.ui) { - const titlebarOffset = this.partService.getTitleBarOffset(); + if (this.ui) { + const titlebarOffset = this.layoutService.getTitleBarOffset(); this.ui.container.style.top = `${titlebarOffset}px`; const style = this.ui.container.style; - const width = Math.min(this.layoutDimensions.width * 0.62 /* golden cut */, QuickInputService.MAX_WIDTH); + const width = Math.min(this.layoutService.dimension.width * 0.62 /* golden cut */, QuickInputService.MAX_WIDTH); style.width = width + 'px'; style.marginLeft = '-' + (width / 2) + 'px'; @@ -1400,7 +1463,7 @@ export const QuickPickManyToggle: ICommandAndKeybindingRule = { id: 'workbench.action.quickPickManyToggle', weight: KeybindingWeight.WorkbenchContrib, when: inQuickOpenContext, - primary: undefined, + primary: 0, handler: accessor => { const quickInputService = accessor.get(IQuickInputService); quickInputService.toggle(); @@ -1418,6 +1481,8 @@ export class BackAction extends Action { public run(): Promise { this.quickInputService.back(); - return Promise.resolve(null); + return Promise.resolve(); } } + +registerSingleton(IQuickInputService, QuickInputService, true); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputBox.ts b/src/vs/workbench/browser/parts/quickinput/quickInputBox.ts index f287b01b8a..98ed7666db 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputBox.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputBox.ts @@ -11,6 +11,7 @@ import { ITheme } from 'vs/platform/theme/common/themeService'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import Severity from 'vs/base/common/severity'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; const $ = dom.$; @@ -34,6 +35,12 @@ export class QuickInputBox { }); } + onMouseDown = (handler: (event: StandardMouseEvent) => void): IDisposable => { + return dom.addDisposableListener(this.inputBox.inputElement, dom.EventType.MOUSE_DOWN, (e: MouseEvent) => { + handler(new StandardMouseEvent(e)); + }); + } + onDidChange = (handler: (event: string) => void): IDisposable => { return this.inputBox.onDidChange(handler); } diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts index 32195e5101..cf997f89cf 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -220,6 +220,7 @@ export class QuickInputList { private elementsToIndexes = new Map(); matchOnDescription = false; matchOnDetail = false; + matchOnLabel = true; private _onChangedAllVisibleChecked = new Emitter(); onChangedAllVisibleChecked: Event = this._onChangedAllVisibleChecked.event; private _onChangedCheckedCount = new Emitter(); @@ -397,6 +398,9 @@ export class QuickInputList { this.list.setFocus(items .filter(item => this.elementsToIndexes.has(item)) .map(item => this.elementsToIndexes.get(item)!)); + if (items.length > 0) { + this.list.reveal(this.list.getFocus()[0]); + } } getActiveDescendant() { @@ -468,6 +472,9 @@ export class QuickInputList { } filter(query: string) { + if (!(this.matchOnLabel || this.matchOnDescription || this.matchOnDetail)) { + return; + } query = query.trim(); // Reset filtering @@ -485,7 +492,7 @@ export class QuickInputList { // Filter by value (since we support octicons, use octicon aware fuzzy matching) else { this.elements.forEach(element => { - const labelHighlights = matchesFuzzyOcticonAware(query, parseOcticons(element.saneLabel)) || undefined; + const labelHighlights = this.matchOnLabel ? matchesFuzzyOcticonAware(query, parseOcticons(element.saneLabel)) || undefined : undefined; const descriptionHighlights = this.matchOnDescription ? matchesFuzzyOcticonAware(query, parseOcticons(element.saneDescription || '')) || undefined : undefined; const detailHighlights = this.matchOnDetail ? matchesFuzzyOcticonAware(query, parseOcticons(element.saneDetail || '')) || undefined : undefined; diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts index b94a3f01d1..58bda0bffc 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenActions.ts @@ -54,7 +54,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const registry = Registry.as(ActionExtensions.WorkbenchActions); -const globalQuickOpenKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.KEY_P, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_E], mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_P, secondary: null } }; +const globalQuickOpenKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.KEY_P, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_E], mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_P, secondary: undefined } }; KeybindingsRegistry.registerKeybindingRule({ id: QUICKOPEN_ACTION_ID, @@ -96,6 +96,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ secondary: [globalQuickOpenKeybinding.secondary[0] | KeyMod.Shift], mac: { primary: globalQuickOpenKeybinding.mac.primary | KeyMod.Shift, - secondary: null + secondary: undefined } }); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index 23f682344b..be7b009326 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -25,12 +25,12 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { EditorInput, IWorkbenchEditorConfiguration, IEditorInput } from 'vs/workbench/common/editor'; import { Component } from 'vs/workbench/common/component'; import { Event, Emitter } from 'vs/base/common/event'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { QuickOpenHandler, QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions, EditorQuickOpenEntry, CLOSE_ON_FOCUS_LOST_CONFIG, SEARCH_EDITOR_HISTORY, PRESERVE_INPUT_CONFIG } from 'vs/workbench/browser/quickopen'; import * as errors from 'vs/base/common/errors'; import { IQuickOpenService, IShowOptions } from 'vs/platform/quickOpen/common/quickOpen'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -44,12 +44,13 @@ import { Schemas } from 'vs/base/common/network'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Dimension, addClass } from 'vs/base/browser/dom'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ILabelService } from 'vs/platform/label/common/label'; import { timeout } from 'vs/base/common/async'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; const HELP_PREFIX = '?'; @@ -60,7 +61,7 @@ export class QuickOpenController extends Component implements IQuickOpenService private static readonly MAX_SHORT_RESPONSE_TIME = 500; private static readonly ID = 'workbench.component.quickopen'; - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private readonly _onShow: Emitter = this._register(new Emitter()); get onShow(): Event { return this._onShow.event; } @@ -73,17 +74,16 @@ export class QuickOpenController extends Component implements IQuickOpenService private lastInputValue: string; private lastSubmittedInputValue: string; private quickOpenWidget: QuickOpenWidget; - private dimension: Dimension; private mapResolvedHandlersToPrefix: { [prefix: string]: Promise; } = Object.create(null); private mapContextKeyToContext: { [id: string]: IContextKey; } = Object.create(null); private handlerOnOpenCalled: { [prefix: string]: boolean; } = Object.create(null); private promisesToCompleteOnHide: ValueCallback[] = []; - private previousActiveHandlerDescriptor: QuickOpenHandlerDescriptor; + private previousActiveHandlerDescriptor: QuickOpenHandlerDescriptor | null; private actionProvider = new ContributableActionProvider(); private closeOnFocusLost: boolean; private searchInEditorHistory: boolean; private editorHistoryHandler: EditorHistoryHandler; - private pendingGetResultsInvocation: CancellationTokenSource; + private pendingGetResultsInvocation: CancellationTokenSource | null; constructor( @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @@ -91,7 +91,7 @@ export class QuickOpenController extends Component implements IQuickOpenService @IContextKeyService private readonly contextKeyService: IContextKeyService, @IConfigurationService private readonly configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService @@ -107,8 +107,9 @@ export class QuickOpenController extends Component implements IQuickOpenService private registerListeners(): void { this._register(this.configurationService.onDidChangeConfiguration(() => this.updateConfiguration())); - this._register(this.partService.onTitleBarVisibilityChange(() => this.positionQuickOpenWidget())); + this._register(this.layoutService.onTitleBarVisibilityChange(() => this.positionQuickOpenWidget())); this._register(browser.onDidChangeZoomLevel(() => this.positionQuickOpenWidget())); + this._register(this.layoutService.onLayout(dimension => this.layout(dimension))); } private updateConfiguration(): void { @@ -165,7 +166,7 @@ export class QuickOpenController extends Component implements IQuickOpenService // Telemetry: log that quick open is shown and log the mode const registry = Registry.as(Extensions.Quickopen); - const handlerDescriptor = registry.getQuickOpenHandler(prefix) || registry.getDefaultQuickOpenHandler(); + const handlerDescriptor = (prefix ? registry.getQuickOpenHandler(prefix) : undefined) || registry.getDefaultQuickOpenHandler(); // Trigger onOpen this.resolveHandler(handlerDescriptor); @@ -173,7 +174,7 @@ export class QuickOpenController extends Component implements IQuickOpenService // Create upon first open if (!this.quickOpenWidget) { this.quickOpenWidget = this._register(new QuickOpenWidget( - this.partService.getWorkbenchElement(), + this.layoutService.getWorkbenchElement(), { onOk: () => this.onOk(), onCancel: () => { /* ignore */ }, @@ -195,9 +196,7 @@ export class QuickOpenController extends Component implements IQuickOpenService } // Layout - if (this.dimension) { - this.quickOpenWidget.layout(this.dimension); - } + this.quickOpenWidget.layout(this.layoutService.dimension); // Show quick open with prefix or editor history if (!this.quickOpenWidget.isVisible() || quickNavigateConfiguration) { @@ -206,7 +205,7 @@ export class QuickOpenController extends Component implements IQuickOpenService } else { const editorHistory = this.getEditorHistoryWithGroupLabel(); if (editorHistory.getEntries().length < 2) { - quickNavigateConfiguration = null; // If no entries can be shown, default to normal quick open mode + quickNavigateConfiguration = undefined; // If no entries can be shown, default to normal quick open mode } // Compute auto focus @@ -239,7 +238,7 @@ export class QuickOpenController extends Component implements IQuickOpenService } private positionQuickOpenWidget(): void { - const titlebarOffset = this.partService.getTitleBarOffset(); + const titlebarOffset = this.layoutService.getTitleBarOffset(); if (this.quickOpenWidget) { this.quickOpenWidget.getElement().style.top = `${titlebarOffset}px`; @@ -270,7 +269,10 @@ export class QuickOpenController extends Component implements IQuickOpenService // Complete promises that are waiting while (this.promisesToCompleteOnHide.length) { - this.promisesToCompleteOnHide.pop()(true); + const callback = this.promisesToCompleteOnHide.pop(); + if (callback) { + callback(true); + } } if (reason !== HideReason.FOCUS_LOST) { @@ -297,7 +299,7 @@ export class QuickOpenController extends Component implements IQuickOpenService } private setQuickOpenContextKey(id?: string): void { - let key: IContextKey; + let key: IContextKey | undefined; if (id) { key = this.mapContextKeyToContext[id]; if (!key) { @@ -479,13 +481,13 @@ export class QuickOpenController extends Component implements IQuickOpenService // merge history and default handler results const handlerResults = (result && result.entries) || []; - this.mergeResults(quickOpenModel, handlerResults, resolvedHandler.getGroupLabel()); + this.mergeResults(quickOpenModel, handlerResults, types.withNullAsUndefined(resolvedHandler.getGroupLabel())); } }); }); } - private mergeResults(quickOpenModel: QuickOpenModel, handlerResults: QuickOpenEntry[], groupLabel: string): void { + private mergeResults(quickOpenModel: QuickOpenModel, handlerResults: QuickOpenEntry[], groupLabel: string | undefined): void { // Remove results already showing by checking for a "resource" property const mapEntryToResource = this.mapEntriesToResource(quickOpenModel); @@ -526,7 +528,7 @@ export class QuickOpenController extends Component implements IQuickOpenService const placeHolderLabel = (typeof canRun === 'string') ? canRun : nls.localize('canNotRunPlaceholder', "This quick open handler can not be used in the current context"); const model = new QuickOpenModel([new PlaceholderQuickOpenEntry(placeHolderLabel)], this.actionProvider); - this.showModel(model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), resolvedHandler.getAriaLabel()); + this.showModel(model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); return Promise.resolve(undefined); } @@ -547,9 +549,9 @@ export class QuickOpenController extends Component implements IQuickOpenService if (!token.isCancellationRequested) { if (!result || !result.entries.length) { const model = new QuickOpenModel([new PlaceholderQuickOpenEntry(resolvedHandler.getEmptyLabel(value))]); - this.showModel(model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), resolvedHandler.getAriaLabel()); + this.showModel(model, resolvedHandler.getAutoFocus(value, { model, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); } else { - this.showModel(result, resolvedHandler.getAutoFocus(value, { model: result, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), resolvedHandler.getAriaLabel()); + this.showModel(result, resolvedHandler.getAutoFocus(value, { model: result, quickNavigateConfiguration: this.quickOpenWidget.getQuickNavigateConfiguration() }), types.withNullAsUndefined(resolvedHandler.getAriaLabel())); } } }); @@ -570,15 +572,16 @@ export class QuickOpenController extends Component implements IQuickOpenService } private clearModel(): void { - this.showModel(new QuickOpenModel(), null); + this.showModel(new QuickOpenModel(), undefined); } private mapEntriesToResource(model: QuickOpenModel): { [resource: string]: QuickOpenEntry; } { const entries = model.getEntries(); const mapEntryToPath: { [path: string]: QuickOpenEntry; } = {}; entries.forEach((entry: QuickOpenEntry) => { - if (entry.getResource()) { - mapEntryToPath[entry.getResource().toString()] = entry; + const resource = entry.getResource(); + if (resource) { + mapEntryToPath[resource.toString()] = entry; } }); @@ -620,9 +623,8 @@ export class QuickOpenController extends Component implements IQuickOpenService } layout(dimension: Dimension): void { - this.dimension = dimension; if (this.quickOpenWidget) { - this.quickOpenWidget.layout(this.dimension); + this.quickOpenWidget.layout(dimension); } } } @@ -655,7 +657,7 @@ class EditorHistoryHandler { getResults(searchValue?: string, token?: CancellationToken): QuickOpenEntry[] { // Massage search for scoring - const query = prepareQuery(searchValue); + const query = prepareQuery(searchValue || ''); // Just return all if we are not searching const history = this.historyService.getHistory(); @@ -670,7 +672,7 @@ class EditorHistoryHandler { // For now, only support to match on inputs that provide resource information .filter(input => { - let resource: URI; + let resource: URI | undefined; if (input instanceof EditorInput) { resource = resourceForEditorHistory(input, this.fileService); } else { @@ -690,7 +692,7 @@ class EditorHistoryHandler { return false; } - e.setHighlights(itemScore.labelMatch, itemScore.descriptionMatch); + e.setHighlights(itemScore.labelMatch || [], itemScore.descriptionMatch); return true; }) @@ -707,8 +709,8 @@ class EditorHistoryItemAccessorClass extends QuickOpenItemAccessorClass { super(); } - getItemDescription(entry: QuickOpenEntry): string { - return this.allowMatchOnDescription ? entry.getDescription() : undefined; + getItemDescription(entry: QuickOpenEntry): string | null { + return this.allowMatchOnDescription ? entry.getDescription() : null; } } @@ -721,9 +723,9 @@ export class EditorHistoryEntryGroup extends QuickOpenEntryGroup { export class EditorHistoryEntry extends EditorQuickOpenEntry { private input: IEditorInput | IResourceInput; - private resource: URI; - private label: string; - private description: string; + private resource: URI | undefined; + private label: string | null; + private description: string | null; private dirty: boolean; constructor( @@ -762,7 +764,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { return this.dirty ? 'dirty' : ''; } - getLabel(): string { + getLabel(): string | null { return this.label; } @@ -776,12 +778,12 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { return nls.localize('entryAriaLabel', "{0}, recently opened", this.getLabel()); } - getDescription(): string { + getDescription(): string | null { return this.description; } - getResource(): URI { - return this.resource; + getResource(): URI | null { + return this.resource || null; } getInput(): IEditorInput | IResourceInput { @@ -806,7 +808,7 @@ export class EditorHistoryEntry extends EditorQuickOpenEntry { } } -function resourceForEditorHistory(input: EditorInput, fileService: IFileService): URI { +function resourceForEditorHistory(input: EditorInput, fileService: IFileService): URI | undefined { const resource = input ? input.getResource() : undefined; // For the editor history we only prefer resources that are either untitled or @@ -846,7 +848,7 @@ export class RemoveFromEditorHistoryAction extends Action { return { input: h, - iconClasses: getIconClasses(this.modelService, this.modeService, entry.getResource()), + iconClasses: getIconClasses(this.modelService, this.modeService, entry.getResource() || undefined), label: entry.getLabel(), description: entry.getDescription() }; @@ -859,3 +861,5 @@ export class RemoveFromEditorHistoryAction extends Action { }); } } + +registerSingleton(IQuickOpenService, QuickOpenController, true); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/quickopen/quickopen.ts b/src/vs/workbench/browser/parts/quickopen/quickopen.ts index 105907a193..0589273d31 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickopen.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickopen.ts @@ -8,23 +8,37 @@ import { Action } from 'vs/base/common/actions'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ICommandHandler, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -export const inQuickOpenContext = ContextKeyExpr.has('inQuickOpen'); +const inQuickOpenKey = 'inQuickOpen'; +export const InQuickOpenContextKey = new RawContextKey(inQuickOpenKey, false); +export const inQuickOpenContext = ContextKeyExpr.has(inQuickOpenKey); export const defaultQuickOpenContextKey = 'inFilesPicker'; export const defaultQuickOpenContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(defaultQuickOpenContextKey)); export const QUICKOPEN_ACTION_ID = 'workbench.action.quickOpen'; export const QUICKOPEN_ACION_LABEL = nls.localize('quickOpen', "Go to File..."); -CommandsRegistry.registerCommand(QUICKOPEN_ACTION_ID, function (accessor: ServicesAccessor, prefix: string | null = null) { - const quickOpenService = accessor.get(IQuickOpenService); +CommandsRegistry.registerCommand({ + id: QUICKOPEN_ACTION_ID, + handler: function (accessor: ServicesAccessor, prefix: string | null = null) { + const quickOpenService = accessor.get(IQuickOpenService); - return quickOpenService.show(typeof prefix === 'string' ? prefix : undefined).then(() => { - return undefined; - }); + return quickOpenService.show(typeof prefix === 'string' ? prefix : undefined).then(() => { + return undefined; + }); + }, + description: { + description: `Quick open`, + args: [{ + name: 'prefix', + schema: { + 'type': 'string' + } + }] + } }); export const QUICKOPEN_FOCUS_SECONDARY_ACTION_ID = 'workbench.action.quickOpenPreviousEditor'; diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 5f86046f33..a1d0e0caca 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -12,59 +12,64 @@ import { Viewlet, ViewletRegistry, Extensions as ViewletExtensions, ViewletDescr import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IPartService, Parts, Position as SideBarPosition } from 'vs/workbench/services/part/common/partService'; -import { IViewlet } from 'vs/workbench/common/viewlet'; +import { IWorkbenchLayoutService, Parts, Position as SideBarPosition } from 'vs/workbench/services/layout/browser/layoutService'; +import { IViewlet, SidebarFocusContext, ActiveViewletContext } from 'vs/workbench/common/viewlet'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER } from 'vs/workbench/common/theme'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { EventType, addDisposableListener, trackFocus, Dimension } from 'vs/base/browser/dom'; +import { EventType, addDisposableListener, trackFocus } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ISerializableView } from 'vs/base/browser/ui/grid/grid'; import { LayoutPriority } from 'vs/base/browser/ui/grid/gridview'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -export const SidebarFocusContext = new RawContextKey('sideBarFocus', false); -export const ActiveViewletContext = new RawContextKey('activeViewlet', ''); +export class SidebarPart extends CompositePart implements IViewletService { -export class SidebarPart extends CompositePart implements ISerializableView, IViewletService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; static readonly activeViewletSettingsKey = 'workbench.sidebar.activeviewletid'; + //#region IView + + readonly minimumWidth: number = 170; + readonly maximumWidth: number = Number.POSITIVE_INFINITY; + readonly minimumHeight: number = 0; + readonly maximumHeight: number = Number.POSITIVE_INFINITY; + + readonly snapSize: number = 50; + readonly priority: LayoutPriority = LayoutPriority.Low; + + //#endregion + + get onDidViewletRegister(): Event { return >this.viewletRegistry.onDidRegister; } + + private _onDidViewletDeregister = this._register(new Emitter()); + get onDidViewletDeregister(): Event { return this._onDidViewletDeregister.event; } + + get onDidViewletOpen(): Event { return Event.map(this.onDidCompositeOpen.event, compositeEvent => compositeEvent.composite); } + get onDidViewletClose(): Event { return this.onDidCompositeClose.event as Event; } + private viewletRegistry: ViewletRegistry; private sideBarFocusContextKey: IContextKey; private activeViewletContextKey: IContextKey; private blockOpeningViewlet: boolean; - private _onDidViewletDeregister = this._register(new Emitter()); - - element: HTMLElement; - minimumWidth: number = 170; - maximumWidth: number = Number.POSITIVE_INFINITY; - minimumHeight: number = 0; - maximumHeight: number = Number.POSITIVE_INFINITY; - snapSize: number = 50; - priority: LayoutPriority = LayoutPriority.Low; - - private _onDidChange = new Emitter<{ width: number; height: number; }>(); - readonly onDidChange = this._onDidChange.event; constructor( - id: string, @INotificationService notificationService: INotificationService, @IStorageService storageService: IStorageService, @ITelemetryService telemetryService: ITelemetryService, @IContextMenuService contextMenuService: IContextMenuService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @@ -76,7 +81,7 @@ export class SidebarPart extends CompositePart implements ISerializable storageService, telemetryService, contextMenuService, - partService, + layoutService, keybindingService, instantiationService, themeService, @@ -86,56 +91,51 @@ export class SidebarPart extends CompositePart implements ISerializable 'sideBar', 'viewlet', SIDE_BAR_TITLE_FOREGROUND, - id, + Parts.SIDEBAR_PART, { hasTitle: true, borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0 } ); - this.sideBarFocusContextKey = SidebarFocusContext.bindTo(contextKeyService); this.viewletRegistry = Registry.as(ViewletExtensions.Viewlets); + this.sideBarFocusContextKey = SidebarFocusContext.bindTo(contextKeyService); this.activeViewletContextKey = ActiveViewletContext.bindTo(contextKeyService); + this.registerListeners(); + } + + private registerListeners(): void { + + // Viewlet open this._register(this.onDidViewletOpen(viewlet => { this.activeViewletContextKey.set(viewlet.getId()); })); + + // Viewlet close this._register(this.onDidViewletClose(viewlet => { if (this.activeViewletContextKey.get() === viewlet.getId()) { this.activeViewletContextKey.reset(); } })); + + // Viewlet deregister this._register(this.registry.onDidDeregister(async (viewletDescriptor: ViewletDescriptor) => { if (this.getActiveViewlet().getId() === viewletDescriptor.id) { await this.openViewlet(this.getDefaultViewletId()); } + this.removeComposite(viewletDescriptor.id); this._onDidViewletDeregister.fire(viewletDescriptor); })); } - get onDidViewletRegister(): Event { return >this.viewletRegistry.onDidRegister; } - get onDidViewletDeregister(): Event { return this._onDidViewletDeregister.event; } - - get onDidViewletOpen(): Event { - return Event.map(this._onDidCompositeOpen.event, compositeEvent => compositeEvent.composite); - } - - get onDidViewletClose(): Event { - return this._onDidCompositeClose.event as Event; - } - create(parent: HTMLElement): void { this.element = parent; super.create(parent); - const focusTracker = trackFocus(parent); - - focusTracker.onDidFocus(() => { - this.sideBarFocusContextKey.set(true); - }); - focusTracker.onDidBlur(() => { - this.sideBarFocusContextKey.set(false); - }); + const focusTracker = this._register(trackFocus(parent)); + this._register(focusTracker.onDidFocus(() => this.sideBarFocusContextKey.set(true))); + this._register(focusTracker.onDidBlur(() => this.sideBarFocusContextKey.set(false))); } createTitleArea(parent: HTMLElement): HTMLElement { @@ -158,7 +158,7 @@ export class SidebarPart extends CompositePart implements ISerializable container.style.color = this.getColor(SIDE_BAR_FOREGROUND); const borderColor = this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder); - const isPositionLeft = this.partService.getSideBarPosition() === SideBarPosition.LEFT; + const isPositionLeft = this.layoutService.getSideBarPosition() === SideBarPosition.LEFT; container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : null; container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : null; container.style.borderRightColor = isPositionLeft ? borderColor : null; @@ -167,22 +167,12 @@ export class SidebarPart extends CompositePart implements ISerializable container.style.borderLeftColor = !isPositionLeft ? borderColor : null; } - layout(dimension: Dimension): Dimension[]; - layout(width: number, height: number): void; - layout(dim1: Dimension | number, dim2?: number): Dimension[] | void { - if (!this.partService.isVisible(Parts.SIDEBAR_PART)) { - if (dim1 instanceof Dimension) { - return [dim1]; - } - + layout(width: number, height: number): void { + if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) { return; } - if (dim1 instanceof Dimension) { - return super.layout(dim1); - } - - super.layout(dim1, dim2!); + super.layout(width, height); } // Viewlet service @@ -199,15 +189,17 @@ export class SidebarPart extends CompositePart implements ISerializable this.hideActiveComposite(); } - openViewlet(id: string, focus?: boolean): Promise { - if (this.getViewlet(id)) { + openViewlet(id: string | undefined, focus?: boolean): Promise { + if (typeof id === 'string' && this.getViewlet(id)) { return Promise.resolve(this.doOpenViewlet(id, focus)); } + return this.extensionService.whenInstalledExtensionsRegistered() .then(() => { - if (this.getViewlet(id)) { + if (typeof id === 'string' && this.getViewlet(id)) { return this.doOpenViewlet(id, focus); } + return null; }); } @@ -231,10 +223,10 @@ export class SidebarPart extends CompositePart implements ISerializable } // First check if sidebar is hidden and show if so - if (!this.partService.isVisible(Parts.SIDEBAR_PART)) { + if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) { try { this.blockOpeningViewlet = true; - this.partService.setSideBarHidden(false); + this.layoutService.setSideBarHidden(false); } finally { this.blockOpeningViewlet = false; } @@ -244,7 +236,7 @@ export class SidebarPart extends CompositePart implements ISerializable } protected getTitleAreaDropDownAnchorAlignment(): AnchorAlignment { - return this.partService.getSideBarPosition() === SideBarPosition.LEFT ? AnchorAlignment.LEFT : AnchorAlignment.RIGHT; + return this.layoutService.getSideBarPosition() === SideBarPosition.LEFT ? AnchorAlignment.LEFT : AnchorAlignment.RIGHT; } private onTitleAreaContextMenu(event: StandardMouseEvent): void { @@ -279,7 +271,7 @@ class FocusSideBarAction extends Action { id: string, label: string, @IViewletService private readonly viewletService: IViewletService, - @IPartService private readonly partService: IPartService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(id, label); } @@ -287,8 +279,8 @@ class FocusSideBarAction extends Action { run(): Promise { // Show side bar - if (!this.partService.isVisible(Parts.SIDEBAR_PART)) { - return Promise.resolve(this.partService.setSideBarHidden(false)); + if (!this.layoutService.isVisible(Parts.SIDEBAR_PART)) { + return Promise.resolve(this.layoutService.setSideBarHidden(false)); } // Focus into active viewlet @@ -305,3 +297,5 @@ const registry = Registry.as(ActionExtensions.Workbenc registry.registerWorkbenchAction(new SyncActionDescriptor(FocusSideBarAction, FocusSideBarAction.ID, FocusSideBarAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_0 }), 'View: Focus into Side Bar', nls.localize('viewCategory', "View")); + +registerSingleton(IViewletService, SidebarPart); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css index c1c57bf75d..c82a29fcb0 100644 --- a/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css +++ b/src/vs/workbench/browser/parts/statusbar/media/statusbarpart.css @@ -33,25 +33,30 @@ border-right: 5px solid transparent; } -.monaco-workbench .part.statusbar > .statusbar-item.left > :first-child { - margin-right: 5px; +.monaco-workbench .part.statusbar > .statusbar-item.left > :first-child, +.monaco-workbench .part.statusbar > .statusbar-item.right > :first-child +{ + margin-right: 3px; + margin-left: 3px; } .monaco-workbench .part.statusbar > .statusbar-item.right { float: right; } -.monaco-workbench .part.statusbar > .statusbar-item.right > :first-child { - margin-left: 5px; -} - /* adding padding to the most left status bar item */ .monaco-workbench .part.statusbar > .statusbar-item.left:first-child, .monaco-workbench .part.statusbar > .statusbar-item.right + .statusbar-item.left { - padding-left: 10px; + padding-left: 7px; +} +.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.left:first-child, .monaco-workbench .part.statusbar > .statusbar-item.right + .statusbar-item.has-background-color.left { + padding-right: 7px; /* expand padding if background color is configured for the status bar entry to make it look centered properly */ } /* adding padding to the most right status bar item */ .monaco-workbench .part.statusbar > .statusbar-item.right:first-child { - padding-right: 10px; + padding-right: 7px; +} +.monaco-workbench .part.statusbar > .statusbar-item.has-background-color.right:first-child { + padding-left: 7px; /* expand padding if background color is configured for the status bar entry to make it look centered properly */ } .monaco-workbench .part.statusbar > .statusbar-item a { diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 98f817ac15..e81b39f090 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -6,60 +6,57 @@ import 'vs/css!./media/statusbarpart'; import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { Registry } from 'vs/platform/registry/common/platform'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Part } from 'vs/workbench/browser/part'; import { IStatusbarRegistry, Extensions, IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Action } from 'vs/base/common/actions'; -import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; +import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector, ThemeColor } from 'vs/platform/theme/common/themeService'; import { STATUS_BAR_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND, STATUS_BAR_BORDER, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER } from 'vs/workbench/common/theme'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { isThemeColor } from 'vs/editor/common/editorCommon'; import { Color } from 'vs/base/common/color'; -import { addClass, EventHelper, createStyleSheet, addDisposableListener, Dimension } from 'vs/base/browser/dom'; +import { addClass, EventHelper, createStyleSheet, addDisposableListener } from 'vs/base/browser/dom'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { Emitter } from 'vs/base/common/event'; -import { ISerializableView } from 'vs/base/browser/ui/grid/grid'; -import { Parts } from 'vs/workbench/services/part/common/partService'; +import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +export class StatusbarPart extends Part implements IStatusbarService { -export class StatusbarPart extends Part implements IStatusbarService, ISerializableView { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private static readonly PRIORITY_PROP = 'statusbar-entry-priority'; private static readonly ALIGNMENT_PROP = 'statusbar-entry-alignment'; - element: HTMLElement; + //#region IView + + readonly minimumWidth: number = 0; + readonly maximumWidth: number = Number.POSITIVE_INFINITY; + readonly minimumHeight: number = 22; + readonly maximumHeight: number = 22; + + //#endregion + private statusMsgDispose: IDisposable; - - - minimumWidth: number = 0; - maximumWidth: number = Number.POSITIVE_INFINITY; - minimumHeight: number = 22; - maximumHeight: number = 22; - - private _onDidChange = new Emitter<{ width: number; height: number; }>(); - readonly onDidChange = this._onDidChange.event; - private styleElement: HTMLStyleElement; constructor( - id: string, @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IStorageService storageService: IStorageService + @IStorageService storageService: IStorageService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(id, { hasTitle: false }, themeService, storageService); + super(Parts.STATUSBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); this.registerListeners(); } @@ -152,7 +149,7 @@ export class StatusbarPart extends Part implements IStatusbarService, ISerializa return this.element; } - protected updateStyles(): void { + updateStyles(): void { super.updateStyles(); const container = this.getContainer(); @@ -233,14 +230,8 @@ export class StatusbarPart extends Part implements IStatusbarService, ISerializa return dispose; } - layout(dimension: Dimension): Dimension[]; - layout(width: number, height: number): void; - layout(dim1: Dimension | number, dim2?: number): Dimension[] | void { - if (dim1 instanceof Dimension) { - return super.layout(dim1); - } else { - super.layout(new Dimension(dim1, dim2!)); - } + layout(width: number, height: number): void { + super.layoutContents(width, height); } toJSON(): object { @@ -292,18 +283,13 @@ class StatusBarEntryItem implements IStatusbarItem { textContainer.title = this.entry.tooltip; } - // Color - let color = this.entry.color; - if (color) { - if (isThemeColor(color)) { - let colorId = color.id; - color = (this.themeService.getTheme().getColor(colorId) || Color.transparent).toString(); - toDispose.push(this.themeService.onThemeChange(theme => { - let colorValue = (this.themeService.getTheme().getColor(colorId) || Color.transparent).toString(); - textContainer.style.color = colorValue; - })); - } - textContainer.style.color = color; + // Color (only applies to text container) + toDispose.push(this.applyColor(textContainer, this.entry.color)); + + // Background Color (applies to parent element to fully fill container) + if (this.entry.backgroundColor) { + toDispose.push(this.applyColor(el, this.entry.backgroundColor, true)); + addClass(el, 'has-background-color'); } // Context Menu @@ -328,6 +314,24 @@ class StatusBarEntryItem implements IStatusbarItem { }; } + private applyColor(container: HTMLElement, color: string | ThemeColor | undefined, isBackground?: boolean): IDisposable { + const disposable: IDisposable[] = []; + + if (color) { + if (isThemeColor(color)) { + const colorId = color.id; + color = (this.themeService.getTheme().getColor(colorId) || Color.transparent).toString(); + disposable.push(this.themeService.onThemeChange(theme => { + const colorValue = (theme.getColor(colorId) || Color.transparent).toString(); + isBackground ? container.style.backgroundColor = colorValue : container.style.color = colorValue; + })); + } + isBackground ? container.style.backgroundColor = color : container.style.color = color; + } + + return combinedDisposable(disposable); + } + private executeCommand(id: string, args?: any[]) { args = args || []; @@ -382,3 +386,5 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { collector.addRule(`.monaco-workbench .part.statusbar > .statusbar-item a.status-bar-info:hover { background-color: ${statusBarProminentItemHoverBackground}; }`); } }); + +registerSingleton(IStatusbarService, StatusbarPart); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 6e6b2d172f..7413f2e16f 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -42,8 +42,8 @@ /* Windows/Linux: Rules for custom title (icon, window controls) */ -.windows > .monaco-workbench .part.titlebar, -.linux > .monaco-workbench .part.titlebar { +.monaco-workbench.windows .part.titlebar, +.monaco-workbench.linux .part.titlebar { padding: 0; height: 30px; line-height: 30px; @@ -51,17 +51,17 @@ overflow: visible; } -.windows > .monaco-workbench .part.titlebar > .window-title, -.linux > .monaco-workbench .part.titlebar > .window-title { +.monaco-workbench.windows .part.titlebar > .window-title, +.monaco-workbench.linux .part.titlebar > .window-title { cursor: default; } -.linux > .monaco-workbench .part.titlebar > .window-title { +.monaco-workbench.linux .part.titlebar > .window-title { font-size: inherit; } -.windows > .monaco-workbench .part.titlebar > .resizer, -.linux > .monaco-workbench .part.titlebar > .resizer { +.monaco-workbench.windows .part.titlebar > .resizer, +.monaco-workbench.linux .part.titlebar > .resizer { -webkit-app-region: no-drag; position: absolute; top: 0; @@ -69,17 +69,16 @@ height: 20%; } -.windows > .monaco-workbench.fullscreen .part.titlebar > .resizer, -.linux > .monaco-workbench.fullscreen .part.titlebar > .resizer { +.monaco-workbench.windows.fullscreen .part.titlebar > .resizer, +.monaco-workbench.linux.fullscreen .part.titlebar > .resizer { display: none; } - .monaco-workbench .part.titlebar > .window-appicon { width: 35px; height: 100%; position: relative; - z-index: 99; + z-index: 3000; background-image: url('code-icon.svg'); background-repeat: no-repeat; background-position: center center; @@ -97,7 +96,7 @@ flex-shrink: 0; text-align: center; position: relative; - z-index: 99; + z-index: 3000; -webkit-app-region: no-drag; height: 100%; width: 138px; diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index c18f6ac64d..f8b4ffdcb6 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { IMenubarMenu, IMenubarMenuItemAction, IMenubarMenuItemSubmenu, IMenubarKeybinding, IMenubarService, IMenubarData, MenubarMenuItem } from 'vs/platform/menubar/common/menubar'; import { IMenuService, MenuId, IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { registerThemingParticipant, ITheme, ICssStyleCollector, IThemeService } from 'vs/platform/theme/common/themeService'; -import { IWindowService, MenuBarVisibility, IWindowsService, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { IWindowService, MenuBarVisibility, IWindowsService, getTitleBarStyle, URIType } from 'vs/platform/windows/common/windows'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction, Action } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -17,8 +17,7 @@ import { isMacintosh, isLinux } from 'vs/base/common/platform'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IRecentlyOpened } from 'vs/platform/history/common/history'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { IRecentlyOpened, isRecentFolder, IRecent, isRecentWorkspace } from 'vs/platform/history/common/history'; import { RunOnceScheduler } from 'vs/base/common/async'; import { MENUBAR_SELECTION_FOREGROUND, MENUBAR_SELECTION_BACKGROUND, MENUBAR_SELECTION_BORDER, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; import { URI } from 'vs/base/common/uri'; @@ -32,6 +31,9 @@ import { MenuBar } from 'vs/base/browser/ui/menu/menubar'; import { SubmenuAction } from 'vs/base/browser/ui/menu/menu'; import { attachMenuStyler } from 'vs/platform/theme/common/styler'; import { assign } from 'vs/base/common/objects'; +import { mnemonicMenuLabel, unmnemonicLabel } from 'vs/base/common/labels'; +import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { withNullAsUndefined } from 'vs/base/common/types'; export class MenubarControl extends Disposable { @@ -57,7 +59,7 @@ export class MenubarControl extends Disposable { // 'Terminal': IMenu; 'Window'?: IMenu; 'Help': IMenu; - // [index: string]: IMenu; + // [index: string]: IMenu | undefined; }; // {{SQL CARBON EDIT}} - Disable unused menus @@ -76,9 +78,10 @@ export class MenubarControl extends Disposable { private menuUpdater: RunOnceScheduler; private container: HTMLElement; private recentlyOpened: IRecentlyOpened; + private alwaysOnMnemonics: boolean; - private _onVisibilityChange: Emitter; - private _onFocusStateChange: Emitter; + private readonly _onVisibilityChange: Emitter; + private readonly _onFocusStateChange: Emitter; private static MAX_MENU_RECENT_ENTRIES = 10; @@ -96,7 +99,8 @@ export class MenubarControl extends Disposable { @IStorageService private readonly storageService: IStorageService, @INotificationService private readonly notificationService: INotificationService, @IPreferencesService private readonly preferencesService: IPreferencesService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService ) { super(); @@ -120,8 +124,11 @@ export class MenubarControl extends Disposable { this.menuUpdater = this._register(new RunOnceScheduler(() => this.doUpdateMenubar(false), 200)); if (isMacintosh || this.currentTitlebarStyleSetting !== 'custom') { - for (let topLevelMenuName of Object.keys(this.topLevelMenus)) { - this._register(this.topLevelMenus[topLevelMenuName].onDidChange(() => this.updateMenubar())); + for (const topLevelMenuName of Object.keys(this.topLevelMenus)) { + const menu = this.topLevelMenus[topLevelMenuName]; + if (menu) { + this._register(menu.onDidChange(() => this.updateMenubar())); + } } this.doUpdateMenubar(true); @@ -134,7 +141,9 @@ export class MenubarControl extends Disposable { this.recentlyOpened = recentlyOpened; }); - this.detectAndRecommendCustomTitlebar(); + this.notifyExistingLinuxUser(); + + this.notifyUserOfCustomMenubarAccessibility(); this.registerListeners(); } @@ -194,8 +203,8 @@ export class MenubarControl extends Disposable { this.updateMenubar(); } - if (event.affectsConfiguration('window.menuBarVisibility')) { - this.detectAndRecommendCustomTitlebar(); + if (event.affectsConfiguration('editor.accessibilitySupport')) { + this.notifyUserOfCustomMenubarAccessibility(); } } @@ -206,41 +215,63 @@ export class MenubarControl extends Disposable { }); } - private detectAndRecommendCustomTitlebar(): void { - // {{SQL CARBON EDIT}} - Disable the custom titlebar recommendation - // if (!isLinux) { - // return; - // } + // TODO@sbatten remove after feb19 + private notifyExistingLinuxUser(): void { + /*// {{SQL CARBON EDIT}} - Disable the custom titlebar recommendation + if (!isLinux) { + return; + } - // if (!this.storageService.getBoolean('menubar/electronFixRecommended', StorageScope.GLOBAL, false)) { - // if (this.currentMenubarVisibility === 'hidden' || this.currentTitlebarStyleSetting === 'custom') { - // // Issue will not arise for user, abort notification - // return; - // } + const isNewUser = !this.storageService.get('telemetry.lastSessionDate', StorageScope.GLOBAL); + const hasBeenNotified = this.storageService.getBoolean('menubar/linuxTitlebarRevertNotified', StorageScope.GLOBAL, false); + const titleBarConfiguration = this.configurationService.inspect('window.titleBarStyle'); + const customShown = getTitleBarStyle(this.configurationService, this.environmentService) === 'custom'; - // const message = nls.localize('menubar.electronFixRecommendation', "If you experience hard to read text in the menu bar, we recommend trying out the custom title bar."); - // this.notificationService.prompt(Severity.Info, message, [ - // { - // label: nls.localize('goToSetting', "Open Settings"), - // run: () => { - // return this.preferencesService.openGlobalSettings(undefined, { query: 'window.titleBarStyle' }); - // } - // }, - // { - // label: nls.localize('moreInfo', "More Info"), - // run: () => { - // window.open('https://go.microsoft.com/fwlink/?linkid=2038566'); - // } - // }, - // { - // label: nls.localize('neverShowAgain', "Don't Show Again"), - // run: () => { - // this.storageService.store('menubar/electronFixRecommended', true, StorageScope.GLOBAL); - // } - // } - // ]); - // } - // {{SQL CARBON EDIT}} - End + if (!hasBeenNotified) { + this.storageService.store('menubar/linuxTitlebarRevertNotified', true, StorageScope.GLOBAL); + } + + if (isNewUser || hasBeenNotified || (titleBarConfiguration && titleBarConfiguration.user) || customShown) { + return; + } + + const message = nls.localize('menubar.linuxTitlebarRevertNotification', "We have updated the default title bar on Linux to use the native setting. If you prefer, you can go back to the custom setting. More information is available in our [online documentation](https://go.microsoft.com/fwlink/?linkid=2074137)."); + this.notificationService.prompt(Severity.Info, message, [ + { + label: nls.localize('goToSetting', "Open Settings"), + run: () => { + return this.preferencesService.openGlobalSettings(undefined, { query: 'window.titleBarStyle' }); + } + } + ]); + */ + } + + private notifyUserOfCustomMenubarAccessibility(): void { + if (isMacintosh) { + return; + } + + const hasBeenNotified = this.storageService.getBoolean('menubar/accessibleMenubarNotified', StorageScope.GLOBAL, false); + const usingCustomMenubar = getTitleBarStyle(this.configurationService, this.environmentService) === 'custom'; + const detected = this.accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; + const config = this.configurationService.getValue('editor.accessibilitySupport'); + + if (hasBeenNotified || usingCustomMenubar || !(config === 'on' || (config === 'auto' && detected))) { + return; + } + + const message = nls.localize('menubar.customTitlebarAccessibilityNotification', "Accessibility support is enabled for you. For the most accessible experience, we recommend the custom title bar style."); + this.notificationService.prompt(Severity.Info, message, [ + { + label: nls.localize('goToSetting', "Open Settings"), + run: () => { + return this.preferencesService.openGlobalSettings(undefined, { query: 'window.titleBarStyle' }); + } + } + ]); + + this.storageService.store('menubar/accessibleMenubarNotified', true, StorageScope.GLOBAL); } private registerListeners(): void { @@ -322,26 +353,34 @@ export class MenubarControl extends Disposable { return label; } - private createOpenRecentMenuAction(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI, commandId: string, isFile: boolean): IAction & { uri: URI } { + private createOpenRecentMenuAction(recent: IRecent, isFile: boolean): IAction & { uri: URI } { let label: string; let uri: URI; + let commandId: string; + let typeHint: URIType | undefined; - if (isSingleFolderWorkspaceIdentifier(workspace) && !isFile) { - label = this.labelService.getWorkspaceLabel(workspace, { verbose: true }); - uri = workspace; - } else if (isWorkspaceIdentifier(workspace)) { - label = this.labelService.getWorkspaceLabel(workspace, { verbose: true }); - uri = URI.file(workspace.configPath); + if (isRecentFolder(recent)) { + uri = recent.folderUri; + label = recent.label || this.labelService.getWorkspaceLabel(uri, { verbose: true }); + commandId = 'openRecentFolder'; + typeHint = 'folder'; + } else if (isRecentWorkspace(recent)) { + uri = recent.workspace.configPath; + label = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true }); + commandId = 'openRecentWorkspace'; + typeHint = 'file'; } else { - uri = workspace; - label = this.labelService.getUriLabel(uri); + uri = recent.fileUri; + label = recent.label || this.labelService.getUriLabel(uri); + commandId = 'openRecentFile'; + typeHint = 'file'; } - const ret: IAction = new Action(commandId, label, undefined, undefined, (event) => { + const ret: IAction = new Action(commandId, unmnemonicLabel(label), undefined, undefined, (event) => { const openInNewWindow = event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey))); - return this.windowService.openWindow([uri], { + return this.windowService.openWindow([{ uri, typeHint }], { forceNewWindow: openInNewWindow, forceOpenWorkspaceAsFile: isFile }); @@ -362,7 +401,7 @@ export class MenubarControl extends Disposable { if (workspaces.length > 0) { for (let i = 0; i < MenubarControl.MAX_MENU_RECENT_ENTRIES && i < workspaces.length; i++) { - result.push(this.createOpenRecentMenuAction(workspaces[i], 'openRecentWorkspace', false)); + result.push(this.createOpenRecentMenuAction(workspaces[i], false)); } result.push(new Separator()); @@ -370,7 +409,7 @@ export class MenubarControl extends Disposable { if (files.length > 0) { for (let i = 0; i < MenubarControl.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) { - result.push(this.createOpenRecentMenuAction(files[i], 'openRecentFile', false)); + result.push(this.createOpenRecentMenuAction(files[i], true)); } result.push(new Separator()); @@ -408,7 +447,7 @@ export class MenubarControl extends Disposable { return new Action('update.checking', nls.localize('checkingForUpdates', "Checking For Updates..."), undefined, false); case StateType.AvailableForDownload: - return new Action('update.downloadNow', nls.localize({ key: 'download now', comment: ['&& denotes a mnemonic'] }, "D&&ownload Now"), null, true, () => + return new Action('update.downloadNow', nls.localize({ key: 'download now', comment: ['&& denotes a mnemonic'] }, "D&&ownload Now"), undefined, true, () => this.updateService.downloadUpdate()); case StateType.Downloading: @@ -437,6 +476,7 @@ export class MenubarControl extends Disposable { if (!isMacintosh) { const updateAction = this.getUpdateAction(); if (updateAction) { + updateAction.label = mnemonicMenuLabel(updateAction.label); target.push(updateAction); target.push(new Separator()); } @@ -459,12 +499,17 @@ export class MenubarControl extends Disposable { } )); + this.accessibilityService.alwaysUnderlineAccessKeys().then(val => { + this.alwaysOnMnemonics = val; + this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics }); + }); + this._register(this.menubar.onFocusStateChange(e => this._onFocusStateChange.fire(e))); this._register(this.menubar.onVisibilityChange(e => this._onVisibilityChange.fire(e))); this._register(attachMenuStyler(this.menubar, this.themeService)); } else { - this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id) }); + this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics }); } // Update the menu actions @@ -480,10 +525,10 @@ export class MenubarControl extends Disposable { const submenu = this.menuService.createMenu(action.item.submenu, this.contextKeyService); const submenuActions: SubmenuAction[] = []; updateActions(submenu, submenuActions); - target.push(new SubmenuAction(action.label, submenuActions)); + target.push(new SubmenuAction(mnemonicMenuLabel(action.label), submenuActions)); submenu.dispose(); } else { - action.label = this.calculateActionLabel(action); + action.label = mnemonicMenuLabel(this.calculateActionLabel(action)); target.push(action); } } @@ -494,28 +539,30 @@ export class MenubarControl extends Disposable { target.pop(); }; - for (let title of Object.keys(this.topLevelMenus)) { + for (const title of Object.keys(this.topLevelMenus)) { const menu = this.topLevelMenus[title]; - if (firstTime) { + if (firstTime && menu) { this._register(menu.onDidChange(() => { - const actions = []; + const actions: IAction[] = []; updateActions(menu, actions); - this.menubar.updateMenu({ actions: actions, label: this.topLevelTitles[title] }); + this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); })); } - const actions = []; - updateActions(menu, actions); + const actions: IAction[] = []; + if (menu) { + updateActions(menu, actions); + } if (!firstTime) { - this.menubar.updateMenu({ actions: actions, label: this.topLevelTitles[title] }); + this.menubar.updateMenu({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); } else { - this.menubar.push({ actions: actions, label: this.topLevelTitles[title] }); + this.menubar.push({ actions: actions, label: mnemonicMenuLabel(this.topLevelTitles[title]) }); } } } - private getMenubarKeybinding(id: string): IMenubarKeybinding { + private getMenubarKeybinding(id: string): IMenubarKeybinding | undefined { const binding = this.keybindingService.lookupKeybinding(id); if (!binding) { return undefined; @@ -524,19 +571,19 @@ export class MenubarControl extends Disposable { // first try to resolve a native accelerator const electronAccelerator = binding.getElectronAccelerator(); if (electronAccelerator) { - return { label: electronAccelerator, userSettingsLabel: binding.getUserSettingsLabel() }; + return { label: electronAccelerator, userSettingsLabel: withNullAsUndefined(binding.getUserSettingsLabel()) }; } // we need this fallback to support keybindings that cannot show in electron menus (e.g. chords) const acceleratorLabel = binding.getLabel(); if (acceleratorLabel) { - return { label: acceleratorLabel, isNative: false, userSettingsLabel: binding.getUserSettingsLabel() }; + return { label: acceleratorLabel, isNative: false, userSettingsLabel: withNullAsUndefined(binding.getUserSettingsLabel()) }; } - return null; + return undefined; } - private populateMenuItems(menu: IMenu, menuToPopulate: IMenubarMenu, keybindings: { [id: string]: IMenubarKeybinding }) { + private populateMenuItems(menu: IMenu, menuToPopulate: IMenubarMenu, keybindings: { [id: string]: IMenubarKeybinding | undefined }) { let groups = menu.getActions(); for (let group of groups) { const [, actions] = group; @@ -604,15 +651,17 @@ export class MenubarControl extends Disposable { } menubarData.keybindings = this.getAdditionalKeybindings(); - for (let topLevelMenuName of Object.keys(this.topLevelMenus)) { + for (const topLevelMenuName of Object.keys(this.topLevelMenus)) { const menu = this.topLevelMenus[topLevelMenuName]; - let menubarMenu: IMenubarMenu = { items: [] }; - this.populateMenuItems(menu, menubarMenu, menubarData.keybindings); - if (menubarMenu.items.length === 0) { - // Menus are incomplete - return false; + if (menu) { + const menubarMenu: IMenubarMenu = { items: [] }; + this.populateMenuItems(menu, menubarMenu, menubarData.keybindings); + if (menubarMenu.items.length === 0) { + // Menus are incomplete + return false; + } + menubarData.menus[topLevelMenuName] = menubarMenu; } - menubarData.menus[topLevelMenuName] = menubarMenu; } return true; @@ -632,7 +681,7 @@ export class MenubarControl extends Disposable { } if (this.menubar) { - this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id) }); + this.menubar.update({ enableMnemonics: this.currentEnableMenuBarMnemonics, visibility: this.currentMenubarVisibility, getKeybinding: (action) => this.keybindingService.lookupKeybinding(action.id), alwaysOnMnemonics: this.alwaysOnMnemonics }); } } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index f44d17f268..9cce7e4576 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/titlebarpart'; -import * as paths from 'vs/base/common/paths'; +import { dirname, posix } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { Part } from 'vs/workbench/browser/part'; import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService'; @@ -28,17 +28,16 @@ import { Color } from 'vs/base/common/color'; import { trim } from 'vs/base/common/strings'; import { EventType, EventHelper, Dimension, isAncestor, hide, show, removeClass, addClass, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { MenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { template, getBaseLabel } from 'vs/base/common/labels'; import { ILabelService } from 'vs/platform/label/common/label'; import { Event, Emitter } from 'vs/base/common/event'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { ISerializableView } from 'vs/base/browser/ui/grid/grid'; -import { Parts } from 'vs/workbench/services/part/common/partService'; +import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -export class TitlebarPart extends Part implements ITitleService, ISerializableView { - - _serviceBrand: any; +export class TitlebarPart extends Part implements ITitleService { private static readonly NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]"); private static readonly NLS_USER_IS_ADMIN = isWindows ? nls.localize('userIsAdmin', "[Administrator]") : nls.localize('userIsSudo', "[Superuser]"); @@ -46,7 +45,20 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi private static readonly TITLE_DIRTY = '\u25cf '; private static readonly TITLE_SEPARATOR = isMacintosh ? ' — ' : ' - '; // macOS uses special - separator - element: HTMLElement; + //#region IView + + readonly minimumWidth: number = 0; + readonly maximumWidth: number = Number.POSITIVE_INFINITY; + get minimumHeight(): number { return isMacintosh ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); } + get maximumHeight(): number { return isMacintosh ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); } + + //#endregion + + private _onMenubarVisibilityChange = this._register(new Emitter()); + get onMenubarVisibilityChange(): Event { return this._onMenubarVisibilityChange.event; } + + _serviceBrand: ServiceIdentifier; + private title: HTMLElement; private dragRegion: HTMLElement; private windowControls: HTMLElement; @@ -55,6 +67,7 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi private menubarPart: MenubarControl; private menubar: HTMLElement; private resizer: HTMLElement; + private lastLayoutDimensions: Dimension; private pendingTitle: string; private representedFileName: string; @@ -64,16 +77,9 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi private properties: ITitleProperties; private activeEditorListeners: IDisposable[]; - minimumWidth: number = 0; - maximumWidth: number = Number.POSITIVE_INFINITY; - get minimumHeight(): number { return isMacintosh ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); } - get maximumHeight(): number { return isMacintosh ? 22 / getZoomFactor() : (30 / (this.configurationService.getValue('window.menuBarVisibility') === 'hidden' ? getZoomFactor() : 1)); } - - private _onDidChange = new Emitter<{ width: number; height: number; }>(); - readonly onDidChange = this._onDidChange.event; + private titleUpdater: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0)); constructor( - id: string, @IContextMenuService private readonly contextMenuService: IContextMenuService, @IWindowService private readonly windowService: IWindowService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -84,9 +90,10 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService themeService: IThemeService, @ILabelService private readonly labelService: ILabelService, - @IStorageService storageService: IStorageService + @IStorageService storageService: IStorageService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(id, { hasTitle: false }, themeService, storageService); + super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); this.properties = { isPure: true, isAdmin: false }; this.activeEditorListeners = []; @@ -98,10 +105,10 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi this._register(this.windowService.onDidChangeFocus(focused => focused ? this.onFocus() : this.onBlur())); this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationChanged(e))); this._register(this.editorService.onDidActiveEditorChange(() => this.onActiveEditorChange())); - this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.doUpdateTitle())); - this._register(this.contextService.onDidChangeWorkbenchState(() => this.doUpdateTitle())); - this._register(this.contextService.onDidChangeWorkspaceName(() => this.doUpdateTitle())); - this._register(this.labelService.onDidChangeFormatters(() => this.doUpdateTitle())); + this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.titleUpdater.schedule())); + this._register(this.contextService.onDidChangeWorkbenchState(() => this.titleUpdater.schedule())); + this._register(this.contextService.onDidChangeWorkspaceName(() => this.titleUpdater.schedule())); + this._register(this.labelService.onDidChangeFormatters(() => this.titleUpdater.schedule())); } private onBlur(): void { @@ -116,7 +123,7 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi private onConfigurationChanged(event: IConfigurationChangeEvent): void { if (event.affectsConfiguration('window.title')) { - this.doUpdateTitle(); + this.titleUpdater.schedule(); } if (event.affectsConfiguration('window.doubleClickIconToClose')) { @@ -136,6 +143,8 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi } this.adjustTitleMarginToCenter(); + + this._onMenubarVisibilityChange.fire(visible); } } @@ -149,10 +158,6 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi } } - onMenubarVisibilityChange(): Event { - return this.menubarPart.onVisibilityChange; - } - private onActiveEditorChange(): void { // Dispose old listeners @@ -160,13 +165,13 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi this.activeEditorListeners = []; // Calculate New Window Title - this.doUpdateTitle(); + this.titleUpdater.schedule(); // Apply listener for dirty and label changes const activeEditor = this.editorService.activeEditor; if (activeEditor instanceof EditorInput) { - this.activeEditorListeners.push(activeEditor.onDidChangeDirty(() => this.doUpdateTitle())); - this.activeEditorListeners.push(activeEditor.onDidChangeLabel(() => this.doUpdateTitle())); + this.activeEditorListeners.push(activeEditor.onDidChangeDirty(() => this.titleUpdater.schedule())); + this.activeEditorListeners.push(activeEditor.onDidChangeLabel(() => this.titleUpdater.schedule())); } // Represented File Name @@ -174,7 +179,7 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi } private updateRepresentedFilename(): void { - const file = toResource(this.editorService.activeEditor, { supportSideBySide: true, filter: 'file' }); + const file = toResource(this.editorService.activeEditor || null, { supportSideBySide: true, filter: 'file' }); const path = file ? file.fsPath : ''; // Apply to window @@ -202,7 +207,9 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi } if ((isWindows || isLinux) && this.title) { - this.adjustTitleMarginToCenter(); + if (this.lastLayoutDimensions) { + this.updateLayout(this.lastLayoutDimensions); + } } } @@ -232,7 +239,7 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi this.properties.isAdmin = isAdmin; this.properties.isPure = isPure; - this.doUpdateTitle(); + this.titleUpdater.schedule(); } } @@ -258,7 +265,7 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi const workspace = this.contextService.getWorkspace(); // Compute root - let root: URI; + let root: URI | undefined; if (workspace.configuration) { root = workspace.configuration; } else if (workspace.folders.length) { @@ -275,7 +282,7 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi // Compute folder resource // Single Root Workspace: always the root single workspace in this case // Otherwise: root folder of the currently active file if any - const folder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? workspace.folders[0] : this.contextService.getWorkspaceFolder(toResource(editor, { supportSideBySide: true })); + const folder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER ? workspace.folders[0] : this.contextService.getWorkspaceFolder(toResource(editor || null, { supportSideBySide: true })!); // Variables const activeEditorShort = editor ? editor.getTitle(Verbosity.SHORT) : ''; @@ -343,7 +350,7 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi if (this.pendingTitle) { this.title.innerText = this.pendingTitle; } else { - this.doUpdateTitle(); + this.titleUpdater.schedule(); } // Maximize/Restore on doubleclick @@ -452,7 +459,7 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi this.adjustTitleMarginToCenter(); } - protected updateStyles(): void { + updateStyles(): void { super.updateStyles(); // Part container @@ -465,7 +472,7 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi const titleBackground = this.getColor(this.isInactive ? TITLE_BAR_INACTIVE_BACKGROUND : TITLE_BAR_ACTIVE_BACKGROUND); this.element.style.backgroundColor = titleBackground; - if (Color.fromHex(titleBackground).isLighter()) { + if (titleBackground && Color.fromHex(titleBackground).isLighter()) { addClass(this.element, 'light'); } else { removeClass(this.element, 'light'); @@ -487,8 +494,7 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi const setting = this.configurationService.getValue('window.doubleClickIconToClose'); if (setting) { this.appIcon.style['-webkit-app-region'] = 'no-drag'; - } - else { + } else { this.appIcon.style['-webkit-app-region'] = 'drag'; } } @@ -514,7 +520,7 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi const actions: IAction[] = []; if (this.representedFileName) { - const segments = this.representedFileName.split(paths.sep); + const segments = this.representedFileName.split(posix.sep); for (let i = segments.length; i > 0; i--) { const isFile = (i === segments.length); @@ -523,16 +529,16 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi pathOffset++; // for segments which are not the file name we want to open the folder } - const path = segments.slice(0, pathOffset).join(paths.sep); + const path = segments.slice(0, pathOffset).join(posix.sep); let label: string; if (!isFile) { - label = getBaseLabel(paths.dirname(path)); + label = getBaseLabel(dirname(path)); } else { label = getBaseLabel(path); } - actions.push(new ShowItemInFolderAction(path, label || paths.sep, this.windowsService)); + actions.push(new ShowItemInFolderAction(path, label || posix.sep, this.windowsService)); } } @@ -554,6 +560,8 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi } updateLayout(dimension: Dimension): void { + this.lastLayoutDimensions = dimension; + if (getTitleBarStyle(this.configurationService, this.environmentService) === 'custom') { // Only prevent zooming behavior on macOS or when the menubar is not visible if (isMacintosh || this.configurationService.getValue('window.menuBarVisibility') === 'hidden') { @@ -573,25 +581,16 @@ export class TitlebarPart extends Part implements ITitleService, ISerializableVi runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter()); if (this.menubarPart) { - const menubarDimension = new Dimension(undefined, dimension.height); + const menubarDimension = new Dimension(0, dimension.height); this.menubarPart.layout(menubarDimension); } } } - layout(dimension: Dimension): Dimension[]; - layout(width: number, height: number): void; - layout(dim1: Dimension | number, dim2?: number): Dimension[] | void { - if (dim1 instanceof Dimension) { - this.updateLayout(dim1); + layout(width: number, height: number): void { + this.updateLayout(new Dimension(width, height)); - return super.layout(dim1); - } - - const dimensions = new Dimension(dim1, dim2); - this.updateLayout(dimensions); - - super.layout(dimensions); + super.layoutContents(width, height); } toJSON(): object { @@ -608,7 +607,7 @@ class ShowItemInFolderAction extends Action { } run(): Promise { - return this.windowsService.showItemInFolder(this.path); + return this.windowsService.showItemInFolder(URI.file(this.path)); } } @@ -631,3 +630,5 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } }); + +registerSingleton(ITitleService, TitlebarPart); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/views/customView.ts b/src/vs/workbench/browser/parts/views/customView.ts index 5dfd766a60..0bd89617cc 100644 --- a/src/vs/workbench/browser/parts/views/customView.ts +++ b/src/vs/workbench/browser/parts/views/customView.ts @@ -13,7 +13,7 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { ContextAwareMenuItemActionItem, fillInActionBarActions, fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IViewsService, ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, ViewsRegistry, ViewContainer, ITreeItemLabel } from 'vs/workbench/common/views'; +import { IViewsService, ITreeView, ITreeItem, TreeItemCollapsibleState, ITreeViewDataProvider, TreeViewItemHandleArg, ITreeViewDescriptor, IViewsRegistry, ViewContainer, ITreeItemLabel, Extensions } from 'vs/workbench/common/views'; import { IViewletViewOptions, FileIconThemableWorkbenchTree } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -26,7 +26,7 @@ import { IDataSource, ITree, IRenderer, ContextMenuEvent } from 'vs/base/parts/t import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; import { ActionBar, IActionItemProvider, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { URI } from 'vs/base/common/uri'; -import { basename } from 'vs/base/common/paths'; +import { dirname, basename } from 'vs/base/common/resources'; import { LIGHT, FileThemeIcon, FolderThemeIcon, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { FileKind } from 'vs/platform/files/common/files'; import { WorkbenchTreeController } from 'vs/platform/list/browser/listService'; @@ -43,7 +43,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IMarkdownRenderResult } from 'vs/editor/contrib/markdown/markdownRenderer'; import { ILabelService } from 'vs/platform/label/common/label'; -import { dirname } from 'vs/base/common/resources'; +import { Registry } from 'vs/platform/registry/common/platform'; export class CustomTreeViewPanel extends ViewletPanel { @@ -58,7 +58,7 @@ export class CustomTreeViewPanel extends ViewletPanel { @IViewsService viewsService: IViewsService, ) { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService); - const { treeView } = (ViewsRegistry.getView(options.id)); + const { treeView } = (Registry.as(Extensions.ViewsRegistry).getView(options.id)); this.treeView = treeView; this.treeView.onDidChangeActions(() => this.updateActions(), this, this.disposables); this.disposables.push(toDisposable(() => this.treeView.setVisibility(false))); @@ -87,8 +87,8 @@ export class CustomTreeViewPanel extends ViewletPanel { return [...this.treeView.getSecondaryActions()]; } - getActionItem(action: IAction): IActionItem { - return action instanceof MenuItemAction ? new ContextAwareMenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService) : undefined; + getActionItem(action: IAction): IActionItem | null { + return action instanceof MenuItemAction ? new ContextAwareMenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService) : null; } getOptimalWidth(): number { @@ -164,9 +164,9 @@ class TitleMenus implements IDisposable { class Root implements ITreeItem { label = { label: 'root' }; handle = '0'; - parentHandle = null; + parentHandle: string | undefined = undefined; collapsibleState = TreeItemCollapsibleState.Expanded; - children = undefined; + children: ITreeItem[] | undefined = undefined; } const noDataProviderMessage = localize('no-dataprovider', "There is no data provider registered that can provide view data."); @@ -191,21 +191,21 @@ export class CustomTreeView extends Disposable implements ITreeView { private menus: TitleMenus; private markdownRenderer: MarkdownRenderer; - private markdownResult: IMarkdownRenderResult; + private markdownResult: IMarkdownRenderResult | null; - private _onDidExpandItem: Emitter = this._register(new Emitter()); + private readonly _onDidExpandItem: Emitter = this._register(new Emitter()); readonly onDidExpandItem: Event = this._onDidExpandItem.event; - private _onDidCollapseItem: Emitter = this._register(new Emitter()); + private readonly _onDidCollapseItem: Emitter = this._register(new Emitter()); readonly onDidCollapseItem: Event = this._onDidCollapseItem.event; private _onDidChangeSelection: Emitter = this._register(new Emitter()); readonly onDidChangeSelection: Event = this._onDidChangeSelection.event; - private _onDidChangeVisibility: Emitter = this._register(new Emitter()); + private readonly _onDidChangeVisibility: Emitter = this._register(new Emitter()); readonly onDidChangeVisibility: Event = this._onDidChangeVisibility.event; - private _onDidChangeActions: Emitter = this._register(new Emitter()); + private readonly _onDidChangeActions: Emitter = this._register(new Emitter()); readonly onDidChangeActions: Event = this._onDidChangeActions.event; constructor( @@ -235,7 +235,7 @@ export class CustomTreeView extends Disposable implements ITreeView { this.markdownResult.dispose(); } })); - this._register(ViewsRegistry.onDidChangeContainer(({ views, from, to }) => { + this._register(Registry.as(Extensions.ViewsRegistry).onDidChangeContainer(({ views, from, to }) => { if (from === this.viewContainer && views.some(v => v.id === this.id)) { this.viewContainer = to; } @@ -243,15 +243,15 @@ export class CustomTreeView extends Disposable implements ITreeView { this.create(); } - private _dataProvider: ITreeViewDataProvider; - get dataProvider(): ITreeViewDataProvider { + private _dataProvider: ITreeViewDataProvider | null; + get dataProvider(): ITreeViewDataProvider | null { return this._dataProvider; } - set dataProvider(dataProvider: ITreeViewDataProvider) { + set dataProvider(dataProvider: ITreeViewDataProvider | null) { if (dataProvider) { this._dataProvider = new class implements ITreeViewDataProvider { - getChildren(node?: ITreeItem): Promise { + getChildren(node: ITreeItem): Promise { if (node && node.children) { return Promise.resolve(node.children); } @@ -305,7 +305,7 @@ export class CustomTreeView extends Disposable implements ITreeView { getPrimaryActions(): IAction[] { if (this.showCollapseAllAction) { - const collapseAllAction = new Action('vs.tree.collapse', localize('collapse', "Collapse"), 'monaco-tree-action collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve()); + const collapseAllAction = new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action collapse-all', true, () => this.tree ? new CollapseAllAction(this.tree, true).run() : Promise.resolve()); return [...this.menus.getTitleActions(), collapseAllAction]; } else { return this.menus.getTitleActions(); @@ -378,7 +378,7 @@ export class CustomTreeView extends Disposable implements ITreeView { } private createTree() { - const actionItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuItemActionItem, action) : undefined; + const actionItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuItemActionItem, action) : null; const menus = this._register(this.instantiationService.createInstance(TreeMenus, this.id)); this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); const dataSource = this.instantiationService.createInstance(TreeDataSource, this, (task: Promise) => this.progressService.withProgress({ location: this.viewContainer.id }, () => task)); @@ -462,7 +462,7 @@ export class CustomTreeView extends Disposable implements ITreeView { this.elementsToRefresh = []; } for (const element of elements) { - element.children = null; // reset children + element.children = undefined; // reset children } if (this.isVisible) { return this.doRefresh(elements); @@ -508,7 +508,7 @@ export class CustomTreeView extends Disposable implements ITreeView { if (this.tree) { return this.tree.reveal(item); } - return Promise.resolve(null); + return Promise.resolve(); } private activate() { @@ -583,7 +583,7 @@ class TreeDataSource implements IDataSource { } hasChildren(tree: ITree, node: ITreeItem): boolean { - return this.treeView.dataProvider && node.collapsibleState !== TreeItemCollapsibleState.None; + return !!this.treeView.dataProvider && node.collapsibleState !== TreeItemCollapsibleState.None; } getChildren(tree: ITree, node: ITreeItem): Promise { @@ -677,7 +677,7 @@ class TreeRenderer implements IRenderer { renderElement(tree: ITree, node: ITreeItem, templateId: string, templateData: ITreeExplorerTemplateData): void { const resource = node.resourceUri ? URI.revive(node.resourceUri) : null; - const treeItemLabel: ITreeItemLabel = node.label ? node.label : resource ? { label: basename(resource.path) } : undefined; + const treeItemLabel: ITreeItemLabel | undefined = node.label ? node.label : resource ? { label: basename(resource) } : undefined; const description = isString(node.description) ? node.description : resource && node.description === true ? this.labelService.getUriLabel(dirname(resource), { relative: true }) : undefined; const label = treeItemLabel ? treeItemLabel.label : undefined; const matches = treeItemLabel && treeItemLabel.highlights ? treeItemLabel.highlights.map(([start, end]) => ({ start, end })) : undefined; @@ -758,7 +758,7 @@ class Aligner extends Disposable { if (this.hasIcon(parent)) { return false; } - return parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c)); + return !!parent.children && parent.children.every(c => c.collapsibleState === TreeItemCollapsibleState.None || !this.hasIcon(c)); } private hasIcon(node: ITreeItem): boolean { @@ -873,7 +873,7 @@ class TreeMenus extends Disposable implements IDisposable { return this.getActions(MenuId.ViewItemContext, { key: 'viewItem', value: element.contextValue }).secondary; } - private getActions(menuId: MenuId, context: { key: string, value: string }): { primary: IAction[]; secondary: IAction[]; } { + private getActions(menuId: MenuId, context: { key: string, value?: string }): { primary: IAction[]; secondary: IAction[]; } { const contextKeyService = this.contextKeyService.createScoped(); contextKeyService.createKey('view', this.id); contextKeyService.createKey(context.key, context.value); diff --git a/src/vs/workbench/browser/parts/views/panelViewlet.ts b/src/vs/workbench/browser/parts/views/panelViewlet.ts index 6cbffecbfc..25ad73e201 100644 --- a/src/vs/workbench/browser/parts/views/panelViewlet.ts +++ b/src/vs/workbench/browser/parts/views/panelViewlet.ts @@ -24,10 +24,11 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { PanelView, IPanelViewOptions, IPanelOptions, Panel } from 'vs/base/browser/ui/splitview/panelview'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IView } from 'vs/workbench/common/views'; import { IStorageService } from 'vs/platform/storage/common/storage'; +import { withNullAsUndefined } from 'vs/base/common/types'; export interface IPanelColors extends IColorMapping { dropBackground?: ColorIdentifier; @@ -127,7 +128,7 @@ export abstract class ViewletPanel extends Panel implements IView { orientation: ActionsOrientation.HORIZONTAL, actionItemProvider: action => this.getActionItem(action), ariaLabel: nls.localize('viewToolbarAriaLabel', "{0} actions", this.title), - getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id) || undefined, + getKeyBinding: action => withNullAsUndefined(this.keybindingService.lookupKeybinding(action.id)), actionRunner: this.actionRunner }); @@ -221,13 +222,13 @@ export class PanelViewlet extends Viewlet { id: string, private options: IViewsViewletOptions, @IConfigurationService configurationService: IConfigurationService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IContextMenuService protected contextMenuService: IContextMenuService, @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IStorageService storageService: IStorageService ) { - super(id, configurationService, partService, telemetryService, themeService, storageService); + super(id, configurationService, layoutService, telemetryService, themeService, storageService); } create(parent: HTMLElement): void { diff --git a/src/vs/workbench/browser/parts/views/views.ts b/src/vs/workbench/browser/parts/views/views.ts index 7f51ac1de7..99dbcf0bd7 100644 --- a/src/vs/workbench/browser/parts/views/views.ts +++ b/src/vs/workbench/browser/parts/views/views.ts @@ -5,14 +5,13 @@ import 'vs/css!./media/views'; import { Disposable, IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IViewsService, ViewsRegistry, IViewsViewlet, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IView, IViewDescriptorCollection } from 'vs/workbench/common/views'; +import { IViewsService, IViewsViewlet, ViewContainer, IViewDescriptor, IViewContainersRegistry, Extensions as ViewExtensions, IView, IViewDescriptorCollection, IViewsRegistry } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IContextKeyService, IContextKeyChangeEvent, IReadableSet, IContextKey, RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { sortedDiff, firstIndex, move, isNonEmptyArray } from 'vs/base/common/arrays'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { MenuId, MenuRegistry, ICommandAction } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -21,6 +20,8 @@ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/co import { values } from 'vs/base/common/map'; import { IFileIconTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { toggleClass, addClass } from 'vs/base/browser/dom'; +import { ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; function filterViewRegisterEvent(container: ViewContainer, event: Event<{ viewContainer: ViewContainer, views: IViewDescriptor[] }>): Event { return Event.chain(event) @@ -96,10 +97,11 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); - const onRelevantViewsRegistered = filterViewRegisterEvent(container, ViewsRegistry.onViewsRegistered); + const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); + const onRelevantViewsRegistered = filterViewRegisterEvent(container, viewsRegistry.onViewsRegistered); this._register(onRelevantViewsRegistered(this.onViewsRegistered, this)); - const onRelevantViewsMoved = filterViewMoveEvent(container, ViewsRegistry.onDidChangeContainer); + const onRelevantViewsMoved = filterViewMoveEvent(container, viewsRegistry.onDidChangeContainer); this._register(onRelevantViewsMoved(({ added, removed }) => { if (isNonEmptyArray(added)) { this.onViewsRegistered(added); @@ -109,16 +111,16 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl } })); - const onRelevantViewsDeregistered = filterViewRegisterEvent(container, ViewsRegistry.onViewsDeregistered); + const onRelevantViewsDeregistered = filterViewRegisterEvent(container, viewsRegistry.onViewsDeregistered); this._register(onRelevantViewsDeregistered(this.onViewsDeregistered, this)); const onRelevantContextChange = Event.filter(contextKeyService.onDidChangeContext, e => e.affectsSome(this.contextKeys)); this._register(onRelevantContextChange(this.onContextChanged, this)); - this.onViewsRegistered(ViewsRegistry.getViews(container)); + this.onViewsRegistered(viewsRegistry.getViews(container)); } - private onViewsRegistered(viewDescriptors: IViewDescriptor[]): any { + private onViewsRegistered(viewDescriptors: IViewDescriptor[]): void { const added: IViewDescriptor[] = []; for (const viewDescriptor of viewDescriptors) { @@ -145,7 +147,7 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl } } - private onViewsDeregistered(viewDescriptors: IViewDescriptor[]): any { + private onViewsDeregistered(viewDescriptors: IViewDescriptor[]): void { const removed: IViewDescriptor[] = []; for (const viewDescriptor of viewDescriptors) { @@ -174,7 +176,7 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl } } - private onContextChanged(event: IContextKeyChangeEvent): any { + private onContextChanged(event: IContextKeyChangeEvent): void { const removed: IViewDescriptor[] = []; const added: IViewDescriptor[] = []; @@ -203,7 +205,8 @@ class ViewDescriptorCollection extends Disposable implements IViewDescriptorColl } export interface IViewState { - visible: boolean; + visibleGlobal: boolean; + visibleWorkspace: boolean; collapsed: boolean; order?: number; size?: number; @@ -223,7 +226,7 @@ export class ContributableViewsModel extends Disposable { readonly viewDescriptors: IViewDescriptor[] = []; get visibleViewDescriptors(): IViewDescriptor[] { - return this.viewDescriptors.filter(v => this.viewStates.get(v.id)!.visible); + return this.viewDescriptors.filter(v => this.isViewDescriptorVisible(v)); } private _onDidAdd = this._register(new Emitter()); @@ -250,27 +253,35 @@ export class ContributableViewsModel extends Disposable { } isVisible(id: string): boolean { - const state = this.viewStates.get(id); + const viewDescriptor = this.viewDescriptors.filter(v => v.id === id)[0]; - if (!state) { + if (!viewDescriptor) { throw new Error(`Unknown view ${id}`); } - return state.visible; + return this.isViewDescriptorVisible(viewDescriptor); } - setVisible(id: string, visible: boolean): void { + setVisible(id: string, visible: boolean, size?: number): void { const { visibleIndex, viewDescriptor, state } = this.find(id); if (!viewDescriptor.canToggleVisibility) { throw new Error(`Can't toggle this view's visibility`); } - if (state.visible === visible) { + if (this.isViewDescriptorVisible(viewDescriptor) === visible) { return; } - state.visible = visible; + if (viewDescriptor.workspace) { + state.visibleWorkspace = visible; + } else { + state.visibleGlobal = visible; + } + + if (typeof size === 'number') { + state.size = size; + } if (visible) { this._onDidAdd.fire([{ index: visibleIndex, viewDescriptor, size: state.size, collapsed: state.collapsed }]); @@ -329,6 +340,14 @@ export class ContributableViewsModel extends Disposable { }); } + private isViewDescriptorVisible(viewDescriptor: IViewDescriptor): boolean { + const viewState = this.viewStates.get(viewDescriptor.id); + if (!viewState) { + throw new Error(`Unknown view ${viewDescriptor.id}`); + } + return viewDescriptor.workspace ? viewState.visibleWorkspace : viewState.visibleGlobal; + } + private find(id: string): { index: number, visibleIndex: number, viewDescriptor: IViewDescriptor, state: IViewState } { for (let i = 0, visibleIndex = 0; i < this.viewDescriptors.length; i++) { const viewDescriptor = this.viewDescriptors[i]; @@ -341,7 +360,7 @@ export class ContributableViewsModel extends Disposable { return { index: i, visibleIndex, viewDescriptor, state }; } - if (state.visible) { + if (viewDescriptor.workspace ? state.visibleWorkspace : state.visibleGlobal) { visibleIndex++; } } @@ -376,11 +395,16 @@ export class ContributableViewsModel extends Disposable { const viewState = this.viewStates.get(viewDescriptor.id); if (viewState) { // set defaults if not set - viewState.visible = isUndefinedOrNull(viewState.visible) ? !viewDescriptor.hideByDefault : viewState.visible; + if (viewDescriptor.workspace) { + viewState.visibleWorkspace = isUndefinedOrNull(viewState.visibleWorkspace) ? !viewDescriptor.hideByDefault : viewState.visibleWorkspace; + } else { + viewState.visibleGlobal = isUndefinedOrNull(viewState.visibleGlobal) ? !viewDescriptor.hideByDefault : viewState.visibleGlobal; + } viewState.collapsed = isUndefinedOrNull(viewState.collapsed) ? !!viewDescriptor.collapsed : viewState.collapsed; } else { this.viewStates.set(viewDescriptor.id, { - visible: !viewDescriptor.hideByDefault, + visibleGlobal: !viewDescriptor.hideByDefault, + visibleWorkspace: !viewDescriptor.hideByDefault, collapsed: !!viewDescriptor.collapsed }); } @@ -401,9 +425,8 @@ export class ContributableViewsModel extends Disposable { for (let i = 0; i < splice.deleteCount; i++) { const viewDescriptor = this.viewDescriptors[splice.start + i]; - const { state } = this.find(viewDescriptor.id); - if (state.visible) { + if (this.isViewDescriptorVisible(viewDescriptor)) { toRemove.push({ index: startIndex++, viewDescriptor }); } } @@ -411,7 +434,7 @@ export class ContributableViewsModel extends Disposable { for (const viewDescriptor of splice.toInsert) { const state = this.viewStates.get(viewDescriptor.id)!; - if (state.visible) { + if (this.isViewDescriptorVisible(viewDescriptor)) { toAdd.push({ index: startIndex++, viewDescriptor, size: state.size, collapsed: state.collapsed }); } } @@ -435,24 +458,21 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { private readonly hiddenViewsStorageId: string; private storageService: IStorageService; - private contextService: IWorkspaceContextService; constructor( container: ViewContainer, viewletStateStorageId: string, @IViewsService viewsService: IViewsService, @IStorageService storageService: IStorageService, - @IWorkspaceContextService contextService: IWorkspaceContextService ) { const hiddenViewsStorageId = `${viewletStateStorageId}.hidden`; - const viewStates = PersistentContributableViewsModel.loadViewsStates(viewletStateStorageId, hiddenViewsStorageId, storageService, contextService); + const viewStates = PersistentContributableViewsModel.loadViewsStates(viewletStateStorageId, hiddenViewsStorageId, storageService); super(container, viewsService, viewStates); this.viewletStateStorageId = viewletStateStorageId; this.hiddenViewsStorageId = hiddenViewsStorageId; this.storageService = storageService; - this.contextService = contextService; this._register(this.onDidAdd(viewDescriptorRefs => this.saveVisibilityStates(viewDescriptorRefs.map(r => r.viewDescriptor)))); this._register(this.onDidRemove(viewDescriptorRefs => this.saveVisibilityStates(viewDescriptorRefs.map(r => r.viewDescriptor)))); @@ -479,27 +499,49 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { } private saveVisibilityStates(viewDescriptors: IViewDescriptor[]): void { - const storedViewsVisibilityStates = PersistentContributableViewsModel.loadViewsVisibilityState(this.hiddenViewsStorageId, this.storageService, this.contextService); + const globalViews: IViewDescriptor[] = viewDescriptors.filter(v => !v.workspace); + const workspaceViews: IViewDescriptor[] = viewDescriptors.filter(v => v.workspace); + if (globalViews.length) { + this.saveVisibilityStatesInScope(globalViews, StorageScope.GLOBAL); + } + if (workspaceViews.length) { + this.saveVisibilityStatesInScope(workspaceViews, StorageScope.WORKSPACE); + } + } + + private saveVisibilityStatesInScope(viewDescriptors: IViewDescriptor[], scope: StorageScope): void { + const storedViewsVisibilityStates = PersistentContributableViewsModel.loadViewsVisibilityState(this.hiddenViewsStorageId, this.storageService, scope); for (const viewDescriptor of viewDescriptors) { if (viewDescriptor.canToggleVisibility) { const viewState = this.viewStates.get(viewDescriptor.id); - storedViewsVisibilityStates.set(viewDescriptor.id, { id: viewDescriptor.id, isHidden: viewState ? !viewState.visible : false }); + storedViewsVisibilityStates.set(viewDescriptor.id, { id: viewDescriptor.id, isHidden: viewState ? (scope === StorageScope.GLOBAL ? !viewState.visibleGlobal : !viewState.visibleWorkspace) : false }); } } - this.storageService.store(this.hiddenViewsStorageId, JSON.stringify(values(storedViewsVisibilityStates)), StorageScope.GLOBAL); + this.storageService.store(this.hiddenViewsStorageId, JSON.stringify(values(storedViewsVisibilityStates)), scope); } - private static loadViewsStates(viewletStateStorageId: string, hiddenViewsStorageId: string, storageService: IStorageService, contextService: IWorkspaceContextService): Map { + private static loadViewsStates(viewletStateStorageId: string, hiddenViewsStorageId: string, storageService: IStorageService): Map { const viewStates = new Map(); const storedViewsStates = JSON.parse(storageService.get(viewletStateStorageId, StorageScope.WORKSPACE, '{}')); - const viewsVisibilityStates = PersistentContributableViewsModel.loadViewsVisibilityState(hiddenViewsStorageId, storageService, contextService); - for (const { id, isHidden } of values(viewsVisibilityStates)) { + const globalVisibilityStates = this.loadViewsVisibilityState(hiddenViewsStorageId, storageService, StorageScope.GLOBAL); + const workspaceVisibilityStates = this.loadViewsVisibilityState(hiddenViewsStorageId, storageService, StorageScope.WORKSPACE); + + for (const { id, isHidden } of values(globalVisibilityStates)) { const viewState = storedViewsStates[id]; if (viewState) { - viewStates.set(id, { ...viewState, ...{ visible: !isHidden } }); + viewStates.set(id, { ...viewState, ...{ visibleGlobal: !isHidden } }); } else { // New workspace - viewStates.set(id, { ...{ visible: !isHidden } }); + viewStates.set(id, { ...{ visibleGlobal: !isHidden } }); + } + } + for (const { id, isHidden } of values(workspaceVisibilityStates)) { + const viewState = storedViewsStates[id]; + if (viewState) { + viewStates.set(id, { ...viewState, ...{ visibleWorkspace: !isHidden } }); + } else { + // New workspace + viewStates.set(id, { ...{ visibleWorkspace: !isHidden } }); } } for (const id of Object.keys(storedViewsStates)) { @@ -510,8 +552,8 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { return viewStates; } - private static loadViewsVisibilityState(hiddenViewsStorageId: string, storageService: IStorageService, contextService: IWorkspaceContextService): Map { - const storedVisibilityStates = >JSON.parse(storageService.get(hiddenViewsStorageId, StorageScope.GLOBAL, '[]')); + private static loadViewsVisibilityState(hiddenViewsStorageId: string, storageService: IStorageService, scope: StorageScope): Map { + const storedVisibilityStates = >JSON.parse(storageService.get(hiddenViewsStorageId, scope, '[]')); let hasDuplicates = false; const storedViewsVisibilityStates = storedVisibilityStates.reduce((result, storedState) => { if (typeof storedState === 'string' /* migration */) { @@ -525,7 +567,7 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { }, new Map()); if (hasDuplicates) { - storageService.store(hiddenViewsStorageId, JSON.stringify(values(storedViewsVisibilityStates)), StorageScope.GLOBAL); + storageService.store(hiddenViewsStorageId, JSON.stringify(values(storedViewsVisibilityStates)), scope); } return storedViewsVisibilityStates; @@ -534,7 +576,7 @@ export class PersistentContributableViewsModel extends ContributableViewsModel { export class ViewsService extends Disposable implements IViewsService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; private readonly viewDescriptorCollections: Map; private readonly viewDisposable: Map; @@ -550,14 +592,15 @@ export class ViewsService extends Disposable implements IViewsService { this.viewDisposable = new Map(); this.activeViewContextKeys = new Map>(); - const viewContainersRegistry = Registry.as(ViewContainerExtensions.ViewContainersRegistry); + const viewContainersRegistry = Registry.as(ViewExtensions.ViewContainersRegistry); + const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); viewContainersRegistry.all.forEach(viewContainer => { - this.onDidRegisterViews(viewContainer, ViewsRegistry.getViews(viewContainer)); + this.onDidRegisterViews(viewContainer, viewsRegistry.getViews(viewContainer)); this.onDidRegisterViewContainer(viewContainer); }); - this._register(ViewsRegistry.onViewsRegistered(({ views, viewContainer }) => this.onDidRegisterViews(viewContainer, views))); - this._register(ViewsRegistry.onViewsDeregistered(({ views }) => this.onDidDeregisterViews(views))); - this._register(ViewsRegistry.onDidChangeContainer(({ views, to }) => { this.onDidDeregisterViews(views); this.onDidRegisterViews(to, views); })); + this._register(viewsRegistry.onViewsRegistered(({ views, viewContainer }) => this.onDidRegisterViews(viewContainer, views))); + this._register(viewsRegistry.onViewsDeregistered(({ views }) => this.onDidDeregisterViews(views))); + this._register(viewsRegistry.onDidChangeContainer(({ views, to }) => { this.onDidDeregisterViews(views); this.onDidRegisterViews(to, views); })); this._register(toDisposable(() => { this.viewDisposable.forEach(disposable => disposable.dispose()); this.viewDisposable.clear(); @@ -576,7 +619,7 @@ export class ViewsService extends Disposable implements IViewsService { } openView(id: string, focus: boolean): Promise { - const viewContainer = ViewsRegistry.getViewContainer(id); + const viewContainer = Registry.as(ViewExtensions.ViewsRegistry).getViewContainer(id); if (viewContainer) { const viewletDescriptor = this.viewletService.getViewlet(viewContainer.id); if (viewletDescriptor) { @@ -682,4 +725,6 @@ export function createFileIconThemableTreeContainerScope(container: HTMLElement, onDidChangeFileIconTheme(themeService.getFileIconTheme()); return themeService.onDidFileIconThemeChange(onDidChangeFileIconTheme); -} \ No newline at end of file +} + +registerSingleton(IViewsService, ViewsService); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 41f0659f58..7053b2f079 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -25,7 +25,7 @@ import { IWorkbenchThemeService, IFileIconTheme } from 'vs/workbench/services/th import { ITreeConfiguration, ITreeOptions } from 'vs/base/parts/tree/browser/tree'; import { Event } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { localize } from 'vs/nls'; import { IAddedViewDescriptorRef, IViewDescriptorRef, PersistentContributableViewsModel } from 'vs/workbench/browser/parts/views/views'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -43,7 +43,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView private readonly visibleViewsCountFromCache: number; private readonly visibleViewsStorageId: string; - private readonly viewsModel: PersistentContributableViewsModel; + protected readonly viewsModel: PersistentContributableViewsModel; private viewDisposables: IDisposable[] = []; constructor( @@ -51,7 +51,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView viewletStateStorageId: string, showHeaderInTitleWhenSingleView: boolean, @IConfigurationService configurationService: IConfigurationService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, @IStorageService protected storageService: IStorageService, @IInstantiationService protected instantiationService: IInstantiationService, @@ -60,14 +60,14 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView @IExtensionService protected extensionService: IExtensionService, @IWorkspaceContextService protected contextService: IWorkspaceContextService ) { - super(id, { showHeaderInTitleWhenSingleView, dnd: new DefaultPanelDndController() }, configurationService, partService, contextMenuService, telemetryService, themeService, storageService); + super(id, { showHeaderInTitleWhenSingleView, dnd: new DefaultPanelDndController() }, configurationService, layoutService, contextMenuService, telemetryService, themeService, storageService); const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).get(id); this.viewsModel = this._register(this.instantiationService.createInstance(PersistentContributableViewsModel, container, viewletStateStorageId)); this.viewletState = this.getMemento(StorageScope.WORKSPACE); this.visibleViewsStorageId = `${id}.numberOfVisibleViews`; - this.visibleViewsCountFromCache = this.storageService.getInteger(this.visibleViewsStorageId, StorageScope.WORKSPACE, 1); + this.visibleViewsCountFromCache = this.storageService.getNumber(this.visibleViewsStorageId, StorageScope.WORKSPACE, 1); this._register(toDisposable(() => this.viewDisposables = dispose(this.viewDisposables))); } @@ -178,7 +178,7 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView } protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPanel { - return this.instantiationService.createInstance(viewDescriptor.ctor, options) as ViewletPanel; + return (this.instantiationService as any).createInstance(viewDescriptor.ctorDescriptor.ctor, ...(viewDescriptor.ctorDescriptor.arguments || []), options) as ViewletPanel; } protected getView(id: string): ViewletPanel { @@ -197,7 +197,6 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView viewletState: this.viewletState }); panel.render(); - panel.setVisible(true); const contextMenuDisposable = DOM.addDisposableListener(panel.draggableElement, 'contextmenu', e => { e.stopPropagation(); e.preventDefault(); diff --git a/src/vs/workbench/browser/quickopen.ts b/src/vs/workbench/browser/quickopen.ts index 84db945207..429e0e6138 100644 --- a/src/vs/workbench/browser/quickopen.ts +++ b/src/vs/workbench/browser/quickopen.ts @@ -50,14 +50,14 @@ export class QuickOpenHandler { /** * The ARIA label to apply when this quick open handler is active in quick open. */ - getAriaLabel() { + getAriaLabel(): string | null { return null; } /** * Extra CSS class name to add to the quick open widget to do custom styling of entries. */ - getClass(): string { + getClass(): string | null { return null; } @@ -102,7 +102,7 @@ export class QuickOpenHandler { /** * Allows to return a label that will be placed to the side of the results from this handler or null if none. */ - getGroupLabel(): string { + getGroupLabel(): string | null { return null; } @@ -129,16 +129,16 @@ export interface QuickOpenHandlerHelpEntry { export class QuickOpenHandlerDescriptor { prefix: string; description: string; - contextKey: string; + contextKey?: string; helpEntries: QuickOpenHandlerHelpEntry[]; instantProgress: boolean; private id: string; private ctor: IConstructorSignature0; - constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string, description: string, instantProgress?: boolean); - constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string, helpEntries: QuickOpenHandlerHelpEntry[], instantProgress?: boolean); - constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string, param: any, instantProgress: boolean = false) { + constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string | undefined, description: string, instantProgress?: boolean); + constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string | undefined, helpEntries: QuickOpenHandlerHelpEntry[], instantProgress?: boolean); + constructor(ctor: IConstructorSignature0, id: string, prefix: string, contextKey: string | undefined, param: any, instantProgress: boolean = false) { this.ctor = ctor; this.id = id; this.prefix = prefix; @@ -185,7 +185,7 @@ export interface IQuickOpenRegistry { /** * Get a specific quick open handler for a given prefix. */ - getQuickOpenHandler(prefix: string): QuickOpenHandlerDescriptor; + getQuickOpenHandler(prefix: string): QuickOpenHandlerDescriptor | null; /** * Returns the default quick open handler. @@ -213,8 +213,8 @@ class QuickOpenRegistry implements IQuickOpenRegistry { return this.handlers.slice(0); } - getQuickOpenHandler(text: string): QuickOpenHandlerDescriptor { - return text ? arrays.first(this.handlers, h => strings.startsWith(text, h.prefix), null) : null; + getQuickOpenHandler(text: string): QuickOpenHandlerDescriptor | null { + return text ? arrays.first(this.handlers, h => strings.startsWith(text, h.prefix), null) : null; } getDefaultQuickOpenHandler(): QuickOpenHandlerDescriptor { @@ -229,12 +229,12 @@ export interface IEditorQuickOpenEntry { /** * The editor input used for this entry when opening. */ - getInput(): IResourceInput | IEditorInput; + getInput(): IResourceInput | IEditorInput | null; /** * The editor options used for this entry when opening. */ - getOptions(): IEditorOptions; + getOptions(): IEditorOptions | null; } /** @@ -250,11 +250,11 @@ export class EditorQuickOpenEntry extends QuickOpenEntry implements IEditorQuick return this._editorService; } - getInput(): IResourceInput | IEditorInput { + getInput(): IResourceInput | IEditorInput | null { return null; } - getOptions(): IEditorOptions { + getOptions(): IEditorOptions | null { return null; } @@ -264,7 +264,7 @@ export class EditorQuickOpenEntry extends QuickOpenEntry implements IEditorQuick if (mode === Mode.OPEN || mode === Mode.OPEN_IN_BACKGROUND) { const sideBySide = context.keymods.ctrlCmd; - let openOptions: IEditorOptions; + let openOptions: IEditorOptions | undefined; if (mode === Mode.OPEN_IN_BACKGROUND) { openOptions = { pinned: true, preserveFocus: true }; } else if (context.keymods.alt) { @@ -280,7 +280,7 @@ export class EditorQuickOpenEntry extends QuickOpenEntry implements IEditorQuick opts = EditorOptions.create(openOptions); } - this.editorService.openEditor(input, opts, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); + this.editorService.openEditor(input, opts || undefined, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } else { const resourceInput = input; @@ -301,11 +301,11 @@ export class EditorQuickOpenEntry extends QuickOpenEntry implements IEditorQuick */ export class EditorQuickOpenEntryGroup extends QuickOpenEntryGroup implements IEditorQuickOpenEntry { - getInput(): IEditorInput | IResourceInput { + getInput(): IEditorInput | IResourceInput | null { return null; } - getOptions(): IEditorOptions { + getOptions(): IEditorOptions | null { return null; } } diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts new file mode 100644 index 0000000000..96d47cf334 --- /dev/null +++ b/src/vs/workbench/browser/style.ts @@ -0,0 +1,137 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./media/style'; + +import { registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; +import { foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; +import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; + +registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + + // Foreground + const windowForeground = theme.getColor(foreground); + if (windowForeground) { + collector.addRule(`.monaco-workbench { color: ${windowForeground}; }`); + } + + // Selection + const windowSelectionBackground = theme.getColor(selectionBackground); + if (windowSelectionBackground) { + collector.addRule(`.monaco-workbench ::selection { background-color: ${windowSelectionBackground}; }`); + } + + // Input placeholder + const placeholderForeground = theme.getColor(inputPlaceholderForeground); + if (placeholderForeground) { + collector.addRule(`.monaco-workbench input::-webkit-input-placeholder { color: ${placeholderForeground}; }`); + collector.addRule(`.monaco-workbench textarea::-webkit-input-placeholder { color: ${placeholderForeground}; }`); + } + + // List highlight + const listHighlightForegroundColor = theme.getColor(listHighlightForeground); + if (listHighlightForegroundColor) { + collector.addRule(` + .monaco-workbench .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight, + .monaco-workbench .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { + color: ${listHighlightForegroundColor}; + } + `); + } + + // We need to set the workbench background color so that on Windows we get subpixel-antialiasing. + const workbenchBackground = WORKBENCH_BACKGROUND(theme); + collector.addRule(`.monaco-workbench { background-color: ${workbenchBackground}; }`); + + // Scrollbars + const scrollbarShadowColor = theme.getColor(scrollbarShadow); + if (scrollbarShadowColor) { + collector.addRule(` + .monaco-workbench .monaco-scrollable-element > .shadow.top { + box-shadow: ${scrollbarShadowColor} 0 6px 6px -6px inset; + } + + .monaco-workbench .monaco-scrollable-element > .shadow.left { + box-shadow: ${scrollbarShadowColor} 6px 0 6px -6px inset; + } + + .monaco-workbench .monaco-scrollable-element > .shadow.top.left { + box-shadow: ${scrollbarShadowColor} 6px 6px 6px -6px inset; + } + `); + } + + const scrollbarSliderBackgroundColor = theme.getColor(scrollbarSliderBackground); + if (scrollbarSliderBackgroundColor) { + collector.addRule(` + .monaco-workbench .monaco-scrollable-element > .scrollbar > .slider { + background: ${scrollbarSliderBackgroundColor}; + } + `); + } + + const scrollbarSliderHoverBackgroundColor = theme.getColor(scrollbarSliderHoverBackground); + if (scrollbarSliderHoverBackgroundColor) { + collector.addRule(` + .monaco-workbench .monaco-scrollable-element > .scrollbar > .slider:hover { + background: ${scrollbarSliderHoverBackgroundColor}; + } + `); + } + + const scrollbarSliderActiveBackgroundColor = theme.getColor(scrollbarSliderActiveBackground); + if (scrollbarSliderActiveBackgroundColor) { + collector.addRule(` + .monaco-workbench .monaco-scrollable-element > .scrollbar > .slider.active { + background: ${scrollbarSliderActiveBackgroundColor}; + } + `); + } + + // Focus outline + const focusOutline = theme.getColor(focusBorder); + if (focusOutline) { + collector.addRule(` + .monaco-workbench [tabindex="0"]:focus, + .monaco-workbench .synthetic-focus, + .monaco-workbench select:focus, + .monaco-workbench .monaco-tree.focused.no-focused-item:focus:before, + .monaco-workbench .monaco-list:not(.element-focused):focus:before, + .monaco-workbench input[type="button"]:focus, + .monaco-workbench input[type="text"]:focus, + .monaco-workbench button:focus, + .monaco-workbench textarea:focus, + .monaco-workbench input[type="search"]:focus, + .monaco-workbench input[type="checkbox"]:focus { + outline-color: ${focusOutline}; + } + `); + } + + // High Contrast theme overwrites for outline + if (theme.type === HIGH_CONTRAST) { + collector.addRule(` + .hc-black [tabindex="0"]:focus, + .hc-black .synthetic-focus, + .hc-black select:focus, + .hc-black input[type="button"]:focus, + .hc-black input[type="text"]:focus, + .hc-black textarea:focus, + .hc-black input[type="checkbox"]:focus { + outline-style: solid; + outline-width: 1px; + } + + .hc-black .monaco-tree.focused.no-focused-item:focus:before { + outline-width: 1px; + outline-offset: -2px; + } + + .hc-black .synthetic-focus input { + background: transparent; /* Search input focus fix when in high contrast */ + } + `); + } +}); diff --git a/src/vs/workbench/browser/viewlet.ts b/src/vs/workbench/browser/viewlet.ts index 4ae5a361b3..12b59b918a 100644 --- a/src/vs/workbench/browser/viewlet.ts +++ b/src/vs/workbench/browser/viewlet.ts @@ -7,26 +7,26 @@ import * as nls from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; import { Action, IAction } from 'vs/base/common/actions'; -import { ITree } from 'vs/base/parts/tree/browser/tree'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; import { Composite, CompositeDescriptor, CompositeRegistry } from 'vs/workbench/browser/composite'; import { IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; import { ToggleSidebarVisibilityAction, ToggleSidebarPositionAction } from 'vs/workbench/browser/actions/layoutActions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { AbstractTree } from 'vs/base/browser/ui/tree/abstractTree'; export abstract class Viewlet extends Composite implements IViewlet { constructor(id: string, protected configurationService: IConfigurationService, - private partService: IPartService, + private layoutService: IWorkbenchLayoutService, telemetryService: ITelemetryService, themeService: IThemeService, storageService: IStorageService @@ -39,13 +39,13 @@ export abstract class Viewlet extends Composite implements IViewlet { } getContextMenuActions(): IAction[] { - const toggleSidebarPositionAction = new ToggleSidebarPositionAction(ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.getLabel(this.partService), this.partService, this.configurationService); + const toggleSidebarPositionAction = new ToggleSidebarPositionAction(ToggleSidebarPositionAction.ID, ToggleSidebarPositionAction.getLabel(this.layoutService), this.layoutService, this.configurationService); return [toggleSidebarPositionAction, { id: ToggleSidebarVisibilityAction.ID, label: nls.localize('compositePart.hideSideBarLabel', "Hide Side Bar"), enabled: true, - run: () => this.partService.setSideBarHidden(true) + run: () => this.layoutService.setSideBarHidden(true) }]; } } @@ -130,19 +130,17 @@ Registry.add(Extensions.Viewlets, new ViewletRegistry()); * A reusable action to show a viewlet with a specific id. */ export class ShowViewletAction extends Action { - private viewletId: string; constructor( id: string, name: string, - viewletId: string, + private readonly viewletId: string, @IViewletService protected viewletService: IViewletService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, - @IPartService private readonly partService: IPartService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(id, name); - this.viewletId = viewletId; this.enabled = !!this.viewletService && !!this.editorGroupService; } @@ -169,35 +167,15 @@ export class ShowViewletAction extends Action { const activeViewlet = this.viewletService.getActiveViewlet(); const activeElement = document.activeElement; - return !!(activeViewlet && activeElement && DOM.isAncestor(activeElement, this.partService.getContainer(Parts.SIDEBAR_PART))); + return !!(activeViewlet && activeElement && DOM.isAncestor(activeElement, this.layoutService.getContainer(Parts.SIDEBAR_PART))); } } -// Collapse All action export class CollapseAction extends Action { - - constructor(viewer: ITree, enabled: boolean, clazz: string) { - super('workbench.action.collapse', nls.localize('collapse', "Collapse All"), clazz, enabled, (context: any) => { - if (viewer.getHighlight()) { - return Promise.resolve(null); // Global action disabled if user is in edit mode from another action - } - - viewer.collapseAll(); - viewer.clearSelection(); - viewer.clearFocus(); - viewer.domFocus(); - viewer.focusFirst(); - - return Promise.resolve(null); - }); - } -} - -// Collapse All action for the new tree -export class CollapseAction2 extends Action { - constructor(tree: AsyncDataTree, enabled: boolean, clazz: string) { + constructor(tree: AsyncDataTree | AbstractTree, enabled: boolean, clazz?: string) { super('workbench.action.collapse', nls.localize('collapse', "Collapse All"), clazz, enabled, () => { tree.collapseAll(); + return Promise.resolve(undefined); }); } diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index b39063a049..4d89f096e8 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -8,280 +8,316 @@ import * as nls from 'vs/nls'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { isMacintosh } from 'vs/base/common/platform'; -const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); +// Configuration +(function registerConfiguration(): void { + const registry = Registry.as(ConfigurationExtensions.Configuration); -// Configuration: Workbench -configurationRegistry.registerConfiguration({ - 'id': 'workbench', - 'order': 7, - 'title': nls.localize('workbenchConfigurationTitle', "Workbench"), - 'type': 'object', - 'properties': { - 'workbench.editor.showTabs': { - 'type': 'boolean', - 'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), - 'default': true - }, - 'workbench.editor.highlightModifiedTabs': { - 'type': 'boolean', - 'description': nls.localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not."), - 'default': false - }, - 'workbench.editor.labelFormat': { - 'type': 'string', - 'enum': ['default', 'short', 'medium', 'long'], - 'enumDescriptions': [ - nls.localize('workbench.editor.labelFormat.default', "Show the name of the file. When tabs are enabled and two files have the same name in one group the distinguishing sections of each file's path are added. When tabs are disabled, the path relative to the workspace folder is shown if the editor is active."), - nls.localize('workbench.editor.labelFormat.short', "Show the name of the file followed by its directory name."), - nls.localize('workbench.editor.labelFormat.medium', "Show the name of the file followed by its path relative to the workspace folder."), - nls.localize('workbench.editor.labelFormat.long', "Show the name of the file followed by its absolute path.") - ], - 'default': 'default', - 'description': nls.localize({ - comment: ['This is the description for a setting. Values surrounded by parenthesis are not to be translated.'], - key: 'tabDescription' - }, "Controls the format of the label for an editor."), - }, - 'workbench.editor.tabCloseButton': { - 'type': 'string', - 'enum': ['left', 'right', 'off'], - 'default': 'right', - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'.") - }, - 'workbench.editor.tabSizing': { - 'type': 'string', - 'enum': ['fit', 'shrink'], - 'default': 'fit', - 'enumDescriptions': [ - nls.localize('workbench.editor.tabSizing.fit', "Always keep tabs large enough to show the full editor label."), - nls.localize('workbench.editor.tabSizing.shrink', "Allow tabs to get smaller when the available space is not enough to show all tabs at once.") - ], - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the sizing of editor tabs.") - }, - 'workbench.editor.focusRecentEditorAfterClose': { - 'type': 'boolean', - 'description': nls.localize('focusRecentEditorAfterClose', "Controls whether tabs are closed in most recently used order or from left to right."), - 'default': true - }, - 'workbench.editor.showIcons': { - 'type': 'boolean', - 'description': nls.localize('showIcons', "Controls whether opened editors should show with an icon or not. This requires an icon theme to be enabled as well."), - 'default': true - }, - 'workbench.editor.enablePreview': { - 'type': 'boolean', - 'description': nls.localize('enablePreview', "Controls whether opened editors show as preview. Preview editors are reused until they are pinned (e.g. via double click or editing) and show up with an italic font style."), - 'default': true - }, - 'workbench.editor.enablePreviewFromQuickOpen': { - 'type': 'boolean', - 'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether opened editors from Quick Open show as preview. Preview editors are reused until they are pinned (e.g. via double click or editing)."), - 'default': true - }, - 'workbench.editor.closeOnFileDelete': { - 'type': 'boolean', - 'description': nls.localize('closeOnFileDelete', "Controls whether editors showing a file that was opened during the session should close automatically when getting deleted or renamed by some other process. Disabling this will keep the editor open on such an event. Note that deleting from within the application will always close the editor and that dirty files will never close to preserve your data."), - 'default': false - }, - 'workbench.editor.openPositioning': { - 'type': 'string', - 'enum': ['left', 'right', 'first', 'last'], - 'default': 'right', - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorOpenPositioning' }, "Controls where editors open. Select `left` or `right` to open editors to the left or right of the currently active one. Select `first` or `last` to open editors independently from the currently active one.") - }, - 'workbench.editor.openSideBySideDirection': { - 'type': 'string', - 'enum': ['right', 'down'], - 'default': 'right', - 'markdownDescription': nls.localize('sideBySideDirection', "Controls the default direction of editors that are opened side by side (e.g. from the explorer). By default, editors will open on the right hand side of the currently active one. If changed to `down`, the editors will open below the currently active one.") - }, - 'workbench.editor.closeEmptyGroups': { - 'type': 'boolean', - 'description': nls.localize('closeEmptyGroups', "Controls the behavior of empty editor groups when the last tab in the group is closed. When enabled, empty groups will automatically close. When disabled, empty groups will remain part of the grid."), - 'default': true - }, - 'workbench.editor.revealIfOpen': { - 'type': 'boolean', - 'description': nls.localize('revealIfOpen', "Controls whether an editor is revealed in any of the visible groups if opened. If disabled, an editor will prefer to open in the currently active editor group. If enabled, an already opened editor will be revealed instead of opened again in the currently active editor group. Note that there are some cases where this setting is ignored, e.g. when forcing an editor to open in a specific group or to the side of the currently active group."), - 'default': false - }, - 'workbench.editor.swipeToNavigate': { - 'type': 'boolean', - 'description': nls.localize('swipeToNavigate', "Navigate between open files using three-finger swipe horizontally."), - 'default': false, - 'included': isMacintosh - }, - 'workbench.editor.restoreViewState': { - 'type': 'boolean', - 'description': nls.localize('restoreViewState', "Restores the last view state (e.g. scroll position) when re-opening files after they have been closed."), - 'default': true, - }, - 'workbench.editor.centeredLayoutAutoResize': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('centeredLayoutAutoResize', "Controls if the centered layout should automatically resize to maximum width when more than one group is open. Once only one group is open it will resize back to the original centered width.") - }, - 'workbench.commandPalette.history': { - 'type': 'number', - 'description': nls.localize('commandHistory', "Controls the number of recently used commands to keep in history for the command palette. Set to 0 to disable command history."), - 'default': 50 - }, - 'workbench.commandPalette.preserveInput': { - 'type': 'boolean', - 'description': nls.localize('preserveInput', "Controls whether the last typed input to the command palette should be restored when opening it the next time."), - 'default': false - }, - 'workbench.quickOpen.closeOnFocusLost': { - 'type': 'boolean', - 'description': nls.localize('closeOnFocusLost', "Controls whether Quick Open should close automatically once it loses focus."), - 'default': true - }, - 'workbench.quickOpen.preserveInput': { - 'type': 'boolean', - 'description': nls.localize('workbench.quickOpen.preserveInput', "Controls whether the last typed input to Quick Open should be restored when opening it the next time."), - 'default': false - }, - 'workbench.settings.openDefaultSettings': { - 'type': 'boolean', - 'description': nls.localize('openDefaultSettings', "Controls whether opening settings also opens an editor showing all default settings."), - 'default': false - }, - 'workbench.settings.useSplitJSON': { - 'type': 'boolean', - 'markdownDescription': nls.localize('useSplitJSON', "Controls whether to use the split JSON editor when editing settings as JSON."), - 'default': false - }, - 'workbench.settings.openDefaultKeybindings': { - 'type': 'boolean', - 'description': nls.localize('openDefaultKeybindings', "Controls whether opening keybinding settings also opens an editor showing all default keybindings."), - 'default': true - }, - 'workbench.sideBar.location': { - 'type': 'string', - 'enum': ['left', 'right'], - 'default': 'left', - 'description': nls.localize('sideBarLocation', "Controls the location of the sidebar. It can either show on the left or right of the workbench.") - }, - 'workbench.panel.defaultLocation': { - 'type': 'string', - 'enum': ['bottom', 'right'], - 'default': 'bottom', - 'description': nls.localize('panelDefaultLocation', "Controls the default location of the panel (terminal, debug console, output, problems). It can either show at the bottom or on the right of the workbench.") - }, - 'workbench.statusBar.visible': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('statusBarVisibility', "Controls the visibility of the status bar at the bottom of the workbench.") - }, - 'workbench.activityBar.visible': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('activityBarVisibility', "Controls the visibility of the activity bar in the workbench.") - }, - 'workbench.view.alwaysShowHeaderActions': { - 'type': 'boolean', - 'default': false, - 'description': nls.localize('viewVisibility', "Controls the visibility of view header actions. View header actions may either be always visible, or only visible when that view is focused or hovered over.") - }, - 'workbench.fontAliasing': { - 'type': 'string', - 'enum': ['default', 'antialiased', 'none', 'auto'], - 'default': 'default', - 'description': - nls.localize('fontAliasing', "Controls font aliasing method in the workbench."), - 'enumDescriptions': [ - nls.localize('workbench.fontAliasing.default', "Sub-pixel font smoothing. On most non-retina displays this will give the sharpest text."), - nls.localize('workbench.fontAliasing.antialiased', "Smooth the font on the level of the pixel, as opposed to the subpixel. Can make the font appear lighter overall."), - nls.localize('workbench.fontAliasing.none', "Disables font smoothing. Text will show with jagged sharp edges."), - nls.localize('workbench.fontAliasing.auto', "Applies `default` or `antialiased` automatically based on the DPI of displays.") - ], - 'included': isMacintosh - }, - 'workbench.settings.enableNaturalLanguageSearch': { - 'type': 'boolean', - 'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings. The natural language search is provided by a Microsoft online service."), - 'default': true, - 'scope': ConfigurationScope.WINDOW, - 'tags': ['usesOnlineServices'] - }, - 'workbench.settings.settingsSearchTocBehavior': { - 'type': 'string', - 'enum': ['hide', 'filter'], - 'enumDescriptions': [ - nls.localize('settingsSearchTocBehavior.hide', "Hide the Table of Contents while searching."), - nls.localize('settingsSearchTocBehavior.filter', "Filter the Table of Contents to just categories that have matching settings. Clicking a category will filter the results to that category."), - ], - 'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the settings editor Table of Contents while searching."), - 'default': 'filter', - 'scope': ConfigurationScope.WINDOW - }, - 'workbench.settings.editor': { - 'type': 'string', - 'enum': ['ui', 'json'], - 'enumDescriptions': [ - nls.localize('settings.editor.ui', "Use the settings UI editor."), - nls.localize('settings.editor.json', "Use the JSON file editor."), - ], - 'description': nls.localize('settings.editor.desc', "Determines which settings editor to use by default."), - 'default': 'ui', - 'scope': ConfigurationScope.WINDOW - }, - 'workbench.enableExperiments': { - 'type': 'boolean', - 'description': nls.localize('workbench.enableExperiments', "Fetches experiments to run from a Microsoft online service."), - 'default': true, - 'tags': ['usesOnlineServices'] - }, - 'workbench.useExperimentalGridLayout': { - 'type': 'boolean', - 'description': nls.localize('workbench.useExperimentalGridLayout', "Enables the grid layout for the workbench. This setting may enable additional layout options for workbench components."), - 'default': false, - 'scope': ConfigurationScope.APPLICATION + // Workbench + registry.registerConfiguration({ + 'id': 'workbench', + 'order': 7, + 'title': nls.localize('workbenchConfigurationTitle', "Workbench"), + 'type': 'object', + 'properties': { + 'workbench.editor.showTabs': { + 'type': 'boolean', + 'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), + 'default': true + }, + 'workbench.editor.highlightModifiedTabs': { + 'type': 'boolean', + 'description': nls.localize('highlightModifiedTabs', "Controls whether a top border is drawn on modified (dirty) editor tabs or not."), + 'default': false + }, + 'workbench.editor.labelFormat': { + 'type': 'string', + 'enum': ['default', 'short', 'medium', 'long'], + 'enumDescriptions': [ + nls.localize('workbench.editor.labelFormat.default', "Show the name of the file. When tabs are enabled and two files have the same name in one group the distinguishing sections of each file's path are added. When tabs are disabled, the path relative to the workspace folder is shown if the editor is active."), + nls.localize('workbench.editor.labelFormat.short', "Show the name of the file followed by its directory name."), + nls.localize('workbench.editor.labelFormat.medium', "Show the name of the file followed by its path relative to the workspace folder."), + nls.localize('workbench.editor.labelFormat.long', "Show the name of the file followed by its absolute path.") + ], + 'default': 'default', + 'description': nls.localize({ + comment: ['This is the description for a setting. Values surrounded by parenthesis are not to be translated.'], + key: 'tabDescription' + }, "Controls the format of the label for an editor."), + }, + 'workbench.editor.tabCloseButton': { + 'type': 'string', + 'enum': ['left', 'right', 'off'], + 'default': 'right', + 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'.") + }, + 'workbench.editor.tabSizing': { + 'type': 'string', + 'enum': ['fit', 'shrink'], + 'default': 'fit', + 'enumDescriptions': [ + nls.localize('workbench.editor.tabSizing.fit', "Always keep tabs large enough to show the full editor label."), + nls.localize('workbench.editor.tabSizing.shrink', "Allow tabs to get smaller when the available space is not enough to show all tabs at once.") + ], + 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the sizing of editor tabs.") + }, + 'workbench.editor.focusRecentEditorAfterClose': { + 'type': 'boolean', + 'description': nls.localize('focusRecentEditorAfterClose', "Controls whether tabs are closed in most recently used order or from left to right."), + 'default': true + }, + 'workbench.editor.showIcons': { + 'type': 'boolean', + 'description': nls.localize('showIcons', "Controls whether opened editors should show with an icon or not. This requires an icon theme to be enabled as well."), + 'default': true + }, + 'workbench.editor.enablePreview': { + 'type': 'boolean', + 'description': nls.localize('enablePreview', "Controls whether opened editors show as preview. Preview editors are reused until they are pinned (e.g. via double click or editing) and show up with an italic font style."), + 'default': true + }, + 'workbench.editor.enablePreviewFromQuickOpen': { + 'type': 'boolean', + 'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether opened editors from Quick Open show as preview. Preview editors are reused until they are pinned (e.g. via double click or editing)."), + 'default': true + }, + 'workbench.editor.closeOnFileDelete': { + 'type': 'boolean', + 'description': nls.localize('closeOnFileDelete', "Controls whether editors showing a file that was opened during the session should close automatically when getting deleted or renamed by some other process. Disabling this will keep the editor open on such an event. Note that deleting from within the application will always close the editor and that dirty files will never close to preserve your data."), + 'default': false + }, + 'workbench.editor.openPositioning': { + 'type': 'string', + 'enum': ['left', 'right', 'first', 'last'], + 'default': 'right', + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorOpenPositioning' }, "Controls where editors open. Select `left` or `right` to open editors to the left or right of the currently active one. Select `first` or `last` to open editors independently from the currently active one.") + }, + 'workbench.editor.openSideBySideDirection': { + 'type': 'string', + 'enum': ['right', 'down'], + 'default': 'right', + 'markdownDescription': nls.localize('sideBySideDirection', "Controls the default direction of editors that are opened side by side (e.g. from the explorer). By default, editors will open on the right hand side of the currently active one. If changed to `down`, the editors will open below the currently active one.") + }, + 'workbench.editor.closeEmptyGroups': { + 'type': 'boolean', + 'description': nls.localize('closeEmptyGroups', "Controls the behavior of empty editor groups when the last tab in the group is closed. When enabled, empty groups will automatically close. When disabled, empty groups will remain part of the grid."), + 'default': true + }, + 'workbench.editor.revealIfOpen': { + 'type': 'boolean', + 'description': nls.localize('revealIfOpen', "Controls whether an editor is revealed in any of the visible groups if opened. If disabled, an editor will prefer to open in the currently active editor group. If enabled, an already opened editor will be revealed instead of opened again in the currently active editor group. Note that there are some cases where this setting is ignored, e.g. when forcing an editor to open in a specific group or to the side of the currently active group."), + 'default': false + }, + 'workbench.editor.swipeToNavigate': { + 'type': 'boolean', + 'description': nls.localize('swipeToNavigate', "Navigate between open files using three-finger swipe horizontally."), + 'default': false, + 'included': isMacintosh + }, + 'workbench.editor.restoreViewState': { + 'type': 'boolean', + 'description': nls.localize('restoreViewState', "Restores the last view state (e.g. scroll position) when re-opening files after they have been closed."), + 'default': true, + }, + 'workbench.editor.centeredLayoutAutoResize': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('centeredLayoutAutoResize', "Controls if the centered layout should automatically resize to maximum width when more than one group is open. Once only one group is open it will resize back to the original centered width.") + }, + 'workbench.commandPalette.history': { + 'type': 'number', + 'description': nls.localize('commandHistory', "Controls the number of recently used commands to keep in history for the command palette. Set to 0 to disable command history."), + 'default': 50 + }, + 'workbench.commandPalette.preserveInput': { + 'type': 'boolean', + 'description': nls.localize('preserveInput', "Controls whether the last typed input to the command palette should be restored when opening it the next time."), + 'default': false + }, + 'workbench.quickOpen.closeOnFocusLost': { + 'type': 'boolean', + 'description': nls.localize('closeOnFocusLost', "Controls whether Quick Open should close automatically once it loses focus."), + 'default': true + }, + 'workbench.quickOpen.preserveInput': { + 'type': 'boolean', + 'description': nls.localize('workbench.quickOpen.preserveInput', "Controls whether the last typed input to Quick Open should be restored when opening it the next time."), + 'default': false + }, + 'workbench.settings.openDefaultSettings': { + 'type': 'boolean', + 'description': nls.localize('openDefaultSettings', "Controls whether opening settings also opens an editor showing all default settings."), + 'default': false + }, + 'workbench.settings.useSplitJSON': { + 'type': 'boolean', + 'markdownDescription': nls.localize('useSplitJSON', "Controls whether to use the split JSON editor when editing settings as JSON."), + 'default': false + }, + 'workbench.settings.openDefaultKeybindings': { + 'type': 'boolean', + 'description': nls.localize('openDefaultKeybindings', "Controls whether opening keybinding settings also opens an editor showing all default keybindings."), + 'default': false + }, + 'workbench.sideBar.location': { + 'type': 'string', + 'enum': ['left', 'right'], + 'default': 'left', + 'description': nls.localize('sideBarLocation', "Controls the location of the sidebar. It can either show on the left or right of the workbench.") + }, + 'workbench.panel.defaultLocation': { + 'type': 'string', + 'enum': ['bottom', 'right'], + 'default': 'bottom', + 'description': nls.localize('panelDefaultLocation', "Controls the default location of the panel (terminal, debug console, output, problems). It can either show at the bottom or on the right of the workbench.") + }, + 'workbench.statusBar.visible': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('statusBarVisibility', "Controls the visibility of the status bar at the bottom of the workbench.") + }, + 'workbench.activityBar.visible': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('activityBarVisibility', "Controls the visibility of the activity bar in the workbench.") + }, + 'workbench.view.alwaysShowHeaderActions': { + 'type': 'boolean', + 'default': false, + 'description': nls.localize('viewVisibility', "Controls the visibility of view header actions. View header actions may either be always visible, or only visible when that view is focused or hovered over.") + }, + 'workbench.fontAliasing': { + 'type': 'string', + 'enum': ['default', 'antialiased', 'none', 'auto'], + 'default': 'default', + 'description': + nls.localize('fontAliasing', "Controls font aliasing method in the workbench."), + 'enumDescriptions': [ + nls.localize('workbench.fontAliasing.default', "Sub-pixel font smoothing. On most non-retina displays this will give the sharpest text."), + nls.localize('workbench.fontAliasing.antialiased', "Smooth the font on the level of the pixel, as opposed to the subpixel. Can make the font appear lighter overall."), + nls.localize('workbench.fontAliasing.none', "Disables font smoothing. Text will show with jagged sharp edges."), + nls.localize('workbench.fontAliasing.auto', "Applies `default` or `antialiased` automatically based on the DPI of displays.") + ], + 'included': isMacintosh + }, + 'workbench.settings.enableNaturalLanguageSearch': { + 'type': 'boolean', + 'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings. The natural language search is provided by a Microsoft online service."), + 'default': true, + 'scope': ConfigurationScope.WINDOW, + 'tags': ['usesOnlineServices'] + }, + 'workbench.settings.settingsSearchTocBehavior': { + 'type': 'string', + 'enum': ['hide', 'filter'], + 'enumDescriptions': [ + nls.localize('settingsSearchTocBehavior.hide', "Hide the Table of Contents while searching."), + nls.localize('settingsSearchTocBehavior.filter', "Filter the Table of Contents to just categories that have matching settings. Clicking a category will filter the results to that category."), + ], + 'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the settings editor Table of Contents while searching."), + 'default': 'filter', + 'scope': ConfigurationScope.WINDOW + }, + 'workbench.settings.editor': { + 'type': 'string', + 'enum': ['ui', 'json'], + 'enumDescriptions': [ + nls.localize('settings.editor.ui', "Use the settings UI editor."), + nls.localize('settings.editor.json', "Use the JSON file editor."), + ], + 'description': nls.localize('settings.editor.desc', "Determines which settings editor to use by default."), + 'default': 'ui', + 'scope': ConfigurationScope.WINDOW + }, + 'workbench.enableExperiments': { + 'type': 'boolean', + 'description': nls.localize('workbench.enableExperiments', "Fetches experiments to run from a Microsoft online service."), + 'default': true, + 'tags': ['usesOnlineServices'] + }, + 'workbench.useExperimentalGridLayout': { + 'type': 'boolean', + 'description': nls.localize('workbench.useExperimentalGridLayout', "Enables the grid layout for the workbench. This setting may enable additional layout options for workbench components."), + 'default': false, + 'scope': ConfigurationScope.APPLICATION + } } - } -}); + }); -// Configuration: Zen Mode -configurationRegistry.registerConfiguration({ - 'id': 'zenMode', - 'order': 9, - 'title': nls.localize('zenModeConfigurationTitle', "Zen Mode"), - 'type': 'object', - 'properties': { - 'zenMode.fullScreen': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('zenMode.fullScreen', "Controls whether turning on Zen Mode also puts the workbench into full screen mode.") - }, - 'zenMode.centerLayout': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('zenMode.centerLayout', "Controls whether turning on Zen Mode also centers the layout.") - }, - 'zenMode.hideTabs': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('zenMode.hideTabs', "Controls whether turning on Zen Mode also hides workbench tabs.") - }, - 'zenMode.hideStatusBar': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('zenMode.hideStatusBar', "Controls whether turning on Zen Mode also hides the status bar at the bottom of the workbench.") - }, - 'zenMode.hideActivityBar': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('zenMode.hideActivityBar', "Controls whether turning on Zen Mode also hides the activity bar at the left of the workbench.") - }, - 'zenMode.hideLineNumbers': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('zenMode.hideLineNumbers', "Controls whether turning on Zen Mode also hides the editor line numbers.") - }, - 'zenMode.restore': { - 'type': 'boolean', - 'default': false, - 'description': nls.localize('zenMode.restore', "Controls whether a window should restore to zen mode if it was exited in zen mode.") + // Window + + let windowTitleDescription = nls.localize('windowTitle', "Controls the window title based on the active editor. Variables are substituted based on the context:"); + windowTitleDescription += [ + nls.localize('activeEditorShort', "`\${activeEditorShort}`: the file name (e.g. myFile.txt)."), + nls.localize('activeEditorMedium', "`\${activeEditorMedium}`: the path of the file relative to the workspace folder (e.g. myFolder/myFileFolder/myFile.txt)."), + nls.localize('activeEditorLong', "`\${activeEditorLong}`: the full path of the file (e.g. /Users/Development/myFolder/myFileFolder/myFile.txt)."), + nls.localize('activeFolderShort', "`\${activeFolderShort}`: the name of the folder the file is contained in (e.g. myFileFolder)."), + nls.localize('activeFolderMedium', "`\${activeFolderMedium}`: the path of the folder the file is contained in, relative to the workspace folder (e.g. myFolder/myFileFolder)."), + nls.localize('activeFolderLong', "`\${activeFolderLong}`: the full path of the folder the file is contained in (e.g. /Users/Development/myFolder/myFileFolder)."), + nls.localize('folderName', "`\${folderName}`: name of the workspace folder the file is contained in (e.g. myFolder)."), + nls.localize('folderPath', "`\${folderPath}`: file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)."), + nls.localize('rootName', "`\${rootName}`: name of the workspace (e.g. myFolder or myWorkspace)."), + nls.localize('rootPath', "`\${rootPath}`: file path of the workspace (e.g. /Users/Development/myWorkspace)."), + nls.localize('appName', "`\${appName}`: e.g. VS Code."), + nls.localize('dirty', "`\${dirty}`: a dirty indicator if the active editor is dirty."), + nls.localize('separator', "`\${separator}`: a conditional separator (\" - \") that only shows when surrounded by variables with values or static text.") + ].join('\n- '); // intentionally concatenated to not produce a string that is too long for translations + + registry.registerConfiguration({ + 'id': 'window', + 'order': 8, + 'title': nls.localize('windowConfigurationTitle', "Window"), + 'type': 'object', + 'properties': { + 'window.title': { + 'type': 'string', + 'default': isMacintosh ? '${activeEditorShort}${separator}${rootName}' : '${dirty}${activeEditorShort}${separator}${rootName}${separator}${appName}', + 'markdownDescription': windowTitleDescription + } } - } -}); \ No newline at end of file + }); + + // Zen Mode + registry.registerConfiguration({ + 'id': 'zenMode', + 'order': 9, + 'title': nls.localize('zenModeConfigurationTitle', "Zen Mode"), + 'type': 'object', + 'properties': { + 'zenMode.fullScreen': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('zenMode.fullScreen', "Controls whether turning on Zen Mode also puts the workbench into full screen mode.") + }, + 'zenMode.centerLayout': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('zenMode.centerLayout', "Controls whether turning on Zen Mode also centers the layout.") + }, + 'zenMode.hideTabs': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('zenMode.hideTabs', "Controls whether turning on Zen Mode also hides workbench tabs.") + }, + 'zenMode.hideStatusBar': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('zenMode.hideStatusBar', "Controls whether turning on Zen Mode also hides the status bar at the bottom of the workbench.") + }, + 'zenMode.hideActivityBar': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('zenMode.hideActivityBar', "Controls whether turning on Zen Mode also hides the activity bar at the left of the workbench.") + }, + 'zenMode.hideLineNumbers': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('zenMode.hideLineNumbers', "Controls whether turning on Zen Mode also hides the editor line numbers.") + }, + 'zenMode.restore': { + 'type': 'boolean', + 'default': false, + 'description': nls.localize('zenMode.restore', "Controls whether a window should restore to zen mode if it was exited in zen mode.") + } + } + }); +})(); \ No newline at end of file diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts new file mode 100644 index 0000000000..b268bfa885 --- /dev/null +++ b/src/vs/workbench/browser/workbench.ts @@ -0,0 +1,564 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/workbench/browser/style'; + +import { localize } from 'vs/nls'; +import { setFileNameComparer } from 'vs/base/common/comparers'; +import { Event, Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event'; +import { addClasses, addClass, removeClasses } from 'vs/base/browser/dom'; +import { runWhenIdle, IdleValue } from 'vs/base/common/async'; +import { getZoomLevel } from 'vs/base/browser/browser'; +import { mark } from 'vs/base/common/performance'; +import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { isWindows, isLinux } from 'vs/base/common/platform'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; +import { IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions'; +import { getServices } from 'vs/platform/instantiation/common/extensions'; +import { Position, Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; +import { LifecyclePhase, ILifecycleService, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { NotificationService } from 'vs/workbench/services/notification/common/notificationService'; +import { NotificationsCenter } from 'vs/workbench/browser/parts/notifications/notificationsCenter'; +import { NotificationsAlerts } from 'vs/workbench/browser/parts/notifications/notificationsAlerts'; +import { NotificationsStatus } from 'vs/workbench/browser/parts/notifications/notificationsStatus'; +import { registerNotificationCommands } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; +import { NotificationsToasts } from 'vs/workbench/browser/parts/notifications/notificationsToasts'; +import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { setARIAContainer } from 'vs/base/browser/ui/aria/aria'; +import { restoreFontInfo, readFontInfo, saveFontInfo } from 'vs/editor/browser/config/configuration'; +import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; +import { ILogService } from 'vs/platform/log/common/log'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { WorkbenchContextKeysHandler } from 'vs/workbench/browser/contextkeys'; +import { coalesce } from 'vs/base/common/arrays'; +import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; +import { Layout } from 'vs/workbench/browser/layout'; + +// {{SQL CARBON EDIT}} +import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; +import { ConnectionManagementService } from 'sql/platform/connection/common/connectionManagementService'; +import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService'; +import { ConnectionDialogService } from 'sql/workbench/services/connection/browser/connectionDialogService'; +import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService'; +import { ErrorMessageService } from 'sql/workbench/services/errorMessage/browser/errorMessageService'; +import { ServerGroupController } from 'sql/workbench/services/serverGroup/browser/serverGroupController'; +import { IServerGroupController } from 'sql/platform/serverGroup/common/serverGroupController'; +import { IAngularEventingService } from 'sql/platform/angularEventing/common/angularEventingService'; +import { AngularEventingService } from 'sql/platform/angularEventing/node/angularEventingService'; +import { ICapabilitiesService, CapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; +import { ICredentialsService, CredentialsService } from 'sql/platform/credentials/common/credentialsService'; +import { ISerializationService, SerializationService } from 'sql/platform/serialization/common/serializationService'; +import { IMetadataService, MetadataService } from 'sql/platform/metadata/common/metadataService'; +import { IObjectExplorerService, ObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService'; +import { ITaskService, TaskService } from 'sql/platform/taskHistory/common/taskService'; +import { IQueryModelService } from 'sql/platform/query/common/queryModel'; +import { QueryModelService } from 'sql/platform/query/common/queryModelService'; +import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService'; +import { QueryEditorService } from 'sql/workbench/services/queryEditor/browser/queryEditorService'; +import { IQueryManagementService, QueryManagementService } from 'sql/platform/query/common/queryManagement'; +import { IEditorDescriptorService, EditorDescriptorService } from 'sql/workbench/services/queryEditor/common/editorDescriptorService'; +import { IScriptingService, ScriptingService } from 'sql/platform/scripting/common/scriptingService'; +import { IAdminService, AdminService } from 'sql/workbench/services/admin/common/adminService'; +import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces'; +import { JobManagementService } from 'sql/platform/jobManagement/common/jobManagementService'; +import { IDacFxService, DacFxService } from 'sql/platform/dacfx/common/dacFxService'; +import { IBackupService } from 'sql/platform/backup/common/backupService'; +import { BackupService } from 'sql/platform/backup/common/backupServiceImp'; +import { IBackupUiService } from 'sql/workbench/services/backup/common/backupUiService'; +import { BackupUiService } from 'sql/workbench/services/backup/browser/backupUiService'; +import { IRestoreDialogController, IRestoreService } from 'sql/platform/restore/common/restoreService'; +import { RestoreService, RestoreDialogController } from 'sql/platform/restore/common/restoreServiceImpl'; +import { INewDashboardTabDialogService } from 'sql/workbench/services/dashboard/common/newDashboardTabDialog'; +import { NewDashboardTabDialogService } from 'sql/workbench/services/dashboard/browser/newDashboardTabDialogService'; +import { IFileBrowserService } from 'sql/platform/fileBrowser/common/interfaces'; +import { FileBrowserService } from 'sql/platform/fileBrowser/common/fileBrowserService'; +import { IFileBrowserDialogController } from 'sql/workbench/services/fileBrowser/common/fileBrowserDialogController'; +import { FileBrowserDialogController } from 'sql/workbench/services/fileBrowser/browser/fileBrowserDialogController'; +import { IInsightsDialogService } from 'sql/workbench/services/insights/common/insightsDialogService'; +import { InsightsDialogService } from 'sql/workbench/services/insights/browser/insightsDialogService'; +import { IAccountManagementService } from 'sql/platform/accountManagement/common/interfaces'; +import { AccountManagementService } from 'sql/workbench/services/accountManagement/browser/accountManagementService'; +import { IProfilerService } from 'sql/workbench/services/profiler/common/interfaces'; +import { ProfilerService } from 'sql/workbench/services/profiler/common/profilerService'; +import { ISqlOAuthService } from 'sql/platform/oAuth/common/sqlOAuthService'; +import { SqlOAuthService } from 'sql/platform/oAuth/electron-browser/sqlOAuthServiceImpl'; +import { IClipboardService as sqlIClipboardService } from 'sql/platform/clipboard/common/clipboardService'; +import { ClipboardService as sqlClipboardService } from 'sql/platform/clipboard/electron-browser/clipboardService'; +import { AccountPickerService } from 'sql/platform/accountManagement/browser/accountPickerService'; +import { IAccountPickerService } from 'sql/platform/accountManagement/common/accountPicker'; +import { IResourceProviderService } from 'sql/workbench/services/resourceProvider/common/resourceProviderService'; +import { ResourceProviderService } from 'sql/workbench/services/resourceProvider/browser/resourceProviderService'; +import { IDashboardViewService } from 'sql/platform/dashboard/common/dashboardViewService'; +import { DashboardViewService } from 'sql/platform/dashboard/common/dashboardViewServiceImpl'; +import { IModelViewService } from 'sql/platform/modelComponents/common/modelViewService'; +import { ModelViewService } from 'sql/platform/modelComponents/common/modelViewServiceImpl'; +import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService'; +import { DashboardService } from 'sql/platform/dashboard/browser/dashboardServiceImpl'; +import { NotebookService } from 'sql/workbench/services/notebook/common/notebookServiceImpl'; +import { INotebookService } from 'sql/workbench/services/notebook/common/notebookService'; +import { ICommandLineProcessing } from 'sql/workbench/services/commandLine/common/commandLine'; +import { CommandLineService } from 'sql/workbench/services/commandLine/common/commandLineService'; +import { OEShimService, IOEShimService } from 'sql/parts/objectExplorer/common/objectExplorerViewTreeShim'; +// {{SQL CARBON EDIT}} - End + +export class Workbench extends Layout { + + private readonly _onShutdown = this._register(new Emitter()); + get onShutdown(): Event { return this._onShutdown.event; } + + private readonly _onWillShutdown = this._register(new Emitter()); + get onWillShutdown(): Event { return this._onWillShutdown.event; } + + constructor( + parent: HTMLElement, + private readonly serviceCollection: ServiceCollection, + logService: ILogService + ) { + super(parent); + + this.registerErrorHandler(logService); + } + + private registerErrorHandler(logService: ILogService): void { + + // Listen on unhandled rejection events + window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => { + + // See https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent + onUnexpectedError(event.reason); + + // Prevent the printing of this event to the console + event.preventDefault(); + }); + + // Install handler for unexpected errors + setUnexpectedErrorHandler(error => this.handleUnexpectedError(error, logService)); + + // Inform user about loading issues from the loader + (window).require.config({ + onError: err => { + if (err.errorCode === 'load') { + onUnexpectedError(new Error(localize('loaderErrorNative', "Failed to load a required file. Please restart the application to try again. Details: {0}", JSON.stringify(err)))); + } + } + }); + } + + private previousUnexpectedError: { message: string | undefined, time: number } = { message: undefined, time: 0 }; + private handleUnexpectedError(error: any, logService: ILogService): void { + const message = toErrorMessage(error, true); + if (!message) { + return; + } + + const now = Date.now(); + if (message === this.previousUnexpectedError.message && now - this.previousUnexpectedError.time <= 1000) { + return; // Return if error message identical to previous and shorter than 1 second + } + + this.previousUnexpectedError.time = now; + this.previousUnexpectedError.message = message; + + // Log it + logService.error(message); + } + + startup(): IInstantiationService { + try { + + // Configure emitter leak warning threshold + setGlobalLeakWarningThreshold(175); + + // Setup Intl for comparers + setFileNameComparer(new IdleValue(() => { + const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); + return { + collator: collator, + collatorIsNumeric: collator.resolvedOptions().numeric + }; + })); + + // ARIA + setARIAContainer(document.body); + + // Services + const instantiationService = this.initServices(this.serviceCollection); + + instantiationService.invokeFunction(accessor => { + const lifecycleService = accessor.get(ILifecycleService); + const storageService = accessor.get(IStorageService); + const configurationService = accessor.get(IConfigurationService); + + // Layout + this.initLayout(accessor); + + // Registries + this.startRegistries(accessor); + + // Context Keys + this._register(instantiationService.createInstance(WorkbenchContextKeysHandler)); + + // Register Listeners + this.registerListeners(lifecycleService, storageService, configurationService); + + // Render Workbench + this.renderWorkbench(instantiationService, accessor.get(INotificationService) as NotificationService, storageService, configurationService); + + // Workbench Layout + this.createWorkbenchLayout(instantiationService); + + // Layout + this.layout(); + + // Restore + this.restoreWorkbench(accessor.get(IEditorService), accessor.get(IEditorGroupsService), accessor.get(IViewletService), accessor.get(IPanelService), accessor.get(ILogService), lifecycleService).then(undefined, error => onUnexpectedError(error)); + }); + + return instantiationService; + } catch (error) { + onUnexpectedError(error); + + throw error; // rethrow because this is a critical issue we cannot handle properly here + } + } + + // {{SQL CARBON EDIT}} + /* + private sendUsageEvents(telemetryService: ITelemetryService): void { + const dailyLastUseDate = Date.parse(this.storageService.get('telemetry.dailyLastUseDate', StorageScope.GLOBAL, '0')); + const weeklyLastUseDate = Date.parse(this.storageService.get('telemetry.weeklyLastUseDate', StorageScope.GLOBAL, '0')); + const monthlyLastUseDate = Date.parse(this.storageService.get('telemetry.monthlyLastUseDate', StorageScope.GLOBAL, '0')); + + let today = new Date().toUTCString(); + + // daily user event + if (this.diffInDays(Date.parse(today), dailyLastUseDate) >= 1) { + // daily first use + telemetryService.publicLog('telemetry.dailyFirstUse', { dailyFirstUse: true }); + this.storageService.store('telemetry.dailyLastUseDate', today, StorageScope.GLOBAL); + } + + // weekly user event + if (this.diffInDays(Date.parse(today), weeklyLastUseDate) >= 7) { + // weekly first use + telemetryService.publicLog('telemetry.weeklyFirstUse', { weeklyFirstUse: true }); + this.storageService.store('telemetry.weeklyLastUseDate', today, StorageScope.GLOBAL); + } + + // monthly user events + if (this.diffInDays(Date.parse(today), monthlyLastUseDate) >= 30) { + telemetryService.publicLog('telemetry.monthlyUse', { monthlyFirstUse: true }); + this.storageService.store('telemetry.monthlyLastUseDate', today, StorageScope.GLOBAL); + } + } + + // {{SQL CARBON EDIT}} + private diffInDays(nowDate: number, lastUseDate: number): number { + return (nowDate - lastUseDate) / (24 * 3600 * 1000); + } + */ + + private initServices(serviceCollection: ServiceCollection): IInstantiationService { + + // Layout Service + serviceCollection.set(IWorkbenchLayoutService, this); + + // + // NOTE: DO NOT ADD ANY OTHER SERVICE INTO THE COLLECTION HERE. + // INSTEAD, CONTRIBUTE IT VIA WORKBENCH.MAIN.TS + // + + // All Contributed Services + const contributedServices = getServices(); + for (let contributedService of contributedServices) { + serviceCollection.set(contributedService.id, contributedService.descriptor); + } + + const instantiationService = new InstantiationService(serviceCollection, true); + + // {{SQL CARBON EDIT}} + // SQL Tools services + serviceCollection.set(IDashboardService, instantiationService.createInstance(DashboardService)); + serviceCollection.set(IDashboardViewService, instantiationService.createInstance(DashboardViewService)); + serviceCollection.set(IModelViewService, instantiationService.createInstance(ModelViewService)); + serviceCollection.set(IAngularEventingService, instantiationService.createInstance(AngularEventingService)); + serviceCollection.set(INewDashboardTabDialogService, instantiationService.createInstance(NewDashboardTabDialogService)); + serviceCollection.set(ISqlOAuthService, instantiationService.createInstance(SqlOAuthService)); + serviceCollection.set(sqlIClipboardService, instantiationService.createInstance(sqlClipboardService)); + serviceCollection.set(ICapabilitiesService, instantiationService.createInstance(CapabilitiesService)); + serviceCollection.set(IErrorMessageService, instantiationService.createInstance(ErrorMessageService)); + serviceCollection.set(IConnectionDialogService, instantiationService.createInstance(ConnectionDialogService)); + serviceCollection.set(IServerGroupController, instantiationService.createInstance(ServerGroupController)); + serviceCollection.set(ICredentialsService, instantiationService.createInstance(CredentialsService)); + serviceCollection.set(IResourceProviderService, instantiationService.createInstance(ResourceProviderService)); + serviceCollection.set(IAccountManagementService, instantiationService.createInstance(AccountManagementService, undefined)); + serviceCollection.set(IConnectionManagementService, instantiationService.createInstance(ConnectionManagementService, undefined, undefined)); + serviceCollection.set(ISerializationService, instantiationService.createInstance(SerializationService)); + serviceCollection.set(IQueryManagementService, instantiationService.createInstance(QueryManagementService)); + serviceCollection.set(IQueryModelService, instantiationService.createInstance(QueryModelService)); + serviceCollection.set(IQueryEditorService, instantiationService.createInstance(QueryEditorService)); + serviceCollection.set(IEditorDescriptorService, instantiationService.createInstance(EditorDescriptorService)); + serviceCollection.set(ITaskService, instantiationService.createInstance(TaskService)); + serviceCollection.set(IMetadataService, instantiationService.createInstance(MetadataService)); + serviceCollection.set(IObjectExplorerService, instantiationService.createInstance(ObjectExplorerService)); + serviceCollection.set(IOEShimService, instantiationService.createInstance(OEShimService)); + serviceCollection.set(IScriptingService, instantiationService.createInstance(ScriptingService)); + serviceCollection.set(IAdminService, instantiationService.createInstance(AdminService)); + serviceCollection.set(IJobManagementService, instantiationService.createInstance(JobManagementService)); + serviceCollection.set(IBackupService, instantiationService.createInstance(BackupService)); + serviceCollection.set(IBackupUiService, instantiationService.createInstance(BackupUiService)); + serviceCollection.set(IRestoreService, instantiationService.createInstance(RestoreService)); + serviceCollection.set(IRestoreDialogController, instantiationService.createInstance(RestoreDialogController)); + serviceCollection.set(IFileBrowserService, instantiationService.createInstance(FileBrowserService)); + serviceCollection.set(IFileBrowserDialogController, instantiationService.createInstance(FileBrowserDialogController)); + serviceCollection.set(IInsightsDialogService, instantiationService.createInstance(InsightsDialogService)); + serviceCollection.set(INotebookService, instantiationService.createInstance(NotebookService)); + serviceCollection.set(IAccountPickerService, instantiationService.createInstance(AccountPickerService)); + serviceCollection.set(IProfilerService, instantiationService.createInstance(ProfilerService)); + serviceCollection.set(ICommandLineProcessing, instantiationService.createInstance(CommandLineService)); + serviceCollection.set(IDacFxService, instantiationService.createInstance(DacFxService)); + + // {{SQL CARBON EDIT}} - End + + // Wrap up + instantiationService.invokeFunction(accessor => { + const lifecycleService = accessor.get(ILifecycleService); + + // TODO@Ben TODO@Sandeep debt around cyclic dependencies + const fileService = accessor.get(IFileService); + const configurationService = accessor.get(IConfigurationService) as any; + + if (typeof configurationService.acquireFileService === 'function') { + configurationService.acquireFileService(fileService); + } + + if (typeof configurationService.acquireInstantiationService === 'function') { + configurationService.acquireInstantiationService(instantiationService); + } + + // Signal to lifecycle that services are set + lifecycleService.phase = LifecyclePhase.Ready; + }); + + return instantiationService; + } + + private startRegistries(accessor: ServicesAccessor): void { + Registry.as(ActionBarExtensions.Actionbar).start(accessor); + Registry.as(WorkbenchExtensions.Workbench).start(accessor); + Registry.as(EditorExtensions.EditorInputFactories).start(accessor); + } + + private registerListeners( + lifecycleService: ILifecycleService, + storageService: IStorageService, + configurationService: IConfigurationService + ): void { + + // Lifecycle + this._register(lifecycleService.onWillShutdown(event => this._onWillShutdown.fire(event))); + this._register(lifecycleService.onShutdown(() => { + this._onShutdown.fire(); + this.dispose(); + })); + + // Storage + this._register(storageService.onWillSaveState(() => saveFontInfo(storageService))); + + // Configuration changes + this._register(configurationService.onDidChangeConfiguration(() => this.setFontAliasing(configurationService))); + } + + private fontAliasing: 'default' | 'antialiased' | 'none' | 'auto'; + private setFontAliasing(configurationService: IConfigurationService) { + const aliasing = configurationService.getValue<'default' | 'antialiased' | 'none' | 'auto'>('workbench.fontAliasing'); + if (this.fontAliasing === aliasing) { + return; + } + + this.fontAliasing = aliasing; + + // Remove all + const fontAliasingValues: (typeof aliasing)[] = ['antialiased', 'none', 'auto']; + removeClasses(this.container, ...fontAliasingValues.map(value => `monaco-font-aliasing-${value}`)); + + // Add specific + if (fontAliasingValues.some(option => option === aliasing)) { + addClass(this.container, `monaco-font-aliasing-${aliasing}`); + } + } + + private renderWorkbench(instantiationService: IInstantiationService, notificationService: NotificationService, storageService: IStorageService, configurationService: IConfigurationService): void { + + // State specific classes + const platformClass = isWindows ? 'windows' : isLinux ? 'linux' : 'mac'; + const workbenchClasses = coalesce([ + 'monaco-workbench', + platformClass, + this.state.sideBar.hidden ? 'nosidebar' : undefined, + this.state.panel.hidden ? 'nopanel' : undefined, + this.state.statusBar.hidden ? 'nostatusbar' : undefined, + this.state.fullscreen ? 'fullscreen' : undefined + ]); + + addClasses(this.container, ...workbenchClasses); + addClasses(document.body, platformClass); // used by our fonts + + // Apply font aliasing + this.setFontAliasing(configurationService); + + // Warm up font cache information before building up too many dom elements + restoreFontInfo(storageService); + readFontInfo(BareFontInfo.createFromRawSettings(configurationService.getValue('editor'), getZoomLevel())); + + // Create Parts + [ + { id: Parts.TITLEBAR_PART, role: 'contentinfo', classes: ['titlebar'] }, + { id: Parts.ACTIVITYBAR_PART, role: 'navigation', classes: ['activitybar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] }, + { id: Parts.SIDEBAR_PART, role: 'complementary', classes: ['sidebar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] }, + { id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.state.editor.restoreEditors } }, + { id: Parts.PANEL_PART, role: 'complementary', classes: ['panel', this.state.panel.position === Position.BOTTOM ? 'bottom' : 'right'] }, + { id: Parts.STATUSBAR_PART, role: 'contentinfo', classes: ['statusbar'] } + ].forEach(({ id, role, classes, options }) => { + const partContainer = this.createPart(id, role, classes); + + if (!configurationService.getValue('workbench.useExperimentalGridLayout')) { + // TODO@Ben cleanup once moved to grid + // Insert all workbench parts at the beginning. Issue #52531 + // This is primarily for the title bar to allow overriding -webkit-app-region + this.container.insertBefore(partContainer, this.container.lastChild); + } + + this.getPart(id).create(partContainer, options); + }); + + // Notification Handlers + this.createNotificationsHandlers(instantiationService, notificationService); + + // Add Workbench to DOM + this.parent.appendChild(this.container); + } + + private createPart(id: string, role: string, classes: string[]): HTMLElement { + const part = document.createElement('div'); + addClasses(part, 'part', ...classes); + part.id = id; + part.setAttribute('role', role); + + return part; + } + + private createNotificationsHandlers(instantiationService: IInstantiationService, notificationService: NotificationService): void { + + // Instantiate Notification components + const notificationsCenter = this._register(instantiationService.createInstance(NotificationsCenter, this.container, notificationService.model)); + const notificationsToasts = this._register(instantiationService.createInstance(NotificationsToasts, this.container, notificationService.model)); + this._register(instantiationService.createInstance(NotificationsAlerts, notificationService.model)); + const notificationsStatus = instantiationService.createInstance(NotificationsStatus, notificationService.model); + + // Visibility + this._register(notificationsCenter.onDidChangeVisibility(() => { + notificationsStatus.update(notificationsCenter.isVisible); + notificationsToasts.update(notificationsCenter.isVisible); + })); + + // Register Commands + registerNotificationCommands(notificationsCenter, notificationsToasts); + } + + private restoreWorkbench( + editorService: IEditorService, + editorGroupService: IEditorGroupsService, + viewletService: IViewletService, + panelService: IPanelService, + logService: ILogService, + lifecycleService: ILifecycleService + ): Promise { + const restorePromises: Promise[] = []; + + // Restore editors + mark('willRestoreEditors'); + restorePromises.push(editorGroupService.whenRestored.then(() => { + + function openEditors(editors: IResourceEditor[], editorService: IEditorService) { + if (editors.length) { + return editorService.openEditors(editors); + } + + return Promise.resolve(undefined); + } + + if (Array.isArray(this.state.editor.editorsToOpen)) { + return openEditors(this.state.editor.editorsToOpen, editorService); + } + + return this.state.editor.editorsToOpen.then(editors => openEditors(editors, editorService)); + }).then(() => mark('didRestoreEditors'))); + + // Restore Sidebar + if (this.state.sideBar.viewletToRestore) { + mark('willRestoreViewlet'); + restorePromises.push(viewletService.openViewlet(this.state.sideBar.viewletToRestore) + .then(viewlet => { + if (!viewlet) { + return viewletService.openViewlet(viewletService.getDefaultViewletId()); // fallback to default viewlet as needed + } + + return viewlet; + }) + .then(() => mark('didRestoreViewlet'))); + } + + // Restore Panel + if (this.state.panel.panelToRestore) { + mark('willRestorePanel'); + panelService.openPanel(this.state.panel.panelToRestore); + mark('didRestorePanel'); + } + + // Restore Zen Mode + if (this.state.zenMode.restore) { + this.toggleZenMode(true, true); + } + + // Restore Editor Center Mode + if (this.state.editor.restoreCentered) { + this.centerEditorLayout(true); + } + + // Emit a warning after 10s if restore does not complete + const restoreTimeoutHandle = setTimeout(() => logService.warn('Workbench did not finish loading in 10 seconds, that might be a problem that should be reported.'), 10000); + + return Promise.all(restorePromises) + .then(() => clearTimeout(restoreTimeoutHandle)) + .catch(error => onUnexpectedError(error)) + .finally(() => { + + // Set lifecycle phase to `Restored` + lifecycleService.phase = LifecyclePhase.Restored; + + // Set lifecycle phase to `Eventually` after a short delay and when idle (min 2.5sec, max 5sec) + setTimeout(() => { + this._register(runWhenIdle(() => { + lifecycleService.phase = LifecyclePhase.Eventually; + }, 2500)); + }, 2500); + + // Telemetry: startup metrics + mark('didStartWorkbench'); + }); + } +} diff --git a/src/vs/workbench/buildfile.js b/src/vs/workbench/buildfile.js index 3d8cf84e47..11cb83e6cd 100644 --- a/src/vs/workbench/buildfile.js +++ b/src/vs/workbench/buildfile.js @@ -5,29 +5,29 @@ 'use strict'; function createModuleDescription(name, exclude) { - var result = {}; - var excludes = ['vs/css', 'vs/nls']; + const result = {}; + + let excludes = ['vs/css', 'vs/nls']; result.name = name; if (Array.isArray(exclude) && exclude.length > 0) { excludes = excludes.concat(exclude); } result.exclude = excludes; + return result; } exports.collectModules = function () { - var modules = [ - createModuleDescription('vs/workbench/parts/output/common/outputLinkComputer', ['vs/base/common/worker/simpleWorker', 'vs/editor/common/services/editorSimpleWorker']), + return [ + createModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer', ['vs/base/common/worker/simpleWorker', 'vs/editor/common/services/editorSimpleWorker']), - createModuleDescription('vs/workbench/parts/debug/node/telemetryApp', []), + createModuleDescription('vs/workbench/contrib/debug/node/telemetryApp', []), createModuleDescription('vs/workbench/services/search/node/searchApp', []), - + createModuleDescription('vs/workbench/services/files/node/watcher/unix/watcherApp', []), createModuleDescription('vs/workbench/services/files/node/watcher/nsfw/watcherApp', []), createModuleDescription('vs/workbench/services/extensions/node/extensionHostProcess', []), ]; - - return modules; }; \ No newline at end of file diff --git a/src/vs/workbench/common/actions.ts b/src/vs/workbench/common/actions.ts index b98d9a0380..734678a6dd 100644 --- a/src/vs/workbench/common/actions.ts +++ b/src/vs/workbench/common/actions.ts @@ -44,7 +44,7 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR KeybindingsRegistry.registerKeybindingRule({ id: descriptor.id, weight: weight, - when: descriptor.keybindingContext, + when: (descriptor.keybindingContext || when ? ContextKeyExpr.and(descriptor.keybindingContext, when) : null), primary: keybindings ? keybindings.primary : 0, secondary: keybindings && keybindings.secondary, win: keybindings && keybindings.win, @@ -54,7 +54,7 @@ Registry.add(Extensions.WorkbenchActions, new class implements IWorkbenchActionR // menu item // TODO@Rob slightly weird if-check required because of - // https://github.com/Microsoft/vscode/blob/master/src/vs/workbench/parts/search/electron-browser/search.contribution.ts#L266 + // https://github.com/Microsoft/vscode/blob/master/src/vs/workbench/contrib/search/electron-browser/search.contribution.ts#L266 if (descriptor.label) { let idx = alias.indexOf(': '); diff --git a/src/vs/workbench/common/activity.ts b/src/vs/workbench/common/activity.ts index 36d9b2f132..f4b5194e79 100644 --- a/src/vs/workbench/common/activity.ts +++ b/src/vs/workbench/common/activity.ts @@ -27,7 +27,7 @@ export interface IGlobalActivityRegistry { export class GlobalActivityRegistry implements IGlobalActivityRegistry { - private activityDescriptors = new Set>(); + private readonly activityDescriptors = new Set>(); registerActivity(descriptor: IConstructorSignature0): void { this.activityDescriptors.add(descriptor); @@ -36,6 +36,7 @@ export class GlobalActivityRegistry implements IGlobalActivityRegistry { getActivities(): IConstructorSignature0[] { const result: IConstructorSignature0[] = []; this.activityDescriptors.forEach(d => result.push(d)); + return result; } } diff --git a/src/vs/workbench/common/component.ts b/src/vs/workbench/common/component.ts index 1e7b79f76d..c956882eaf 100644 --- a/src/vs/workbench/common/component.ts +++ b/src/vs/workbench/common/component.ts @@ -9,11 +9,10 @@ import { Themable } from 'vs/workbench/common/theme'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; export class Component extends Themable { - private id: string; - private memento: Memento; + private readonly memento: Memento; constructor( - id: string, + private readonly id: string, themeService: IThemeService, storageService: IStorageService ) { diff --git a/src/vs/workbench/common/contextkeys.ts b/src/vs/workbench/common/contextkeys.ts new file mode 100644 index 0000000000..368ae3dd23 --- /dev/null +++ b/src/vs/workbench/common/contextkeys.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { isMacintosh, isLinux, isWindows } from 'vs/base/common/platform'; + +export const IsMacContext = new RawContextKey('isMac', isMacintosh); +export const IsLinuxContext = new RawContextKey('isLinux', isLinux); +export const IsWindowsContext = new RawContextKey('isWindows', isWindows); + +export const HasMacNativeTabsContext = new RawContextKey('hasMacNativeTabs', false); + +export const SupportsWorkspacesContext = new RawContextKey('supportsWorkspaces', true); +export const SupportsOpenFileFolderContext = new RawContextKey('supportsOpenFileFolder', isMacintosh); + +export const IsDevelopmentContext = new RawContextKey('isDevelopment', false); + +export const WorkbenchStateContext = new RawContextKey('workbenchState', undefined); + +export const WorkspaceFolderCountContext = new RawContextKey('workspaceFolderCount', 0); diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index 70c83e6c2e..20bc5d7e0e 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { runWhenIdle, IdleDeadline } from 'vs/base/common/async'; @@ -34,15 +34,14 @@ export interface IWorkbenchContributionsRegistry { /** * Starts the registry by providing the required services. */ - start(instantiationService: IInstantiationService, lifecycleService: ILifecycleService): void; + start(accessor: ServicesAccessor): void; } - class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry { private instantiationService: IInstantiationService; private lifecycleService: ILifecycleService; - private toBeInstantiated: Map[]> = new Map[]>(); + private readonly toBeInstantiated: Map[]> = new Map[]>(); registerWorkbenchContribution(ctor: IWorkbenchContributionSignature, phase: LifecyclePhase = LifecyclePhase.Starting): void { @@ -63,12 +62,12 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry } } - start(instantiationService: IInstantiationService, lifecycleService: ILifecycleService): void { - this.instantiationService = instantiationService; - this.lifecycleService = lifecycleService; + start(accessor: ServicesAccessor): void { + this.instantiationService = accessor.get(IInstantiationService); + this.lifecycleService = accessor.get(ILifecycleService); [LifecyclePhase.Starting, LifecyclePhase.Ready, LifecyclePhase.Restored, LifecyclePhase.Eventually].forEach(phase => { - this.instantiateByPhase(instantiationService, lifecycleService, phase); + this.instantiateByPhase(this.instantiationService, this.lifecycleService, phase); }); } diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 44b27f4a45..56bc295b4c 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -10,12 +10,12 @@ import { URI } from 'vs/base/common/uri'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IEditor as ICodeEditor, IEditorViewState, ScrollType, IDiffEditor } from 'vs/editor/common/editorCommon'; import { IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput } from 'vs/platform/editor/common/editor'; -import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, IConstructorSignature0, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; import { ITextModel } from 'vs/editor/common/model'; import { Schemas } from 'vs/base/common/network'; -import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ICompositeControl } from 'vs/workbench/common/composite'; import { ActionRunner, IAction } from 'vs/base/common/actions'; @@ -142,7 +142,7 @@ export interface IEditorControl extends ICompositeControl { } export interface IFileInputFactory { - createFileInput(resource: URI, encoding: string, instantiationService: IInstantiationService): IFileEditorInput; + createFileInput(resource: URI, encoding: string | undefined, instantiationService: IInstantiationService): IFileEditorInput; isFileInput(obj: any): obj is IFileEditorInput; } @@ -175,7 +175,10 @@ export interface IEditorInputFactoryRegistry { */ getEditorInputFactory(editorInputId: string): IEditorInputFactory; - setInstantiationService(service: IInstantiationService): void; + /** + * Starts the registry by providing the required services. + */ + start(accessor: ServicesAccessor): void; } export interface IEditorInputFactory { @@ -184,13 +187,13 @@ export interface IEditorInputFactory { * Returns a string representation of the provided editor input that contains enough information * to deserialize back to the original editor input from the deserialize() method. */ - serialize(editorInput: EditorInput): string; + serialize(editorInput: EditorInput): string | null; /** * Returns an editor input from the provided serialized form of the editor input. This form matches * the value returned from the serialize() method. */ - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput; + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | null; } export interface IUntitledResourceInput extends IBaseResourceInput { @@ -541,7 +544,12 @@ export class SideBySideEditorInput extends EditorInput { static readonly ID: string = 'workbench.editorinputs.sidebysideEditorInput'; - constructor(private name: string, private description: string, private _details: EditorInput, private _master: EditorInput) { + constructor( + private readonly name: string, + private readonly description: string | null, + private readonly _details: EditorInput, + private readonly _master: EditorInput + ) { super(); this.registerListeners(); @@ -573,6 +581,7 @@ export class SideBySideEditorInput extends EditorInput { getTelemetryDescriptor(): object { const descriptor = this.master.getTelemetryDescriptor(); + return objects.assign(descriptor, super.getTelemetryDescriptor()); } @@ -610,7 +619,7 @@ export class SideBySideEditorInput extends EditorInput { return this.name; } - getDescription(): string { + getDescription(): string | null { return this.description; } @@ -649,7 +658,7 @@ export class EditorModel extends Disposable implements IEditorModel { /** * Causes this model to load returning a promise when loading is completed. */ - load(): Promise { + load(): Promise { return Promise.resolve(this); } @@ -699,6 +708,7 @@ export class EditorOptions implements IEditorOptions { options.pinned = settings.pinned; options.index = settings.index; options.inactive = settings.inactive; + options.ignoreError = settings.ignoreError; return options; } @@ -742,6 +752,12 @@ export class EditorOptions implements IEditorOptions { * in the background. */ inactive: boolean | undefined; + + /** + * Will not show an error in case opening the editor fails and thus allows to show a custom error + * message as needed. By default, an error will be presented as notification if opening was not possible. + */ + ignoreError: boolean | undefined; } /** @@ -756,9 +772,9 @@ export class TextEditorOptions extends EditorOptions { private revealInCenterIfOutsideViewport: boolean; private editorViewState: IEditorViewState | null; - static from(input?: IBaseResourceInput): TextEditorOptions | null { + static from(input?: IBaseResourceInput): TextEditorOptions | undefined { if (!input || !input.options) { - return null; + return undefined; } return TextEditorOptions.create(input.options); @@ -807,6 +823,10 @@ export class TextEditorOptions extends EditorOptions { textEditorOptions.inactive = true; } + if (options.ignoreError) { + textEditorOptions.ignoreError = true; + } + if (typeof options.index === 'number') { textEditorOptions.index = options.index; } @@ -942,12 +962,12 @@ export type GroupIdentifier = number; export interface IWorkbenchEditorConfiguration { workbench: { - editor: IWorkbenchEditorPartConfiguration, + editor: IEditorPartConfiguration, iconTheme: string; }; } -export interface IWorkbenchEditorPartConfiguration { +interface IEditorPartConfiguration { showTabs?: boolean; highlightModifiedTabs?: boolean; tabCloseButton?: 'left' | 'right' | 'off'; @@ -966,12 +986,16 @@ export interface IWorkbenchEditorPartConfiguration { restoreViewState?: boolean; } +export interface IEditorPartOptions extends IEditorPartConfiguration { + iconTheme?: string; +} + export interface IResourceOptions { supportSideBySide?: boolean; filter?: string | string[]; } -export function toResource(editor: IEditorInput, options?: IResourceOptions): URI | null { +export function toResource(editor: IEditorInput | null | undefined, options?: IResourceOptions): URI | null { if (!editor) { return null; } @@ -1032,17 +1056,17 @@ class EditorInputFactoryRegistry implements IEditorInputFactoryRegistry { private instantiationService: IInstantiationService; private fileInputFactory: IFileInputFactory; private editorInputFactoryConstructors: { [editorInputId: string]: IConstructorSignature0 } = Object.create(null); - private editorInputFactoryInstances: { [editorInputId: string]: IEditorInputFactory } = Object.create(null); + private readonly editorInputFactoryInstances: { [editorInputId: string]: IEditorInputFactory } = Object.create(null); - setInstantiationService(service: IInstantiationService): void { - this.instantiationService = service; + start(accessor: ServicesAccessor): void { + this.instantiationService = accessor.get(IInstantiationService); for (let key in this.editorInputFactoryConstructors) { const element = this.editorInputFactoryConstructors[key]; this.createEditorInputFactory(key, element); } - this.editorInputFactoryConstructors = {}; + this.editorInputFactoryConstructors = Object.create(null); } private createEditorInputFactory(editorInputId: string, ctor: IConstructorSignature0): void { diff --git a/src/vs/workbench/common/editor/binaryEditorModel.ts b/src/vs/workbench/common/editor/binaryEditorModel.ts index 334262c91b..fc7659d4e3 100644 --- a/src/vs/workbench/common/editor/binaryEditorModel.ts +++ b/src/vs/workbench/common/editor/binaryEditorModel.ts @@ -13,15 +13,13 @@ import { DataUri } from 'vs/base/common/resources'; * An editor model that just represents a resource that can be loaded. */ export class BinaryEditorModel extends EditorModel { - private name: string; - private resource: URI; private size: number; - private etag: string; - private mime: string; + private etag: string | undefined; + private readonly mime: string; constructor( - resource: URI, - name: string, + private readonly resource: URI, + private readonly name: string, @IFileService private readonly fileService: IFileService ) { super(); @@ -70,7 +68,7 @@ export class BinaryEditorModel extends EditorModel { /** * The etag of the binary resource if known. */ - getETag(): string { + getETag(): string | undefined { return this.etag; } diff --git a/src/vs/workbench/common/editor/dataUriEditorInput.ts b/src/vs/workbench/common/editor/dataUriEditorInput.ts index 546df5de78..1e33e6593e 100644 --- a/src/vs/workbench/common/editor/dataUriEditorInput.ts +++ b/src/vs/workbench/common/editor/dataUriEditorInput.ts @@ -17,14 +17,10 @@ export class DataUriEditorInput extends EditorInput { static readonly ID: string = 'workbench.editors.dataUriEditorInput'; - private resource: URI; - private readonly name: string | undefined; - private readonly description: string | undefined; - constructor( - name: string, - description: string, - resource: URI, + private readonly name: string | undefined, + private readonly description: string | undefined, + private readonly resource: URI, @IInstantiationService private readonly instantiationService: IInstantiationService ) { super(); diff --git a/src/vs/workbench/common/editor/diffEditorInput.ts b/src/vs/workbench/common/editor/diffEditorInput.ts index 05f8cb5c1b..6588c7f6fd 100644 --- a/src/vs/workbench/common/editor/diffEditorInput.ts +++ b/src/vs/workbench/common/editor/diffEditorInput.ts @@ -16,9 +16,9 @@ export class DiffEditorInput extends SideBySideEditorInput { static readonly ID = 'workbench.editors.diffEditorInput'; - private cachedModel: DiffEditorModel; + private cachedModel: DiffEditorModel | null; - constructor(name: string, description: string, original: EditorInput, modified: EditorInput, private forceOpenAsBinary?: boolean) { + constructor(name: string, description: string | null, original: EditorInput, modified: EditorInput, private readonly forceOpenAsBinary?: boolean) { super(name, description, original, modified); } diff --git a/src/vs/workbench/common/editor/diffEditorModel.ts b/src/vs/workbench/common/editor/diffEditorModel.ts index 75e06975a1..a68e8bb0a4 100644 --- a/src/vs/workbench/common/editor/diffEditorModel.ts +++ b/src/vs/workbench/common/editor/diffEditorModel.ts @@ -11,33 +11,39 @@ import { IEditorModel } from 'vs/platform/editor/common/editor'; * and the modified version. */ export class DiffEditorModel extends EditorModel { - protected _originalModel: IEditorModel; - protected _modifiedModel: IEditorModel; + protected readonly _originalModel: IEditorModel | null; + protected readonly _modifiedModel: IEditorModel | null; - constructor(originalModel: IEditorModel, modifiedModel: IEditorModel) { + constructor(originalModel: IEditorModel | null, modifiedModel: IEditorModel | null) { super(); this._originalModel = originalModel; this._modifiedModel = modifiedModel; } - get originalModel(): EditorModel { + get originalModel(): EditorModel | null { + if (!this._originalModel) { + return null; + } return this._originalModel as EditorModel; } - get modifiedModel(): EditorModel { + get modifiedModel(): EditorModel | null { + if (!this._modifiedModel) { + return null; + } return this._modifiedModel as EditorModel; } load(): Promise { return Promise.all([ - this._originalModel.load(), - this._modifiedModel.load() + this._originalModel ? this._originalModel.load() : Promise.resolve(undefined), + this._modifiedModel ? this._modifiedModel.load() : Promise.resolve(undefined), ]).then(() => this); } isResolved(): boolean { - return this.originalModel.isResolved() && this.modifiedModel.isResolved(); + return !!this.originalModel && this.originalModel.isResolved() && !!this.modifiedModel && this.modifiedModel.isResolved(); } dispose(): void { diff --git a/src/vs/workbench/common/editor/editorGroup.ts b/src/vs/workbench/common/editor/editorGroup.ts index a216567c1c..76b56fc5d4 100644 --- a/src/vs/workbench/common/editor/editorGroup.ts +++ b/src/vs/workbench/common/editor/editorGroup.ts @@ -710,7 +710,7 @@ export class EditorGroup extends Disposable { this.editors = coalesce(data.editors.map(e => { const factory = registry.getEditorInputFactory(e.id); if (factory) { - const editor = factory.deserialize(this.instantiationService, e.value); + const editor = factory.deserialize(this.instantiationService, e.value)!; this.registerEditorListeners(editor); this.updateResourceMap(editor, false /* add */); diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index 75e9ac7c5b..7bafadbb2d 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -6,10 +6,8 @@ import { EditorInput, ITextEditorModel } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { IReference } from 'vs/base/common/lifecycle'; -import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUtils'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; -import { IHashService } from 'vs/workbench/services/hash/common/hashService'; /** * A read-only text editor input whos contents are made of the provided resource that points to an existing @@ -19,17 +17,13 @@ export class ResourceEditorInput extends EditorInput { static readonly ID: string = 'workbench.editors.resourceEditorInput'; - private modelReference: Promise>; - private resource: URI; - private name: string; - private description: string; + private modelReference: Promise> | null; constructor( - name: string, - description: string, - resource: URI, - @ITextModelService private readonly textModelResolverService: ITextModelService, - @IHashService private readonly hashService: IHashService + private name: string, + private description: string | null, + private readonly resource: URI, + @ITextModelService private readonly textModelResolverService: ITextModelService ) { super(); @@ -57,7 +51,7 @@ export class ResourceEditorInput extends EditorInput { } } - getDescription(): string { + getDescription(): string | null { return this.description; } @@ -68,18 +62,6 @@ export class ResourceEditorInput extends EditorInput { } } - getTelemetryDescriptor(): object { - const descriptor = super.getTelemetryDescriptor(); - descriptor['resource'] = telemetryURIDescriptor(this.resource, path => this.hashService.createSHA1(path)); - - /* __GDPR__FRAGMENT__ - "EditorTelemetryDescriptor" : { - "resource": { "${inline}": [ "${URIDescriptor}" ] } - } - */ - return descriptor; - } - resolve(): Promise { if (!this.modelReference) { this.modelReference = this.textModelResolverService.createModelReference(this.resource); @@ -92,7 +74,7 @@ export class ResourceEditorInput extends EditorInput { ref.dispose(); this.modelReference = null; - return Promise.reject(new Error(`Unexpected model for ResourceInput: ${this.resource}`)); + return Promise.reject(new Error(`Unexpected model for ResourceInput: ${this.resource}`)); } return model; diff --git a/src/vs/workbench/common/editor/textDiffEditorModel.ts b/src/vs/workbench/common/editor/textDiffEditorModel.ts index 6367f0fd08..37d7cfc51f 100644 --- a/src/vs/workbench/common/editor/textDiffEditorModel.ts +++ b/src/vs/workbench/common/editor/textDiffEditorModel.ts @@ -13,7 +13,7 @@ import { DiffEditorModel } from 'vs/workbench/common/editor/diffEditorModel'; * and the modified version. */ export class TextDiffEditorModel extends DiffEditorModel { - private _textDiffEditorModel: IDiffEditorModel; + private _textDiffEditorModel: IDiffEditorModel | null; constructor(originalModel: BaseTextEditorModel, modifiedModel: BaseTextEditorModel) { super(originalModel, modifiedModel); @@ -56,7 +56,7 @@ export class TextDiffEditorModel extends DiffEditorModel { } } - get textDiffEditorModel(): IDiffEditorModel { + get textDiffEditorModel(): IDiffEditorModel | null { return this._textDiffEditorModel; } diff --git a/src/vs/workbench/common/editor/textEditorModel.ts b/src/vs/workbench/common/editor/textEditorModel.ts index ad1f55d7f8..9a7ad05c57 100644 --- a/src/vs/workbench/common/editor/textEditorModel.ts +++ b/src/vs/workbench/common/editor/textEditorModel.ts @@ -6,7 +6,7 @@ import { ITextModel, ITextBufferFactory } from 'vs/editor/common/model'; import { EditorModel } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; -import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; +import { ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IDisposable } from 'vs/base/common/lifecycle'; @@ -19,8 +19,8 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd protected createdEditorModel: boolean; - private textEditorModelHandle: URI; - private modelDisposeListener: IDisposable; + private textEditorModelHandle: URI | null; + private modelDisposeListener: IDisposable | null; constructor( @IModelService protected modelService: IModelService, @@ -59,7 +59,7 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd }); } - get textEditorModel(): ITextModel { + get textEditorModel(): ITextModel | null { return this.textEditorModelHandle ? this.modelService.getModel(this.textEditorModelHandle) : null; } @@ -68,14 +68,14 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd /** * Creates the text editor model with the provided value, modeId (can be comma separated for multiple values) and optional resource URL. */ - protected createTextEditorModel(value: ITextBufferFactory, resource?: URI, modeId?: string): EditorModel { + protected createTextEditorModel(value: ITextBufferFactory, resource: URI | undefined, modeId?: string): EditorModel { const firstLineText = this.getFirstLineText(value); const languageSelection = this.getOrCreateMode(this.modeService, modeId, firstLineText); return this.doCreateTextEditorModel(value, languageSelection, resource); } - private doCreateTextEditorModel(value: ITextBufferFactory, languageSelection: ILanguageSelection, resource: URI): EditorModel { + private doCreateTextEditorModel(value: ITextBufferFactory, languageSelection: ILanguageSelection, resource: URI | undefined): EditorModel { let model = resource && this.modelService.getModel(resource); if (!model) { model = this.modelService.createModel(value, languageSelection, resource); @@ -111,7 +111,7 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd * * @param firstLineText optional first line of the text buffer to set the mode on. This can be used to guess a mode from content. */ - protected getOrCreateMode(modeService: IModeService, modeId: string, firstLineText?: string): ILanguageSelection { + protected getOrCreateMode(modeService: IModeService, modeId: string | undefined, firstLineText?: string): ILanguageSelection { return modeService.create(modeId); } @@ -126,7 +126,7 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd this.modelService.updateModel(this.textEditorModel, newValue); } - createSnapshot(): ITextSnapshot { + createSnapshot(): ITextSnapshot | null { const model = this.textEditorModel; if (model) { return model.createSnapshot(true /* Preserve BOM */); @@ -135,7 +135,7 @@ export abstract class BaseTextEditorModel extends EditorModel implements ITextEd return null; } - isResolved(): boolean { + isResolved(): this is IResolvedTextEditorModel { return !!this.textEditorModelHandle; } diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledEditorInput.ts index 9b2d39bdb5..7eb60256df 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledEditorInput.ts @@ -7,16 +7,15 @@ import { URI } from 'vs/base/common/uri'; import { suggestFilename } from 'vs/base/common/mime'; import { memoize } from 'vs/base/common/decorators'; import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; -import * as paths from 'vs/base/common/paths'; -import * as resources from 'vs/base/common/resources'; +import { basename } from 'vs/base/common/path'; +import { basenameOrAuthority, dirname } from 'vs/base/common/resources'; import { EditorInput, IEncodingSupport, EncodingMode, ConfirmResult, Verbosity } from 'vs/workbench/common/editor'; import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUtils'; -import { IHashService } from 'vs/workbench/services/hash/common/hashService'; import { ILabelService } from 'vs/platform/label/common/label'; +import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; /** * An editor input to be used for untitled text buffers. @@ -25,9 +24,8 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport static readonly ID: string = 'workbench.editors.untitledEditorInput'; - private _hasAssociatedFilePath: boolean; private cachedModel: UntitledEditorModel; - private modelResolve: Promise; + private modelResolve?: Promise; private readonly _onDidModelChangeContent: Emitter = this._register(new Emitter()); get onDidModelChangeContent(): Event { return this._onDidModelChangeContent.event; } @@ -36,19 +34,16 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport get onDidModelChangeEncoding(): Event { return this._onDidModelChangeEncoding.event; } constructor( - private resource: URI, - hasAssociatedFilePath: boolean, - private modeId: string, - private initialValue: string, + private readonly resource: URI, + private readonly _hasAssociatedFilePath: boolean, + private readonly modeId: string, + private readonly initialValue: string, private preferredEncoding: string, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITextFileService private readonly textFileService: ITextFileService, - @IHashService private readonly hashService: IHashService, @ILabelService private readonly labelService: ILabelService ) { super(); - - this._hasAssociatedFilePath = hasAssociatedFilePath; } get hasAssociatedFilePath(): boolean { @@ -63,7 +58,7 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport return this.resource; } - getModeId(): string { + getModeId(): string | null { if (this.cachedModel) { return this.cachedModel.getModeId(); } @@ -72,44 +67,38 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport } getName(): string { - return this.hasAssociatedFilePath ? resources.basenameOrAuthority(this.resource) : this.resource.path; + return this.hasAssociatedFilePath ? basenameOrAuthority(this.resource) : this.resource.path; } @memoize private get shortDescription(): string { - return paths.basename(this.labelService.getUriLabel(resources.dirname(this.resource))); + return basename(this.labelService.getUriLabel(dirname(this.resource))); } @memoize private get mediumDescription(): string { - return this.labelService.getUriLabel(resources.dirname(this.resource), { relative: true }); + return this.labelService.getUriLabel(dirname(this.resource), { relative: true }); } @memoize private get longDescription(): string { - return this.labelService.getUriLabel(resources.dirname(this.resource)); + return this.labelService.getUriLabel(dirname(this.resource)); } - getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string { + getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string | null { if (!this.hasAssociatedFilePath) { return null; } - let description: string; switch (verbosity) { case Verbosity.SHORT: - description = this.shortDescription; - break; + return this.shortDescription; case Verbosity.LONG: - description = this.longDescription; - break; + return this.longDescription; case Verbosity.MEDIUM: default: - description = this.mediumDescription; - break; + return this.mediumDescription; } - - return description; } @memoize @@ -127,25 +116,21 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport return this.labelService.getUriLabel(this.resource); } - getTitle(verbosity: Verbosity): string { + getTitle(verbosity: Verbosity): string | null { if (!this.hasAssociatedFilePath) { return this.getName(); } - let title: string; switch (verbosity) { case Verbosity.SHORT: - title = this.shortTitle; - break; + return this.shortTitle; case Verbosity.MEDIUM: - title = this.mediumTitle; - break; + return this.mediumTitle; case Verbosity.LONG: - title = this.longTitle; - break; + return this.longTitle; } - return title; + return null; } isDirty(): boolean { @@ -214,7 +199,7 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport } } - resolve(): Promise { + resolve(): Promise { // Join a model resolve if we have had one before if (this.modelResolve) { @@ -239,18 +224,6 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport return model; } - getTelemetryDescriptor(): object { - const descriptor = super.getTelemetryDescriptor(); - descriptor['resource'] = telemetryURIDescriptor(this.getResource(), path => this.hashService.createSHA1(path)); - - /* __GDPR__FRAGMENT__ - "EditorTelemetryDescriptor" : { - "resource": { "${inline}": [ "${URIDescriptor}" ] } - } - */ - return descriptor; - } - matches(otherInput: any): boolean { if (super.matches(otherInput) === true) { return true; diff --git a/src/vs/workbench/common/editor/untitledEditorModel.ts b/src/vs/workbench/common/editor/untitledEditorModel.ts index ecb1db22f6..240a4f389e 100644 --- a/src/vs/workbench/common/editor/untitledEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledEditorModel.ts @@ -16,6 +16,7 @@ import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { ITextBufferFactory } from 'vs/editor/common/model'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; +import { IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; export class UntitledEditorModel extends BaseTextEditorModel implements IEncodingSupport { @@ -30,16 +31,16 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin private readonly _onDidChangeEncoding: Emitter = this._register(new Emitter()); get onDidChangeEncoding(): Event { return this._onDidChangeEncoding.event; } - private dirty: boolean; - private versionId: number; - private contentChangeEventScheduler: RunOnceScheduler; + private dirty: boolean = false; + private versionId: number = 0; + private readonly contentChangeEventScheduler: RunOnceScheduler; private configuredEncoding: string; constructor( - private modeId: string, - private resource: URI, - private hasAssociatedFilePath: boolean, - private initialValue: string, + private readonly modeId: string, + private readonly resource: URI, + private _hasAssociatedFilePath: boolean, + private readonly initialValue: string, private preferredEncoding: string, @IModeService modeService: IModeService, @IModelService modelService: IModelService, @@ -48,14 +49,15 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin ) { super(modelService, modeService); - this.dirty = false; - this.versionId = 0; - this.contentChangeEventScheduler = this._register(new RunOnceScheduler(() => this._onDidChangeContent.fire(), UntitledEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY)); this.registerListeners(); } + get hasAssociatedFilePath(): boolean { + return this._hasAssociatedFilePath; + } + protected getOrCreateMode(modeService: IModeService, modeId: string, firstLineText?: string): ILanguageSelection { if (!modeId || modeId === PLAINTEXT_MODE_ID) { return modeService.createByFilepathOrFirstLine(this.resource.fsPath, firstLineText); // lookup mode via resource path if the provided modeId is unspecific @@ -86,12 +88,12 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin return this.versionId; } - getModeId(): string { + getModeId(): string | null { if (this.textEditorModel) { return this.textEditorModel.getLanguageIdentifier().language; } - return null; + return this.modeId; } getEncoding(): string { @@ -134,20 +136,20 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin this.contentChangeEventScheduler.schedule(); } - load(): Promise { + load(): Promise { // Check for backups first - return this.backupFileService.loadBackupResource(this.resource).then(backupResource => { + return this.backupFileService.loadBackupResource(this.resource).then((backupResource) => { if (backupResource) { return this.backupFileService.resolveBackupContent(backupResource); } - return null; + return undefined; }).then(backupTextBufferFactory => { const hasBackup = !!backupTextBufferFactory; // untitled associated to file path are dirty right away as well as untitled with content - this.setDirty(this.hasAssociatedFilePath || hasBackup); + this.setDirty(this._hasAssociatedFilePath || hasBackup); let untitledContents: ITextBufferFactory; if (backupTextBufferFactory) { @@ -169,22 +171,29 @@ export class UntitledEditorModel extends BaseTextEditorModel implements IEncodin // Encoding this.configuredEncoding = this.configurationService.getValue(this.resource, 'files.encoding'); + // We know for a fact there is a text editor model here + const textEditorModel = this.textEditorModel!; + // Listen to content changes - this._register(this.textEditorModel.onDidChangeContent(() => this.onModelContentChanged())); + this._register(textEditorModel.onDidChangeContent(() => this.onModelContentChanged())); // Listen to mode changes - this._register(this.textEditorModel.onDidChangeLanguage(() => this.onConfigurationChange())); // mode change can have impact on config + this._register(textEditorModel.onDidChangeLanguage(() => this.onConfigurationChange())); // mode change can have impact on config - return this; + return this as UntitledEditorModel & IResolvedTextEditorModel; }); } private onModelContentChanged(): void { + if (!this.isResolved()) { + return; + } + this.versionId++; // mark the untitled editor as non-dirty once its content becomes empty and we do // not have an associated path set. we never want dirty indicator in that case. - if (!this.hasAssociatedFilePath && this.textEditorModel.getLineCount() === 1 && this.textEditorModel.getLineContent(1) === '') { + if (!this._hasAssociatedFilePath && this.textEditorModel && this.textEditorModel.getLineCount() === 1 && this.textEditorModel.getLineContent(1) === '') { this.setDirty(false); } diff --git a/src/vs/workbench/common/memento.ts b/src/vs/workbench/common/memento.ts index 563ad518f5..7f1e366d88 100644 --- a/src/vs/workbench/common/memento.ts +++ b/src/vs/workbench/common/memento.ts @@ -13,7 +13,7 @@ export class Memento { private static readonly COMMON_PREFIX = 'memento/'; - private id: string; + private readonly id: string; constructor(id: string, private storageService: IStorageService) { this.id = Memento.COMMON_PREFIX + id; @@ -59,7 +59,7 @@ export class Memento { } class ScopedMemento { - private mementoObj: object; + private readonly mementoObj: object; constructor(private id: string, private scope: StorageScope, private storageService: IStorageService) { this.mementoObj = this.load(); diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index 1b1b7e801b..d52d1f2182 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -48,7 +48,7 @@ export class NotificationHandle implements INotificationHandle { private readonly _onDidClose: Emitter = new Emitter(); get onDidClose(): Event { return this._onDidClose.event; } - constructor(private item: INotificationViewItem, private closeItem: (item: INotificationViewItem) => void) { + constructor(private readonly item: INotificationViewItem, private readonly closeItem: (item: INotificationViewItem) => void) { this.registerListeners(); } @@ -88,7 +88,7 @@ export class NotificationsModel extends Disposable implements INotificationsMode private readonly _onDidNotificationChange: Emitter = this._register(new Emitter()); get onDidNotificationChange(): Event { return this._onDidNotificationChange.event; } - private _notifications: INotificationViewItem[] = []; + private readonly _notifications: INotificationViewItem[] = []; get notifications(): INotificationViewItem[] { return this._notifications; @@ -235,7 +235,7 @@ export interface INotificationViewItemProgress extends INotificationProgress { } export class NotificationViewItemProgress extends Disposable implements INotificationViewItemProgress { - private _state: INotificationViewItemProgressState; + private readonly _state: INotificationViewItemProgressState; private readonly _onDidChange: Emitter = this._register(new Emitter()); get onDidChange(): Event { return this._onDidChange.event; } @@ -580,10 +580,10 @@ export class NotificationViewItem extends Disposable implements INotificationVie export class ChoiceAction extends Action { - private _onDidRun = new Emitter(); + private readonly _onDidRun = new Emitter(); get onDidRun(): Event { return this._onDidRun.event; } - private _keepOpen: boolean; + private readonly _keepOpen: boolean; constructor(id: string, choice: IPromptChoice) { super(id, choice.label, undefined, true, () => { diff --git a/src/vs/workbench/common/panel.ts b/src/vs/workbench/common/panel.ts index cb4fc659ed..0600d9d787 100644 --- a/src/vs/workbench/common/panel.ts +++ b/src/vs/workbench/common/panel.ts @@ -4,5 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { IComposite } from 'vs/workbench/common/composite'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export const ActivePanelContext = new RawContextKey('activePanel', ''); +export const PanelFocusContext = new RawContextKey('panelFocus', false); export interface IPanel extends IComposite { } diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index 59e88ca572..44e0cf14c3 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -4,11 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import * as paths from 'vs/base/common/paths'; +import * as objects from 'vs/base/common/objects'; +import { Event, Emitter } from 'vs/base/common/event'; +import { basename, extname, relativePath } from 'vs/base/common/resources'; import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IFileService } from 'vs/platform/files/common/files'; import { Disposable } from 'vs/base/common/lifecycle'; +import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { withNullAsUndefined } from 'vs/base/common/types'; export class ResourceContextKey extends Disposable implements IContextKey { @@ -20,11 +26,11 @@ export class ResourceContextKey extends Disposable implements IContextKey { static HasResource = new RawContextKey('resourceSet', false); static IsFileSystemResource = new RawContextKey('isFileSystemResource', false); - private readonly _resourceKey: IContextKey; - private readonly _schemeKey: IContextKey; - private readonly _filenameKey: IContextKey; + private readonly _resourceKey: IContextKey; + private readonly _schemeKey: IContextKey; + private readonly _filenameKey: IContextKey; private readonly _langIdKey: IContextKey; - private readonly _extensionKey: IContextKey; + private readonly _extensionKey: IContextKey; private readonly _hasResource: IContextKey; private readonly _isFileSystemResource: IContextKey; @@ -54,15 +60,15 @@ export class ResourceContextKey extends Disposable implements IContextKey { })); } - set(value: URI) { + set(value: URI | null) { if (!ResourceContextKey._uriEquals(this._resourceKey.get(), value)) { this._resourceKey.set(value); - this._schemeKey.set(value && value.scheme); - this._filenameKey.set(value && paths.basename(value.fsPath)); + this._schemeKey.set(value ? value.scheme : null); + this._filenameKey.set(value ? basename(value) : null); this._langIdKey.set(value ? this._modeService.getModeIdByFilepathOrFirstLine(value.fsPath) : null); - this._extensionKey.set(value && paths.extname(value.fsPath)); + this._extensionKey.set(value ? extname(value) : null); this._hasResource.set(!!value); - this._isFileSystemResource.set(value && this._fileService.canHandleResource(value)); + this._isFileSystemResource.set(value ? this._fileService.canHandleResource(value) : false); } } @@ -77,7 +83,7 @@ export class ResourceContextKey extends Disposable implements IContextKey { } get(): URI | undefined { - return this._resourceKey.get(); + return withNullAsUndefined(this._resourceKey.get()); } private static _uriEquals(a: URI | undefined | null, b: URI | undefined | null): boolean { @@ -95,3 +101,103 @@ export class ResourceContextKey extends Disposable implements IContextKey { && a.toString() === b.toString(); // for equal we use the normalized toString-form } } + +export class ResourceGlobMatcher extends Disposable { + + private static readonly NO_ROOT: string | null = null; + + private readonly _onExpressionChange: Emitter = this._register(new Emitter()); + get onExpressionChange(): Event { return this._onExpressionChange.event; } + + private readonly mapRootToParsedExpression: Map = new Map(); + private readonly mapRootToExpressionConfig: Map = new Map(); + + constructor( + private globFn: (root?: URI) => IExpression, + private shouldUpdate: (event: IConfigurationChangeEvent) => boolean, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super(); + + this.updateExcludes(false); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (this.shouldUpdate(e)) { + this.updateExcludes(true); + } + })); + + this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.updateExcludes(true))); + } + + private updateExcludes(fromEvent: boolean): void { + let changed = false; + + // Add excludes per workspaces that got added + this.contextService.getWorkspace().folders.forEach(folder => { + const rootExcludes = this.globFn(folder.uri); + if (!this.mapRootToExpressionConfig.has(folder.uri.toString()) || !objects.equals(this.mapRootToExpressionConfig.get(folder.uri.toString()), rootExcludes)) { + changed = true; + + this.mapRootToParsedExpression.set(folder.uri.toString(), parse(rootExcludes)); + this.mapRootToExpressionConfig.set(folder.uri.toString(), objects.deepClone(rootExcludes)); + } + }); + + // Remove excludes per workspace no longer present + this.mapRootToExpressionConfig.forEach((value, root) => { + if (root === ResourceGlobMatcher.NO_ROOT) { + return; // always keep this one + } + + if (root && !this.contextService.getWorkspaceFolder(URI.parse(root))) { + this.mapRootToParsedExpression.delete(root); + this.mapRootToExpressionConfig.delete(root); + + changed = true; + } + }); + + // Always set for resources outside root as well + const globalExcludes = this.globFn(); + if (!this.mapRootToExpressionConfig.has(ResourceGlobMatcher.NO_ROOT) || !objects.equals(this.mapRootToExpressionConfig.get(ResourceGlobMatcher.NO_ROOT), globalExcludes)) { + changed = true; + + this.mapRootToParsedExpression.set(ResourceGlobMatcher.NO_ROOT, parse(globalExcludes)); + this.mapRootToExpressionConfig.set(ResourceGlobMatcher.NO_ROOT, objects.deepClone(globalExcludes)); + } + + if (fromEvent && changed) { + this._onExpressionChange.fire(); + } + } + + matches(resource: URI): boolean { + const folder = this.contextService.getWorkspaceFolder(resource); + + let expressionForRoot: ParsedExpression; + if (folder && this.mapRootToParsedExpression.has(folder.uri.toString())) { + expressionForRoot = this.mapRootToParsedExpression.get(folder.uri.toString())!; + } else { + expressionForRoot = this.mapRootToParsedExpression.get(ResourceGlobMatcher.NO_ROOT)!; + } + + // If the resource if from a workspace, convert its absolute path to a relative + // path so that glob patterns have a higher probability to match. For example + // a glob pattern of "src/**" will not match on an absolute path "/folder/src/file.txt" + // but can match on "src/file.txt" + let resourcePathToMatch: string; + if (folder) { + resourcePathToMatch = relativePath(folder.uri, resource)!; // always uses forward slashes + } else { + resourcePathToMatch = resource.fsPath; // TODO@isidor: support non-file URIs + } + + return !!expressionForRoot(resourcePathToMatch); + } +} \ No newline at end of file diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index fe508cf63e..0b76203552 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -378,8 +378,8 @@ export const SIDE_BAR_TITLE_FOREGROUND = registerColor('sideBarTitle.foreground' export const SIDE_BAR_DRAG_AND_DROP_BACKGROUND = registerColor('sideBar.dropBackground', { dark: Color.white.transparent(0.12), - light: Color.white.transparent(0.12), - hc: Color.white.transparent(0.12), + light: Color.black.transparent(0.1), + hc: Color.white.transparent(0.3), }, nls.localize('sideBarDragAndDropBackground', "Drag and drop feedback color for the side bar sections. The color should have transparency so that the side bar sections can still shine through. The side bar is the container for views like explorer and search.")); export const SIDE_BAR_SECTION_HEADER_BACKGROUND = registerColor('sideBarSectionHeader.background', { diff --git a/src/vs/workbench/common/viewlet.ts b/src/vs/workbench/common/viewlet.ts index 71238ecdf5..0ec64b9d5d 100644 --- a/src/vs/workbench/common/viewlet.ts +++ b/src/vs/workbench/common/viewlet.ts @@ -4,6 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { IComposite } from 'vs/workbench/common/composite'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; + +export const SidebarVisibleContext = new RawContextKey('sidebarVisible', false); +export const SideBarVisibleContext = new RawContextKey('sideBarVisible', false); +export const SidebarFocusContext = new RawContextKey('sideBarFocus', false); +export const ActiveViewletContext = new RawContextKey('activeViewlet', ''); export interface IViewlet extends IComposite { diff --git a/src/vs/workbench/common/views.ts b/src/vs/workbench/common/views.ts index 9999847780..e5c7207e32 100644 --- a/src/vs/workbench/common/views.ts +++ b/src/vs/workbench/common/views.ts @@ -10,7 +10,7 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ITreeViewDataProvider } from 'vs/workbench/common/views'; import { localize } from 'vs/nls'; import { IViewlet } from 'vs/workbench/common/viewlet'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { values, keys } from 'vs/base/common/map'; @@ -24,6 +24,7 @@ export const TEST_VIEW_CONTAINER_ID = 'workbench.view.extension.test'; export namespace Extensions { export const ViewContainersRegistry = 'workbench.registry.view.containers'; + export const ViewsRegistry = 'workbench.registry.view'; } export interface IViewContainersRegistry { @@ -50,7 +51,7 @@ export interface IViewContainersRegistry { * * @returns the registered ViewContainer. */ - registerViewContainer(id: string, extensionId?: ExtensionIdentifier): ViewContainer; + registerViewContainer(id: string, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier): ViewContainer; /** * Deregisters the given view container @@ -67,7 +68,7 @@ export interface IViewContainersRegistry { } export class ViewContainer { - protected constructor(readonly id: string, readonly extensionId: ExtensionIdentifier) { } + protected constructor(readonly id: string, readonly hideIfEmpty: boolean, readonly extensionId?: ExtensionIdentifier) { } } class ViewContainersRegistryImpl implements IViewContainersRegistry { @@ -84,7 +85,7 @@ class ViewContainersRegistryImpl implements IViewContainersRegistry { return values(this.viewContainers); } - registerViewContainer(id: string, extensionId: ExtensionIdentifier): ViewContainer { + registerViewContainer(id: string, hideIfEmpty?: boolean, extensionId?: ExtensionIdentifier): ViewContainer { const existing = this.viewContainers.get(id); if (existing) { return existing; @@ -92,7 +93,7 @@ class ViewContainersRegistryImpl implements IViewContainersRegistry { const viewContainer = new class extends ViewContainer { constructor() { - super(id, extensionId); + super(id, !!hideIfEmpty, extensionId); } }; this.viewContainers.set(id, viewContainer); @@ -121,8 +122,7 @@ export interface IViewDescriptor { readonly name: string; - // TODO@Sandeep do we really need this?! - readonly ctor: any; + readonly ctorDescriptor: { ctor: any, arguments?: any[] }; readonly when?: ContextKeyExpr; @@ -137,6 +137,8 @@ export interface IViewDescriptor { // Applies only to newly created views readonly hideByDefault?: boolean; + readonly workspace?: boolean; + readonly focusCommand?: { id: string, keybindings?: IKeybindings }; } @@ -167,7 +169,7 @@ export interface IViewsRegistry { getViewContainer(id: string): ViewContainer | null; } -export const ViewsRegistry: IViewsRegistry = new class implements IViewsRegistry { +class ViewsRegistry implements IViewsRegistry { private readonly _onViewsRegistered: Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = new Emitter<{ views: IViewDescriptor[], viewContainer: ViewContainer }>(); readonly onViewsRegistered: Event<{ views: IViewDescriptor[], viewContainer: ViewContainer }> = this._onViewsRegistered.event; @@ -268,7 +270,9 @@ export const ViewsRegistry: IViewsRegistry = new class implements IViewsRegistry } return viewsToDeregister; } -}; +} + +Registry.add(Extensions.ViewsRegistry, new ViewsRegistry()); export interface IView { @@ -285,7 +289,7 @@ export interface IViewsViewlet extends IViewlet { export const IViewsService = createDecorator('viewsService'); export interface IViewsService { - _serviceBrand: any; + _serviceBrand: ServiceIdentifier; openView(id: string, focus?: boolean): Promise; @@ -296,11 +300,11 @@ export interface IViewsService { export interface ITreeView extends IDisposable { - dataProvider: ITreeViewDataProvider; + dataProvider: ITreeViewDataProvider | null; showCollapseAllAction: boolean; - message: string | IMarkdownString; + message?: string | IMarkdownString; readonly visible: boolean; @@ -322,7 +326,7 @@ export interface ITreeView extends IDisposable { layout(height: number): void; - show(container: HTMLElement); + show(container: HTMLElement): void; getOptimalWidth(): number; @@ -378,7 +382,7 @@ export interface ITreeItem { handle: string; - parentHandle: string; + parentHandle?: string; collapsibleState: TreeItemCollapsibleState; diff --git a/src/vs/workbench/parts/backup/common/backup.contribution.ts b/src/vs/workbench/contrib/backup/common/backup.contribution.ts similarity index 83% rename from src/vs/workbench/parts/backup/common/backup.contribution.ts rename to src/vs/workbench/contrib/backup/common/backup.contribution.ts index 882ed235c2..6dbf82cfd3 100644 --- a/src/vs/workbench/parts/backup/common/backup.contribution.ts +++ b/src/vs/workbench/contrib/backup/common/backup.contribution.ts @@ -5,8 +5,8 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { BackupModelTracker } from 'vs/workbench/parts/backup/common/backupModelTracker'; -import { BackupRestorer } from 'vs/workbench/parts/backup/common/backupRestorer'; +import { BackupModelTracker } from 'vs/workbench/contrib/backup/common/backupModelTracker'; +import { BackupRestorer } from 'vs/workbench/contrib/backup/common/backupRestorer'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; // Register Backup Model Tracker diff --git a/src/vs/workbench/parts/backup/common/backupModelTracker.ts b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts similarity index 91% rename from src/vs/workbench/parts/backup/common/backupModelTracker.ts rename to src/vs/workbench/contrib/backup/common/backupModelTracker.ts index 6458df9899..d97aa04106 100644 --- a/src/vs/workbench/parts/backup/common/backupModelTracker.ts +++ b/src/vs/workbench/contrib/backup/common/backupModelTracker.ts @@ -65,14 +65,24 @@ export class BackupModelTracker extends Disposable implements IWorkbenchContribu // Do not backup when auto save after delay is configured if (!this.configuredAutoSaveAfterDelay) { const model = this.textFileService.models.get(event.resource); - this.backupFileService.backupResource(model.getResource(), model.createSnapshot(), model.getVersionId()); + if (model) { + const snapshot = model.createSnapshot(); + if (snapshot) { + this.backupFileService.backupResource(model.getResource(), snapshot, model.getVersionId()); + } + } } } } private onUntitledModelChanged(resource: Uri): void { if (this.untitledEditorService.isDirty(resource)) { - this.untitledEditorService.loadOrCreate({ resource }).then(model => this.backupFileService.backupResource(resource, model.createSnapshot(), model.getVersionId())); + this.untitledEditorService.loadOrCreate({ resource }).then(model => { + const snapshot = model.createSnapshot(); + if (snapshot) { + this.backupFileService.backupResource(resource, snapshot, model.getVersionId()); + } + }); } else { this.discardBackup(resource); } diff --git a/src/vs/workbench/parts/backup/common/backupRestorer.ts b/src/vs/workbench/contrib/backup/common/backupRestorer.ts similarity index 100% rename from src/vs/workbench/parts/backup/common/backupRestorer.ts rename to src/vs/workbench/contrib/backup/common/backupRestorer.ts diff --git a/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts b/src/vs/workbench/contrib/cli/node/cli.contribution.ts similarity index 98% rename from src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts rename to src/vs/workbench/contrib/cli/node/cli.contribution.ts index 9d743e5ab2..1b22a53c7a 100644 --- a/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts +++ b/src/vs/workbench/contrib/cli/node/cli.contribution.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as cp from 'child_process'; import * as pfs from 'vs/base/node/pfs'; import * as platform from 'vs/base/common/platform'; @@ -13,7 +13,7 @@ import { Action } from 'vs/base/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/accessibility.css b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css similarity index 100% rename from src/vs/workbench/parts/codeEditor/electron-browser/accessibility.css rename to src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.css diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/accessibility.ts b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts similarity index 98% rename from src/vs/workbench/parts/codeEditor/electron-browser/accessibility.ts rename to src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts index 8bebfc5491..37eed2a399 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/accessibility.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/accessibility/accessibility.ts @@ -29,6 +29,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { IOpenerService } from 'vs/platform/opener/common/opener'; import { contrastBorder, editorWidgetBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; const CONTEXT_ACCESSIBILITY_WIDGET_VISIBLE = new RawContextKey('accessibilityHelpWidgetVisible', false); @@ -201,14 +202,14 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget { switch (configuredValue) { case 'auto': switch (actualValue) { - case platform.AccessibilitySupport.Unknown: + case AccessibilitySupport.Unknown: // Should never happen in VS Code text += '\n\n - ' + nls.localize('auto_unknown', "The editor is configured to use platform APIs to detect when a Screen Reader is attached, but the current runtime does not support this."); break; - case platform.AccessibilitySupport.Enabled: + case AccessibilitySupport.Enabled: text += '\n\n - ' + nls.localize('auto_on', "The editor has automatically detected a Screen Reader is attached."); break; - case platform.AccessibilitySupport.Disabled: + case AccessibilitySupport.Disabled: text += '\n\n - ' + nls.localize('auto_off', "The editor is configured to automatically detect when a Screen Reader is attached, which is not the case at this time."); text += ' ' + emergencyTurnOnMessage; break; diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/codeEditor.contribution.ts b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts similarity index 78% rename from src/vs/workbench/parts/codeEditor/electron-browser/codeEditor.contribution.ts rename to src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts index 2009caf36d..c7b0cfcaef 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/codeEditor.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/codeEditor.contribution.ts @@ -3,16 +3,15 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import '../browser/menuPreventer'; -import './accessibility'; +import './menuPreventer'; +import './accessibility/accessibility'; import './inspectKeybindings'; import './largeFileOptimizations'; import './selectionClipboard'; -import './sleepResumeRepaintMinimap'; -import './textMate/inspectTMScopes'; +import './inspectTMScopes/inspectTMScopes'; import './toggleMinimap'; import './toggleMultiCursorModifier'; import './toggleRenderControlCharacter'; import './toggleRenderWhitespace'; import './toggleWordWrap'; -import './workbenchReferenceSearch'; \ No newline at end of file +import './workbenchReferenceSearch'; diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/inspectKeybindings.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts similarity index 84% rename from src/vs/workbench/parts/codeEditor/electron-browser/inspectKeybindings.ts rename to src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts index 36a33efdf9..1719929f91 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/inspectKeybindings.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectKeybindings.ts @@ -9,7 +9,6 @@ import { EditorAction, ServicesAccessor, registerEditorAction } from 'vs/editor/ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IUntitledResourceInput } from 'vs/workbench/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { WorkbenchKeybindingService } from 'vs/workbench/services/keybinding/electron-browser/keybindingService'; class InspectKeyMap extends EditorAction { @@ -26,9 +25,7 @@ class InspectKeyMap extends EditorAction { const keybindingService = accessor.get(IKeybindingService); const editorService = accessor.get(IEditorService); - if (keybindingService instanceof WorkbenchKeybindingService) { - editorService.openEditor({ contents: keybindingService.dumpDebugInfo(), options: { pinned: true } } as IUntitledResourceInput); - } + editorService.openEditor({ contents: keybindingService._dumpDebugInfo(), options: { pinned: true } } as IUntitledResourceInput); } } diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/textMate/inspectTMScopes.css b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.css similarity index 83% rename from src/vs/workbench/parts/codeEditor/electron-browser/textMate/inspectTMScopes.css rename to src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.css index 6bdbc8f26d..53865e7f95 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/textMate/inspectTMScopes.css +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.css @@ -10,7 +10,7 @@ } .tm-token { - font-family: monospace; + font-family: var(--monaco-monospace-font); } .tm-metadata-separator { @@ -29,10 +29,10 @@ } .tm-metadata-value { - font-family: monospace; + font-family: var(--monaco-monospace-font); text-align: right; } .tm-theme-selector { - font-family: monospace; + font-family: var(--monaco-monospace-font); } diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/textMate/inspectTMScopes.ts b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts similarity index 98% rename from src/vs/workbench/parts/codeEditor/electron-browser/textMate/inspectTMScopes.ts rename to src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts index 3c50847943..224867a642 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/textMate/inspectTMScopes.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/inspectTMScopes/inspectTMScopes.ts @@ -21,10 +21,9 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry'; import { HIGH_CONTRAST, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { findMatchingThemeRule } from 'vs/workbench/services/textMate/electron-browser/TMHelper'; -import { ITextMateService } from 'vs/workbench/services/textMate/electron-browser/textMateService'; +import { findMatchingThemeRule } from 'vs/workbench/services/textMate/common/TMHelper'; +import { ITextMateService, IGrammar, IToken, StackElement } from 'vs/workbench/services/textMate/common/textMateService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { IGrammar, IToken, StackElement } from 'vscode-textmate'; class InspectTMScopesController extends Disposable implements IEditorContribution { diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint.ts b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts similarity index 99% rename from src/vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint.ts rename to src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts index 6875c31693..ab2919196a 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint.ts @@ -16,7 +16,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ITextMateService } from 'vs/workbench/services/textMate/electron-browser/textMateService'; +import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; interface IRegExp { pattern: string; diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/largeFileOptimizations.ts b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts similarity index 98% rename from src/vs/workbench/parts/codeEditor/electron-browser/largeFileOptimizations.ts rename to src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts index 79c436259a..de41caecca 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/largeFileOptimizations.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/largeFileOptimizations.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { Disposable } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; diff --git a/src/vs/workbench/parts/codeEditor/browser/menuPreventer.ts b/src/vs/workbench/contrib/codeEditor/browser/menuPreventer.ts similarity index 100% rename from src/vs/workbench/parts/codeEditor/browser/menuPreventer.ts rename to src/vs/workbench/contrib/codeEditor/browser/menuPreventer.ts diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/browser/selectionClipboard.ts similarity index 91% rename from src/vs/workbench/parts/codeEditor/electron-browser/selectionClipboard.ts rename to src/vs/workbench/contrib/codeEditor/browser/selectionClipboard.ts index 65e7b7296c..0669d98adb 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/selectionClipboard.ts @@ -3,9 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { clipboard } from 'electron'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Disposable } from 'vs/base/common/lifecycle'; +import * as process from 'vs/base/common/process'; import * as platform from 'vs/base/common/platform'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; @@ -14,13 +14,13 @@ import { ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursor import { Range } from 'vs/editor/common/core/range'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { EndOfLinePreference } from 'vs/editor/common/model'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; export class SelectionClipboard extends Disposable implements IEditorContribution { private static SELECTION_LENGTH_LIMIT = 65536; private static readonly ID = 'editor.contrib.selectionClipboard'; - constructor(editor: ICodeEditor, @IContextKeyService contextKeyService: IContextKeyService) { + constructor(editor: ICodeEditor, @IClipboardService clipboardService: IClipboardService) { super(); if (platform.isLinux) { @@ -53,7 +53,7 @@ export class SelectionClipboard extends Disposable implements IEditorContributio process.nextTick(() => { // TODO@Alex: electron weirdness: calling clipboard.readText('selection') generates a paste event, so no need to execute paste ourselves - clipboard.readText('selection'); + clipboardService.readText('selection'); // keybindingService.executeCommand(Handler.Paste, { // text: clipboard.readText('selection'), // pasteOnNewLine: false @@ -92,7 +92,7 @@ export class SelectionClipboard extends Disposable implements IEditorContributio } let textToCopy = result.join(model.getEOL()); - clipboard.writeText(textToCopy, 'selection'); + clipboardService.writeText(textToCopy, 'selection'); }, 100)); this._register(editor.onDidChangeCursorSelection((e: ICursorSelectionChangedEvent) => { diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/simpleEditorOptions.ts b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts similarity index 52% rename from src/vs/workbench/parts/codeEditor/electron-browser/simpleEditorOptions.ts rename to src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts index 242981b8e8..a6f90bd70a 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/simpleEditorOptions.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/simpleEditorOptions.ts @@ -3,13 +3,39 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import { ContextMenuController } from 'vs/editor/contrib/contextmenu/contextmenu'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; -import { MenuPreventer } from 'vs/workbench/parts/codeEditor/browser/menuPreventer'; -import { SelectionClipboard } from 'vs/workbench/parts/codeEditor/electron-browser/selectionClipboard'; -import { TabCompletionController } from 'vs/workbench/parts/snippets/electron-browser/tabCompletion'; +import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; +import { SelectionClipboard } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; +import { TabCompletionController } from 'vs/workbench/contrib/snippets/browser/tabCompletion'; + +export function getSimpleEditorOptions(): IEditorOptions { + return { + wordWrap: 'on', + overviewRulerLanes: 0, + glyphMargin: false, + lineNumbers: 'off', + folding: false, + selectOnLineNumbers: false, + hideCursorInOverviewRuler: true, + selectionHighlight: false, + scrollbar: { + horizontal: 'hidden' + }, + lineDecorationsWidth: 0, + overviewRulerBorder: false, + scrollBeyondLastLine: false, + renderLineHighlight: 'none', + fixedOverflowWidgets: true, + acceptSuggestionOnEnter: 'smart', + minimap: { + enabled: false + } + }; +} export function getSimpleCodeEditorWidgetOptions(): ICodeEditorWidgetOptions { return { @@ -23,4 +49,4 @@ export function getSimpleCodeEditorWidgetOptions(): ICodeEditorWidgetOptions { TabCompletionController, ] }; -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/media/WordWrap_16x.svg b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/WordWrap_16x.svg similarity index 100% rename from src/vs/workbench/parts/codeEditor/electron-browser/media/WordWrap_16x.svg rename to src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/WordWrap_16x.svg diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/media/suggestEnabledInput.css b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css similarity index 100% rename from src/vs/workbench/parts/codeEditor/electron-browser/media/suggestEnabledInput.css rename to src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.css diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/suggestEnabledInput.ts b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts similarity index 96% rename from src/vs/workbench/parts/codeEditor/electron-browser/suggestEnabledInput.ts rename to src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts index 218599e0b8..bc9e541fd5 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/suggestEnabledInput.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/suggestEnabledInput'; +import 'vs/css!./suggestEnabledInput'; import { $, Dimension, addClass, append, removeClass } from 'vs/base/browser/dom'; import { Widget } from 'vs/base/browser/ui/widget'; import { Color } from 'vs/base/common/color'; @@ -29,9 +29,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ColorIdentifier, editorSelectionBackground, inputBackground, inputBorder, inputForeground, inputPlaceholderForeground, selectionBackground } from 'vs/platform/theme/common/colorRegistry'; import { IStyleOverrides, IThemable, attachStyler } from 'vs/platform/theme/common/styler'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { MenuPreventer } from 'vs/workbench/parts/codeEditor/browser/menuPreventer'; -import { getSimpleEditorOptions } from 'vs/workbench/parts/codeEditor/browser/simpleEditorOptions'; -import { SelectionClipboard } from 'vs/workbench/parts/codeEditor/electron-browser/selectionClipboard'; +import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; +import { getSimpleEditorOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; +import { SelectionClipboard } from 'vs/workbench/contrib/codeEditor/browser/selectionClipboard'; interface SuggestResultsProvider { /** @@ -64,6 +64,7 @@ interface SuggestEnabledInputOptions { * Defaults to the empty string. */ placeholderText?: string; + value?: string; /** * Context key tracking the focus state of this element @@ -170,6 +171,8 @@ export class SuggestEnabledInput extends Widget implements IThemable { triggerCharacters: suggestionProvider.triggerCharacters || [] }; + this.setValue(options.value || ''); + this.disposables.push(modes.CompletionProviderRegistry.register({ scheme: scopeHandle.scheme, pattern: '**/' + scopeHandle.path, hasAccessToAllModels: true }, { triggerCharacters: validatedSuggestProvider.triggerCharacters, provideCompletionItems: (model: ITextModel, position: Position, _context: modes.CompletionContext) => { @@ -291,7 +294,6 @@ function getSuggestEnabledInputOptions(ariaLabel?: string): IEditorOptions { ariaLabel: ariaLabel || '', snippetSuggestions: 'none', - suggest: { filterGraceful: false }, - iconsInSuggestions: false + suggest: { filterGraceful: false, showIcons: false } }; } diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/toggleMinimap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts similarity index 100% rename from src/vs/workbench/parts/codeEditor/electron-browser/toggleMinimap.ts rename to src/vs/workbench/contrib/codeEditor/browser/toggleMinimap.ts diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/toggleMultiCursorModifier.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts similarity index 100% rename from src/vs/workbench/parts/codeEditor/electron-browser/toggleMultiCursorModifier.ts rename to src/vs/workbench/contrib/codeEditor/browser/toggleMultiCursorModifier.ts diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderControlCharacter.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts similarity index 100% rename from src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderControlCharacter.ts rename to src/vs/workbench/contrib/codeEditor/browser/toggleRenderControlCharacter.ts diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderWhitespace.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts similarity index 100% rename from src/vs/workbench/parts/codeEditor/electron-browser/toggleRenderWhitespace.ts rename to src/vs/workbench/contrib/codeEditor/browser/toggleRenderWhitespace.ts diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts similarity index 97% rename from src/vs/workbench/parts/codeEditor/electron-browser/toggleWordWrap.ts rename to src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index 868d3ae0d4..cf543ca444 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -279,7 +279,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_WORD_WRAP_ID, title: nls.localize('unwrapMinified', "Disable wrapping for this file"), - iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/parts/codeEditor/electron-browser/media/WordWrap_16x.svg')) } + iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/contrib/codeEditor/electron-browser/media/WordWrap_16x.svg')) } }, group: 'navigation', order: 1, @@ -293,7 +293,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_WORD_WRAP_ID, title: nls.localize('wrapMinified', "Enable wrapping for this file"), - iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/parts/codeEditor/electron-browser/media/WordWrap_16x.svg')) } + iconLocation: { dark: URI.parse(require.toUrl('vs/workbench/contrib/codeEditor/electron-browser/media/WordWrap_16x.svg')) } }, group: 'navigation', order: 1, diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/workbenchReferenceSearch.ts b/src/vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch.ts similarity index 100% rename from src/vs/workbench/parts/codeEditor/electron-browser/workbenchReferenceSearch.ts rename to src/vs/workbench/contrib/codeEditor/browser/workbenchReferenceSearch.ts diff --git a/src/vs/workbench/parts/emmet/browser/emmet.browser.contribution.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts similarity index 90% rename from src/vs/workbench/parts/emmet/browser/emmet.browser.contribution.ts rename to src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts index 6adf4e184d..5af54b676a 100644 --- a/src/vs/workbench/parts/emmet/browser/emmet.browser.contribution.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution.ts @@ -3,4 +3,4 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import './actions/showEmmetCommands'; +import './sleepResumeRepaintMinimap'; diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/sleepResumeRepaintMinimap.ts b/src/vs/workbench/contrib/codeEditor/electron-browser/sleepResumeRepaintMinimap.ts similarity index 100% rename from src/vs/workbench/parts/codeEditor/electron-browser/sleepResumeRepaintMinimap.ts rename to src/vs/workbench/contrib/codeEditor/electron-browser/sleepResumeRepaintMinimap.ts diff --git a/src/vs/workbench/contrib/codeinset/common/codeInset.ts b/src/vs/workbench/contrib/codeinset/common/codeInset.ts new file mode 100644 index 0000000000..3ef92e295d --- /dev/null +++ b/src/vs/workbench/contrib/codeinset/common/codeInset.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITextModel } from 'vs/editor/common/model'; +import { onUnexpectedExternalError } from 'vs/base/common/errors'; +import { mergeSort } from 'vs/base/common/arrays'; +import { Event } from 'vs/base/common/event'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry'; +import { ProviderResult } from 'vs/editor/common/modes'; +import { IRange } from 'vs/editor/common/core/range'; + +export interface ICodeInsetSymbol { + id: string; + range: IRange; + height?: number; +} + +export interface CodeInsetProvider { + onDidChange?: Event; + provideCodeInsets(model: ITextModel, token: CancellationToken): ProviderResult; + resolveCodeInset(model: ITextModel, codeInset: ICodeInsetSymbol, token: CancellationToken): ProviderResult; +} + +export const CodeInsetProviderRegistry = new LanguageFeatureRegistry(); + +export interface ICodeInsetData { + symbol: ICodeInsetSymbol; + provider: CodeInsetProvider; + resolved?: boolean; +} + +export function getCodeInsetData(model: ITextModel, token: CancellationToken): Promise { + + const symbols: ICodeInsetData[] = []; + const providers = CodeInsetProviderRegistry.ordered(model); + + const promises = providers.map(provider => + Promise.resolve(provider.provideCodeInsets(model, token)).then(result => { + if (Array.isArray(result)) { + for (let symbol of result) { + symbols.push({ symbol, provider }); + } + } + }).catch(onUnexpectedExternalError)); + + return Promise.all(promises).then(() => { + + return mergeSort(symbols, (a, b) => { + // sort by lineNumber, provider-rank, and column + if (a.symbol.range.startLineNumber < b.symbol.range.startLineNumber) { + return -1; + } else if (a.symbol.range.startLineNumber > b.symbol.range.startLineNumber) { + return 1; + } else if (providers.indexOf(a.provider) < providers.indexOf(b.provider)) { + return -1; + } else if (providers.indexOf(a.provider) > providers.indexOf(b.provider)) { + return 1; + } else if (a.symbol.range.startColumn < b.symbol.range.startColumn) { + return -1; + } else if (a.symbol.range.startColumn > b.symbol.range.startColumn) { + return 1; + } else { + return 0; + } + }); + }); +} diff --git a/src/vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution.ts b/src/vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution.ts new file mode 100644 index 0000000000..303fd1700e --- /dev/null +++ b/src/vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution.ts @@ -0,0 +1,353 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { CancelablePromise, createCancelablePromise, RunOnceScheduler } from 'vs/base/common/async'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { StableEditorScrollState } from 'vs/editor/browser/core/editorState'; +import * as editorBrowser from 'vs/editor/browser/editorBrowser'; +import * as editorCommon from 'vs/editor/common/editorCommon'; +import { IModelDecorationsChangeAccessor } from 'vs/editor/common/model'; +import { CodeInsetProviderRegistry, getCodeInsetData, ICodeInsetData } from '../common/codeInset'; +import { CodeInsetWidget, CodeInsetHelper } from './codeInsetWidget'; +import { registerEditorContribution } from 'vs/editor/browser/editorExtensions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; +// import { localize } from 'vs/nls'; + +export class CodeInsetController implements editorCommon.IEditorContribution { + + static get(editor: editorBrowser.ICodeEditor): CodeInsetController { + return editor.getContribution(CodeInsetController.ID); + } + + private static readonly ID: string = 'css.editor.codeInset'; + + private _isEnabled: boolean; + + private _globalToDispose: IDisposable[]; + private _localToDispose: IDisposable[]; + private _insetWidgets: CodeInsetWidget[]; + private _pendingWebviews = new Map any>(); + private _currentFindCodeInsetSymbolsPromise: CancelablePromise | null; + private _modelChangeCounter: number; + private _currentResolveCodeInsetSymbolsPromise: CancelablePromise | null; + private _detectVisibleInsets: RunOnceScheduler; + + constructor( + private _editor: editorBrowser.ICodeEditor, + @IConfigurationService private readonly _configService: IConfigurationService, + ) { + this._isEnabled = this._configService.getValue('editor.codeInsets'); + + this._globalToDispose = []; + this._localToDispose = []; + this._insetWidgets = []; + this._currentFindCodeInsetSymbolsPromise = null; + this._modelChangeCounter = 0; + + this._globalToDispose.push(this._editor.onDidChangeModel(() => this._onModelChange())); + this._globalToDispose.push(this._editor.onDidChangeModelLanguage(() => this._onModelChange())); + this._globalToDispose.push(this._configService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('editor.codeInsets')) { + let prevIsEnabled = this._isEnabled; + this._isEnabled = this._configService.getValue('editor.codeInsets'); + if (prevIsEnabled !== this._isEnabled) { + this._onModelChange(); + } + } + })); + this._globalToDispose.push(CodeInsetProviderRegistry.onDidChange(this._onModelChange, this)); + this._onModelChange(); + } + + dispose(): void { + this._localDispose(); + this._globalToDispose = dispose(this._globalToDispose); + } + + acceptWebview(symbolId: string, webviewElement: WebviewElement): boolean { + const pendingWebview = this._pendingWebviews.get(symbolId); + if (pendingWebview) { + pendingWebview(webviewElement); + this._pendingWebviews.delete(symbolId); + return true; + } + return false; + } + + private _localDispose(): void { + if (this._currentFindCodeInsetSymbolsPromise) { + this._currentFindCodeInsetSymbolsPromise.cancel(); + this._currentFindCodeInsetSymbolsPromise = null; + this._modelChangeCounter++; + } + if (this._currentResolveCodeInsetSymbolsPromise) { + this._currentResolveCodeInsetSymbolsPromise.cancel(); + this._currentResolveCodeInsetSymbolsPromise = null; + } + this._localToDispose = dispose(this._localToDispose); + } + + getId(): string { + return CodeInsetController.ID; + } + + private _onModelChange(): void { + this._localDispose(); + + const model = this._editor.getModel(); + if (!model || !this._isEnabled || !CodeInsetProviderRegistry.has(model)) { + return; + } + + for (const provider of CodeInsetProviderRegistry.all(model)) { + if (typeof provider.onDidChange === 'function') { + let registration = provider.onDidChange(() => scheduler.schedule()); + this._localToDispose.push(registration); + } + } + + this._detectVisibleInsets = new RunOnceScheduler(() => { + this._onViewportChanged(); + }, 500); + + const scheduler = new RunOnceScheduler(() => { + const counterValue = ++this._modelChangeCounter; + if (this._currentFindCodeInsetSymbolsPromise) { + this._currentFindCodeInsetSymbolsPromise.cancel(); + } + + this._currentFindCodeInsetSymbolsPromise = createCancelablePromise(token => getCodeInsetData(model, token)); + + this._currentFindCodeInsetSymbolsPromise.then(codeInsetData => { + if (counterValue === this._modelChangeCounter) { // only the last one wins + this._renderCodeInsetSymbols(codeInsetData); + this._detectVisibleInsets.schedule(); + } + }, onUnexpectedError); + }, 250); + + this._localToDispose.push(scheduler); + + this._localToDispose.push(this._detectVisibleInsets); + + this._localToDispose.push(this._editor.onDidChangeModelContent(() => { + this._editor.changeDecorations(changeAccessor => { + this._editor.changeViewZones(viewAccessor => { + let toDispose: CodeInsetWidget[] = []; + let lastInsetLineNumber: number = -1; + this._insetWidgets.forEach(inset => { + if (!inset.isValid() || lastInsetLineNumber === inset.getLineNumber()) { + // invalid -> Inset collapsed, attach range doesn't exist anymore + // line_number -> insets should never be on the same line + toDispose.push(inset); + } + else { + inset.reposition(viewAccessor); + lastInsetLineNumber = inset.getLineNumber(); + } + }); + let helper = new CodeInsetHelper(); + toDispose.forEach((l) => { + l.dispose(helper, viewAccessor); + this._insetWidgets.splice(this._insetWidgets.indexOf(l), 1); + }); + helper.commit(changeAccessor); + }); + }); + // Compute new `visible` code insets + this._detectVisibleInsets.schedule(); + // Ask for all references again + scheduler.schedule(); + })); + + this._localToDispose.push(this._editor.onDidScrollChange(e => { + if (e.scrollTopChanged && this._insetWidgets.length > 0) { + this._detectVisibleInsets.schedule(); + } + })); + + this._localToDispose.push(this._editor.onDidLayoutChange(() => { + this._detectVisibleInsets.schedule(); + })); + + this._localToDispose.push(toDisposable(() => { + if (this._editor.getModel()) { + const scrollState = StableEditorScrollState.capture(this._editor); + this._editor.changeDecorations((changeAccessor) => { + this._editor.changeViewZones((accessor) => { + this._disposeAllInsets(changeAccessor, accessor); + }); + }); + scrollState.restore(this._editor); + } else { + // No accessors available + this._disposeAllInsets(null, null); + } + })); + + scheduler.schedule(); + } + + private _disposeAllInsets(decChangeAccessor: IModelDecorationsChangeAccessor | null, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor | null): void { + let helper = new CodeInsetHelper(); + this._insetWidgets.forEach((Inset) => Inset.dispose(helper, viewZoneChangeAccessor)); + if (decChangeAccessor) { + helper.commit(decChangeAccessor); + } + this._insetWidgets = []; + } + + private _renderCodeInsetSymbols(symbols: ICodeInsetData[]): void { + if (!this._editor.hasModel()) { + return; + } + + let maxLineNumber = this._editor.getModel().getLineCount(); + let groups: ICodeInsetData[][] = []; + let lastGroup: ICodeInsetData[] | undefined; + + for (let symbol of symbols) { + let line = symbol.symbol.range.startLineNumber; + if (line < 1 || line > maxLineNumber) { + // invalid code Inset + continue; + } else if (lastGroup && lastGroup[lastGroup.length - 1].symbol.range.startLineNumber === line) { + // on same line as previous + lastGroup.push(symbol); + } else { + // on later line as previous + lastGroup = [symbol]; + groups.push(lastGroup); + } + } + + const scrollState = StableEditorScrollState.capture(this._editor); + + this._editor.changeDecorations(changeAccessor => { + this._editor.changeViewZones(accessor => { + + let codeInsetIndex = 0, groupsIndex = 0, helper = new CodeInsetHelper(); + + while (groupsIndex < groups.length && codeInsetIndex < this._insetWidgets.length) { + + let symbolsLineNumber = groups[groupsIndex][0].symbol.range.startLineNumber; + let codeInsetLineNumber = this._insetWidgets[codeInsetIndex].getLineNumber(); + + if (codeInsetLineNumber < symbolsLineNumber) { + this._insetWidgets[codeInsetIndex].dispose(helper, accessor); + this._insetWidgets.splice(codeInsetIndex, 1); + } else if (codeInsetLineNumber === symbolsLineNumber) { + this._insetWidgets[codeInsetIndex].updateCodeInsetSymbols(groups[groupsIndex], helper); + groupsIndex++; + codeInsetIndex++; + } else { + this._insetWidgets.splice( + codeInsetIndex, + 0, + new CodeInsetWidget(groups[groupsIndex], this._editor, helper) + ); + codeInsetIndex++; + groupsIndex++; + } + } + + // Delete extra code insets + while (codeInsetIndex < this._insetWidgets.length) { + this._insetWidgets[codeInsetIndex].dispose(helper, accessor); + this._insetWidgets.splice(codeInsetIndex, 1); + } + + // Create extra symbols + while (groupsIndex < groups.length) { + this._insetWidgets.push(new CodeInsetWidget( + groups[groupsIndex], + this._editor, helper + )); + groupsIndex++; + } + + helper.commit(changeAccessor); + }); + }); + + scrollState.restore(this._editor); + } + + private _onViewportChanged(): void { + if (this._currentResolveCodeInsetSymbolsPromise) { + this._currentResolveCodeInsetSymbolsPromise.cancel(); + this._currentResolveCodeInsetSymbolsPromise = null; + } + + const model = this._editor.getModel(); + if (!model) { + return; + } + + const allWidgetRequests: ICodeInsetData[][] = []; + const insetWidgets: CodeInsetWidget[] = []; + this._insetWidgets.forEach(inset => { + const widgetRequests = inset.computeIfNecessary(model); + if (widgetRequests) { + allWidgetRequests.push(widgetRequests); + insetWidgets.push(inset); + } + }); + + if (allWidgetRequests.length === 0) { + return; + } + + this._currentResolveCodeInsetSymbolsPromise = createCancelablePromise(token => { + + const allPromises = allWidgetRequests.map((widgetRequests, r) => { + + const widgetPromises = widgetRequests.map(request => { + if (request.resolved) { + return Promise.resolve(undefined); + } + let a = new Promise(resolve => { + this._pendingWebviews.set(request.symbol.id, element => { + request.resolved = true; + insetWidgets[r].adoptWebview(element); + resolve(); + }); + }); + let b = request.provider.resolveCodeInset(model, request.symbol, token); + return Promise.all([a, b]); + }); + + return Promise.all(widgetPromises); + }); + + return Promise.all(allPromises); + }); + + this._currentResolveCodeInsetSymbolsPromise.then(() => { + this._currentResolveCodeInsetSymbolsPromise = null; + }).catch(err => { + this._currentResolveCodeInsetSymbolsPromise = null; + onUnexpectedError(err); + }); + } +} + +registerEditorContribution(CodeInsetController); + + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + id: 'editor', + properties: { + // ['editor.codeInsets']: { + // description: localize('editor.codeInsets', "Enable/disable editor code insets"), + // type: 'boolean', + // default: false + // } + } +}); diff --git a/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.css b/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.css new file mode 100644 index 0000000000..16f2a7acee --- /dev/null +++ b/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.css @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .codelens-decoration { + overflow: hidden; + display: inline-block; + text-overflow: ellipsis; +} + +.monaco-editor .codelens-decoration > span, +.monaco-editor .codelens-decoration > a { + -moz-user-select: none; + -webkit-user-select: none; + -ms-user-select: none; + user-select: none; + white-space: nowrap; + vertical-align: sub; +} + +.monaco-editor .codelens-decoration > a { + text-decoration: none; +} + +.monaco-editor .codelens-decoration > a:hover { + text-decoration: underline; + cursor: pointer; +} + +.monaco-editor .codelens-decoration.invisible-cl { + opacity: 0; +} + +@keyframes fadein { 0% { opacity:0; visibility:visible;} 100% { opacity:1; } } +@-moz-keyframes fadein { 0% { opacity:0; visibility:visible;} 100% { opacity:1; } } +@-o-keyframes fadein { 0% { opacity:0; visibility:visible;} 100% { opacity:1; } } +@-webkit-keyframes fadein { 0% { opacity:0; visibility:visible;} 100% { opacity:1; } } + +.monaco-editor .codelens-decoration.fadein { + -webkit-animation: fadein 0.5s linear; + -moz-animation: fadein 0.5s linear; + -o-animation: fadein 0.5s linear; + animation: fadein 0.5s linear; +} diff --git a/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.ts b/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.ts new file mode 100644 index 0000000000..9260ee47b2 --- /dev/null +++ b/src/vs/workbench/contrib/codeinset/electron-browser/codeInsetWidget.ts @@ -0,0 +1,192 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./codeInsetWidget'; +import { Range } from 'vs/editor/common/core/range'; +import * as editorBrowser from 'vs/editor/browser/editorBrowser'; +import { ICodeInsetData } from '../common/codeInset'; +import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; +import { IModelDeltaDecoration, IModelDecorationsChangeAccessor, ITextModel } from 'vs/editor/common/model'; +import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; + + +export interface IDecorationIdCallback { + (decorationId: string): void; +} + +export class CodeInsetHelper { + + private _removeDecorations: string[]; + private _addDecorations: IModelDeltaDecoration[]; + private _addDecorationsCallbacks: IDecorationIdCallback[]; + + constructor() { + this._removeDecorations = []; + this._addDecorations = []; + this._addDecorationsCallbacks = []; + } + + addDecoration(decoration: IModelDeltaDecoration, callback: IDecorationIdCallback): void { + this._addDecorations.push(decoration); + this._addDecorationsCallbacks.push(callback); + } + + removeDecoration(decorationId: string): void { + this._removeDecorations.push(decorationId); + } + + commit(changeAccessor: IModelDecorationsChangeAccessor): void { + let resultingDecorations = changeAccessor.deltaDecorations(this._removeDecorations, this._addDecorations); + for (let i = 0, len = resultingDecorations.length; i < len; i++) { + this._addDecorationsCallbacks[i](resultingDecorations[i]); + } + } +} + +export class CodeInsetWidget { + + private readonly _editor: editorBrowser.ICodeEditor; + private _webview: WebviewElement; + private _viewZone: editorBrowser.IViewZone; + private _viewZoneId?: number = undefined; + private _decorationIds: string[]; + private _data: ICodeInsetData[]; + private _range: Range; + + constructor( + data: ICodeInsetData[], // all the insets on the same line (often just one) + editor: editorBrowser.ICodeEditor, + helper: CodeInsetHelper + ) { + this._editor = editor; + this._data = data; + this._decorationIds = new Array(this._data.length); + + this._data.forEach((codeInsetData, i) => { + + helper.addDecoration({ + range: codeInsetData.symbol.range, + options: ModelDecorationOptions.EMPTY + }, id => this._decorationIds[i] = id); + + // the range contains all insets on this line + if (!this._range) { + this._range = Range.lift(codeInsetData.symbol.range); + } else { + this._range = Range.plusRange(this._range, codeInsetData.symbol.range); + } + }); + } + + public dispose(helper: CodeInsetHelper, viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor | null): void { + while (this._decorationIds.length) { + const decoration = this._decorationIds.pop(); + if (decoration) { + helper.removeDecoration(decoration); + } + } + if (viewZoneChangeAccessor) { + if (typeof this._viewZoneId !== 'undefined') { + viewZoneChangeAccessor.removeZone(this._viewZoneId); + } + this._viewZone = undefined!; + } + if (this._webview) { + this._webview.dispose(); + } + } + + public isValid(): boolean { + return this._editor.hasModel() && this._decorationIds.some((id, i) => { + const range = this._editor.getModel()!.getDecorationRange(id); + const symbol = this._data[i].symbol; + return !!range && Range.isEmpty(symbol.range) === range.isEmpty(); + }); + } + + public updateCodeInsetSymbols(data: ICodeInsetData[], helper: CodeInsetHelper): void { + while (this._decorationIds.length) { + const decoration = this._decorationIds.pop(); + if (decoration) { + helper.removeDecoration(decoration); + } + } + this._data = data; + this._decorationIds = new Array(this._data.length); + this._data.forEach((codeInsetData, i) => { + helper.addDecoration({ + range: codeInsetData.symbol.range, + options: ModelDecorationOptions.EMPTY + }, id => this._decorationIds[i] = id); + }); + } + + public computeIfNecessary(model: ITextModel): ICodeInsetData[] { + // Read editor current state + for (let i = 0; i < this._decorationIds.length; i++) { + const range = model.getDecorationRange(this._decorationIds[i]); + if (range) { + this._data[i].symbol.range = range; + } + } + return this._data; + } + + public getLineNumber(): number { + if (this._editor.hasModel()) { + const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]); + if (range) { + return range.startLineNumber; + } + } + return -1; + } + + public adoptWebview(webview: WebviewElement): void { + + const lineNumber = this._range.endLineNumber; + this._editor.changeViewZones(accessor => { + + if (this._viewZoneId) { + accessor.removeZone(this._viewZoneId); + this._webview.dispose(); + } + + const div = document.createElement('div'); + webview.mountTo(div); + webview.onMessage((e: { type: string, payload: any }) => { + // The webview contents can use a "size-info" message to report its size. + if (e && e.type === 'size-info') { + const margin = e.payload.height > 0 ? 5 : 0; + this._viewZone.heightInPx = e.payload.height + margin; + this._editor.changeViewZones(accessor => { + if (this._viewZoneId) { + accessor.layoutZone(this._viewZoneId); + } + }); + } + }); + this._viewZone = { + afterLineNumber: lineNumber, + heightInPx: 50, + domNode: div + }; + this._viewZoneId = accessor.addZone(this._viewZone); + this._webview = webview; + }); + } + + public reposition(viewZoneChangeAccessor: editorBrowser.IViewZoneChangeAccessor): void { + if (this.isValid() && this._editor.hasModel()) { + const range = this._editor.getModel().getDecorationRange(this._decorationIds[0]); + if (range) { + this._viewZone.afterLineNumber = range.endLineNumber; + } + if (this._viewZoneId) { + viewZoneChangeAccessor.layoutZone(this._viewZoneId); + } + } + } +} diff --git a/src/vs/workbench/parts/comments/common/commentModel.ts b/src/vs/workbench/contrib/comments/common/commentModel.ts similarity index 84% rename from src/vs/workbench/parts/comments/common/commentModel.ts rename to src/vs/workbench/contrib/comments/common/commentModel.ts index d872df8081..b5f229aa9e 100644 --- a/src/vs/workbench/parts/comments/common/commentModel.ts +++ b/src/vs/workbench/contrib/comments/common/commentModel.ts @@ -41,12 +41,12 @@ export class ResourceWithCommentThreads { constructor(resource: URI, commentThreads: CommentThread[]) { this.id = resource.toString(); this.resource = resource; - this.commentThreads = commentThreads.map(thread => ResourceWithCommentThreads.createCommentNode(resource, thread)); + this.commentThreads = commentThreads.filter(thread => thread.comments.length).map(thread => ResourceWithCommentThreads.createCommentNode(resource, thread)); } public static createCommentNode(resource: URI, commentThread: CommentThread): CommentNode { const { threadId, comments, range } = commentThread; - const commentNodes: CommentNode[] = comments.map(comment => new CommentNode(threadId, resource, comment, range)); + const commentNodes: CommentNode[] = comments.map(comment => new CommentNode(threadId!, resource, comment, range)); if (commentNodes.length > 1) { commentNodes[0].replies = commentNodes.slice(1, commentNodes.length); } @@ -72,10 +72,7 @@ export class CommentsModel { public updateCommentThreads(event: ICommentThreadChangedEvent): boolean { const { owner, removed, changed, added } = event; - const threadsForOwner = this.commentThreadsMap.get(owner); - if (!threadsForOwner) { - return false; - } + let threadsForOwner = this.commentThreadsMap.get(owner) || []; removed.forEach(thread => { // Find resource that has the comment thread @@ -99,16 +96,22 @@ export class CommentsModel { // Find comment node on resource that is that thread and replace it const index = firstIndex(matchingResourceData.commentThreads, (commentThread) => commentThread.threadId === thread.threadId); - matchingResourceData.commentThreads[index] = ResourceWithCommentThreads.createCommentNode(URI.parse(matchingResourceData.id), thread); + if (index >= 0) { + matchingResourceData.commentThreads[index] = ResourceWithCommentThreads.createCommentNode(URI.parse(matchingResourceData.id), thread); + } else { + matchingResourceData.commentThreads.push(ResourceWithCommentThreads.createCommentNode(URI.parse(matchingResourceData.id), thread)); + } }); added.forEach(thread => { const existingResource = threadsForOwner.filter(resourceWithThreads => resourceWithThreads.resource.toString() === thread.resource); if (existingResource.length) { const resource = existingResource[0]; - resource.commentThreads.push(ResourceWithCommentThreads.createCommentNode(resource.resource, thread)); + if (thread.comments.length) { + resource.commentThreads.push(ResourceWithCommentThreads.createCommentNode(resource.resource, thread)); + } } else { - threadsForOwner.push(new ResourceWithCommentThreads(URI.parse(thread.resource), [thread])); + threadsForOwner.push(new ResourceWithCommentThreads(URI.parse(thread.resource!), [thread])); } }); @@ -134,7 +137,7 @@ export class CommentsModel { const resourceCommentThreads: ResourceWithCommentThreads[] = []; const commentThreadsByResource = new Map(); for (const group of groupBy(commentThreads, CommentsModel._compareURIs)) { - commentThreadsByResource.set(group[0].resource, new ResourceWithCommentThreads(URI.parse(group[0].resource), group)); + commentThreadsByResource.set(group[0].resource!, new ResourceWithCommentThreads(URI.parse(group[0].resource!), group)); } commentThreadsByResource.forEach((v, i, m) => { @@ -145,8 +148,8 @@ export class CommentsModel { } private static _compareURIs(a: CommentThread, b: CommentThread) { - const resourceA = a.resource.toString(); - const resourceB = b.resource.toString(); + const resourceA = a.resource!.toString(); + const resourceB = b.resource!.toString(); if (resourceA < resourceB) { return -1; } else if (resourceA > resourceB) { diff --git a/src/typings/error-ex.d.ts b/src/vs/workbench/contrib/comments/common/commentThreadWidget.ts similarity index 82% rename from src/typings/error-ex.d.ts rename to src/vs/workbench/contrib/comments/common/commentThreadWidget.ts index 09b5f75ffa..fc2215284c 100644 --- a/src/typings/error-ex.d.ts +++ b/src/vs/workbench/contrib/comments/common/commentThreadWidget.ts @@ -3,6 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -declare module 'error-ex' { - export function errorEx(name) : void; +export interface ICommentThreadWidget { + submitComment: () => Promise; } diff --git a/src/vs/workbench/parts/comments/electron-browser/commentGlyphWidget.ts b/src/vs/workbench/contrib/comments/electron-browser/commentGlyphWidget.ts similarity index 100% rename from src/vs/workbench/parts/comments/electron-browser/commentGlyphWidget.ts rename to src/vs/workbench/contrib/comments/electron-browser/commentGlyphWidget.ts diff --git a/src/vs/workbench/contrib/comments/electron-browser/commentNode.ts b/src/vs/workbench/contrib/comments/electron-browser/commentNode.ts new file mode 100644 index 0000000000..3f120dc512 --- /dev/null +++ b/src/vs/workbench/contrib/comments/electron-browser/commentNode.ts @@ -0,0 +1,628 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; +import * as modes from 'vs/editor/common/modes'; +import { ActionsOrientation, ActionItem, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Button } from 'vs/base/browser/ui/button/button'; +import { Action, IActionRunner } from 'vs/base/common/actions'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { URI } from 'vs/base/common/uri'; +import { ITextModel } from 'vs/editor/common/model'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { inputValidationErrorBorder } from 'vs/platform/theme/common/colorRegistry'; +import { attachButtonStyler } from 'vs/platform/theme/common/styler'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { ICommentService } from 'vs/workbench/contrib/comments/electron-browser/commentService'; +import { SimpleCommentEditor } from 'vs/workbench/contrib/comments/electron-browser/simpleCommentEditor'; +import { Selection } from 'vs/editor/common/core/selection'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { Emitter, Event } from 'vs/base/common/event'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { assign } from 'vs/base/common/objects'; +import { MarkdownString } from 'vs/base/common/htmlContent'; +import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { DropdownMenuActionItem } from 'vs/base/browser/ui/dropdown/dropdown'; +import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; +import { ToggleReactionsAction, ReactionAction, ReactionActionItem } from './reactionsAction'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; + +const UPDATE_COMMENT_LABEL = nls.localize('label.updateComment', "Update comment"); +const UPDATE_IN_PROGRESS_LABEL = nls.localize('label.updatingComment', "Updating comment..."); + +export class CommentNode extends Disposable { + private _domNode: HTMLElement; + private _body: HTMLElement; + private _md: HTMLElement; + private _clearTimeout: any; + + private _editAction: Action; + private _commentEditContainer: HTMLElement; + private _commentDetailsContainer: HTMLElement; + private _actionsToolbarContainer: HTMLElement; + private _reactionsActionBar?: ActionBar; + private _reactionActionsContainer?: HTMLElement; + private _commentEditor: SimpleCommentEditor | null; + private _commentEditorDisposables: IDisposable[] = []; + private _commentEditorModel: ITextModel; + private _updateCommentButton: Button; + private _errorEditingContainer: HTMLElement; + private _isPendingLabel: HTMLElement; + + private _deleteAction: Action; + protected actionRunner?: IActionRunner; + protected toolbar: ToolBar; + + private _onDidDelete = new Emitter(); + + public get domNode(): HTMLElement { + return this._domNode; + } + + public isEditing: boolean; + + constructor( + private commentThread: modes.CommentThread | modes.CommentThread2, + public comment: modes.Comment, + private owner: string, + private resource: URI, + private parentEditor: ICodeEditor, + private parentThread: ICommentThreadWidget, + private markdownRenderer: MarkdownRenderer, + @IThemeService private themeService: IThemeService, + @IInstantiationService private instantiationService: IInstantiationService, + @ICommentService private commentService: ICommentService, + @ICommandService private commandService: ICommandService, + @IModelService private modelService: IModelService, + @IModeService private modeService: IModeService, + @IDialogService private dialogService: IDialogService, + @INotificationService private notificationService: INotificationService, + @IContextMenuService private contextMenuService: IContextMenuService + ) { + super(); + + this._domNode = dom.$('div.review-comment'); + this._domNode.tabIndex = 0; + const avatar = dom.append(this._domNode, dom.$('div.avatar-container')); + if (comment.userIconPath) { + const img = dom.append(avatar, dom.$('img.avatar')); + img.src = comment.userIconPath.toString(); + img.onerror = _ => img.remove(); + } + this._commentDetailsContainer = dom.append(this._domNode, dom.$('.review-comment-contents')); + + this.createHeader(this._commentDetailsContainer); + + this._body = dom.append(this._commentDetailsContainer, dom.$('div.comment-body')); + this._md = this.markdownRenderer.render(comment.body).element; + this._body.appendChild(this._md); + + if (this.comment.commentReactions && this.comment.commentReactions.length) { + this.createReactionsContainer(this._commentDetailsContainer); + } + + this._domNode.setAttribute('aria-label', `${comment.userName}, ${comment.body.value}`); + this._domNode.setAttribute('role', 'treeitem'); + this._clearTimeout = null; + } + + public get onDidDelete(): Event { + return this._onDidDelete.event; + } + + private createHeader(commentDetailsContainer: HTMLElement): void { + const header = dom.append(commentDetailsContainer, dom.$('div.comment-title')); + const author = dom.append(header, dom.$('strong.author')); + author.innerText = this.comment.userName; + + this._isPendingLabel = dom.append(header, dom.$('span.isPending')); + + if (this.comment.label) { + this._isPendingLabel.innerText = this.comment.label; + } else if (this.comment.isDraft) { + this._isPendingLabel.innerText = 'Pending'; + } else { + this._isPendingLabel.innerText = ''; + } + + this._actionsToolbarContainer = dom.append(header, dom.$('.comment-actions.hidden')); + this.createActionsToolbar(); + } + + private createActionsToolbar() { + const actions: Action[] = []; + + let reactionGroup = this.commentService.getReactionGroup(this.owner); + if (reactionGroup && reactionGroup.length) { + let commentThread = this.commentThread as modes.CommentThread2; + if (commentThread.commentThreadHandle) { + let toggleReactionAction = this.createReactionPicker2(); + actions.push(toggleReactionAction); + } else { + let toggleReactionAction = this.createReactionPicker(); + actions.push(toggleReactionAction); + } + } + + if (this.comment.canEdit || this.comment.editCommand) { + this._editAction = this.createEditAction(this._commentDetailsContainer); + actions.push(this._editAction); + } + + if (this.comment.canDelete || this.comment.deleteCommand) { + this._deleteAction = this.createDeleteAction(); + actions.push(this._deleteAction); + } + + if (actions.length) { + this.toolbar = new ToolBar(this._actionsToolbarContainer, this.contextMenuService, { + actionItemProvider: action => { + if (action.id === ToggleReactionsAction.ID) { + return new DropdownMenuActionItem( + action, + (action).menuActions, + this.contextMenuService, + action => { + return this.actionItemProvider(action as Action); + }, + this.actionRunner!, + undefined, + 'toolbar-toggle-pickReactions', + () => { return AnchorAlignment.RIGHT; } + ); + } + return this.actionItemProvider(action as Action); + }, + orientation: ActionsOrientation.HORIZONTAL + }); + + this.registerActionBarListeners(this._actionsToolbarContainer); + this.toolbar.setActions(actions, [])(); + this._toDispose.push(this.toolbar); + } + } + + actionItemProvider(action: Action) { + let options = {}; + if (action.id === 'comment.delete' || action.id === 'comment.edit' || action.id === ToggleReactionsAction.ID) { + options = { label: false, icon: true }; + } else { + options = { label: true, icon: true }; + } + + if (action.id === ReactionAction.ID) { + let item = new ReactionActionItem(action); + return item; + } else { + let item = new ActionItem({}, action, options); + return item; + } + } + + private createReactionPicker2(): ToggleReactionsAction { + let toggleReactionActionItem: DropdownMenuActionItem; + let toggleReactionAction = this._register(new ToggleReactionsAction(() => { + if (toggleReactionActionItem) { + toggleReactionActionItem.show(); + } + }, nls.localize('commentToggleReaction', "Toggle Reaction"))); + + let reactionMenuActions: Action[] = []; + let reactionGroup = this.commentService.getReactionGroup(this.owner); + if (reactionGroup && reactionGroup.length) { + reactionMenuActions = reactionGroup.map((reaction) => { + return new Action(`reaction.command.${reaction.label}`, `${reaction.label}`, '', true, async () => { + try { + await this.commentService.toggleReaction(this.owner, this.resource, this.commentThread as modes.CommentThread2, this.comment, reaction); + } catch (e) { + const error = e.message + ? nls.localize('commentToggleReactionError', "Toggling the comment reaction failed: {0}.", e.message) + : nls.localize('commentToggleReactionDefaultError', "Toggling the comment reaction failed"); + this.notificationService.error(error); + } + }); + }); + } + + toggleReactionAction.menuActions = reactionMenuActions; + + toggleReactionActionItem = new DropdownMenuActionItem( + toggleReactionAction, + (toggleReactionAction).menuActions, + this.contextMenuService, + action => { + if (action.id === ToggleReactionsAction.ID) { + return toggleReactionActionItem; + } + return this.actionItemProvider(action as Action); + }, + this.actionRunner, + undefined, + 'toolbar-toggle-pickReactions', + () => { return AnchorAlignment.RIGHT; } + ); + + return toggleReactionAction; + } + + private createReactionPicker(): ToggleReactionsAction { + let toggleReactionActionItem: DropdownMenuActionItem; + let toggleReactionAction = this._register(new ToggleReactionsAction(() => { + if (toggleReactionActionItem) { + toggleReactionActionItem.show(); + } + }, nls.localize('commentAddReaction', "Add Reaction"))); + + let reactionMenuActions: Action[] = []; + let reactionGroup = this.commentService.getReactionGroup(this.owner); + if (reactionGroup && reactionGroup.length) { + reactionMenuActions = reactionGroup.map((reaction) => { + return new Action(`reaction.command.${reaction.label}`, `${reaction.label}`, '', true, async () => { + try { + await this.commentService.addReaction(this.owner, this.resource, this.comment, reaction); + } catch (e) { + const error = e.message + ? nls.localize('commentAddReactionError', "Deleting the comment reaction failed: {0}.", e.message) + : nls.localize('commentAddReactionDefaultError', "Deleting the comment reaction failed"); + this.notificationService.error(error); + } + }); + }); + } + + toggleReactionAction.menuActions = reactionMenuActions; + + toggleReactionActionItem = new DropdownMenuActionItem( + toggleReactionAction, + (toggleReactionAction).menuActions, + this.contextMenuService, + action => { + if (action.id === ToggleReactionsAction.ID) { + return toggleReactionActionItem; + } + return this.actionItemProvider(action as Action); + }, + this.actionRunner!, + undefined, + 'toolbar-toggle-pickReactions', + () => { return AnchorAlignment.RIGHT; } + ); + + return toggleReactionAction; + } + + private createReactionsContainer(commentDetailsContainer: HTMLElement): void { + this._reactionActionsContainer = dom.append(commentDetailsContainer, dom.$('div.comment-reactions')); + this._reactionsActionBar = new ActionBar(this._reactionActionsContainer, { + actionItemProvider: action => { + if (action.id === ToggleReactionsAction.ID) { + return new DropdownMenuActionItem( + action, + (action).menuActions, + this.contextMenuService, + action => { + return this.actionItemProvider(action as Action); + }, + this.actionRunner!, + undefined, + 'toolbar-toggle-pickReactions', + () => { return AnchorAlignment.RIGHT; } + ); + } + return this.actionItemProvider(action as Action); + } + }); + this._toDispose.push(this._reactionsActionBar); + + this.comment.commentReactions!.map(reaction => { + let action = new ReactionAction(`reaction.${reaction.label}`, `${reaction.label}`, reaction.hasReacted && reaction.canEdit ? 'active' : '', reaction.canEdit, async () => { + try { + let commentThread = this.commentThread as modes.CommentThread2; + if (commentThread.commentThreadHandle) { + await this.commentService.toggleReaction(this.owner, this.resource, this.commentThread as modes.CommentThread2, this.comment, reaction); + } else { + if (reaction.hasReacted) { + await this.commentService.deleteReaction(this.owner, this.resource, this.comment, reaction); + } else { + await this.commentService.addReaction(this.owner, this.resource, this.comment, reaction); + } + } + } catch (e) { + let error: string; + + if (reaction.hasReacted) { + error = e.message + ? nls.localize('commentDeleteReactionError', "Deleting the comment reaction failed: {0}.", e.message) + : nls.localize('commentDeleteReactionDefaultError', "Deleting the comment reaction failed"); + } else { + error = e.message + ? nls.localize('commentAddReactionError', "Deleting the comment reaction failed: {0}.", e.message) + : nls.localize('commentAddReactionDefaultError', "Deleting the comment reaction failed"); + } + this.notificationService.error(error); + } + }, reaction.iconPath, reaction.count); + + if (this._reactionsActionBar) { + this._reactionsActionBar.push(action, { label: true, icon: true }); + } + }); + + let reactionGroup = this.commentService.getReactionGroup(this.owner); + if (reactionGroup && reactionGroup.length) { + let commentThread = this.commentThread as modes.CommentThread2; + if (commentThread.commentThreadHandle) { + let toggleReactionAction = this.createReactionPicker2(); + this._reactionsActionBar.push(toggleReactionAction, { label: false, icon: true }); + } else { + let toggleReactionAction = this.createReactionPicker(); + this._reactionsActionBar.push(toggleReactionAction, { label: false, icon: true }); + } + } + } + + private createCommentEditor(): void { + const container = dom.append(this._commentEditContainer, dom.$('.edit-textarea')); + this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, container, SimpleCommentEditor.getEditorOptions(), this.parentEditor, this.parentThread); + const resource = URI.parse(`comment:commentinput-${this.comment.commentId}-${Date.now()}.md`); + this._commentEditorModel = this.modelService.createModel('', this.modeService.createByFilepathOrFirstLine(resource.path), resource, false); + + this._commentEditor.setModel(this._commentEditorModel); + this._commentEditor.setValue(this.comment.body.value); + this._commentEditor.layout({ width: container.clientWidth - 14, height: 90 }); + this._commentEditor.focus(); + + const lastLine = this._commentEditorModel.getLineCount(); + const lastColumn = this._commentEditorModel.getLineContent(lastLine).length + 1; + this._commentEditor.setSelection(new Selection(lastLine, lastColumn, lastLine, lastColumn)); + + let commentThread = this.commentThread as modes.CommentThread2; + if (commentThread.commentThreadHandle) { + commentThread.input = { + uri: this._commentEditor.getModel()!.uri, + value: this.comment.body.value + }; + this.commentService.setActiveCommentThread(commentThread); + + this._commentEditorDisposables.push(this._commentEditor.onDidFocusEditorWidget(() => { + commentThread.input = { + uri: this._commentEditor!.getModel()!.uri, + value: this.comment.body.value + }; + this.commentService.setActiveCommentThread(commentThread); + })); + + this._commentEditorDisposables.push(this._commentEditor.onDidChangeModelContent(e => { + if (commentThread.input && this._commentEditor && this._commentEditor.getModel()!.uri === commentThread.input.uri) { + let newVal = this._commentEditor.getValue(); + if (newVal !== commentThread.input.value) { + let input = commentThread.input; + input.value = newVal; + commentThread.input = input; + } + } + })); + } + + this._toDispose.push(this._commentEditor); + this._toDispose.push(this._commentEditorModel); + } + + private removeCommentEditor() { + this.isEditing = false; + this._editAction.enabled = true; + this._body.classList.remove('hidden'); + + this._commentEditorModel.dispose(); + this._commentEditorDisposables.forEach(dispose => dispose.dispose()); + this._commentEditorDisposables = []; + if (this._commentEditor) { + this._commentEditor.dispose(); + this._commentEditor = null; + } + + this._commentEditContainer.remove(); + } + + async editComment(): Promise { + if (!this._commentEditor) { + throw new Error('No comment editor'); + } + + this._updateCommentButton.enabled = false; + this._updateCommentButton.label = UPDATE_IN_PROGRESS_LABEL; + + try { + const newBody = this._commentEditor.getValue(); + + if (this.comment.editCommand) { + let commentThread = this.commentThread as modes.CommentThread2; + commentThread.input = { + uri: this._commentEditor.getModel()!.uri, + value: newBody + }; + this.commentService.setActiveCommentThread(commentThread); + let commandId = this.comment.editCommand.id; + let args = this.comment.editCommand.arguments || []; + + await this.commandService.executeCommand(commandId, ...args); + } else { + await this.commentService.editComment(this.owner, this.resource, this.comment, newBody); + } + + this._updateCommentButton.enabled = true; + this._updateCommentButton.label = UPDATE_COMMENT_LABEL; + this._commentEditor.getDomNode()!.style.outline = ''; + this.removeCommentEditor(); + const editedComment = assign({}, this.comment, { body: new MarkdownString(newBody) }); + this.update(editedComment); + } catch (e) { + this._updateCommentButton.enabled = true; + this._updateCommentButton.label = UPDATE_COMMENT_LABEL; + + this._commentEditor.getDomNode()!.style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`; + this._errorEditingContainer.textContent = e.message + ? nls.localize('commentEditError', "Updating the comment failed: {0}.", e.message) + : nls.localize('commentEditDefaultError', "Updating the comment failed."); + this._errorEditingContainer.classList.remove('hidden'); + this._commentEditor.focus(); + } + } + + private createDeleteAction(): Action { + return new Action('comment.delete', nls.localize('label.delete', "Delete"), 'octicon octicon-x', true, () => { + return this.dialogService.confirm({ + message: nls.localize('confirmDelete', "Delete comment?"), + type: 'question', + primaryButton: nls.localize('label.delete', "Delete") + }).then(async result => { + if (result.confirmed) { + try { + if (this.comment.deleteCommand) { + this.commentService.setActiveCommentThread(this.commentThread as modes.CommentThread2); + let commandId = this.comment.deleteCommand.id; + let args = this.comment.deleteCommand.arguments || []; + + await this.commandService.executeCommand(commandId, ...args); + } else { + const didDelete = await this.commentService.deleteComment(this.owner, this.resource, this.comment); + if (didDelete) { + this._onDidDelete.fire(this); + } else { + throw Error(); + } + } + } catch (e) { + const error = e.message + ? nls.localize('commentDeletionError', "Deleting the comment failed: {0}.", e.message) + : nls.localize('commentDeletionDefaultError', "Deleting the comment failed"); + this.notificationService.error(error); + } + } + }); + }); + } + + private createEditAction(commentDetailsContainer: HTMLElement): Action { + return new Action('comment.edit', nls.localize('label.edit', "Edit"), 'octicon octicon-pencil', true, () => { + this.isEditing = true; + this._body.classList.add('hidden'); + this._commentEditContainer = dom.append(commentDetailsContainer, dom.$('.edit-container')); + this.createCommentEditor(); + + this._errorEditingContainer = dom.append(this._commentEditContainer, dom.$('.validation-error.hidden')); + const formActions = dom.append(this._commentEditContainer, dom.$('.form-actions')); + + const cancelEditButton = new Button(formActions); + cancelEditButton.label = nls.localize('label.cancel', "Cancel"); + this._toDispose.push(attachButtonStyler(cancelEditButton, this.themeService)); + + this._toDispose.push(cancelEditButton.onDidClick(_ => { + this.removeCommentEditor(); + })); + + this._updateCommentButton = new Button(formActions); + this._updateCommentButton.label = UPDATE_COMMENT_LABEL; + this._toDispose.push(attachButtonStyler(this._updateCommentButton, this.themeService)); + + this._toDispose.push(this._updateCommentButton.onDidClick(_ => { + this.editComment(); + })); + + this._commentEditorDisposables.push(this._commentEditor!.onDidChangeModelContent(_ => { + this._updateCommentButton.enabled = !!this._commentEditor!.getValue(); + })); + + this._editAction.enabled = false; + return Promise.resolve(); + }); + } + + private registerActionBarListeners(actionsContainer: HTMLElement): void { + this._toDispose.push(dom.addDisposableListener(this._domNode, 'mouseenter', () => { + actionsContainer.classList.remove('hidden'); + })); + + this._toDispose.push(dom.addDisposableListener(this._domNode, 'focus', () => { + actionsContainer.classList.remove('hidden'); + })); + + this._toDispose.push(dom.addDisposableListener(this._domNode, 'mouseleave', () => { + if (!this._domNode.contains(document.activeElement)) { + actionsContainer.classList.add('hidden'); + } + })); + + this._toDispose.push(dom.addDisposableListener(this._domNode, 'focusout', (e: FocusEvent) => { + if (!this._domNode.contains((e.relatedTarget))) { + actionsContainer.classList.add('hidden'); + + if (this._commentEditor && this._commentEditor.getValue() === this.comment.body.value) { + this.removeCommentEditor(); + } + } + })); + } + + update(newComment: modes.Comment) { + + if (newComment.body !== this.comment.body) { + this._body.removeChild(this._md); + this._md = this.markdownRenderer.render(newComment.body).element; + this._body.appendChild(this._md); + } + + const shouldUpdateActions = newComment.editCommand !== this.comment.editCommand || newComment.deleteCommand !== this.comment.deleteCommand; + this.comment = newComment; + + if (shouldUpdateActions) { + dom.clearNode(this._actionsToolbarContainer); + this.createActionsToolbar(); + } + + + if (newComment.label) { + this._isPendingLabel.innerText = newComment.label; + } else if (newComment.isDraft) { + this._isPendingLabel.innerText = 'Pending'; + } else { + this._isPendingLabel.innerText = ''; + } + + // update comment reactions + if (this._reactionActionsContainer) { + this._reactionActionsContainer.remove(); + } + + if (this._reactionsActionBar) { + this._reactionsActionBar.clear(); + } + + if (this.comment.commentReactions && this.comment.commentReactions.length) { + this.createReactionsContainer(this._commentDetailsContainer); + } + } + + focus() { + this.domNode.focus(); + if (!this._clearTimeout) { + dom.addClass(this.domNode, 'focus'); + this._clearTimeout = setTimeout(() => { + dom.removeClass(this.domNode, 'focus'); + }, 3000); + } + } + + dispose() { + this._toDispose.forEach(disposeable => disposeable.dispose()); + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/comments/electron-browser/commentService.ts b/src/vs/workbench/contrib/comments/electron-browser/commentService.ts similarity index 65% rename from src/vs/workbench/parts/comments/electron-browser/commentService.ts rename to src/vs/workbench/contrib/comments/electron-browser/commentService.ts index 8d678e72d5..0a03972f90 100644 --- a/src/vs/workbench/parts/comments/electron-browser/commentService.ts +++ b/src/vs/workbench/contrib/comments/electron-browser/commentService.ts @@ -3,17 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CommentThread, DocumentCommentProvider, CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction } from 'vs/editor/common/modes'; +import { CommentThread, DocumentCommentProvider, CommentThreadChangedEvent, CommentInfo, Comment, CommentReaction, CommentingRanges, CommentThread2 } from 'vs/editor/common/modes'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { Range } from 'vs/editor/common/core/range'; +import { Range, IRange } from 'vs/editor/common/core/range'; import { keys } from 'vs/base/common/map'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { MainThreadDocumentCommentProvider } from 'vs/workbench/api/electron-browser/mainThreadComments'; import { assign } from 'vs/base/common/objects'; -import { ICommentThreadChangedEvent } from 'vs/workbench/parts/comments/common/commentModel'; +import { ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; +import { MainThreadCommentController } from 'vs/workbench/api/electron-browser/mainThreadComments'; export const ICommentService = createDecorator('commentService'); @@ -36,28 +36,37 @@ export interface ICommentService { readonly onDidSetResourceCommentInfos: Event; readonly onDidSetAllCommentThreads: Event; readonly onDidUpdateCommentThreads: Event; + readonly onDidChangeActiveCommentThread: Event; + readonly onDidChangeActiveCommentingRange: Event<{ range: Range, commentingRangesInfo: CommentingRanges }>; + readonly onDidChangeInput: Event; readonly onDidSetDataProvider: Event; readonly onDidDeleteDataProvider: Event; setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void; setWorkspaceComments(owner: string, commentsByResource: CommentThread[]): void; removeWorkspaceComments(owner: string): void; - registerDataProvider(owner: string, commentProvider: MainThreadDocumentCommentProvider): void; + registerCommentController(owner: string, commentControl: MainThreadCommentController): void; + unregisterCommentController(owner: string): void; + registerDataProvider(owner: string, commentProvider: DocumentCommentProvider): void; unregisterDataProvider(owner: string): void; updateComments(ownerId: string, event: CommentThreadChangedEvent): void; createNewCommentThread(owner: string, resource: URI, range: Range, text: string): Promise; replyToCommentThread(owner: string, resource: URI, range: Range, thread: CommentThread, text: string): Promise; editComment(owner: string, resource: URI, comment: Comment, text: string): Promise; deleteComment(owner: string, resource: URI, comment: Comment): Promise; - getComments(resource: URI): Promise; + getComments(resource: URI): Promise<(ICommentInfo | null)[]>; + getCommentingRanges(resource: URI): Promise; startDraft(owner: string, resource: URI): void; deleteDraft(owner: string, resource: URI): void; finishDraft(owner: string, resource: URI): void; - getStartDraftLabel(owner: string): string; - getDeleteDraftLabel(owner: string): string; - getFinishDraftLabel(owner: string): string; + getStartDraftLabel(owner: string): string | undefined; + getDeleteDraftLabel(owner: string): string | undefined; + getFinishDraftLabel(owner: string): string | undefined; addReaction(owner: string, resource: URI, comment: Comment, reaction: CommentReaction): Promise; deleteReaction(owner: string, resource: URI, comment: Comment, reaction: CommentReaction): Promise; - getReactionGroup(owner: string): CommentReaction[]; + getReactionGroup(owner: string): CommentReaction[] | undefined; + toggleReaction(owner: string, resource: URI, thread: CommentThread2, comment: Comment, reaction: CommentReaction): Promise; + setActiveCommentThread(commentThread: CommentThread | null); + setInput(input: string); } export class CommentService extends Disposable implements ICommentService { @@ -78,12 +87,36 @@ export class CommentService extends Disposable implements ICommentService { private readonly _onDidUpdateCommentThreads: Emitter = this._register(new Emitter()); readonly onDidUpdateCommentThreads: Event = this._onDidUpdateCommentThreads.event; + private readonly _onDidChangeActiveCommentThread = this._register(new Emitter()); + readonly onDidChangeActiveCommentThread: Event = this._onDidChangeActiveCommentThread.event; + + private readonly _onDidChangeInput: Emitter = this._register(new Emitter()); + readonly onDidChangeInput: Event = this._onDidChangeInput.event; + private readonly _onDidChangeActiveCommentingRange: Emitter<{ + range: Range, commentingRangesInfo: + CommentingRanges + }> = this._register(new Emitter<{ + range: Range, commentingRangesInfo: + CommentingRanges + }>()); + readonly onDidChangeActiveCommentingRange: Event<{ range: Range, commentingRangesInfo: CommentingRanges }> = this._onDidChangeActiveCommentingRange.event; + private _commentProviders = new Map(); + private _commentControls = new Map(); + constructor() { super(); } + setActiveCommentThread(commentThread: CommentThread | null) { + this._onDidChangeActiveCommentThread.fire(commentThread); + } + + setInput(input: string) { + this._onDidChangeInput.fire(input); + } + setDocumentComments(resource: URI, commentInfos: ICommentInfo[]): void { this._onDidSetResourceCommentInfos.fire({ resource, commentInfos }); } @@ -96,7 +129,17 @@ export class CommentService extends Disposable implements ICommentService { this._onDidSetAllCommentThreads.fire({ ownerId: owner, commentThreads: [] }); } - registerDataProvider(owner: string, commentProvider: DocumentCommentProvider) { + registerCommentController(owner: string, commentControl: MainThreadCommentController): void { + this._commentControls.set(owner, commentControl); + this._onDidSetDataProvider.fire(); + } + + unregisterCommentController(owner: string): void { + this._commentControls.delete(owner); + this._onDidDeleteDataProvider.fire(owner); + } + + registerDataProvider(owner: string, commentProvider: DocumentCommentProvider): void { this._commentProviders.set(owner, commentProvider); this._onDidSetDataProvider.fire(); } @@ -201,51 +244,67 @@ export class CommentService extends Disposable implements ICommentService { } } - getReactionGroup(owner: string): CommentReaction[] { - const commentProvider = this._commentProviders.get(owner); + async toggleReaction(owner: string, resource: URI, thread: CommentThread2, comment: Comment, reaction: CommentReaction): Promise { + const commentController = this._commentControls.get(owner); - if (commentProvider) { - return commentProvider.reactionGroup; + if (commentController) { + return commentController.toggleReaction(resource, thread, comment, reaction, CancellationToken.None); + } else { + throw new Error('Not supported'); } - - return null; } - getStartDraftLabel(owner: string): string | null { + getReactionGroup(owner: string): CommentReaction[] | undefined { + const commentProvider = this._commentControls.get(owner); + + if (commentProvider) { + return commentProvider.getReactionGroup(); + } + + const commentController = this._commentControls.get(owner); + + if (commentController) { + return commentController.getReactionGroup(); + } + + return undefined; + } + + getStartDraftLabel(owner: string): string | undefined { const commentProvider = this._commentProviders.get(owner); if (commentProvider) { return commentProvider.startDraftLabel; } - return null; + return undefined; } - getDeleteDraftLabel(owner: string): string { + getDeleteDraftLabel(owner: string): string | undefined { const commentProvider = this._commentProviders.get(owner); if (commentProvider) { return commentProvider.deleteDraftLabel; } - return null; + return undefined; } - getFinishDraftLabel(owner: string): string { + getFinishDraftLabel(owner: string): string | undefined { const commentProvider = this._commentProviders.get(owner); if (commentProvider) { return commentProvider.finishDraftLabel; } - return null; + return undefined; } - getComments(resource: URI): Promise { - const result: Promise[] = []; + async getComments(resource: URI): Promise<(ICommentInfo | null)[]> { + const result: Promise[] = []; for (const owner of keys(this._commentProviders)) { const provider = this._commentProviders.get(owner); - if (provider.provideDocumentComments) { + if (provider && provider.provideDocumentComments) { result.push(provider.provideDocumentComments(resource, CancellationToken.None).then(commentInfo => { if (commentInfo) { return { @@ -262,6 +321,25 @@ export class CommentService extends Disposable implements ICommentService { } } - return Promise.all(result); + let commentControlResult: Promise[] = []; + + this._commentControls.forEach(control => { + commentControlResult.push(control.getDocumentComments(resource, CancellationToken.None)); + }); + + let ret = [...await Promise.all(result), ...await Promise.all(commentControlResult)]; + + return ret; + } + + async getCommentingRanges(resource: URI): Promise { + let commentControlResult: Promise[] = []; + + this._commentControls.forEach(control => { + commentControlResult.push(control.getCommentingRanges(resource, CancellationToken.None)); + }); + + let ret = await Promise.all(commentControlResult); + return ret.reduce((prev, curr) => { prev.push(...curr); return prev; }, []); } } diff --git a/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts b/src/vs/workbench/contrib/comments/electron-browser/commentThreadWidget.ts similarity index 61% rename from src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts rename to src/vs/workbench/contrib/comments/electron-browser/commentThreadWidget.ts index 4cf135bac5..afdb632f55 100644 --- a/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts +++ b/src/vs/workbench/contrib/comments/electron-browser/commentThreadWidget.ts @@ -11,67 +11,63 @@ import { Action } from 'vs/base/common/actions'; import * as arrays from 'vs/base/common/arrays'; import { Color } from 'vs/base/common/color'; import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import * as platform from 'vs/base/common/platform'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import * as strings from 'vs/base/common/strings'; import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser'; import * as modes from 'vs/editor/common/modes'; import { peekViewBorder } from 'vs/editor/contrib/referenceSearch/referencesWidget'; -import { IOptions, ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; +import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import { attachButtonStyler } from 'vs/platform/theme/common/styler'; import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { CommentGlyphWidget } from 'vs/workbench/parts/comments/electron-browser/commentGlyphWidget'; +import { CommentGlyphWidget } from 'vs/workbench/contrib/comments/electron-browser/commentGlyphWidget'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IModelService } from 'vs/editor/common/services/modelService'; import { SimpleCommentEditor } from './simpleCommentEditor'; import { URI } from 'vs/base/common/uri'; import { transparent, editorForeground, textLinkActiveForeground, textLinkForeground, focusBorder, textBlockQuoteBackground, textBlockQuoteBorder, contrastBorder, inputValidationErrorBorder, inputValidationErrorBackground, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { ICommentService } from 'vs/workbench/parts/comments/electron-browser/commentService'; +import { ICommentService } from 'vs/workbench/contrib/comments/electron-browser/commentService'; import { Range, IRange } from 'vs/editor/common/core/range'; import { IPosition } from 'vs/editor/common/core/position'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; -import { CommentNode } from 'vs/workbench/parts/comments/electron-browser/commentNode'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { INotificationService } from 'vs/platform/notification/common/notification'; +import { CommentNode } from 'vs/workbench/contrib/comments/electron-browser/commentNode'; import { ITextModel } from 'vs/editor/common/model'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { generateUuid } from 'vs/base/common/uuid'; +import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; +import { withNullAsUndefined } from 'vs/base/common/types'; export const COMMENTEDITOR_DECORATION_KEY = 'commenteditordecoration'; const COLLAPSE_ACTION_CLASS = 'expand-review-action octicon octicon-x'; const COMMENT_SCHEME = 'comment'; + let INMEM_MODEL_ID = 0; -export class ReviewZoneWidget extends ZoneWidget { +export class ReviewZoneWidget extends ZoneWidget implements ICommentThreadWidget { private _headElement: HTMLElement; protected _headingLabel: HTMLElement; protected _actionbarWidget: ActionBar; private _bodyElement: HTMLElement; + private _parentEditor: ICodeEditor; private _commentEditor: ICodeEditor; private _commentsElement: HTMLElement; private _commentElements: CommentNode[]; private _commentForm: HTMLElement; private _reviewThreadReplyButton: HTMLElement; private _resizeObserver: any; - private _onDidClose = new Emitter(); + private _onDidClose = new Emitter(); private _onDidCreateThread = new Emitter(); private _isCollapsed; private _collapseAction: Action; - private _commentThread: modes.CommentThread; - private _commentGlyph: CommentGlyphWidget; - private _owner: string; - private _pendingComment: string; - private _draftMode: modes.DraftMode; - private _localToDispose: IDisposable[]; + private _commentGlyph?: CommentGlyphWidget; + private _submitActionsDisposables: IDisposable[]; private _globalToDispose: IDisposable[]; private _markdownRenderer: MarkdownRenderer; private _styleElement: HTMLStyleElement; - private _formActions: HTMLElement; + private _formActions: HTMLElement | null; private _error: HTMLElement; public get owner(): string { @@ -81,40 +77,33 @@ export class ReviewZoneWidget extends ZoneWidget { return this._commentThread; } - public get extensionId(): string { + public get extensionId(): string | undefined { return this._commentThread.extensionId; } - public get draftMode(): modes.DraftMode { + public get draftMode(): modes.DraftMode | undefined { return this._draftMode; } constructor( - private instantiationService: IInstantiationService, - private modeService: IModeService, - private modelService: IModelService, - private themeService: IThemeService, - private commentService: ICommentService, - private openerService: IOpenerService, - private dialogService: IDialogService, - private notificationService: INotificationService, - private contextMenuService: IContextMenuService, editor: ICodeEditor, - owner: string, - commentThread: modes.CommentThread, - pendingComment: string, - draftMode: modes.DraftMode, - options: IOptions = { keepEditorSelection: true } + private _owner: string, + private _commentThread: modes.CommentThread | modes.CommentThread2, + private _pendingComment: string, + private _draftMode: modes.DraftMode | undefined, + @IInstantiationService private instantiationService: IInstantiationService, + @IModeService private modeService: IModeService, + @ICommandService private commandService: ICommandService, + @IModelService private modelService: IModelService, + @IThemeService private themeService: IThemeService, + @ICommentService private commentService: ICommentService, + @IOpenerService private openerService: IOpenerService ) { - super(editor, options); + super(editor, { keepEditorSelection: true }); this._resizeObserver = null; - this._owner = owner; - this._commentThread = commentThread; - this._pendingComment = pendingComment; - this._draftMode = draftMode; - this._isCollapsed = commentThread.collapsibleState !== modes.CommentThreadCollapsibleState.Expanded; + this._isCollapsed = _commentThread.collapsibleState !== modes.CommentThreadCollapsibleState.Expanded; this._globalToDispose = []; - this._localToDispose = []; + this._submitActionsDisposables = []; this._formActions = null; this.create(); @@ -128,9 +117,10 @@ export class ReviewZoneWidget extends ZoneWidget { this._applyTheme(this.themeService.getTheme()); this._markdownRenderer = new MarkdownRenderer(editor, this.modeService, this.openerService); + this._parentEditor = editor; } - public get onDidClose(): Event { + public get onDidClose(): Event { return this._onDidClose.event; } @@ -139,13 +129,14 @@ export class ReviewZoneWidget extends ZoneWidget { } public getPosition(): IPosition | undefined { - let position: IPosition = this.position; - if (position) { - return position; + if (this.position) { + return this.position; } - position = this._commentGlyph.getPosition().position; - return position; + if (this._commentGlyph) { + return withNullAsUndefined(this._commentGlyph.getPosition().position); + } + return undefined; } protected revealLine(lineNumber: number) { @@ -172,7 +163,7 @@ export class ReviewZoneWidget extends ZoneWidget { this.editor.revealRangeInCenter(this._commentThread.range); } - public getPendingComment(): string { + public getPendingComment(): string | null { if (this._commentEditor) { let model = this._commentEditor.getModel(); @@ -192,6 +183,10 @@ export class ReviewZoneWidget extends ZoneWidget { this._bodyElement = dom.$('.body'); container.appendChild(this._bodyElement); + + dom.addDisposableListener(this._bodyElement, dom.EventType.FOCUS_IN, e => { + this.commentService.setActiveCommentThread(this._commentThread); + }); } protected _fillHead(container: HTMLElement): void { @@ -205,24 +200,38 @@ export class ReviewZoneWidget extends ZoneWidget { this._disposables.push(this._actionbarWidget); this._collapseAction = new Action('review.expand', nls.localize('label.collapse', "Collapse"), COLLAPSE_ACTION_CLASS, true, () => { - if (this._commentThread.comments.length === 0) { + if (this._commentThread.comments.length === 0 && (this._commentThread as modes.CommentThread2).commentThreadHandle === undefined) { this.dispose(); - return null; + return Promise.resolve(); } this._isCollapsed = true; this.hide(); - return null; + return Promise.resolve(); }); this._actionbarWidget.push(this._collapseAction, { label: false, icon: true }); } - toggleExpand() { - this._collapseAction.run(); + public getGlyphPosition(): number { + if (this._commentGlyph) { + return this._commentGlyph.getPosition().position!.lineNumber; + } + return 0; } - update(commentThread: modes.CommentThread) { + toggleExpand(lineNumber: number) { + if (this._isCollapsed) { + this.show({ lineNumber: lineNumber, column: 1 }, 2); + } else { + this.hide(); + if (this._commentThread === null || this._commentThread.threadId === null) { + this.dispose(); + } + } + } + + async update(commentThread: modes.CommentThread | modes.CommentThread2) { const oldCommentsLen = this._commentElements.length; const newCommentsLen = commentThread.comments.length; @@ -252,6 +261,7 @@ export class ReviewZoneWidget extends ZoneWidget { let currentComment = commentThread.comments[i]; let oldCommentNode = this._commentElements.filter(commentNode => commentNode.comment.commentId === currentComment.commentId); if (oldCommentNode.length) { + oldCommentNode[0].update(currentComment); lastCommentElement = oldCommentNode[0].domNode; newCommentNodeList.unshift(oldCommentNode[0]); } else { @@ -274,8 +284,14 @@ export class ReviewZoneWidget extends ZoneWidget { // Move comment glyph widget and show position if the line has changed. const lineNumber = this._commentThread.range.startLineNumber; - if (this._commentGlyph.getPosition().position.lineNumber !== lineNumber) { - this._commentGlyph.setLineNumber(lineNumber); + if (this._commentGlyph) { + if (this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) { + this._commentGlyph.setLineNumber(lineNumber); + } + } + + if (!this._reviewThreadReplyButton) { + this.createReplyButton(); } if (!this._isCollapsed) { @@ -283,16 +299,22 @@ export class ReviewZoneWidget extends ZoneWidget { } } - updateDraftMode(draftMode: modes.DraftMode) { - this._draftMode = draftMode; + updateDraftMode(draftMode: modes.DraftMode | undefined) { + if (this._draftMode !== draftMode) { + this._draftMode = draftMode; - if (this._formActions) { - let model = this._commentEditor.getModel(); - dom.clearNode(this._formActions); - this.createCommentWidgetActions(this._formActions, model); + if (this._formActions && this._commentEditor.hasModel()) { + const model = this._commentEditor.getModel(); + dom.clearNode(this._formActions); + this.createCommentWidgetActions(this._formActions, model); + } } } + protected _onWidth(widthInPixel: number): void { + this._commentEditor.layout({ height: (this._commentEditor.hasWidgetFocus() ? 5 : 1) * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ }); + } + protected _doLayout(heightInPixel: number, widthInPixel: number): void { this._commentEditor.layout({ height: (this._commentEditor.hasWidgetFocus() ? 5 : 1) * 18, width: widthInPixel - 54 /* margin 20px * 10 + scrollbar 14px*/ }); } @@ -300,8 +322,9 @@ export class ReviewZoneWidget extends ZoneWidget { display(lineNumber: number) { this._commentGlyph = new CommentGlyphWidget(this.editor, lineNumber); - this._localToDispose.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); - this._localToDispose.push(this.editor.onMouseUp(e => this.onEditorMouseUp(e))); + this._disposables.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); + this._disposables.push(this.editor.onMouseUp(e => this.onEditorMouseUp(e))); + let headHeight = Math.ceil(this.editor.getConfiguration().lineHeight * 1.2); this._headElement.style.height = `${headHeight}px`; this._headElement.style.lineHeight = this._headElement.style.height; @@ -319,18 +342,73 @@ export class ReviewZoneWidget extends ZoneWidget { const hasExistingComments = this._commentThread.comments.length > 0; this._commentForm = dom.append(this._bodyElement, dom.$('.comment-form')); - this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, this._commentForm, SimpleCommentEditor.getEditorOptions()); - const modeId = hasExistingComments ? this._commentThread.threadId : ++INMEM_MODEL_ID; + this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, this._commentForm, SimpleCommentEditor.getEditorOptions(), this._parentEditor, this); + + const modeId = generateUuid() + '-' + (hasExistingComments ? this._commentThread.threadId : ++INMEM_MODEL_ID); const params = JSON.stringify({ extensionId: this.extensionId, commentThreadId: this.commentThread.threadId }); const resource = URI.parse(`${COMMENT_SCHEME}:commentinput-${modeId}.md?${params}`); const model = this.modelService.createModel(this._pendingComment || '', this.modeService.createByFilepathOrFirstLine(resource.path), resource, false); - this._localToDispose.push(model); + this._disposables.push(model); this._commentEditor.setModel(model); - this._localToDispose.push(this._commentEditor); - this._localToDispose.push(this._commentEditor.getModel().onDidChangeContent(() => this.setCommentEditorDecorations())); + this._disposables.push(this._commentEditor); + this._disposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => this.setCommentEditorDecorations())); + if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { + this._disposables.push(this._commentEditor.onDidFocusEditorWidget(() => { + let commentThread = this._commentThread as modes.CommentThread2; + commentThread.input = { + uri: this._commentEditor.getModel()!.uri, + value: this._commentEditor.getValue() + }; + this.commentService.setActiveCommentThread(this._commentThread); + })); + + this._disposables.push(this._commentEditor.getModel()!.onDidChangeContent(() => { + let modelContent = this._commentEditor.getValue(); + let thread = (this._commentThread as modes.CommentThread2); + if (thread.input && thread.input.uri === this._commentEditor.getModel()!.uri && thread.input.value !== modelContent) { + let newInput: modes.CommentInput = thread.input; + newInput.value = modelContent; + thread.input = newInput; + } + })); + + this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeInput(input => { + let thread = (this._commentThread as modes.CommentThread2); + + if (thread.input && thread.input.uri !== this._commentEditor.getModel()!.uri) { + return; + } + if (!input) { + return; + } + + if (this._commentEditor.getValue() !== input.value) { + this._commentEditor.setValue(input.value); + + if (input.value === '') { + this._pendingComment = ''; + if (dom.hasClass(this._commentForm, 'expand')) { + dom.removeClass(this._commentForm, 'expand'); + } + this._commentEditor.getDomNode()!.style.outline = ''; + this._error.textContent = ''; + dom.addClass(this._error, 'hidden'); + } + } + })); + + this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeComments(async _ => { + await this.update(this._commentThread); + })); + + this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeLabel(_ => { + this.createThreadLabel(); + })); + } + this.setCommentEditorDecorations(); // Only add the additional step of clicking a reply button to expand the textarea when there are existing comments @@ -343,30 +421,56 @@ export class ReviewZoneWidget extends ZoneWidget { } } - - this._localToDispose.push(this._commentEditor.onKeyDown((ev: IKeyboardEvent) => { - const hasExistingComments = this._commentThread.comments.length > 0; - - if (this._commentEditor.getModel().getValueLength() === 0 && ev.keyCode === KeyCode.Escape) { - if (hasExistingComments) { - if (dom.hasClass(this._commentForm, 'expand')) { - dom.removeClass(this._commentForm, 'expand'); - } - } else { - this.dispose(); - } - } - - if (this._commentEditor.getModel().getValueLength() !== 0 && ev.keyCode === KeyCode.Enter && (ev.ctrlKey || ev.metaKey)) { - let lineNumber = this._commentGlyph.getPosition().position.lineNumber; - this.createComment(lineNumber); - } - })); - this._error = dom.append(this._commentForm, dom.$('.validation-error.hidden')); this._formActions = dom.append(this._commentForm, dom.$('.form-actions')); - this.createCommentWidgetActions(this._formActions, model); + if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { + this.createCommentWidgetActions2(this._formActions, model); + + this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeAcceptInputCommand(_ => { + if (this._formActions) { + dom.clearNode(this._formActions); + this.createCommentWidgetActions2(this._formActions, model); + } + })); + + this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeAdditionalCommands(_ => { + if (this._formActions) { + dom.clearNode(this._formActions); + this.createCommentWidgetActions2(this._formActions, model); + } + })); + + this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeRange(range => { + // Move comment glyph widget and show position if the line has changed. + const lineNumber = this._commentThread.range.startLineNumber; + if (this._commentGlyph) { + if (this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) { + this._commentGlyph.setLineNumber(lineNumber); + } + } + + if (!this._isCollapsed) { + this.show({ lineNumber, column: 1 }, 2); + } + })); + + this._disposables.push((this._commentThread as modes.CommentThread2).onDidChangeCollasibleState(state => { + if (state === modes.CommentThreadCollapsibleState.Expanded && this._isCollapsed) { + const lineNumber = this._commentThread.range.startLineNumber; + + this.show({ lineNumber, column: 1 }, 2); + return; + } + + if (state === modes.CommentThreadCollapsibleState.Collapsed && !this._isCollapsed) { + this.hide(); + return; + } + })); + } else { + this.createCommentWidgetActions(this._formActions, model); + } this._resizeObserver = new MutationObserver(this._refresh.bind(this)); @@ -382,9 +486,9 @@ export class ReviewZoneWidget extends ZoneWidget { } // If there are no existing comments, place focus on the text area. This must be done after show, which also moves focus. - if (this._commentThread.reply && !this._commentThread.comments.length) { + if ((this._commentThread as modes.CommentThread).reply && !this._commentThread.comments.length) { this._commentEditor.focus(); - } else if (this._commentEditor.getModel().getValueLength() > 0) { + } else if (this._commentEditor.getModel()!.getValueLength() > 0) { if (!dom.hasClass(this._commentForm, 'expand')) { dom.addClass(this._commentForm, 'expand'); } @@ -394,17 +498,23 @@ export class ReviewZoneWidget extends ZoneWidget { private handleError(e: Error) { this._error.textContent = e.message; - this._commentEditor.getDomNode().style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`; + this._commentEditor.getDomNode()!.style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`; dom.removeClass(this._error, 'hidden'); } + private getActiveComment(): CommentNode | ReviewZoneWidget { + return this._commentElements.filter(node => node.isEditing)[0] || this; + } + private createCommentWidgetActions(container: HTMLElement, model: ITextModel) { + dispose(this._submitActionsDisposables); + const button = new Button(container); - this._localToDispose.push(attachButtonStyler(button, this.themeService)); + this._submitActionsDisposables.push(attachButtonStyler(button, this.themeService)); button.label = 'Add comment'; button.enabled = model.getValueLength() > 0; - this._localToDispose.push(this._commentEditor.onDidChangeModelContent(_ => { + this._submitActionsDisposables.push(this._commentEditor.onDidChangeModelContent(_ => { if (this._commentEditor.getValue()) { button.enabled = true; } else { @@ -413,8 +523,7 @@ export class ReviewZoneWidget extends ZoneWidget { })); button.onDidClick(async () => { - let lineNumber = this._commentGlyph.getPosition().position.lineNumber; - this.createComment(lineNumber); + this.createComment(); }); if (this._draftMode === modes.DraftMode.NotSupported) { @@ -426,13 +535,13 @@ export class ReviewZoneWidget extends ZoneWidget { const deleteDraftLabel = this.commentService.getDeleteDraftLabel(this._owner); if (deleteDraftLabel) { const deletedraftButton = new Button(container); - this._disposables.push(attachButtonStyler(deletedraftButton, this.themeService)); + this._submitActionsDisposables.push(attachButtonStyler(deletedraftButton, this.themeService)); deletedraftButton.label = deleteDraftLabel; deletedraftButton.enabled = true; this._disposables.push(deletedraftButton.onDidClick(async () => { try { - await this.commentService.deleteDraft(this._owner, this.editor.getModel().uri); + await this.commentService.deleteDraft(this._owner, this.editor.getModel()!.uri); } catch (e) { this.handleError(e); } @@ -442,17 +551,16 @@ export class ReviewZoneWidget extends ZoneWidget { const submitDraftLabel = this.commentService.getFinishDraftLabel(this._owner); if (submitDraftLabel) { const submitdraftButton = new Button(container); - this._disposables.push(attachButtonStyler(submitdraftButton, this.themeService)); - submitdraftButton.label = this.commentService.getFinishDraftLabel(this._owner); + this._submitActionsDisposables.push(attachButtonStyler(submitdraftButton, this.themeService)); + submitdraftButton.label = this.commentService.getFinishDraftLabel(this._owner)!; submitdraftButton.enabled = true; submitdraftButton.onDidClick(async () => { try { - let lineNumber = this._commentGlyph.getPosition().position.lineNumber; if (this._commentEditor.getValue()) { - await this.createComment(lineNumber); + await this.createComment(); } - await this.commentService.finishDraft(this._owner, this.editor.getModel().uri); + await this.commentService.finishDraft(this._owner, this.editor.getModel()!.uri); } catch (e) { this.handleError(e); } @@ -465,10 +573,10 @@ export class ReviewZoneWidget extends ZoneWidget { if (startDraftLabel) { const draftButton = new Button(container); this._disposables.push(attachButtonStyler(draftButton, this.themeService)); - draftButton.label = this.commentService.getStartDraftLabel(this._owner); + draftButton.label = this.commentService.getStartDraftLabel(this._owner)!; draftButton.enabled = model.getValueLength() > 0; - this._localToDispose.push(this._commentEditor.onDidChangeModelContent(_ => { + this._submitActionsDisposables.push(this._commentEditor.onDidChangeModelContent(_ => { if (this._commentEditor.getValue()) { draftButton.enabled = true; } else { @@ -478,9 +586,8 @@ export class ReviewZoneWidget extends ZoneWidget { this._disposables.push(draftButton.onDidClick(async () => { try { - await this.commentService.startDraft(this._owner, this.editor.getModel().uri); - let lineNumber = this._commentGlyph.getPosition().position.lineNumber; - await this.createComment(lineNumber); + await this.commentService.startDraft(this._owner, this.editor.getModel()!.uri); + await this.createComment(); } catch (e) { this.handleError(e); } @@ -491,20 +598,62 @@ export class ReviewZoneWidget extends ZoneWidget { } } + /** + * Command based actions. + */ + private createCommentWidgetActions2(container: HTMLElement, model: ITextModel) { + let commentThread = this._commentThread as modes.CommentThread2; + + const { acceptInputCommand } = commentThread; + if (acceptInputCommand) { + const button = new Button(container); + this._disposables.push(attachButtonStyler(button, this.themeService)); + + button.label = acceptInputCommand.title; + this._disposables.push(button.onDidClick(async () => { + commentThread.input = { + uri: this._commentEditor.getModel()!.uri, + value: this._commentEditor.getValue() + }; + this.commentService.setActiveCommentThread(this._commentThread); + await this.commandService.executeCommand(acceptInputCommand.id, ...(acceptInputCommand.arguments || [])); + })); + + button.enabled = model.getValueLength() > 0; + this._disposables.push(this._commentEditor.onDidChangeModelContent(_ => { + if (this._commentEditor.getValue()) { + button.enabled = true; + } else { + button.enabled = false; + } + })); + } + + commentThread.additionalCommands.reverse().forEach(command => { + const button = new Button(container); + this._disposables.push(attachButtonStyler(button, this.themeService)); + + button.label = command.title; + this._disposables.push(button.onDidClick(async () => { + commentThread.input = { + uri: this._commentEditor.getModel()!.uri, + value: this._commentEditor.getValue() + }; + this.commentService.setActiveCommentThread(this._commentThread); + await this.commandService.executeCommand(command.id, ...(command.arguments || [])); + })); + }); + } + private createNewCommentNode(comment: modes.Comment): CommentNode { - let newCommentNode = new CommentNode( + let newCommentNode = this.instantiationService.createInstance(CommentNode, + this._commentThread, comment, this.owner, - this.editor.getModel().uri, - this._markdownRenderer, - this.themeService, - this.instantiationService, - this.commentService, - this.modelService, - this.modeService, - this.dialogService, - this.notificationService, - this.contextMenuService); + this.editor.getModel()!.uri, + this._parentEditor, + this, + this._markdownRenderer); this._disposables.push(newCommentNode); this._disposables.push(newCommentNode.onDidDelete(deletedNode => { @@ -530,15 +679,52 @@ export class ReviewZoneWidget extends ZoneWidget { return newCommentNode; } - private async createComment(lineNumber: number): Promise { + async submitComment(): Promise { + const activeComment = this.getActiveComment(); + if (activeComment instanceof ReviewZoneWidget) { + if ((this._commentThread as modes.CommentThread2).commentThreadHandle) { + let commentThread = this._commentThread as modes.CommentThread2; + + if (commentThread.acceptInputCommand) { + commentThread.input = { + uri: this._commentEditor.getModel()!.uri, + value: this._commentEditor.getValue() + }; + this.commentService.setActiveCommentThread(this._commentThread); + let commandId = commentThread.acceptInputCommand.id; + let args = commentThread.acceptInputCommand.arguments || []; + + await this.commandService.executeCommand(commandId, ...args); + return; + } + } else { + this.createComment(); + } + } + + if (activeComment instanceof CommentNode) { + activeComment.editComment(); + } + } + + async createComment(): Promise { try { + if (this._commentEditor.getModel()!.getValueLength() === 0) { + return; + } + if (!this._commentGlyph) { + return; + } + let newCommentThread; + const lineNumber = this._commentGlyph.getPosition().position!.lineNumber; const isReply = this._commentThread.threadId !== null; + if (isReply) { newCommentThread = await this.commentService.replyToCommentThread( this._owner, - this.editor.getModel().uri, + this.editor.getModel()!.uri, new Range(lineNumber, 1, lineNumber, 1), this._commentThread, this._commentEditor.getValue() @@ -546,7 +732,7 @@ export class ReviewZoneWidget extends ZoneWidget { } else { newCommentThread = await this.commentService.createNewCommentThread( this._owner, - this.editor.getModel().uri, + this.editor.getModel()!.uri, new Range(lineNumber, 1, lineNumber, 1), this._commentEditor.getValue() ); @@ -562,7 +748,7 @@ export class ReviewZoneWidget extends ZoneWidget { if (dom.hasClass(this._commentForm, 'expand')) { dom.removeClass(this._commentForm, 'expand'); } - this._commentEditor.getDomNode().style.outline = ''; + this._commentEditor.getDomNode()!.style.outline = ''; this._error.textContent = ''; dom.addClass(this._error, 'hidden'); this.update(newCommentThread); @@ -575,18 +761,24 @@ export class ReviewZoneWidget extends ZoneWidget { this._error.textContent = e.message ? nls.localize('commentCreationError', "Adding a comment failed: {0}.", e.message) : nls.localize('commentCreationDefaultError', "Adding a comment failed. Please try again or report an issue with the extension if the problem persists."); - this._commentEditor.getDomNode().style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`; + this._commentEditor.getDomNode()!.style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`; dom.removeClass(this._error, 'hidden'); } } private createThreadLabel() { - let label: string; - if (this._commentThread.comments.length) { - const participantsList = this._commentThread.comments.filter(arrays.uniqueFilter(comment => comment.userName)).map(comment => `@${comment.userName}`).join(', '); - label = nls.localize('commentThreadParticipants', "Participants: {0}", participantsList); - } else { - label = nls.localize('startThread', "Start discussion"); + let label: string | undefined; + if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { + label = (this._commentThread as modes.CommentThread2).label; + } + + if (label === undefined) { + if (this._commentThread.comments.length) { + const participantsList = this._commentThread.comments.filter(arrays.uniqueFilter(comment => comment.userName)).map(comment => `@${comment.userName}`).join(', '); + label = nls.localize('commentThreadParticipants', "Participants: {0}", participantsList); + } else { + label = nls.localize('startThread', "Start discussion"); + } } this._headingLabel.innerHTML = strings.escape(label); @@ -602,14 +794,18 @@ export class ReviewZoneWidget extends ZoneWidget { private createReplyButton() { this._reviewThreadReplyButton = dom.append(this._commentForm, dom.$('button.review-thread-reply-button')); - this._reviewThreadReplyButton.title = nls.localize('reply', "Reply..."); + if ((this._commentThread as modes.CommentThread2).commentThreadHandle !== undefined) { + // this._reviewThreadReplyButton.title = (this._commentThread as modes.CommentThread2).acceptInputCommands.title; + } else { + this._reviewThreadReplyButton.title = nls.localize('reply', "Reply..."); + } this._reviewThreadReplyButton.textContent = nls.localize('reply', "Reply..."); // bind click/escape actions for reviewThreadReplyButton and textArea - this._localToDispose.push(dom.addDisposableListener(this._reviewThreadReplyButton, 'click', _ => this.expandReplyArea())); - this._localToDispose.push(dom.addDisposableListener(this._reviewThreadReplyButton, 'focus', _ => this.expandReplyArea())); + this._disposables.push(dom.addDisposableListener(this._reviewThreadReplyButton, 'click', _ => this.expandReplyArea())); + this._disposables.push(dom.addDisposableListener(this._reviewThreadReplyButton, 'focus', _ => this.expandReplyArea())); this._commentEditor.onDidBlurEditorWidget(() => { - if (this._commentEditor.getModel().getValueLength() === 0 && dom.hasClass(this._commentForm, 'expand')) { + if (this._commentEditor.getModel()!.getValueLength() === 0 && dom.hasClass(this._commentForm, 'expand')) { dom.removeClass(this._commentForm, 'expand'); } }); @@ -631,14 +827,13 @@ export class ReviewZoneWidget extends ZoneWidget { private setCommentEditorDecorations() { const model = this._commentEditor && this._commentEditor.getModel(); if (model) { - let valueLength = model.getValueLength(); + const valueLength = model.getValueLength(); const hasExistingComments = this._commentThread.comments.length > 0; - let keybinding = platform.isMacintosh ? 'Cmd+Enter' : 'Ctrl+Enter'; - let placeholder = valueLength > 0 + const placeholder = valueLength > 0 ? '' - : (hasExistingComments - ? `Reply... (press ${keybinding} to submit)` - : `Type a new comment (press ${keybinding} to submit)`); + : hasExistingComments + ? nls.localize('reply', "Reply...") + : nls.localize('newComment', "Type a new comment"); const decorations = [{ range: { startLineNumber: 0, @@ -649,7 +844,7 @@ export class ReviewZoneWidget extends ZoneWidget { renderOptions: { after: { contentText: placeholder, - color: transparent(editorForeground, 0.4)(this.themeService.getTheme()).toString() + color: `${transparent(editorForeground, 0.4)(this.themeService.getTheme())}` } } }]; @@ -658,7 +853,7 @@ export class ReviewZoneWidget extends ZoneWidget { } } - private mouseDownInfo: { lineNumber: number }; + private mouseDownInfo: { lineNumber: number } | null; private onEditorMouseDown(e: IEditorMouseEvent): void { this.mouseDownInfo = null; @@ -710,19 +905,12 @@ export class ReviewZoneWidget extends ZoneWidget { return; } - if (this._commentGlyph && this._commentGlyph.getPosition().position.lineNumber !== lineNumber) { + if (this._commentGlyph && this._commentGlyph.getPosition().position!.lineNumber !== lineNumber) { return; } if (e.target.element.className.indexOf('comment-thread') >= 0) { - if (this._isCollapsed) { - this.show({ lineNumber: lineNumber, column: 1 }, 2); - } else { - this.hide(); - if (this._commentThread === null || this._commentThread.threadId === null) { - this.dispose(); - } - } + this.toggleExpand(lineNumber); } } @@ -814,11 +1002,11 @@ export class ReviewZoneWidget extends ZoneWidget { if (this._commentGlyph) { this._commentGlyph.dispose(); - this._commentGlyph = null; + this._commentGlyph = undefined; } this._globalToDispose.forEach(global => global.dispose()); - this._localToDispose.forEach(local => local.dispose()); + this._submitActionsDisposables.forEach(local => local.dispose()); this._onDidClose.fire(undefined); } } \ No newline at end of file diff --git a/src/vs/workbench/parts/comments/electron-browser/comments.contribution.ts b/src/vs/workbench/contrib/comments/electron-browser/comments.contribution.ts similarity index 87% rename from src/vs/workbench/parts/comments/electron-browser/comments.contribution.ts rename to src/vs/workbench/contrib/comments/electron-browser/comments.contribution.ts index e274f88393..544a2996ee 100644 --- a/src/vs/workbench/parts/comments/electron-browser/comments.contribution.ts +++ b/src/vs/workbench/contrib/comments/electron-browser/comments.contribution.ts @@ -6,8 +6,8 @@ import * as nls from 'vs/nls'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; -import 'vs/workbench/parts/comments/electron-browser/commentsEditorContribution'; -import { ICommentService, CommentService } from 'vs/workbench/parts/comments/electron-browser/commentService'; +import 'vs/workbench/contrib/comments/electron-browser/commentsEditorContribution'; +import { ICommentService, CommentService } from 'vs/workbench/contrib/comments/electron-browser/commentService'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; export interface ICommentsConfiguration { diff --git a/src/vs/workbench/parts/comments/electron-browser/commentsEditorContribution.ts b/src/vs/workbench/contrib/comments/electron-browser/commentsEditorContribution.ts similarity index 66% rename from src/vs/workbench/parts/comments/electron-browser/commentsEditorContribution.ts rename to src/vs/workbench/contrib/comments/electron-browser/commentsEditorContribution.ts index f4587140df..e9f67aff05 100644 --- a/src/vs/workbench/parts/comments/electron-browser/commentsEditorContribution.ts +++ b/src/vs/workbench/contrib/comments/electron-browser/commentsEditorContribution.ts @@ -6,39 +6,38 @@ import 'vs/css!./media/review'; import * as nls from 'vs/nls'; import { $ } from 'vs/base/browser/dom'; -import { findFirstInSorted } from 'vs/base/common/arrays'; +import { findFirstInSorted, coalesce } from 'vs/base/common/arrays'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ICodeEditor, IEditorMouseEvent, IViewZone, MouseTargetType } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IEditorMouseEvent, IViewZone, MouseTargetType, isDiffEditor, isCodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { registerEditorContribution, EditorAction, registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { IEditorContribution, IModelChangedEvent } from 'vs/editor/common/editorCommon'; -import { IRange } from 'vs/editor/common/core/range'; +import { IRange, Range } from 'vs/editor/common/core/range'; import * as modes from 'vs/editor/common/modes'; import { peekViewResultsBackground, peekViewResultsSelectionBackground, peekViewTitleBackground } from 'vs/editor/contrib/referenceSearch/referencesWidget'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { editorForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { CommentThreadCollapsibleState } from 'vs/workbench/api/node/extHostTypes'; -import { ReviewZoneWidget, COMMENTEDITOR_DECORATION_KEY } from 'vs/workbench/parts/comments/electron-browser/commentThreadWidget'; -import { ICommentService, ICommentInfo } from 'vs/workbench/parts/comments/electron-browser/commentService'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { ReviewZoneWidget, COMMENTEDITOR_DECORATION_KEY } from 'vs/workbench/contrib/comments/electron-browser/commentThreadWidget'; +import { ICommentService, ICommentInfo } from 'vs/workbench/contrib/comments/electron-browser/commentService'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IModelDecorationOptions } from 'vs/editor/common/model'; import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; -import { overviewRulerCommentingRangeForeground } from 'vs/workbench/parts/comments/electron-browser/commentGlyphWidget'; +import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/common/async'; +import { overviewRulerCommentingRangeForeground } from 'vs/workbench/contrib/comments/electron-browser/commentGlyphWidget'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { STATUS_BAR_ITEM_HOVER_BACKGROUND, STATUS_BAR_ITEM_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme'; +import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { ctxCommentEditorFocused, SimpleCommentEditor } from 'vs/workbench/contrib/comments/electron-browser/simpleCommentEditor'; +import { onUnexpectedError } from 'vs/base/common/errors'; -export const ctxReviewPanelVisible = new RawContextKey('reviewPanelVisible', false); +export const ctxCommentThreadVisible = new RawContextKey('commentThreadVisible', false); export const ID = 'editor.contrib.review'; @@ -66,7 +65,7 @@ class CommentingRangeDecoration { return this._decorationId; } - constructor(private _editor: ICodeEditor, private _ownerId: string, private _extensionId: string, private _range: IRange, private _reply: modes.Command, commentingOptions: ModelDecorationOptions) { + constructor(private _editor: ICodeEditor, private _ownerId: string, private _extensionId: string | undefined, private _range: IRange, private _reply: modes.Command | undefined, commentingOptions: ModelDecorationOptions, private commentingRangesInfo?: modes.CommentingRanges) { const startLineNumber = _range.startLineNumber; const endLineNumber = _range.endLineNumber; let commentingRangeDecorations = [{ @@ -83,11 +82,12 @@ class CommentingRangeDecoration { } } - public getCommentAction(): { replyCommand: modes.Command, ownerId: string, extensionId: string } { + public getCommentAction(): { replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, commentingRangesInfo: modes.CommentingRanges | undefined } { return { extensionId: this._extensionId, replyCommand: this._reply, - ownerId: this._ownerId + ownerId: this._ownerId, + commentingRangesInfo: this.commentingRangesInfo }; } @@ -96,7 +96,7 @@ class CommentingRangeDecoration { } public getActiveRange() { - return this._editor.getModel().getDecorationRange(this._decorationId); + return this._editor.getModel()!.getDecorationRange(this._decorationId); } } class CommentingRangeDecorator { @@ -122,9 +122,15 @@ class CommentingRangeDecorator { let commentingRangeDecorations: CommentingRangeDecoration[] = []; for (const info of commentInfos) { - info.commentingRanges.forEach(range => { - commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, range, info.reply, this.decorationOptions)); - }); + if (Array.isArray(info.commentingRanges)) { + info.commentingRanges.forEach(range => { + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, range, info.reply, this.decorationOptions)); + }); + } else { + (info.commentingRanges ? info.commentingRanges.ranges : []).forEach(range => { + commentingRangeDecorations.push(new CommentingRangeDecoration(editor, info.owner, info.extensionId, range, (info.commentingRanges as modes.CommentingRanges).newCommentThreadCommand, this.decorationOptions, info.commentingRanges as modes.CommentingRanges)); + }); + } } let oldDecorations = this.commentingRangeDecorations.map(decoration => decoration.id); @@ -136,7 +142,7 @@ class CommentingRangeDecorator { public getMatchedCommentAction(line: number) { for (const decoration of this.commentingRangeDecorations) { const range = decoration.getActiveRange(); - if (range.startLineNumber <= line && line <= range.endLineNumber) { + if (range && range.startLineNumber <= line && line <= range.endLineNumber) { return decoration.getCommentAction(); } } @@ -154,31 +160,28 @@ export class ReviewController implements IEditorContribution { private globalToDispose: IDisposable[]; private localToDispose: IDisposable[]; private editor: ICodeEditor; - private _newCommentWidget: ReviewZoneWidget; + private _newCommentWidget?: ReviewZoneWidget; private _commentWidgets: ReviewZoneWidget[]; - private _reviewPanelVisible: IContextKey; + private _commentThreadVisible: IContextKey; private _commentInfos: ICommentInfo[]; private _commentingRangeDecorator: CommentingRangeDecorator; private mouseDownInfo: { lineNumber: number } | null = null; private _commentingRangeSpaceReserved = false; - private _computePromise: CancelablePromise | null; - + private _computePromise: CancelablePromise> | null; + private _computeCommentingRangePromise: CancelablePromise | null; + private _computeCommentingRangeScheduler: Delayer> | null; private _pendingCommentCache: { [key: number]: { [key: string]: string } }; - private _pendingNewCommentCache: { [key: string]: { lineNumber: number, replyCommand: modes.Command, ownerId: string, extensionId: string, pendingComment: string, draftMode: modes.DraftMode } }; + private _pendingNewCommentCache: { [key: string]: { lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, pendingComment: string, draftMode: modes.DraftMode | undefined } }; constructor( editor: ICodeEditor, - @IContextKeyService contextKeyService: IContextKeyService, - @IThemeService private readonly themeService: IThemeService, + @IContextKeyService readonly contextKeyService: IContextKeyService, @ICommentService private readonly commentService: ICommentService, + @ICommandService private readonly _commandService: ICommandService, @INotificationService private readonly notificationService: INotificationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IModeService private readonly modeService: IModeService, - @IModelService private readonly modelService: IModelService, @ICodeEditorService private readonly codeEditorService: ICodeEditorService, - @IOpenerService private readonly openerService: IOpenerService, - @IDialogService private readonly dialogService: IDialogService, - @IContextMenuService private readonly contextMenuService: IContextMenuService, + @IContextMenuService readonly contextMenuService: IContextMenuService, ) { this.editor = editor; this.globalToDispose = []; @@ -187,17 +190,16 @@ export class ReviewController implements IEditorContribution { this._commentWidgets = []; this._pendingCommentCache = {}; this._pendingNewCommentCache = {}; - this._newCommentWidget = null; this._computePromise = null; - this._reviewPanelVisible = ctxReviewPanelVisible.bindTo(contextKeyService); + this._commentThreadVisible = ctxCommentThreadVisible.bindTo(contextKeyService); this._commentingRangeDecorator = new CommentingRangeDecorator(); this.globalToDispose.push(this.commentService.onDidDeleteDataProvider(ownerId => { // Remove new comment widget and glyph, refresh comments if (this._newCommentWidget && this._newCommentWidget.owner === ownerId) { this._newCommentWidget.dispose(); - this._newCommentWidget = null; + this._newCommentWidget = undefined; } delete this._pendingCommentCache[ownerId]; @@ -206,7 +208,7 @@ export class ReviewController implements IEditorContribution { this.globalToDispose.push(this.commentService.onDidSetDataProvider(_ => this.beginCompute())); this.globalToDispose.push(this.commentService.onDidSetResourceCommentInfos(e => { - const editorURI = this.editor && this.editor.getModel() && this.editor.getModel().uri; + const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri; if (editorURI && editorURI.toString() === e.resource.toString()) { this.setComments(e.commentInfos.filter(commentInfo => commentInfo !== null)); } @@ -219,7 +221,7 @@ export class ReviewController implements IEditorContribution { private beginCompute(): Promise { this._computePromise = createCancelablePromise(token => { - const editorURI = this.editor && this.editor.getModel() && this.editor.getModel().uri; + const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri; if (editorURI) { return this.commentService.getComments(editorURI); @@ -229,11 +231,36 @@ export class ReviewController implements IEditorContribution { }); return this._computePromise.then(commentInfos => { - this.setComments(commentInfos.filter(commentInfo => commentInfo !== null)); + this.setComments(coalesce(commentInfos)); this._computePromise = null; }, error => console.log(error)); } + private beginComputeCommentingRanges() { + if (this._computeCommentingRangeScheduler) { + if (this._computeCommentingRangePromise) { + this._computeCommentingRangePromise.cancel(); + this._computeCommentingRangePromise = null; + } + + this._computeCommentingRangeScheduler.trigger(() => { + const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri; + + if (editorURI) { + return this.commentService.getComments(editorURI); + } + + return Promise.resolve([]); + }).then(commentInfos => { + const meaningfulCommentInfos = coalesce(commentInfos); + this._commentingRangeDecorator.update(this.editor, meaningfulCommentInfos); + }, (err) => { + onUnexpectedError(err); + return null; + }); + } + } + public static get(editor: ICodeEditor): ReviewController { return editor.getContribution(ID); } @@ -250,7 +277,7 @@ export class ReviewController implements IEditorContribution { } public nextCommentThread(): void { - if (!this._commentWidgets.length) { + if (!this._commentWidgets.length || !this.editor.hasModel()) { return; } @@ -311,9 +338,9 @@ export class ReviewController implements IEditorContribution { if (this._newCommentWidget) { this._newCommentWidget.dispose(); - this._newCommentWidget = null; + this._newCommentWidget = undefined; } - this.editor = null; + this.editor = null!; // Strict null override — nulling out in dispose } public onModelChanged(e: IModelChangedEvent): void { @@ -342,7 +369,7 @@ export class ReviewController implements IEditorContribution { } this._newCommentWidget.dispose(); - this._newCommentWidget = null; + this._newCommentWidget = undefined; } this.removeCommentWidgetsAndStoreCache(); @@ -354,10 +381,21 @@ export class ReviewController implements IEditorContribution { this.localToDispose.push(this.editor.onMouseDown(e => this.onEditorMouseDown(e))); this.localToDispose.push(this.editor.onMouseUp(e => this.onEditorMouseUp(e))); - this.localToDispose.push(this.editor.onDidChangeModelContent(() => { + + this._computeCommentingRangeScheduler = new Delayer(200); + this.localToDispose.push({ + dispose: () => { + if (this._computeCommentingRangeScheduler) { + this._computeCommentingRangeScheduler.cancel(); + } + this._computeCommentingRangeScheduler = null; + } + }); + this.localToDispose.push(this.editor.onDidChangeModelContent(async () => { + this.beginComputeCommentingRanges(); })); this.localToDispose.push(this.commentService.onDidUpdateCommentThreads(e => { - const editorURI = this.editor && this.editor.getModel() && this.editor.getModel().uri; + const editorURI = this.editor && this.editor.hasModel() && this.editor.getModel().uri; if (!editorURI) { return; } @@ -367,9 +405,9 @@ export class ReviewController implements IEditorContribution { return; } - let added = e.added.filter(thread => thread.resource.toString() === editorURI.toString()); - let removed = e.removed.filter(thread => thread.resource.toString() === editorURI.toString()); - let changed = e.changed.filter(thread => thread.resource.toString() === editorURI.toString()); + let added = e.added.filter(thread => thread.resource && thread.resource.toString() === editorURI.toString()); + let removed = e.removed.filter(thread => thread.resource && thread.resource.toString() === editorURI.toString()); + let changed = e.changed.filter(thread => thread.resource && thread.resource.toString() === editorURI.toString()); let draftMode = e.draftMode; commentInfo.forEach(info => info.draftMode = draftMode); @@ -396,9 +434,7 @@ export class ReviewController implements IEditorContribution { } }); added.forEach(thread => { - let zoneWidget = new ReviewZoneWidget(this.instantiationService, this.modeService, this.modelService, this.themeService, this.commentService, this.openerService, this.dialogService, this.notificationService, this.contextMenuService, this.editor, e.owner, thread, null, draftMode); - zoneWidget.display(thread.range.startLineNumber); - this._commentWidgets.push(zoneWidget); + this.displayCommentThread(e.owner, thread, null, draftMode); this._commentInfos.filter(info => info.owner === e.owner)[0].threads.push(thread); }); @@ -407,15 +443,21 @@ export class ReviewController implements IEditorContribution { this.beginCompute(); } - private addComment(lineNumber: number, replyCommand: modes.Command, ownerId: string, extensionId: string, draftMode: modes.DraftMode, pendingComment: string) { - if (this._newCommentWidget !== null) { - this.notificationService.warn(`Please submit the comment at line ${this._newCommentWidget.position.lineNumber} before creating a new one.`); + private displayCommentThread(owner: string, thread: modes.CommentThread | modes.CommentThread2, pendingComment: string | null, draftMode: modes.DraftMode | undefined): void { + const zoneWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, owner, thread, pendingComment, draftMode); + zoneWidget.display(thread.range.startLineNumber); + this._commentWidgets.push(zoneWidget); + } + + private addComment(lineNumber: number, replyCommand: modes.Command | undefined, ownerId: string, extensionId: string | undefined, draftMode: modes.DraftMode | undefined, pendingComment: string | null) { + if (this._newCommentWidget) { + this.notificationService.warn(`Please submit the comment at line ${this._newCommentWidget.position ? this._newCommentWidget.position.lineNumber : -1} before creating a new one.`); return; } // add new comment - this._reviewPanelVisible.set(true); - this._newCommentWidget = new ReviewZoneWidget(this.instantiationService, this.modeService, this.modelService, this.themeService, this.commentService, this.openerService, this.dialogService, this.notificationService, this.contextMenuService, this.editor, ownerId, { + this._commentThreadVisible.set(true); + this._newCommentWidget = this.instantiationService.createInstance(ReviewZoneWidget, this.editor, ownerId, { extensionId: extensionId, threadId: null, resource: null, @@ -430,24 +472,24 @@ export class ReviewController implements IEditorContribution { collapsibleState: CommentThreadCollapsibleState.Expanded, }, pendingComment, draftMode); - this.localToDispose.push(this._newCommentWidget.onDidClose(e => { + this.localToDispose.push(this._newCommentWidget!.onDidClose(e => { this.clearNewCommentWidget(); })); - this.localToDispose.push(this._newCommentWidget.onDidCreateThread(commentWidget => { + this.localToDispose.push(this._newCommentWidget!.onDidCreateThread(commentWidget => { const thread = commentWidget.commentThread; this._commentWidgets.push(commentWidget); this._commentInfos.filter(info => info.owner === commentWidget.owner)[0].threads.push(thread); this.clearNewCommentWidget(); })); - this._newCommentWidget.display(lineNumber); + this._newCommentWidget!.display(lineNumber); } private clearNewCommentWidget() { - this._newCommentWidget = null; + this._newCommentWidget = undefined; - if (this.editor && this.editor.getModel()) { + if (this.editor && this.editor.hasModel()) { delete this._pendingNewCommentCache[this.editor.getModel().uri.toString()]; } } @@ -503,20 +545,49 @@ export class ReviewController implements IEditorContribution { } if (e.target.element.className.indexOf('comment-diff-added') >= 0) { - const lineNumber = e.target.position.lineNumber; - let newCommentInfo = this._commentingRangeDecorator.getMatchedCommentAction(lineNumber); - if (!newCommentInfo) { - return; - } - const { replyCommand, ownerId, extensionId } = newCommentInfo; + const lineNumber = e.target.position!.lineNumber; + this.addCommentAtLine(lineNumber); + } + } - let commentInfo = this._commentInfos.filter(info => info.owner === ownerId); + public addOrToggleCommentAtLine(lineNumber: number): void { + // The widget's position is undefined until the widget has been displayed, so rely on the glyph position instead + const existingCommentsAtLine = this._commentWidgets.filter(widget => widget.getGlyphPosition() === lineNumber); + if (existingCommentsAtLine.length) { + existingCommentsAtLine.forEach(widget => widget.toggleExpand(lineNumber)); + return; + } else { + this.addCommentAtLine(lineNumber); + } + } + + public addCommentAtLine(lineNumber: number): void { + const newCommentInfo = this._commentingRangeDecorator.getMatchedCommentAction(lineNumber); + if (!newCommentInfo || !this.editor.hasModel()) { + return; + } + + const { replyCommand, ownerId, extensionId, commentingRangesInfo } = newCommentInfo; + + if (commentingRangesInfo) { + let range = new Range(lineNumber, 1, lineNumber, 1); + if (commentingRangesInfo.newCommentThreadCommand) { + if (replyCommand) { + const commandId = replyCommand.id; + const args = replyCommand.arguments || []; + + this._commandService.executeCommand(commandId, ...args); + } + } else if (commentingRangesInfo.newCommentThreadCallback) { + commentingRangesInfo.newCommentThreadCallback(this.editor.getModel().uri, range); + } + } else { + const commentInfo = this._commentInfos.filter(info => info.owner === ownerId); if (!commentInfo || !commentInfo.length) { return; } - let draftMode = commentInfo[0].draftMode; - + const draftMode = commentInfo[0].draftMode; this.addComment(lineNumber, replyCommand, ownerId, extensionId, draftMode, null); } } @@ -530,12 +601,13 @@ export class ReviewController implements IEditorContribution { this._commentInfos = commentInfos; let lineDecorationsWidth: number = this.editor.getConfiguration().layoutInfo.decorationsWidth; - if (this._commentInfos.some(info => Boolean(info.commentingRanges && info.commentingRanges.length))) { + if (this._commentInfos.some(info => Boolean(info.commentingRanges && (Array.isArray(info.commentingRanges) ? info.commentingRanges : info.commentingRanges.ranges).length))) { if (!this._commentingRangeSpaceReserved) { this._commentingRangeSpaceReserved = true; let extraEditorClassName: string[] = []; - if (this.editor.getRawConfiguration().extraEditorClassName) { - extraEditorClassName = this.editor.getRawConfiguration().extraEditorClassName.split(' '); + const configuredExtraClassName = this.editor.getRawConfiguration().extraEditorClassName; + if (configuredExtraClassName) { + extraEditorClassName = configuredExtraClassName.split(' '); } if (this.editor.getConfiguration().contribInfo.folding) { @@ -564,7 +636,7 @@ export class ReviewController implements IEditorContribution { this._commentInfos.forEach(info => { let providerCacheStore = this._pendingCommentCache[info.owner]; info.threads.forEach(thread => { - let pendingComment: string = null; + let pendingComment: string | null = null; if (providerCacheStore) { pendingComment = providerCacheStore[thread.threadId]; } @@ -573,25 +645,23 @@ export class ReviewController implements IEditorContribution { thread.collapsibleState = modes.CommentThreadCollapsibleState.Expanded; } - let zoneWidget = new ReviewZoneWidget(this.instantiationService, this.modeService, this.modelService, this.themeService, this.commentService, this.openerService, this.dialogService, this.notificationService, this.contextMenuService, this.editor, info.owner, thread, pendingComment, info.draftMode); - zoneWidget.display(thread.range.startLineNumber); - this._commentWidgets.push(zoneWidget); + this.displayCommentThread(info.owner, thread, pendingComment, info.draftMode); }); }); const commentingRanges: IRange[] = []; this._commentInfos.forEach(info => { - commentingRanges.push(...info.commentingRanges); + commentingRanges.push(...(Array.isArray(info.commentingRanges) ? info.commentingRanges : info.commentingRanges ? info.commentingRanges.ranges : [])); }); this._commentingRangeDecorator.update(this.editor, this._commentInfos); } public closeWidget(): void { - this._reviewPanelVisible.reset(); + this._commentThreadVisible.reset(); if (this._newCommentWidget) { this._newCommentWidget.dispose(); - this._newCommentWidget = null; + this._newCommentWidget = undefined; } if (this._commentWidgets) { @@ -599,7 +669,7 @@ export class ReviewController implements IEditorContribution { } this.editor.focus(); - this.editor.revealRangeInCenter(this.editor.getSelection()); + this.editor.revealRangeInCenter(this.editor.getSelection()!); } private removeCommentWidgetsAndStoreCache() { @@ -647,28 +717,71 @@ export class NextCommentThreadAction extends EditorAction { } } + registerEditorContribution(ReviewController); registerEditorAction(NextCommentThreadAction); +CommandsRegistry.registerCommand({ + id: 'workbench.action.addComment', + handler: (accessor) => { + const activeEditor = getActiveEditor(accessor); + if (!activeEditor) { + return Promise.resolve(); + } + + const controller = ReviewController.get(activeEditor); + if (!controller) { + return Promise.resolve(); + } + + const position = activeEditor.getPosition(); + controller.addOrToggleCommentAtLine(position.lineNumber); + return Promise.resolve(); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.submitComment', + weight: KeybindingWeight.EditorContrib, + primary: KeyMod.CtrlCmd | KeyCode.Enter, + when: ctxCommentEditorFocused, + handler: (accessor, args) => { + const activeCodeEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); + if (activeCodeEditor instanceof SimpleCommentEditor) { + activeCodeEditor.getParentThread().submitComment(); + } + } +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'closeReviewPanel', weight: KeybindingWeight.EditorContrib, primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], - when: ctxReviewPanelVisible, + when: ctxCommentThreadVisible, handler: closeReviewPanel }); -export function getOuterEditor(accessor: ServicesAccessor): ICodeEditor { - const editor = accessor.get(ICodeEditorService).getFocusedCodeEditor(); - if (editor instanceof EmbeddedCodeEditorWidget) { - return editor.getParentEditor(); +export function getActiveEditor(accessor: ServicesAccessor): IActiveCodeEditor | null { + let activeTextEditorWidget = accessor.get(IEditorService).activeTextEditorWidget; + + if (isDiffEditor(activeTextEditorWidget)) { + if (activeTextEditorWidget.getOriginalEditor().hasTextFocus()) { + activeTextEditorWidget = activeTextEditorWidget.getOriginalEditor(); + } else { + activeTextEditorWidget = activeTextEditorWidget.getModifiedEditor(); + } } - return editor; + + if (!isCodeEditor(activeTextEditorWidget) || !activeTextEditorWidget.hasModel()) { + return null; + } + + return activeTextEditorWidget; } function closeReviewPanel(accessor: ServicesAccessor, args: any) { - const outerEditor = getOuterEditor(accessor); + const outerEditor = getActiveEditor(accessor); if (!outerEditor) { return; } @@ -748,13 +861,11 @@ registerThemingParticipant((theme, collector) => { const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND); if (statusBarItemHoverBackground) { - collector.addRule(`.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label:hover { background-color: ${statusBarItemHoverBackground}; border: 1px solid grey; - border-radius: 3px; }`); + collector.addRule(`.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label.active:hover { background-color: ${statusBarItemHoverBackground};}`); } const statusBarItemActiveBackground = theme.getColor(STATUS_BAR_ITEM_ACTIVE_BACKGROUND); if (statusBarItemActiveBackground) { - collector.addRule(`.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label:active { background-color: ${statusBarItemActiveBackground}; border: 1px solid grey; - border-radius: 3px;}`); + collector.addRule(`.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label:active { background-color: ${statusBarItemActiveBackground}; border: 1px solid transparent;}`); } }); diff --git a/src/vs/workbench/parts/comments/electron-browser/commentsPanel.ts b/src/vs/workbench/contrib/comments/electron-browser/commentsPanel.ts similarity index 92% rename from src/vs/workbench/parts/comments/electron-browser/commentsPanel.ts rename to src/vs/workbench/contrib/comments/electron-browser/commentsPanel.ts index 5fc1fb173d..77ac722f77 100644 --- a/src/vs/workbench/parts/comments/electron-browser/commentsPanel.ts +++ b/src/vs/workbench/contrib/comments/electron-browser/commentsPanel.ts @@ -14,10 +14,10 @@ import { TreeResourceNavigator, WorkbenchTree } from 'vs/platform/list/browser/l import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Panel } from 'vs/workbench/browser/panel'; -import { CommentNode, CommentsModel, ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/parts/comments/common/commentModel'; -import { ReviewController } from 'vs/workbench/parts/comments/electron-browser/commentsEditorContribution'; -import { CommentsDataFilter, CommentsDataSource, CommentsModelRenderer } from 'vs/workbench/parts/comments/electron-browser/commentsTreeViewer'; -import { ICommentService, IWorkspaceCommentThreadsEvent } from 'vs/workbench/parts/comments/electron-browser/commentService'; +import { CommentNode, CommentsModel, ResourceWithCommentThreads, ICommentThreadChangedEvent } from 'vs/workbench/contrib/comments/common/commentModel'; +import { ReviewController } from 'vs/workbench/contrib/comments/electron-browser/commentsEditorContribution'; +import { CommentsDataFilter, CommentsDataSource, CommentsModelRenderer } from 'vs/workbench/contrib/comments/electron-browser/commentsTreeViewer'; +import { ICommentService, IWorkspaceCommentThreadsEvent } from 'vs/workbench/contrib/comments/electron-browser/commentService'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { textLinkForeground, textLinkActiveForeground, focusBorder, textPreformatForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -160,7 +160,7 @@ export class CommentsPanel extends Panel { })); } - private openFile(element: any, pinned: boolean, preserveFocus: boolean, sideBySide: boolean): boolean { + private openFile(element: any, pinned?: boolean, preserveFocus?: boolean, sideBySide?: boolean): boolean { if (!element) { return false; } @@ -188,17 +188,17 @@ export class CommentsPanel extends Panel { const threadToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].threadId : element.threadId; const commentToReveal = element instanceof ResourceWithCommentThreads ? element.commentThreads[0].comment : element.comment; - if (commentToReveal.command) { - this.commandService.executeCommand(commentToReveal.command.id, ...commentToReveal.command.arguments).then(_ => { + if (commentToReveal.selectCommand) { + this.commandService.executeCommand(commentToReveal.selectCommand.id, ...(commentToReveal.selectCommand.arguments || [])).then(_ => { let activeWidget = this.editorService.activeTextEditorWidget; if (isDiffEditor(activeWidget)) { const originalEditorWidget = activeWidget.getOriginalEditor(); const modifiedEditorWidget = activeWidget.getModifiedEditor(); let controller; - if (originalEditorWidget.getModel().uri.toString() === element.resource.toString()) { + if (originalEditorWidget.getModel()!.uri.toString() === element.resource.toString()) { controller = ReviewController.get(originalEditorWidget); - } else if (modifiedEditorWidget.getModel().uri.toString() === element.resource.toString()) { + } else if (modifiedEditorWidget.getModel()!.uri.toString() === element.resource.toString()) { controller = ReviewController.get(modifiedEditorWidget); } diff --git a/src/vs/workbench/parts/comments/electron-browser/commentsTreeViewer.ts b/src/vs/workbench/contrib/comments/electron-browser/commentsTreeViewer.ts similarity index 95% rename from src/vs/workbench/parts/comments/electron-browser/commentsTreeViewer.ts rename to src/vs/workbench/contrib/comments/electron-browser/commentsTreeViewer.ts index de89b1115f..81797bda5f 100644 --- a/src/vs/workbench/parts/comments/electron-browser/commentsTreeViewer.ts +++ b/src/vs/workbench/contrib/comments/electron-browser/commentsTreeViewer.ts @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { IDataSource, IFilter, IRenderer as ITreeRenderer, ITree } from 'vs/base/parts/tree/browser/tree'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; -import { CommentNode, CommentsModel, ResourceWithCommentThreads } from 'vs/workbench/parts/comments/common/commentModel'; +import { CommentNode, CommentsModel, ResourceWithCommentThreads } from 'vs/workbench/contrib/comments/common/commentModel'; export class CommentsDataSource implements IDataSource { public getId(tree: ITree, element: any): string { @@ -23,7 +23,7 @@ export class CommentsDataSource implements IDataSource { return element.id; } if (element instanceof CommentNode) { - return element.comment.commentId; + return `${element.resource.toString()}-${element.comment.commentId}`; } return ''; } @@ -42,7 +42,7 @@ export class CommentsDataSource implements IDataSource { if (element instanceof CommentNode) { return Promise.resolve(element.replies); } - return null; + return Promise.resolve([]); } public getParent(tree: ITree, element: any): Promise { @@ -148,15 +148,12 @@ export class CommentsModelRenderer implements ITreeRenderer { inline: true, actionHandler: { callback: (content) => { - let uri: URI; try { - uri = URI.parse(content); + const uri = URI.parse(content); + this.openerService.open(uri).catch(onUnexpectedError); } catch (err) { // ignore } - if (uri) { - this.openerService.open(uri).catch(onUnexpectedError); - } }, disposeables: templateData.disposables } @@ -167,7 +164,7 @@ export class CommentsModelRenderer implements ITreeRenderer { const image = images[i]; const textDescription = dom.$(''); textDescription.textContent = image.alt ? nls.localize('imageWithLabel', "Image: {0}", image.alt) : nls.localize('image', "Image"); - image.parentNode.replaceChild(textDescription, image); + image.parentNode!.replaceChild(textDescription, image); } templateData.commentText.appendChild(renderedComment); diff --git a/src/vs/workbench/parts/comments/electron-browser/media/close.svg b/src/vs/workbench/contrib/comments/electron-browser/media/close.svg similarity index 100% rename from src/vs/workbench/parts/comments/electron-browser/media/close.svg rename to src/vs/workbench/contrib/comments/electron-browser/media/close.svg diff --git a/src/vs/workbench/parts/comments/electron-browser/media/comment.svg b/src/vs/workbench/contrib/comments/electron-browser/media/comment.svg similarity index 100% rename from src/vs/workbench/parts/comments/electron-browser/media/comment.svg rename to src/vs/workbench/contrib/comments/electron-browser/media/comment.svg diff --git a/src/vs/workbench/parts/comments/electron-browser/media/panel.css b/src/vs/workbench/contrib/comments/electron-browser/media/panel.css similarity index 94% rename from src/vs/workbench/parts/comments/electron-browser/media/panel.css rename to src/vs/workbench/contrib/comments/electron-browser/media/panel.css index 035c91592d..ec20973842 100644 --- a/src/vs/workbench/parts/comments/electron-browser/media/panel.css +++ b/src/vs/workbench/contrib/comments/electron-browser/media/panel.css @@ -42,7 +42,7 @@ } .comments-panel .comments-panel-container .tree-container .comment-container .text code { - font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); } .comments-panel .comments-panel-container .message-box-container { diff --git a/src/vs/workbench/contrib/comments/electron-browser/media/reaction-dark.svg b/src/vs/workbench/contrib/comments/electron-browser/media/reaction-dark.svg new file mode 100644 index 0000000000..b51dfbbd9b --- /dev/null +++ b/src/vs/workbench/contrib/comments/electron-browser/media/reaction-dark.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/vs/workbench/contrib/comments/electron-browser/media/reaction-hc.svg b/src/vs/workbench/contrib/comments/electron-browser/media/reaction-hc.svg new file mode 100644 index 0000000000..2c7fa126dd --- /dev/null +++ b/src/vs/workbench/contrib/comments/electron-browser/media/reaction-hc.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/contrib/comments/electron-browser/media/reaction.svg b/src/vs/workbench/contrib/comments/electron-browser/media/reaction.svg new file mode 100644 index 0000000000..8696b8f2ba --- /dev/null +++ b/src/vs/workbench/contrib/comments/electron-browser/media/reaction.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/vs/workbench/parts/comments/electron-browser/media/review.css b/src/vs/workbench/contrib/comments/electron-browser/media/review.css similarity index 78% rename from src/vs/workbench/parts/comments/electron-browser/media/review.css rename to src/vs/workbench/contrib/comments/electron-browser/media/review.css index 8f51335375..96b5a80c2a 100644 --- a/src/vs/workbench/parts/comments/electron-browser/media/review.css +++ b/src/vs/workbench/contrib/comments/electron-browser/media/review.css @@ -47,6 +47,10 @@ margin-left: auto; } +.monaco-editor .review-widget .body .review-comment .comment-actions .monaco-toolbar { + height: 21px; +} + .monaco-editor .review-widget .body .review-comment .comment-actions .action-item { width: 22px; } @@ -115,10 +119,13 @@ margin: 0; } +.monaco-editor .review-widget .body .review-comment .review-comment-contents .author { + line-height: 22px; +} + .monaco-editor.vs-dark .review-widget .body .review-comment .review-comment-contents .author { color: #fff; font-weight: 600; - line-height: 19px; } .monaco-editor .review-widget .body .review-comment .review-comment-contents .isPending { @@ -141,16 +148,79 @@ } .monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item .action-label { - padding: 2px 5px 2px 5px; + padding: 1px 4px; white-space: pre; text-align: center; - font-size: 14px; - margin: 4px; + font-size: 12px; +} + +.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item .action-label .reaction-icon { + background-size: 12px; + background-position: left center; + background-repeat: no-repeat; + width: 16px; + height: 12px; + -webkit-font-smoothing: antialiased; + display: inline-block; + margin-top: 3px; + margin-right: 4px; +} + +.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item .action-label .reaction-label { + line-height: 20px; + margin-right: 4px; +} + +.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label { + display: inline-block; +} + +.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label.toolbar-toggle-pickReactions { + display: none; + background-image: url(./reaction.svg); + width: 26px; + height: 16px; + background-repeat: no-repeat; + background-position: center; + margin-top: 3px; + border: none; +} + +.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions:hover .action-item a.action-label.toolbar-toggle-pickReactions { + display: inline-block; +} + +.monaco-editor.vs-dark .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label.toolbar-toggle-pickReactions { + background-image: url(./reaction-dark.svg); +} + +.monaco-editor.hc-black .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label.toolbar-toggle-pickReactions { + background-image: url(./reaction-hc.svg); +} + +.monaco-editor .review-widget .body .review-comment .comment-title .action-label.toolbar-toggle-pickReactions { + background-image: url(./reaction.svg); + width: 18px; + height: 18px; + background-size: 100% auto; + background-position: center; + background-repeat: no-repeat; +} + +.monaco-editor.vs-dark .review-widget .body .review-comment .comment-title .action-label.toolbar-toggle-pickReactions { + background-image: url(./reaction-dark.svg); +} + +.monaco-editor.hc-black .review-widget .body .review-comment .comment-title .action-label.toolbar-toggle-pickReactions { + background-image: url(./reaction-hc.svg); +} + +.monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label{ + border: 1px solid transparent; } .monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label.active { border: 1px solid grey; - border-radius: 3px; } .monaco-editor .review-widget .body .review-comment .review-comment-contents .comment-reactions .action-item a.action-label.disabled { diff --git a/src/vs/workbench/contrib/comments/electron-browser/reactionsAction.ts b/src/vs/workbench/contrib/comments/electron-browser/reactionsAction.ts new file mode 100644 index 0000000000..8cb61d4d09 --- /dev/null +++ b/src/vs/workbench/contrib/comments/electron-browser/reactionsAction.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; +import { ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Action, IAction } from 'vs/base/common/actions'; +import { URI, UriComponents } from 'vs/base/common/uri'; + +export class ToggleReactionsAction extends Action { + static readonly ID = 'toolbar.toggle.pickReactions'; + private _menuActions: IAction[]; + private toggleDropdownMenu: () => void; + constructor(toggleDropdownMenu: () => void, title?: string) { + title = title || nls.localize('pickReactions', "Pick Reactions..."); + super(ToggleReactionsAction.ID, title, 'toggle-reactions', true); + this.toggleDropdownMenu = toggleDropdownMenu; + } + run(): Promise { + this.toggleDropdownMenu(); + return Promise.resolve(true); + } + get menuActions() { + return this._menuActions; + } + set menuActions(actions: IAction[]) { + this._menuActions = actions; + } +} +export class ReactionActionItem extends ActionItem { + constructor(action: ReactionAction) { + super(null, action, {}); + } + updateLabel(): void { + let action = this.getAction() as ReactionAction; + if (action.class) { + this.label.classList.add(action.class); + } + + if (!action.icon) { + let reactionLabel = dom.append(this.label, dom.$('span.reaction-label')); + reactionLabel.innerText = action.label; + } else { + let reactionIcon = dom.append(this.label, dom.$('.reaction-icon')); + reactionIcon.style.display = ''; + let uri = URI.revive(action.icon); + reactionIcon.style.backgroundImage = `url('${uri}')`; + reactionIcon.title = action.label; + } + if (action.count) { + let reactionCount = dom.append(this.label, dom.$('span.reaction-count')); + reactionCount.innerText = `${action.count}`; + } + } +} +export class ReactionAction extends Action { + static readonly ID = 'toolbar.toggle.reaction'; + constructor(id: string, label: string = '', cssClass: string = '', enabled: boolean = true, actionCallback?: (event?: any) => Promise, public icon?: UriComponents, public count?: number) { + super(ReactionAction.ID, label, cssClass, enabled, actionCallback); + } +} diff --git a/src/vs/workbench/parts/comments/electron-browser/simpleCommentEditor.ts b/src/vs/workbench/contrib/comments/electron-browser/simpleCommentEditor.ts similarity index 65% rename from src/vs/workbench/parts/comments/electron-browser/simpleCommentEditor.ts rename to src/vs/workbench/contrib/comments/electron-browser/simpleCommentEditor.ts index 59610f6ba2..0944ecf0c1 100644 --- a/src/vs/workbench/parts/comments/electron-browser/simpleCommentEditor.ts +++ b/src/vs/workbench/contrib/comments/electron-browser/simpleCommentEditor.ts @@ -7,29 +7,42 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { EditorAction, EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; // Allowed Editor Contributions: -import { MenuPreventer } from 'vs/workbench/parts/codeEditor/browser/menuPreventer'; +import { MenuPreventer } from 'vs/workbench/contrib/codeEditor/browser/menuPreventer'; import { ContextMenuController } from 'vs/editor/contrib/contextmenu/contextmenu'; import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; -import { TabCompletionController } from 'vs/workbench/parts/snippets/electron-browser/tabCompletion'; +import { TabCompletionController } from 'vs/workbench/contrib/snippets/browser/tabCompletion'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICommentThreadWidget } from 'vs/workbench/contrib/comments/common/commentThreadWidget'; + +export const ctxCommentEditorFocused = new RawContextKey('commentEditorFocused', false); + export class SimpleCommentEditor extends CodeEditorWidget { + private _parentEditor: ICodeEditor; + private _parentThread: ICommentThreadWidget; + private _commentEditorFocused: IContextKey; + constructor( domElement: HTMLElement, options: IEditorOptions, + parentEditor: ICodeEditor, + parentThread: ICommentThreadWidget, @IInstantiationService instantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, @ICommandService commandService: ICommandService, @IContextKeyService contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService, @INotificationService notificationService: INotificationService, + @IAccessibilityService accessibilityService: IAccessibilityService ) { const codeEditorWidgetOptions = { contributions: [ @@ -41,7 +54,22 @@ export class SimpleCommentEditor extends CodeEditorWidget { ] }; - super(domElement, options, codeEditorWidgetOptions, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService); + super(domElement, options, codeEditorWidgetOptions, instantiationService, codeEditorService, commandService, contextKeyService, themeService, notificationService, accessibilityService); + + this._commentEditorFocused = ctxCommentEditorFocused.bindTo(this._contextKeyService); + this._parentEditor = parentEditor; + this._parentThread = parentThread; + + this._register(this.onDidFocusEditorWidget(_ => this._commentEditorFocused.set(true))); + this._register(this.onDidBlurEditorWidget(_ => this._commentEditorFocused.reset())); + } + + getParentEditor(): ICodeEditor { + return this._parentEditor; + } + + getParentThread(): ICommentThreadWidget { + return this._parentThread; } protected _getActions(): EditorAction[] { diff --git a/src/vs/workbench/parts/debug/browser/baseDebugView.ts b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts similarity index 80% rename from src/vs/workbench/parts/debug/browser/baseDebugView.ts rename to src/vs/workbench/contrib/debug/browser/baseDebugView.ts index 140ce0a47f..1f79074d1b 100644 --- a/src/vs/workbench/parts/debug/browser/baseDebugView.ts +++ b/src/vs/workbench/contrib/debug/browser/baseDebugView.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; -import { IExpression, IDebugService } from 'vs/workbench/parts/debug/common/debug'; -import { Expression, Variable } from 'vs/workbench/parts/debug/common/debugModel'; +import { IExpression, IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { Expression, Variable, ExpressionContainer } from 'vs/workbench/contrib/debug/common/debugModel'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; @@ -61,9 +61,10 @@ export function renderExpressionValue(expressionOrValue: IExpression | string, c if (value !== Expression.DEFAULT_VALUE) { dom.addClass(container, 'error'); } - } else if (options.showChanged && (expressionOrValue).valueChanged && value !== Expression.DEFAULT_VALUE) { + } else if ((expressionOrValue instanceof ExpressionContainer) && options.showChanged && expressionOrValue.valueChanged && value !== Expression.DEFAULT_VALUE) { // value changed color has priority over other colors. container.className = 'value changed'; + expressionOrValue.valueChanged = false; } if (options.colorize && typeof expressionOrValue !== 'string') { @@ -141,20 +142,20 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer { - data.name.style.display = 'none'; - data.value.style.display = 'none'; - data.inputBoxContainer.style.display = 'initial'; + const enableInputBox = (expression: IExpression, options: IInputBoxOptions) => { + name.style.display = 'none'; + value.style.display = 'none'; + inputBoxContainer.style.display = 'initial'; - const inputBox = new InputBox(data.inputBoxContainer, this.contextViewService, { + const inputBox = new InputBox(inputBoxContainer, this.contextViewService, { placeholder: options.placeholder, ariaLabel: options.ariaLabel }); @@ -165,7 +166,8 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer { if (!disposed) { @@ -174,15 +176,15 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer { + toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => { const isEscape = e.equals(KeyCode.Escape); const isEnter = e.equals(KeyCode.Enter); if (isEscape || isEnter) { @@ -191,17 +193,17 @@ export abstract class AbstractExpressionsRenderer implements ITreeRenderer { + toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => { wrapUp(true); })); - data.toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'click', e => { + toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'click', e => { // Do not expand / collapse selected elements e.preventDefault(); e.stopPropagation(); })); }; - return data; + return { expression, name, value, label, enableInputBox, inputBoxContainer, toDispose }; } renderElement(node: ITreeNode, index: number, data: IExpressionTemplateData): void { diff --git a/src/vs/workbench/parts/debug/electron-browser/breakpointWidget.ts b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts similarity index 84% rename from src/vs/workbench/parts/debug/electron-browser/breakpointWidget.ts rename to src/vs/workbench/contrib/debug/browser/breakpointWidget.ts index 1f20bcae38..9ba414d3ec 100644 --- a/src/vs/workbench/parts/debug/electron-browser/breakpointWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointWidget.ts @@ -3,17 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!../browser/media/breakpointWidget'; +import 'vs/css!./media/breakpointWidget'; import * as nls from 'vs/nls'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { SelectBox, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; import * as lifecycle from 'vs/base/common/lifecycle'; import * as dom from 'vs/base/browser/dom'; import { Position, IPosition } from 'vs/editor/common/core/position'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { IDebugService, IBreakpoint, BreakpointWidgetContext as Context, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, DEBUG_SCHEME, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, CONTEXT_IN_BREAKPOINT_WIDGET } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, IBreakpoint, BreakpointWidgetContext as Context, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, DEBUG_SCHEME, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, CONTEXT_IN_BREAKPOINT_WIDGET } from 'vs/workbench/contrib/debug/common/debug'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -22,18 +22,17 @@ import { ServicesAccessor, EditorCommand, registerEditorCommand } from 'vs/edito import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IModelService } from 'vs/editor/common/services/modelService'; import { URI as uri } from 'vs/base/common/uri'; -import { CompletionProviderRegistry, CompletionList, CompletionContext } from 'vs/editor/common/modes'; +import { CompletionProviderRegistry, CompletionList, CompletionContext, CompletionItemKind } from 'vs/editor/common/modes'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ITextModel } from 'vs/editor/common/model'; -import { provideSuggestionItems } from 'vs/editor/contrib/suggest/suggest'; +import { provideSuggestionItems, CompletionOptions } from 'vs/editor/contrib/suggest/suggest'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { transparent, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IDecorationOptions } from 'vs/editor/common/editorCommon'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { getSimpleCodeEditorWidgetOptions } from 'vs/workbench/parts/codeEditor/electron-browser/simpleEditorOptions'; -import { getSimpleEditorOptions } from 'vs/workbench/parts/codeEditor/browser/simpleEditorOptions'; +import { getSimpleEditorOptions, getSimpleCodeEditorWidgetOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { IRange, Range } from 'vs/editor/common/core/range'; const $ = dom.$; @@ -48,12 +47,12 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi public _serviceBrand: any; private selectContainer: HTMLElement; - private input: CodeEditorWidget; + private input: IActiveCodeEditor; private toDispose: lifecycle.IDisposable[]; private conditionInput = ''; private hitCountInput = ''; private logMessageInput = ''; - private breakpoint: IBreakpoint; + private breakpoint: IBreakpoint | undefined; constructor(editor: ICodeEditor, private lineNumber: number, private context: Context, @IContextViewService private readonly contextViewService: IContextViewService, @@ -67,9 +66,12 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi super(editor, { showFrame: true, showArrow: false, frameWidth: 1 }); this.toDispose = []; - const uri = this.editor.getModel().uri; - const breakpoints = this.debugService.getModel().getBreakpoints({ lineNumber: this.lineNumber, uri }); - this.breakpoint = breakpoints.length ? breakpoints[0] : undefined; + const model = this.editor.getModel(); + if (model) { + const uri = model.uri; + const breakpoints = this.debugService.getModel().getBreakpoints({ lineNumber: this.lineNumber, uri }); + this.breakpoint = breakpoints.length ? breakpoints[0] : undefined; + } if (this.context === undefined) { if (this.breakpoint && !this.breakpoint.condition && !this.breakpoint.hitCondition && this.breakpoint.logMessage) { @@ -102,7 +104,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi } } - private getInputValue(breakpoint: IBreakpoint): string { + private getInputValue(breakpoint: IBreakpoint | undefined): string { switch (this.context) { case Context.LOG_MESSAGE: return breakpoint && breakpoint.logMessage ? breakpoint.logMessage : this.logMessageInput; @@ -139,7 +141,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi protected _fillContainer(container: HTMLElement): void { this.setCssClass('breakpoint-widget'); - const selectBox = new SelectBox([{ text: nls.localize('expression', "Expression") }, { text: nls.localize('hitCount', "Hit Count") }, { text: nls.localize('logMessage', "Log Message") }], this.context, this.contextViewService, null, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') }); + const selectBox = new SelectBox([{ text: nls.localize('expression', "Expression") }, { text: nls.localize('hitCount', "Hit Count") }, { text: nls.localize('logMessage', "Log Message") }], this.context, this.contextViewService, undefined, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') }); this.toDispose.push(attachSelectBoxStyler(selectBox, this.themeService)); this.selectContainer = $('.breakpoint-select-container'); selectBox.render(dom.append(container, this.selectContainer)); @@ -159,7 +161,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi })); this.input.setPosition({ lineNumber: 1, column: this.input.getModel().getLineMaxColumn(1) }); // Due to an electron bug we have to do the timeout, otherwise we do not get focus - setTimeout(() => this.input.focus(), 100); + setTimeout(() => this.input.focus(), 150); } public close(success: boolean): void { @@ -190,13 +192,16 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi } }, false); } else { - this.debugService.addBreakpoints(this.editor.getModel().uri, [{ - lineNumber: this.lineNumber, - enabled: true, - condition, - hitCondition, - logMessage - }], `breakpointWidget`); + const model = this.editor.getModel(); + if (model) { + this.debugService.addBreakpoints(model.uri, [{ + lineNumber: this.lineNumber, + enabled: true, + condition, + hitCondition, + logMessage + }], `breakpointWidget`); + } } } @@ -216,7 +221,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi const options = getSimpleEditorOptions(); const codeEditorWidgetOptions = getSimpleCodeEditorWidgetOptions(); - this.input = scopedInstatiationService.createInstance(CodeEditorWidget, container, options, codeEditorWidgetOptions); + this.input = scopedInstatiationService.createInstance(CodeEditorWidget, container, options, codeEditorWidgetOptions); CONTEXT_IN_BREAKPOINT_WIDGET.bindTo(scopedContextKeyService).set(true); const model = this.modelService.createModel('', null, uri.parse(`${DEBUG_SCHEME}:${this.editor.getId()}:breakpointinput`), true); this.input.setModel(model); @@ -232,8 +237,9 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi this.toDispose.push(CompletionProviderRegistry.register({ scheme: DEBUG_SCHEME, hasAccessToAllModels: true }, { provideCompletionItems: (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken): Promise => { let suggestionsPromise: Promise; - if (this.context === Context.CONDITION || this.context === Context.LOG_MESSAGE && this.isCurlyBracketOpen()) { - suggestionsPromise = provideSuggestionItems(this.editor.getModel(), new Position(this.lineNumber, 1), 'none', undefined, _context, token).then(suggestions => { + const underlyingModel = this.editor.getModel(); + if (underlyingModel && (this.context === Context.CONDITION || this.context === Context.LOG_MESSAGE && this.isCurlyBracketOpen())) { + suggestionsPromise = provideSuggestionItems(underlyingModel, new Position(this.lineNumber, 1), new CompletionOptions(undefined, new Set().add(CompletionItemKind.Snippet)), _context, token).then(suggestions => { let overwriteBefore = 0; if (this.context === Context.CONDITION) { @@ -263,6 +269,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi } private createDecorations(): IDecorationOptions[] { + const transparentForeground = transparent(editorForeground, 0.4)(this.themeService.getTheme()); return [{ range: { startLineNumber: 0, @@ -273,7 +280,7 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi renderOptions: { after: { contentText: this.placeholder, - color: transparent(editorForeground, 0.4)(this.themeService.getTheme()).toString() + color: transparentForeground ? transparentForeground.toString() : undefined } } }]; @@ -281,13 +288,16 @@ export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWi private isCurlyBracketOpen(): boolean { const value = this.input.getModel().getValue(); - for (let i = this.input.getPosition().column - 2; i >= 0; i--) { - if (value[i] === '{') { - return true; - } + const position = this.input.getPosition(); + if (position) { + for (let i = position.column - 2; i >= 0; i--) { + if (value[i] === '{') { + return true; + } - if (value[i] === '}') { - return false; + if (value[i] === '}') { + return false; + } } } diff --git a/src/vs/workbench/parts/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts similarity index 97% rename from src/vs/workbench/parts/debug/browser/breakpointsView.ts rename to src/vs/workbench/contrib/debug/browser/breakpointsView.ts index c45a548773..ad5fd7ed9d 100644 --- a/src/vs/workbench/parts/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -7,9 +7,9 @@ import * as nls from 'vs/nls'; import * as resources from 'vs/base/common/resources'; import * as dom from 'vs/base/browser/dom'; import { IAction, Action } from 'vs/base/common/actions'; -import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, EDITOR_CONTRIBUTION_ID, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, IDebugEditorContribution } from 'vs/workbench/parts/debug/common/debug'; -import { ExceptionBreakpoint, FunctionBreakpoint, Breakpoint } from 'vs/workbench/parts/debug/common/debugModel'; -import { AddFunctionBreakpointAction, ToggleBreakpointsActivatedAction, RemoveAllBreakpointsAction, RemoveBreakpointAction, EnableAllBreakpointsAction, DisableAllBreakpointsAction, ReapplyBreakpointsAction } from 'vs/workbench/parts/debug/browser/debugActions'; +import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, EDITOR_CONTRIBUTION_ID, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, IDebugEditorContribution } from 'vs/workbench/contrib/debug/common/debug'; +import { ExceptionBreakpoint, FunctionBreakpoint, Breakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { AddFunctionBreakpointAction, ToggleBreakpointsActivatedAction, RemoveAllBreakpointsAction, RemoveBreakpointAction, EnableAllBreakpointsAction, DisableAllBreakpointsAction, ReapplyBreakpointsAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -74,7 +74,7 @@ export class BreakpointsView extends ViewletPanel { this.instantiationService.createInstance(FunctionBreakpointsRenderer), new FunctionBreakpointInputRenderer(this.debugService, this.contextViewService, this.themeService) ], { - identityProvider: { getId: element => element.getId() }, + identityProvider: { getId: (element: IEnablement) => element.getId() }, multipleSelectionSupport: false, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: e => e } }) as WorkbenchList; @@ -150,7 +150,7 @@ export class BreakpointsView extends ViewletPanel { const breakpointType = element instanceof Breakpoint && element.logMessage ? nls.localize('Logpoint', "Logpoint") : nls.localize('Breakpoint', "Breakpoint"); if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) { - actions.push(new Action('workbench.action.debug.openEditorAndEditBreakpoint', nls.localize('editBreakpoint', "Edit {0}...", breakpointType), undefined, true, () => { + actions.push(new Action('workbench.action.debug.openEditorAndEditBreakpoint', nls.localize('editBreakpoint', "Edit {0}...", breakpointType), '', true, () => { if (element instanceof Breakpoint) { return openBreakpointSource(element, false, false, this.debugService, this.editorService).then(editor => { if (editor) { @@ -164,7 +164,7 @@ export class BreakpointsView extends ViewletPanel { this.debugService.getViewModel().setSelectedFunctionBreakpoint(element); this.onBreakpointsChange(); - return undefined; + return Promise.resolve(undefined); })); actions.push(new Separator()); } @@ -252,7 +252,7 @@ class BreakpointsDelegate implements IListVirtualDelegate { return ExceptionBreakpointsRenderer.ID; } - return undefined; + return ''; } } @@ -535,7 +535,7 @@ class FunctionBreakpointInputRenderer implements IListRenderer { +export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolean, preserveFocus: boolean, debugService: IDebugService, editorService: IEditorService): Promise { if (breakpoint.uri.scheme === DEBUG_SCHEME && debugService.state === State.Inactive) { return Promise.resolve(null); } @@ -543,8 +543,8 @@ export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolea const selection = breakpoint.endLineNumber ? { startLineNumber: breakpoint.lineNumber, endLineNumber: breakpoint.endLineNumber, - startColumn: breakpoint.column, - endColumn: breakpoint.endColumn + startColumn: breakpoint.column || 1, + endColumn: breakpoint.endColumn || Constants.MAX_SAFE_SMALL_INTEGER } : { startLineNumber: breakpoint.lineNumber, startColumn: breakpoint.column || 1, diff --git a/src/vs/workbench/parts/debug/electron-browser/callStackView.ts b/src/vs/workbench/contrib/debug/browser/callStackView.ts similarity index 82% rename from src/vs/workbench/parts/debug/electron-browser/callStackView.ts rename to src/vs/workbench/contrib/debug/browser/callStackView.ts index da60fe6604..7907de3341 100644 --- a/src/vs/workbench/parts/debug/electron-browser/callStackView.ts +++ b/src/vs/workbench/contrib/debug/browser/callStackView.ts @@ -7,29 +7,26 @@ import * as nls from 'vs/nls'; import { RunOnceScheduler, ignoreErrors } from 'vs/base/common/async'; import * as dom from 'vs/base/browser/dom'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IDebugService, State, IStackFrame, IDebugSession, IThread, CONTEXT_CALLSTACK_ITEM_TYPE, IDebugModel } from 'vs/workbench/parts/debug/common/debug'; -import { Thread, StackFrame, ThreadAndSessionIds } from 'vs/workbench/parts/debug/common/debugModel'; +import { IDebugService, State, IStackFrame, IDebugSession, IThread, CONTEXT_CALLSTACK_ITEM_TYPE, IDebugModel } from 'vs/workbench/contrib/debug/common/debug'; +import { Thread, StackFrame, ThreadAndSessionIds } from 'vs/workbench/contrib/debug/common/debugModel'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { renderViewTree } from 'vs/workbench/parts/debug/browser/baseDebugView'; +import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IAction } from 'vs/base/common/actions'; -import { RestartAction, StopAction, ContinueAction, StepOverAction, StepIntoAction, StepOutAction, PauseAction, RestartFrameAction, TerminateThreadAction } from 'vs/workbench/parts/debug/browser/debugActions'; -import { CopyStackTraceAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions'; +import { RestartAction, StopAction, ContinueAction, StepOverAction, StepIntoAction, StepOutAction, PauseAction, RestartFrameAction, TerminateThreadAction, CopyStackTraceAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { ILabelService } from 'vs/platform/label/common/label'; -import { DebugSession } from 'vs/workbench/parts/debug/electron-browser/debugSession'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; -import { TreeResourceNavigator2, WorkbenchAsyncDataTree, IListService } from 'vs/platform/list/browser/listService'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { TreeResourceNavigator2, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { onUnexpectedError } from 'vs/base/common/errors'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; @@ -48,7 +45,7 @@ export class CallStackView extends ViewletPanel { private ignoreFocusStackFrameEvent: boolean; private callStackItemType: IContextKey; private dataSource: CallStackDataSource; - private tree: WorkbenchAsyncDataTree; + private tree: WorkbenchAsyncDataTree; private contributedContextMenu: IMenu; constructor( @@ -60,9 +57,7 @@ export class CallStackView extends ViewletPanel { @IEditorService private readonly editorService: IEditorService, @IConfigurationService configurationService: IConfigurationService, @IMenuService menuService: IMenuService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IThemeService private readonly themeService: IThemeService, - @IListService private readonly listService: IListService + @IContextKeyService readonly contextKeyService: IContextKeyService, ) { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService); this.callStackItemType = CONTEXT_CALLSTACK_ITEM_TYPE.bindTo(contextKeyService); @@ -105,7 +100,7 @@ export class CallStackView extends ViewletPanel { const treeContainer = renderViewTree(container); this.dataSource = new CallStackDataSource(); - this.tree = new WorkbenchAsyncDataTree(treeContainer, new CallStackDelegate(), [ + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, treeContainer, new CallStackDelegate(), [ new SessionsRenderer(), new ThreadsRenderer(), this.instantiationService.createInstance(StackFramesRenderer), @@ -124,16 +119,16 @@ export class CallStackView extends ViewletPanel { return `showMore ${element[0].getId()}`; } - return element.getId(); + return (element).getId(); } }, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: e => { - if (e instanceof DebugSession) { + if (isDebugSession(e)) { return e.getLabel(); } if (e instanceof Thread) { - return e.name; + return `${e.name} ${e.stateLabel}`; } if (e instanceof StackFrame || typeof e === 'string') { return e; @@ -145,7 +140,7 @@ export class CallStackView extends ViewletPanel { return nls.localize('showMoreStackFrames2', "Show More Stack Frames"); } } - }, this.contextKeyService, this.listService, this.themeService, this.configurationService, this.keybindingService); + }) as WorkbenchAsyncDataTree; this.tree.setInput(this.debugService.getModel()).then(undefined, onUnexpectedError); @@ -156,7 +151,7 @@ export class CallStackView extends ViewletPanel { return; } - const focusStackFrame = (stackFrame: IStackFrame, thread: IThread, session: IDebugSession) => { + const focusStackFrame = (stackFrame: IStackFrame | undefined, thread: IThread | undefined, session: IDebugSession) => { this.ignoreFocusStackFrameEvent = true; try { this.debugService.focusStackFrame(stackFrame, thread, session, true); @@ -173,7 +168,7 @@ export class CallStackView extends ViewletPanel { if (element instanceof Thread) { focusStackFrame(undefined, element, element.session); } - if (element instanceof DebugSession) { + if (isDebugSession(element)) { focusStackFrame(undefined, undefined, element); } if (element instanceof ThreadAndSessionIds) { @@ -234,7 +229,7 @@ export class CallStackView extends ViewletPanel { } private updateTreeSelection(): void { - if (!this.tree || this.tree.visibleNodeCount === 0) { + if (!this.tree || !this.tree.getInput()) { // Tree not initialized yet return; } @@ -271,7 +266,7 @@ export class CallStackView extends ViewletPanel { private onContextMenu(e: ITreeContextMenuEvent): void { const actions: IAction[] = []; const element = e.element; - if (element instanceof DebugSession) { + if (isDebugSession(element)) { this.callStackItemType.set('session'); actions.push(this.instantiationService.createInstance(RestartAction, RestartAction.ID, RestartAction.LABEL)); actions.push(new StopAction(StopAction.ID, StopAction.LABEL, this.debugService, this.keybindingService)); @@ -294,7 +289,7 @@ export class CallStackView extends ViewletPanel { if (element.thread.session.capabilities.supportsRestartFrame) { actions.push(new RestartFrameAction(RestartFrameAction.ID, RestartFrameAction.LABEL, this.debugService, this.keybindingService)); } - actions.push(new CopyStackTraceAction(CopyStackTraceAction.ID, CopyStackTraceAction.LABEL)); + actions.push(this.instantiationService.createInstance(CopyStackTraceAction, CopyStackTraceAction.ID, CopyStackTraceAction.LABEL)); } else { this.callStackItemType.reset(); } @@ -309,7 +304,7 @@ export class CallStackView extends ViewletPanel { }); } - private getContextForContributedActions(element: CallStackItem): string | number { + private getContextForContributedActions(element: CallStackItem | null): string | number | undefined { if (element instanceof StackFrame) { if (element.source.inMemory) { return element.source.raw.path || element.source.reference; @@ -365,14 +360,13 @@ class SessionsRenderer implements ITreeRenderer, index: number, data: ISessionTemplateData): void { @@ -398,27 +392,20 @@ class ThreadsRenderer implements ITreeRenderer, index: number, data: IThreadTemplateData): void { const thread = element.element; data.thread.title = nls.localize('thread', "Thread"); data.label.set(thread.name, createMatches(element.filterData)); - - if (thread.stopped) { - data.stateLabel.textContent = thread.stoppedDetails.description || - thread.stoppedDetails.reason ? nls.localize({ key: 'pausedOn', comment: ['indicates reason for program being paused'] }, "Paused on {0}", thread.stoppedDetails.reason) : nls.localize('paused', "Paused"); - } else { - data.stateLabel.textContent = nls.localize({ key: 'running', comment: ['indicates state'] }, "Running"); - } + data.stateLabel.textContent = thread.stateLabel; } disposeTemplate(templateData: IThreadTemplateData): void { @@ -436,21 +423,20 @@ class StackFramesRenderer implements ITreeRenderer, index: number, data: IStackFrameTemplateData): void { const stackFrame = element.element; - dom.toggleClass(data.stackFrame, 'disabled', !stackFrame.source || !stackFrame.source.available || stackFrame.source.presentationHint === 'deemphasize'); + dom.toggleClass(data.stackFrame, 'disabled', !stackFrame.source || !stackFrame.source.available || isDeemphasized(stackFrame)); dom.toggleClass(data.stackFrame, 'label', stackFrame.presentationHint === 'label'); dom.toggleClass(data.stackFrame, 'subtle', stackFrame.presentationHint === 'subtle'); @@ -484,10 +470,9 @@ class ErrorsRenderer implements ITreeRenderer, index: number, data: IErrorTemplateData): void { @@ -510,10 +495,9 @@ class LoadMoreRenderer implements ITreeRenderer, index: number, data: ILabelTemplateData): void { @@ -533,15 +517,14 @@ class ShowMoreRenderer implements ITreeRenderer, index: number, data: ILabelTemplateData): void { const stackFrames = element.element; - if (stackFrames.every(sf => sf.source && sf.source.origin && sf.source.origin === stackFrames[0].source.origin)) { + if (stackFrames.every(sf => !!(sf.source && sf.source.origin && sf.source.origin === stackFrames[0].source.origin))) { data.label.textContent = nls.localize('showMoreAndOrigin', "Show {0} More: {1}", stackFrames.length, stackFrames[0].source.origin); } else { data.label.textContent = nls.localize('showMoreStackFrames', "Show {0} More Stack Frames", stackFrames.length); @@ -560,7 +543,7 @@ class CallStackDelegate implements IListVirtualDelegate { } getTemplateId(element: CallStackItem): string { - if (element instanceof DebugSession) { + if (isDebugSession(element)) { return SessionsRenderer.ID; } if (element instanceof Thread) { @@ -575,11 +558,9 @@ class CallStackDelegate implements IListVirtualDelegate { if (element instanceof ThreadAndSessionIds) { return LoadMoreRenderer.ID; } - if (element instanceof Array) { - return ShowMoreRenderer.ID; - } - return undefined; + // element instanceof Array + return ShowMoreRenderer.ID; } } @@ -587,11 +568,19 @@ function isDebugModel(obj: any): obj is IDebugModel { return typeof obj.getSessions === 'function'; } +function isDebugSession(obj: any): obj is IDebugSession { + return typeof obj.getAllThreads === 'function'; +} + +function isDeemphasized(frame: IStackFrame): boolean { + return frame.source.presentationHint === 'deemphasize' || frame.presentationHint === 'deemphasize'; +} + class CallStackDataSource implements IAsyncDataSource { deemphasizedStackFramesToShow: IStackFrame[]; hasChildren(element: IDebugModel | CallStackItem): boolean { - return isDebugModel(element) || element instanceof DebugSession || (element instanceof Thread && element.stopped); + return isDebugModel(element) || isDebugSession(element) || (element instanceof Thread && element.stopped); } getChildren(element: IDebugModel | CallStackItem): Promise { @@ -607,29 +596,32 @@ class CallStackDataSource implements IAsyncDataSourcethreads[0]) : Promise.resolve(threads); - } else if (element instanceof DebugSession) { + } else if (isDebugSession(element)) { return Promise.resolve(element.getAllThreads()); } else { return this.getThreadChildren(element); } } - private getThreadChildren(thread: Thread): Promise> { + private getThreadChildren(thread: Thread): Promise { return this.getThreadCallstack(thread).then(children => { // Check if some stack frames should be hidden under a parent element since they are deemphasized - const result = []; + const result: CallStackItem[] = []; children.forEach((child, index) => { - if (child instanceof StackFrame && child.source && child.source.presentationHint === 'deemphasize') { + if (child instanceof StackFrame && child.source && isDeemphasized(child)) { // Check if the user clicked to show the deemphasized source if (this.deemphasizedStackFramesToShow.indexOf(child) === -1) { - if (result.length && result[result.length - 1] instanceof Array) { - // Collect all the stackframes that will be "collapsed" - result[result.length - 1].push(child); - return; + if (result.length) { + const last = result[result.length - 1]; + if (last instanceof Array) { + // Collect all the stackframes that will be "collapsed" + last.push(child); + return; + } } const nextChild = index < children.length - 1 ? children[index + 1] : undefined; - if (nextChild instanceof StackFrame && nextChild.source && nextChild.source.presentationHint === 'deemphasize') { + if (nextChild instanceof StackFrame && nextChild.source && isDeemphasized(nextChild)) { // Start collecting stackframes that will be "collapsed" result.push([child]); return; @@ -652,7 +644,7 @@ class CallStackDataSource implements IAsyncDataSource { - if (callStack.length === 1 && thread.session.capabilities.supportsDelayedStackTraceLoading && thread.stoppedDetails && thread.stoppedDetails.totalFrames > 1) { + if (callStack.length === 1 && thread.session.capabilities.supportsDelayedStackTraceLoading && thread.stoppedDetails && thread.stoppedDetails.totalFrames && thread.stoppedDetails.totalFrames > 1) { // To reduce flashing of the call stack view simply append the stale call stack // once we have the correct data the tree will refresh and we will no longer display it. callStack = callStack.concat(thread.getStaleCallStack().slice(1)); @@ -661,7 +653,7 @@ class CallStackDataSource implements IAsyncDataSource callStack.length && callStack.length > 1) { + if (thread.stoppedDetails && thread.stoppedDetails.totalFrames && thread.stoppedDetails.totalFrames > callStack.length && callStack.length > 1) { callStack = callStack.concat([new ThreadAndSessionIds(thread.session.getId(), thread.threadId)]); } @@ -678,19 +670,17 @@ class CallStackAccessibilityProvider implements IAccessibilityProvider= 30 && code <= 37) || (code >= 90 && code <= 97)) { - styleNames.push('code-foreground-' + code); - } else if (code === 39) { - // Remove all foreground colour codes + } else if (code === 39 || (code >= 30 && code <= 37) || (code >= 90 && code <= 97)) { + // Remove all previous foreground colour codes styleNames = styleNames.filter(style => !style.match(/^code-foreground-\d+$/)); - } else if ((code >= 40 && code <= 47) || (code >= 100 && code <= 107)) { - styleNames.push('code-background-' + code); - } else if (code === 49) { - // Remove all background colour codes + + if (code !== 39) { + styleNames.push('code-foreground-' + code); + } + } else if (code === 49 || (code >= 40 && code <= 47) || (code >= 100 && code <= 107)) { + // Remove all previous background colour codes styleNames = styleNames.filter(style => !style.match(/^code-background-\d+$/)); + + if (code !== 49) { + styleNames.push('code-background-' + code); + } } } diff --git a/src/vs/workbench/parts/debug/browser/debugActionItems.ts b/src/vs/workbench/contrib/debug/browser/debugActionItems.ts similarity index 95% rename from src/vs/workbench/parts/debug/browser/debugActionItems.ts rename to src/vs/workbench/contrib/debug/browser/debugActionItems.ts index f07c1465ef..3972f8395e 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionItems.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActionItems.ts @@ -12,7 +12,7 @@ import { SelectBox, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selec import { SelectActionItem, IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDebugService, IDebugSession } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; @@ -31,7 +31,7 @@ export class StartDebugActionItem implements IActionItem { private container: HTMLElement; private start: HTMLElement; private selectBox: SelectBox; - private options: { label: string, handler: (() => boolean) }[]; + private options: { label: string, handler?: (() => boolean) }[]; private toDispose: IDisposable[]; private selected: number; @@ -46,7 +46,7 @@ export class StartDebugActionItem implements IActionItem { @IContextViewService contextViewService: IContextViewService, ) { this.toDispose = []; - this.selectBox = new SelectBox([], -1, contextViewService, null, { ariaLabel: nls.localize('debugLaunchConfigurations', 'Debug Launch Configurations') }); + this.selectBox = new SelectBox([], -1, contextViewService, undefined, { ariaLabel: nls.localize('debugLaunchConfigurations', 'Debug Launch Configurations') }); this.toDispose.push(this.selectBox); this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService, { selectBackground: SIDE_BAR_BACKGROUND @@ -102,7 +102,8 @@ export class StartDebugActionItem implements IActionItem { } })); this.toDispose.push(this.selectBox.onDidSelect(e => { - const shouldBeSelected = this.options[e.index].handler(); + const target = this.options[e.index]; + const shouldBeSelected = target.handler ? target.handler() : false; if (shouldBeSelected) { this.selected = e.index; } else { diff --git a/src/vs/workbench/parts/debug/browser/debugActions.ts b/src/vs/workbench/contrib/debug/browser/debugActions.ts similarity index 83% rename from src/vs/workbench/parts/debug/browser/debugActions.ts rename to src/vs/workbench/contrib/debug/browser/debugActions.ts index 9dba7891d3..3501bc6ad1 100644 --- a/src/vs/workbench/parts/debug/browser/debugActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugActions.ts @@ -7,23 +7,23 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import * as lifecycle from 'vs/base/common/lifecycle'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ICommandService } from 'vs/platform/commands/common/commands'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IFileService } from 'vs/platform/files/common/files'; import { IDebugService, State, IDebugSession, IThread, IEnablement, IBreakpoint, IStackFrame, REPL_ID } - from 'vs/workbench/parts/debug/common/debug'; -import { Variable, Expression, Thread, Breakpoint } from 'vs/workbench/parts/debug/common/debugModel'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; + from 'vs/workbench/contrib/debug/common/debug'; +import { Variable, Expression, Thread, Breakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { CollapseAction2 } from 'vs/workbench/browser/viewlet'; +import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { first } from 'vs/base/common/arrays'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { memoize } from 'vs/base/common/decorators'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; export abstract class AbstractDebugAction extends Action { @@ -104,7 +104,7 @@ export class ConfigureAction extends AbstractDebugAction { public run(event?: any): Promise { if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.notificationService.info(nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration.")); - return Promise.resolve(null); + return Promise.resolve(); } const sideBySide = !!(event && (event.ctrlKey || event.metaKey)); @@ -113,7 +113,7 @@ export class ConfigureAction extends AbstractDebugAction { configurationManager.selectConfiguration(configurationManager.getLaunches()[0]); } - return configurationManager.selectedConfiguration.launch.openConfigFile(sideBySide, false); + return configurationManager.selectedConfiguration.launch!.openConfigFile(sideBySide, false); } } @@ -145,7 +145,7 @@ export class StartAction extends AbstractDebugAction { launch = configurationManager.getLaunch(rootUri); if (!launch || launch.getConfigurationNames().length === 0) { const launches = configurationManager.getLaunches(); - launch = first(launches, l => !!l.getConfigurationNames().length, launch); + launch = first(launches, l => !!(l && l.getConfigurationNames().length), launch); } configurationManager.selectConfiguration(launch); @@ -194,12 +194,9 @@ export class SelectAndStartAction extends AbstractDebugAction { constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService, - @ICommandService commandService: ICommandService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IFileService fileService: IFileService, @IQuickOpenService private readonly quickOpenService: IQuickOpenService ) { - super(id, label, undefined, debugService, keybindingService); + super(id, label, '', debugService, keybindingService); } public run(): Promise { @@ -228,11 +225,13 @@ export class RestartAction extends AbstractDebugAction { return new StartAction(StartAction.ID, StartAction.LABEL, this.debugService, this.keybindingService, this.contextService, this.historyService); } - private setLabel(session: IDebugSession): void { - this.updateLabel(session && session.configuration.request === 'attach' ? RestartAction.RECONNECT_LABEL : RestartAction.LABEL); + private setLabel(session: IDebugSession | undefined): void { + if (session) { + this.updateLabel(session && session.configuration.request === 'attach' ? RestartAction.RECONNECT_LABEL : RestartAction.LABEL); + } } - public run(session: IDebugSession): Promise { + public run(session: IDebugSession | undefined): Promise { if (!session || !session.getId) { session = this.debugService.getViewModel().focusedSession; } @@ -262,12 +261,12 @@ export class StepOverAction extends AbstractDebugAction { super(id, label, 'debug-action step-over', debugService, keybindingService, 20); } - public run(thread: IThread): Promise { + public run(thread: IThread | undefined): Promise { if (!(thread instanceof Thread)) { thread = this.debugService.getViewModel().focusedThread; } - return thread ? thread.next() : Promise.resolve(null); + return thread ? thread.next() : Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -283,12 +282,12 @@ export class StepIntoAction extends AbstractDebugAction { super(id, label, 'debug-action step-into', debugService, keybindingService, 30); } - public run(thread: IThread): Promise { + public run(thread: IThread | undefined): Promise { if (!(thread instanceof Thread)) { thread = this.debugService.getViewModel().focusedThread; } - return thread ? thread.stepIn() : Promise.resolve(null); + return thread ? thread.stepIn() : Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -304,12 +303,12 @@ export class StepOutAction extends AbstractDebugAction { super(id, label, 'debug-action step-out', debugService, keybindingService, 40); } - public run(thread: IThread): Promise { + public run(thread: IThread | undefined): Promise { if (!(thread instanceof Thread)) { thread = this.debugService.getViewModel().focusedThread; } - return thread ? thread.stepOut() : Promise.resolve(null); + return thread ? thread.stepOut() : Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -325,7 +324,7 @@ export class StopAction extends AbstractDebugAction { super(id, label, 'debug-action stop', debugService, keybindingService, 80); } - public run(session: IDebugSession): Promise { + public run(session: IDebugSession | undefined): Promise { if (!session || !session.getId) { session = this.debugService.getViewModel().focusedSession; } @@ -364,12 +363,12 @@ export class ContinueAction extends AbstractDebugAction { super(id, label, 'debug-action continue', debugService, keybindingService, 10); } - public run(thread: IThread): Promise { + public run(thread: IThread | undefined): Promise { if (!(thread instanceof Thread)) { thread = this.debugService.getViewModel().focusedThread; } - return thread ? thread.continue() : Promise.resolve(null); + return thread ? thread.continue() : Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -385,7 +384,7 @@ export class PauseAction extends AbstractDebugAction { super(id, label, 'debug-action pause', debugService, keybindingService, 10); } - public run(thread: IThread): Promise { + public run(thread: IThread | undefined): Promise { if (!(thread instanceof Thread)) { thread = this.debugService.getViewModel().focusedThread; if (!thread) { @@ -395,7 +394,7 @@ export class PauseAction extends AbstractDebugAction { } } - return thread ? thread.pause() : Promise.resolve(null); + return thread ? thread.pause() : Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -408,15 +407,15 @@ export class TerminateThreadAction extends AbstractDebugAction { static LABEL = nls.localize('terminateThread', "Terminate Thread"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, undefined, debugService, keybindingService); + super(id, label, '', debugService, keybindingService); } - public run(thread: IThread): Promise { + public run(thread: IThread | undefined): Promise { if (!(thread instanceof Thread)) { thread = this.debugService.getViewModel().focusedThread; } - return thread ? thread.terminate() : Promise.resolve(null); + return thread ? thread.terminate() : Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -429,15 +428,15 @@ export class RestartFrameAction extends AbstractDebugAction { static LABEL = nls.localize('restartFrame', "Restart Frame"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, undefined, debugService, keybindingService); + super(id, label, '', debugService, keybindingService); } - public run(frame: IStackFrame): Promise { + public run(frame: IStackFrame | undefined): Promise { if (!frame) { frame = this.debugService.getViewModel().focusedStackFrame; } - return frame.restart(); + return frame!.restart(); } } @@ -541,7 +540,7 @@ export class ReapplyBreakpointsAction extends AbstractDebugAction { static LABEL = nls.localize('reapplyAllBreakpoints', "Reapply All Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, null, debugService, keybindingService); + super(id, label, '', debugService, keybindingService); this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement())); } @@ -567,7 +566,7 @@ export class AddFunctionBreakpointAction extends AbstractDebugAction { public run(): Promise { this.debugService.addFunctionBreakpoint(); - return Promise.resolve(null); + return Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -581,7 +580,7 @@ export class SetValueAction extends AbstractDebugAction { static LABEL = nls.localize('setValue', "Set Value"); constructor(id: string, label: string, private variable: Variable, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, null, debugService, keybindingService); + super(id, label, '', debugService, keybindingService); } public run(): Promise { @@ -589,12 +588,12 @@ export class SetValueAction extends AbstractDebugAction { this.debugService.getViewModel().setSelectedExpression(this.variable); } - return Promise.resolve(null); + return Promise.resolve(); } protected isEnabled(state: State): boolean { const session = this.debugService.getViewModel().focusedSession; - return super.isEnabled(state) && state === State.Stopped && session && session.capabilities.supportsSetVariable; + return !!(super.isEnabled(state) && state === State.Stopped && session && session.capabilities.supportsSetVariable); } } @@ -623,12 +622,12 @@ export class EditWatchExpressionAction extends AbstractDebugAction { static LABEL = nls.localize('editWatchExpression', "Edit Expression"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, undefined, debugService, keybindingService); + super(id, label, '', debugService, keybindingService); } public run(expression: Expression): Promise { this.debugService.getViewModel().setSelectedExpression(expression); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -656,12 +655,12 @@ export class RemoveWatchExpressionAction extends AbstractDebugAction { static LABEL = nls.localize('removeWatchExpression', "Remove Expression"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { - super(id, label, undefined, debugService, keybindingService); + super(id, label, '', debugService, keybindingService); } public run(expression: Expression): Promise { this.debugService.removeWatchExpressions(expression.getId()); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -676,7 +675,7 @@ export class RemoveAllWatchExpressionsAction extends AbstractDebugAction { public run(): Promise { this.debugService.removeWatchExpressions(); - return Promise.resolve(null); + return Promise.resolve(); } protected isEnabled(state: State): boolean { @@ -690,10 +689,10 @@ export class ToggleReplAction extends TogglePanelAction { private toDispose: lifecycle.IDisposable[]; constructor(id: string, label: string, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IPanelService panelService: IPanelService ) { - super(id, label, REPL_ID, panelService, partService, 'debug-action toggle-repl'); + super(id, label, REPL_ID, panelService, layoutService, 'debug-action toggle-repl'); this.toDispose = []; this.registerListeners(); } @@ -727,7 +726,7 @@ export class FocusReplAction extends Action { public run(): Promise { this.panelService.openPanel(REPL_ID, true); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -740,7 +739,7 @@ export class FocusSessionAction extends AbstractDebugAction { @IKeybindingService keybindingService: IKeybindingService, @IEditorService private readonly editorService: IEditorService ) { - super(id, label, null, debugService, keybindingService, 100); + super(id, label, '', debugService, keybindingService, 100); } public run(sessionName: string): Promise { @@ -764,18 +763,18 @@ export class StepBackAction extends AbstractDebugAction { super(id, label, 'debug-action step-back', debugService, keybindingService, 50); } - public run(thread: IThread): Promise { + public run(thread: IThread | undefined): Promise { if (!(thread instanceof Thread)) { thread = this.debugService.getViewModel().focusedThread; } - return thread ? thread.stepBack() : Promise.resolve(null); + return thread ? thread.stepBack() : Promise.resolve(); } protected isEnabled(state: State): boolean { const session = this.debugService.getViewModel().focusedSession; - return super.isEnabled(state) && state === State.Stopped && - session && session.capabilities.supportsStepBack; + return !!(super.isEnabled(state) && state === State.Stopped && + session && session.capabilities.supportsStepBack); } } @@ -787,22 +786,22 @@ export class ReverseContinueAction extends AbstractDebugAction { super(id, label, 'debug-action reverse-continue', debugService, keybindingService, 60); } - public run(thread: IThread): Promise { + public run(thread: IThread | undefined): Promise { if (!(thread instanceof Thread)) { thread = this.debugService.getViewModel().focusedThread; } - return thread ? thread.reverseContinue() : Promise.resolve(null); + return thread ? thread.reverseContinue() : Promise.resolve(); } protected isEnabled(state: State): boolean { const session = this.debugService.getViewModel().focusedSession; - return super.isEnabled(state) && state === State.Stopped && - session && session.capabilities.supportsStepBack; + return !!(super.isEnabled(state) && state === State.Stopped && + session && session.capabilities.supportsStepBack); } } -export class ReplCollapseAllAction extends CollapseAction2 { +export class ReplCollapseAllAction extends CollapseAction { constructor(tree: AsyncDataTree, private toFocus: { focus(): void; }) { super(tree, true, undefined); } @@ -813,3 +812,85 @@ export class ReplCollapseAllAction extends CollapseAction2 { }); } } + +export class CopyValueAction extends Action { + static readonly ID = 'workbench.debug.viewlet.action.copyValue'; + static LABEL = nls.localize('copyValue', "Copy Value"); + + constructor( + id: string, label: string, private value: any, private context: string, + @IDebugService private readonly debugService: IDebugService, + @IClipboardService private readonly clipboardService: IClipboardService + ) { + super(id, label, 'debug-action copy-value'); + this._enabled = typeof this.value === 'string' || (this.value instanceof Variable && !!this.value.evaluateName); + } + + public run(): Promise { + const stackFrame = this.debugService.getViewModel().focusedStackFrame; + const session = this.debugService.getViewModel().focusedSession; + + if (this.value instanceof Variable && stackFrame && session && this.value.evaluateName) { + return session.evaluate(this.value.evaluateName, stackFrame.frameId, this.context).then(result => { + this.clipboardService.writeText(result.body.result); + }, err => this.clipboardService.writeText(this.value.value)); + } + + this.clipboardService.writeText(this.value); + return Promise.resolve(undefined); + } +} + +export class CopyEvaluatePathAction extends Action { + static readonly ID = 'workbench.debug.viewlet.action.copyEvaluatePath'; + static LABEL = nls.localize('copyAsExpression', "Copy as Expression"); + + constructor( + id: string, label: string, private value: Variable, + @IClipboardService private readonly clipboardService: IClipboardService + ) { + super(id, label); + this._enabled = this.value && !!this.value.evaluateName; + } + + public run(): Promise { + this.clipboardService.writeText(this.value.evaluateName!); + return Promise.resolve(undefined); + } +} + +export class CopyAction extends Action { + static readonly ID = 'workbench.debug.action.copy'; + static LABEL = nls.localize('copy', "Copy"); + + constructor( + id: string, label: string, + @IClipboardService private readonly clipboardService: IClipboardService + ) { + super(id, label); + } + + public run(): Promise { + this.clipboardService.writeText(window.getSelection().toString()); + return Promise.resolve(undefined); + } +} + +export class CopyStackTraceAction extends Action { + static readonly ID = 'workbench.action.debug.copyStackTrace'; + static LABEL = nls.localize('copyStackTrace', "Copy Call Stack"); + + constructor( + id: string, label: string, + @IClipboardService private readonly clipboardService: IClipboardService, + @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService + ) { + super(id, label); + } + + public run(frame: IStackFrame): Promise { + const eol = this.textResourcePropertiesService.getEOL(frame.source.uri); + this.clipboardService.writeText(frame.thread.getCallStack().map(sf => sf.toString()).join(eol)); + return Promise.resolve(undefined); + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/debug/browser/debugCommands.ts b/src/vs/workbench/contrib/debug/browser/debugCommands.ts similarity index 87% rename from src/vs/workbench/parts/debug/browser/debugCommands.ts rename to src/vs/workbench/contrib/debug/browser/debugCommands.ts index 903206d146..a6e94e5748 100644 --- a/src/vs/workbench/parts/debug/browser/debugCommands.ts +++ b/src/vs/workbench/contrib/debug/browser/debugCommands.ts @@ -9,20 +9,20 @@ import { List } from 'vs/base/browser/ui/list/listWidget'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IListService } from 'vs/platform/list/browser/listService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, CONTEXT_BREAKPOINT_SELECTED, IConfig } from 'vs/workbench/parts/debug/common/debug'; -import { Expression, Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/parts/debug/common/debugModel'; -import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions'; +import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, CONTEXT_BREAKPOINT_SELECTED, IConfig } from 'vs/workbench/contrib/debug/common/debug'; +import { Expression, Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel'; +import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { openBreakpointSource } from 'vs/workbench/parts/debug/browser/breakpointsView'; +import { openBreakpointSource } from 'vs/workbench/contrib/debug/browser/breakpointsView'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { InputFocusedContext } from 'vs/platform/workbench/common/contextkeys'; +import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { PanelFocusContext } from 'vs/workbench/browser/parts/panel/panelPart'; +import { PanelFocusContext } from 'vs/workbench/common/panel'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -70,9 +70,11 @@ export function registerCommands(): void { const model = widget.getModel(); if (model) { const position = widget.getPosition(); - const bps = debugService.getModel().getBreakpoints({ uri: model.uri, lineNumber: position.lineNumber }); - if (bps.length) { - debugService.enableOrDisableBreakpoints(!bps[0].enabled, bps[0]); + if (position) { + const bps = debugService.getModel().getBreakpoints({ uri: model.uri, lineNumber: position.lineNumber }); + if (bps.length) { + debugService.enableOrDisableBreakpoints(!bps[0].enabled, bps[0]); + } } } } @@ -90,11 +92,10 @@ export function registerCommands(): void { const debugService = accessor.get(IDebugService); const focused = listService.lastFocusedList; - // Tree only - if (!(focused instanceof List)) { - const element = focused.getFocus(); - if (element instanceof Expression) { - debugService.getViewModel().setSelectedExpression(element); + if (focused) { + const elements = focused.getFocus(); + if (Array.isArray(elements) && elements[0] instanceof Expression) { + debugService.getViewModel().setSelectedExpression(elements[0]); } } } @@ -111,11 +112,10 @@ export function registerCommands(): void { const debugService = accessor.get(IDebugService); const focused = listService.lastFocusedList; - // Tree only - if (!(focused instanceof List)) { - const element = focused.getFocus(); - if (element instanceof Variable) { - debugService.getViewModel().setSelectedExpression(element); + if (focused) { + const elements = focused.getFocus(); + if (Array.isArray(elements) && elements[0] instanceof Variable) { + debugService.getViewModel().setSelectedExpression(elements[0]); } } } @@ -132,11 +132,10 @@ export function registerCommands(): void { const debugService = accessor.get(IDebugService); const focused = listService.lastFocusedList; - // Tree only - if (!(focused instanceof List)) { - const element = focused.getFocus(); - if (element instanceof Expression) { - debugService.removeWatchExpressions(element.getId()); + if (focused) { + const elements = focused.getFocus(); + if (Array.isArray(elements) && elements[0] instanceof Expression) { + debugService.removeWatchExpressions(elements[0].getId()); } } } @@ -153,7 +152,6 @@ export function registerCommands(): void { const debugService = accessor.get(IDebugService); const list = listService.lastFocusedList; - // Tree only if (list instanceof List) { const focused = list.getFocusedElements(); const element = focused.length ? focused[0] : undefined; @@ -195,7 +193,7 @@ export function registerCommands(): void { } const launch = manager.getLaunches().filter(l => l.uri.toString() === launchUri).pop() || manager.selectedConfiguration.launch; - return launch.openConfigFile(false, false).then(({ editor, created }) => { + return launch!.openConfigFile(false, false).then(({ editor, created }) => { if (editor && !created) { const codeEditor = editor.getControl(); if (codeEditor) { @@ -214,6 +212,10 @@ export function registerCommands(): void { const widget = editorService.activeTextEditorWidget; if (isCodeEditor(widget)) { const position = widget.getPosition(); + if (!position || !widget.hasModel()) { + return undefined; + } + const modelUri = widget.getModel().uri; const bp = debugService.getModel().getBreakpoints({ lineNumber: position.lineNumber, uri: modelUri }) .filter(bp => (bp.column === position.column || !bp.column && position.column <= 1)).pop(); diff --git a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts similarity index 74% rename from src/vs/workbench/parts/debug/browser/debugEditorActions.ts rename to src/vs/workbench/contrib/debug/browser/debugEditorActions.ts index e162648701..5ce1853970 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorActions.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorActions.ts @@ -9,14 +9,14 @@ import { Range } from 'vs/editor/common/core/range'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ServicesAccessor, registerEditorAction, EditorAction, IActionOptions } from 'vs/editor/browser/editorExtensions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, REPL_ID, VIEWLET_ID, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, REPL_ID, VIEWLET_ID, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint } from 'vs/workbench/contrib/debug/common/debug'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { openBreakpointSource } from 'vs/workbench/parts/debug/browser/breakpointsView'; +import { openBreakpointSource } from 'vs/workbench/contrib/debug/browser/breakpointsView'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { PanelFocusContext } from 'vs/workbench/browser/parts/panel/panelPart'; +import { PanelFocusContext } from 'vs/workbench/common/panel'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; export const TOGGLE_BREAKPOINT_ID = 'editor.debug.action.toggleBreakpoint'; @@ -39,17 +39,19 @@ class ToggleBreakpointAction extends EditorAction { const debugService = accessor.get(IDebugService); const position = editor.getPosition(); - const modelUri = editor.getModel().uri; - const bps = debugService.getModel().getBreakpoints({ lineNumber: position.lineNumber, uri: modelUri }); + if (editor.hasModel() && position) { + const modelUri = editor.getModel().uri; + const bps = debugService.getModel().getBreakpoints({ lineNumber: position.lineNumber, uri: modelUri }); - if (bps.length) { - return Promise.all(bps.map(bp => debugService.removeBreakpoints(bp.getId()))); - } - if (debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { - return debugService.addBreakpoints(modelUri, [{ lineNumber: position.lineNumber }], 'debugEditorActions.toggleBreakpointAction'); + if (bps.length) { + return Promise.all(bps.map(bp => debugService.removeBreakpoints(bp.getId()))); + } + if (debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { + return debugService.addBreakpoints(modelUri, [{ lineNumber: position.lineNumber }], 'debugEditorActions.toggleBreakpointAction'); + } } - return Promise.resolve(null); + return Promise.resolve(); } } @@ -68,9 +70,9 @@ class ConditionalBreakpointAction extends EditorAction { public run(accessor: ServicesAccessor, editor: ICodeEditor): void { const debugService = accessor.get(IDebugService); - const { lineNumber, column } = editor.getPosition(); - if (debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { - editor.getContribution(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(lineNumber, column); + const position = editor.getPosition(); + if (position && editor.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { + editor.getContribution(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, position.column); } } } @@ -90,9 +92,9 @@ class LogPointAction extends EditorAction { public run(accessor: ServicesAccessor, editor: ICodeEditor): void { const debugService = accessor.get(IDebugService); - const { lineNumber, column } = editor.getPosition(); - if (debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { - editor.getContribution(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.LOG_MESSAGE); + const position = editor.getPosition(); + if (position && editor.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) { + editor.getContribution(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, position.column, BreakpointWidgetContext.LOG_MESSAGE); } } } @@ -134,13 +136,17 @@ class RunToCursorAction extends EditorAction { }); const position = editor.getPosition(); + if (!editor.hasModel() || !position) { + return Promise.resolve(); + } + const uri = editor.getModel().uri; const bpExists = !!(debugService.getModel().getBreakpoints({ column: position.column, lineNumber: position.lineNumber, uri }).length); return (bpExists ? Promise.resolve(null) : >debugService.addBreakpoints(uri, [{ lineNumber: position.lineNumber, column: position.column }], 'debugEditorActions.runToCursorAction')).then((breakpoints) => { if (breakpoints && breakpoints.length) { breakpointToRemove = breakpoints[0]; } - debugService.getViewModel().focusedThread.continue(); + debugService.getViewModel().focusedThread!.continue(); }); } } @@ -163,11 +169,14 @@ class SelectionToReplAction extends EditorAction { public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { const debugService = accessor.get(IDebugService); const panelService = accessor.get(IPanelService); - - const text = editor.getModel().getValueInRange(editor.getSelection()); const viewModel = debugService.getViewModel(); const session = viewModel.focusedSession; - return session.addReplExpression(viewModel.focusedStackFrame, text) + if (!editor.hasModel() || !session) { + return Promise.resolve(); + } + + const text = editor.getModel().getValueInRange(editor.getSelection()); + return session.addReplExpression(viewModel.focusedStackFrame!, text) .then(() => panelService.openPanel(REPL_ID, true)) .then(_ => undefined); } @@ -191,6 +200,9 @@ class SelectionToWatchExpressionsAction extends EditorAction { public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { const debugService = accessor.get(IDebugService); const viewletService = accessor.get(IViewletService); + if (!editor.hasModel()) { + return Promise.resolve(); + } const text = editor.getModel().getValueInRange(editor.getSelection()); return viewletService.openViewlet(VIEWLET_ID).then(() => debugService.addWatchExpression(text)); @@ -215,9 +227,12 @@ class ShowDebugHoverAction extends EditorAction { public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise { const position = editor.getPosition(); + if (!position || !editor.hasModel()) { + return Promise.resolve(); + } const word = editor.getModel().getWordAtPosition(position); if (!word) { - return Promise.resolve(null); + return Promise.resolve(); } const range = new Range(position.lineNumber, position.column, position.lineNumber, word.endColumn); @@ -233,32 +248,34 @@ class GoToBreakpointAction extends EditorAction { public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise { const debugService = accessor.get(IDebugService); const editorService = accessor.get(IEditorService); - const currentUri = editor.getModel().uri; - const currentLine = editor.getPosition().lineNumber; - //Breakpoints returned from `getBreakpoints` are already sorted. - const allEnabledBreakpoints = debugService.getModel().getBreakpoints({ enabledOnly: true }); + if (editor.hasModel()) { + const currentUri = editor.getModel().uri; + const currentLine = editor.getPosition().lineNumber; + //Breakpoints returned from `getBreakpoints` are already sorted. + const allEnabledBreakpoints = debugService.getModel().getBreakpoints({ enabledOnly: true }); - //Try to find breakpoint in current file - let moveBreakpoint = - this.isNext - ? allEnabledBreakpoints.filter(bp => bp.uri.toString() === currentUri.toString() && bp.lineNumber > currentLine).shift() - : allEnabledBreakpoints.filter(bp => bp.uri.toString() === currentUri.toString() && bp.lineNumber < currentLine).pop(); - - //Try to find breakpoints in following files - if (!moveBreakpoint) { - moveBreakpoint = + //Try to find breakpoint in current file + let moveBreakpoint = this.isNext - ? allEnabledBreakpoints.filter(bp => bp.uri.toString() > currentUri.toString()).shift() - : allEnabledBreakpoints.filter(bp => bp.uri.toString() < currentUri.toString()).pop(); - } + ? allEnabledBreakpoints.filter(bp => bp.uri.toString() === currentUri.toString() && bp.lineNumber > currentLine).shift() + : allEnabledBreakpoints.filter(bp => bp.uri.toString() === currentUri.toString() && bp.lineNumber < currentLine).pop(); - //Move to first or last possible breakpoint - if (!moveBreakpoint && allEnabledBreakpoints.length) { - moveBreakpoint = this.isNext ? allEnabledBreakpoints[0] : allEnabledBreakpoints[allEnabledBreakpoints.length - 1]; - } + //Try to find breakpoints in following files + if (!moveBreakpoint) { + moveBreakpoint = + this.isNext + ? allEnabledBreakpoints.filter(bp => bp.uri.toString() > currentUri.toString()).shift() + : allEnabledBreakpoints.filter(bp => bp.uri.toString() < currentUri.toString()).pop(); + } - if (moveBreakpoint) { - return openBreakpointSource(moveBreakpoint, false, true, debugService, editorService); + //Move to first or last possible breakpoint + if (!moveBreakpoint && allEnabledBreakpoints.length) { + moveBreakpoint = this.isNext ? allEnabledBreakpoints[0] : allEnabledBreakpoints[allEnabledBreakpoints.length - 1]; + } + + if (moveBreakpoint) { + return openBreakpointSource(moveBreakpoint, false, true, debugService, editorService); + } } return Promise.resolve(null); @@ -299,8 +316,8 @@ registerEditorAction(GoToPreviousBreakpointAction); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: RunToCursorAction.ID, - title: RunToCursorAction.LABEL, - category: 'Debug' + title: { value: RunToCursorAction.LABEL, original: 'Debug: Run to Cursor' }, + category: nls.localize('debug', "Debug") }, group: 'debug', when: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')), diff --git a/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts similarity index 89% rename from src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts rename to src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts index fc96827662..d57eaafb16 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorContribution.ts @@ -24,15 +24,13 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService import { Range } from 'vs/editor/common/core/range'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IConfigurationService, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { DebugHoverWidget } from 'vs/workbench/parts/debug/electron-browser/debugHover'; -import { RemoveBreakpointAction } from 'vs/workbench/parts/debug/browser/debugActions'; -import { IDebugEditorContribution, IDebugService, State, IBreakpoint, EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo, BreakpointWidgetContext } from 'vs/workbench/parts/debug/common/debug'; -import { BreakpointWidget } from 'vs/workbench/parts/debug/electron-browser/breakpointWidget'; -import { ExceptionWidget } from 'vs/workbench/parts/debug/browser/exceptionWidget'; +import { RemoveBreakpointAction } from 'vs/workbench/contrib/debug/browser/debugActions'; +import { IDebugEditorContribution, IDebugService, State, IBreakpoint, EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo, BreakpointWidgetContext } from 'vs/workbench/contrib/debug/common/debug'; +import { ExceptionWidget } from 'vs/workbench/contrib/debug/browser/exceptionWidget'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { Position } from 'vs/editor/common/core/position'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; @@ -45,6 +43,8 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { getHover } from 'vs/editor/contrib/hover/getHover'; import { IEditorHoverOptions } from 'vs/editor/common/config/editorOptions'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { BreakpointWidget } from 'vs/workbench/contrib/debug/browser/breakpointWidget'; +import { DebugHoverWidget } from 'vs/workbench/contrib/debug/browser/debugHover'; const HOVER_DELAY = 300; const LAUNCH_JSON_REGEX = /launch\.json$/; @@ -62,11 +62,11 @@ export class DebugEditorContribution implements IDebugEditorContribution { private mouseDown = false; private breakpointHintDecoration: string[]; - private breakpointWidget: BreakpointWidget; + private breakpointWidget: BreakpointWidget | undefined; private breakpointWidgetVisible: IContextKey; - private wordToLineNumbersMap: Map; + private wordToLineNumbersMap: Map | undefined; - private exceptionWidget: ExceptionWidget; + private exceptionWidget: ExceptionWidget | undefined; private configurationWidget: FloatingClickWidget; @@ -114,11 +114,11 @@ export class DebugEditorContribution implements IDebugEditorContribution { () => this.debugService.enableOrDisableBreakpoints(!breakpoints[0].enabled, breakpoints[0]) )); } else if (breakpoints.length > 1) { - const sorted = breakpoints.slice().sort((first, second) => first.column - second.column); + const sorted = breakpoints.slice().sort((first, second) => (first.column && second.column) ? first.column - second.column : 1); actions.push(new ContextSubMenu(nls.localize('removeBreakpoints', "Remove Breakpoints"), sorted.map(bp => new Action( 'removeInlineBreakpoint', bp.column ? nls.localize('removeInlineBreakpointOnColumn', "Remove Inline Breakpoint on Column {0}", bp.column) : nls.localize('removeLineBreakpoint', "Remove Line Breakpoint"), - null, + undefined, true, () => this.debugService.removeBreakpoints(bp.getId()) )))); @@ -126,7 +126,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { actions.push(new ContextSubMenu(nls.localize('editBreakpoints', "Edit Breakpoints"), sorted.map(bp => new Action('editBreakpoint', bp.column ? nls.localize('editInlineBreakpointOnColumn', "Edit Inline Breakpoint on Column {0}", bp.column) : nls.localize('editLineBrekapoint', "Edit Line Breakpoint"), - null, + undefined, true, () => Promise.resolve(this.editor.getContribution(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(bp.lineNumber, bp.column)) ) @@ -136,7 +136,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { bp.enabled ? 'disableColumnBreakpoint' : 'enableColumnBreakpoint', bp.enabled ? (bp.column ? nls.localize('disableInlineColumnBreakpoint', "Disable Inline Breakpoint on Column {0}", bp.column) : nls.localize('disableBreakpointOnLine', "Disable Line Breakpoint")) : (bp.column ? nls.localize('enableBreakpoints', "Enable Inline Breakpoint on Column {0}", bp.column) : nls.localize('enableBreakpointOnLine', "Enable Line Breakpoint")), - null, + undefined, true, () => this.debugService.enableOrDisableBreakpoints(!bp.enabled, bp) )))); @@ -144,21 +144,21 @@ export class DebugEditorContribution implements IDebugEditorContribution { actions.push(new Action( 'addBreakpoint', nls.localize('addBreakpoint', "Add Breakpoint"), - null, + undefined, true, () => this.debugService.addBreakpoints(uri, [{ lineNumber }], `debugEditorContextMenu`) )); actions.push(new Action( 'addConditionalBreakpoint', nls.localize('addConditionalBreakpoint', "Add Conditional Breakpoint..."), - null, + undefined, true, () => Promise.resolve(this.editor.getContribution(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(lineNumber, undefined)) )); actions.push(new Action( 'addLogPoint', nls.localize('addLogPoint', "Add Logpoint..."), - null, + undefined, true, () => Promise.resolve(this.editor.getContribution(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(lineNumber, undefined, BreakpointWidgetContext.LOG_MESSAGE)) )); @@ -170,12 +170,13 @@ export class DebugEditorContribution implements IDebugEditorContribution { private registerListeners(): void { this.toDispose.push(this.editor.onMouseDown((e: IEditorMouseEvent) => { const data = e.target.detail as IMarginData; - if (e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || data.isAfterLines || !this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { + const model = this.editor.getModel(); + if (!e.target.position || !model || e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || data.isAfterLines || !this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { return; } - const canSetBreakpoints = this.debugService.getConfigurationManager().canSetBreakpointsIn(this.editor.getModel()); + const canSetBreakpoints = this.debugService.getConfigurationManager().canSetBreakpointsIn(model); const lineNumber = e.target.position.lineNumber; - const uri = this.editor.getModel().uri; + const uri = model.uri; if (e.event.rightButton || (env.isMacintosh && e.event.leftButton && e.event.ctrlKey)) { if (!canSetBreakpoints) { @@ -235,7 +236,8 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => { let showBreakpointHintAtLineNumber = -1; - if (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN && this.debugService.getConfigurationManager().canSetBreakpointsIn(this.editor.getModel()) && + const model = this.editor.getModel(); + if (model && e.target.position && e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN && this.debugService.getConfigurationManager().canSetBreakpointsIn(model) && this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) { const data = e.target.detail as IMarginData; if (!data.isAfterLines) { @@ -268,18 +270,20 @@ export class DebugEditorContribution implements IDebugEditorContribution { })); this.toDispose.push(this.editor.onKeyDown((e: IKeyboardEvent) => this.onKeyDown(e))); this.toDispose.push(this.editor.onDidChangeModelContent(() => { - this.wordToLineNumbersMap = null; + this.wordToLineNumbersMap = undefined; this.updateInlineValuesScheduler.schedule(); })); this.toDispose.push(this.editor.onDidChangeModel(() => { const stackFrame = this.debugService.getViewModel().focusedStackFrame; const model = this.editor.getModel(); - this._applyHoverConfiguration(model, stackFrame); + if (model) { + this._applyHoverConfiguration(model, stackFrame); + } this.closeBreakpointWidget(); this.toggleExceptionWidget(); this.hideHoverWidget(); this.updateConfigurationWidgetVisibility(); - this.wordToLineNumbersMap = null; + this.wordToLineNumbersMap = undefined; this.updateInlineValueDecorations(stackFrame); })); this.toDispose.push(this.editor.onDidScrollChange(() => this.hideHoverWidget)); @@ -290,21 +294,18 @@ export class DebugEditorContribution implements IDebugEditorContribution { })); } - private _applyHoverConfiguration(model: ITextModel, stackFrame: IStackFrame): void { - if (stackFrame && model && model.uri.toString() === stackFrame.source.uri.toString()) { + private _applyHoverConfiguration(model: ITextModel, stackFrame: IStackFrame | undefined): void { + if (stackFrame && model.uri.toString() === stackFrame.source.uri.toString()) { this.editor.updateOptions({ hover: { enabled: false } }); } else { - let overrides: IConfigurationOverrides; - if (model) { - overrides = { - resource: model.uri, - overrideIdentifier: model.getLanguageIdentifier().language - }; - } + let overrides = { + resource: model.uri, + overrideIdentifier: model.getLanguageIdentifier().language + }; const defaultConfiguration = this.configurationService.getValue('editor.hover', overrides); this.editor.updateOptions({ hover: { @@ -326,7 +327,8 @@ export class DebugEditorContribution implements IDebugEditorContribution { if (sf && model && sf.source.uri.toString() === model.uri.toString()) { return this.hoverWidget.showAt(range, focus); } - return undefined; + + return Promise.resolve(); } private marginFreeFromNonDebugDecorations(line: number): boolean { @@ -359,13 +361,15 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.breakpointHintDecoration = this.editor.deltaDecorations(this.breakpointHintDecoration, newDecoration); } - private onFocusStackFrame(sf: IStackFrame): void { + private onFocusStackFrame(sf: IStackFrame | undefined): void { const model = this.editor.getModel(); - this._applyHoverConfiguration(model, sf); - if (model && sf && sf.source.uri.toString() === model.uri.toString()) { - this.toggleExceptionWidget(); - } else { - this.hideHoverWidget(); + if (model) { + this._applyHoverConfiguration(model, sf); + if (sf && sf.source.uri.toString() === model.uri.toString()) { + this.toggleExceptionWidget(); + } else { + this.hideHoverWidget(); + } } this.updateInlineValueDecorations(sf); @@ -390,7 +394,9 @@ export class DebugEditorContribution implements IDebugEditorContribution { @memoize private get provideNonDebugHoverScheduler(): RunOnceScheduler { const scheduler = new RunOnceScheduler(() => { - getHover(this.editor.getModel(), this.nonDebugHoverPosition, CancellationToken.None); + if (this.editor.hasModel()) { + getHover(this.editor.getModel(), this.nonDebugHoverPosition, CancellationToken.None); + } }, HOVER_DELAY); this.toDispose.push(scheduler); @@ -433,7 +439,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { return; } if (targetType === MouseTargetType.CONTENT_TEXT) { - if (!mouseEvent.target.range.equalsRange(this.hoverRange)) { + if (mouseEvent.target.range && !mouseEvent.target.range.equalsRange(this.hoverRange)) { this.hoverRange = mouseEvent.target.range; this.showHoverScheduler.schedule(); } @@ -467,7 +473,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { public closeBreakpointWidget(): void { if (this.breakpointWidget) { this.breakpointWidget.dispose(); - this.breakpointWidget = null; + this.breakpointWidget = undefined; this.breakpointWidgetVisible.reset(); this.editor.focus(); } @@ -485,7 +491,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { } // First call stack frame that is available is the frame where exception has been thrown - const exceptionSf = first(callStack, sf => sf.source && sf.source.available, undefined); + const exceptionSf = first(callStack, sf => !!(sf && sf.source && sf.source.available && sf.source.presentationHint !== 'deemphasize'), undefined); if (!exceptionSf || exceptionSf !== focusedSf) { this.closeExceptionWidget(); return; @@ -515,7 +521,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { private closeExceptionWidget(): void { if (this.exceptionWidget) { this.exceptionWidget.dispose(); - this.exceptionWidget = null; + this.exceptionWidget = undefined; } } @@ -537,8 +543,12 @@ export class DebugEditorContribution implements IDebugEditorContribution { "debug/addLaunchConfiguration" : {} */ this.telemetryService.publicLog('debug/addLaunchConfiguration'); - let configurationsArrayPosition: Position; + let configurationsArrayPosition: Position | undefined; const model = this.editor.getModel(); + if (!model) { + return Promise.resolve(); + } + let depthInArray = 0; let lastProperty: string; @@ -559,12 +569,12 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.editor.focus(); if (!configurationsArrayPosition) { - return Promise.resolve(undefined); + return Promise.resolve(); } const insertLine = (position: Position): Promise => { // Check if there are more characters on a line after a "configurations": [, if yes enter a newline - if (this.editor.getModel().getLineLastNonWhitespaceColumn(position.lineNumber) > position.column) { + if (model.getLineLastNonWhitespaceColumn(position.lineNumber) > position.column) { this.editor.setPosition(position); CoreEditingCommands.LineBreakInsert.runEditorCommand(null, this.editor, null); } @@ -598,7 +608,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { ); } - private updateInlineValueDecorations(stackFrame: IStackFrame): void { + private updateInlineValueDecorations(stackFrame: IStackFrame | undefined): void { const model = this.editor.getModel(); if (!this.configurationService.getValue('debug').inlineValues || !model || !stackFrame || model.uri.toString() !== stackFrame.source.uri.toString()) { @@ -619,14 +629,14 @@ export class DebugEditorContribution implements IDebugEditorContribution { range = range.setStartPosition(scope.range.startLineNumber, scope.range.startColumn); } - return this.createInlineValueDecorationsInsideRange(children, range); + return this.createInlineValueDecorationsInsideRange(children, range, model); }))).then(decorationsPerScope => { const allDecorations = decorationsPerScope.reduce((previous, current) => previous.concat(current), []); this.editor.setDecorations(INLINE_VALUE_DECORATION_KEY, allDecorations); })); } - private createInlineValueDecorationsInsideRange(expressions: ReadonlyArray, range: Range): IDecorationOptions[] { + private createInlineValueDecorationsInsideRange(expressions: ReadonlyArray, range: Range, model: ITextModel): IDecorationOptions[] { const nameValueMap = new Map(); for (let expr of expressions) { nameValueMap.set(expr.name, expr.value); @@ -641,15 +651,16 @@ export class DebugEditorContribution implements IDebugEditorContribution { // Compute unique set of names on each line nameValueMap.forEach((value, name) => { - if (wordToPositionsMap.has(name)) { - for (let position of wordToPositionsMap.get(name)) { + const positions = wordToPositionsMap.get(name); + if (positions) { + for (let position of positions) { if (range.containsPosition(position)) { if (!lineToNamesMap.has(position.lineNumber)) { lineToNamesMap.set(position.lineNumber, []); } - if (lineToNamesMap.get(position.lineNumber).indexOf(name) === -1) { - lineToNamesMap.get(position.lineNumber).push(name); + if (lineToNamesMap.get(position.lineNumber)!.indexOf(name) === -1) { + lineToNamesMap.get(position.lineNumber)!.push(name); } } } @@ -660,7 +671,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { // Compute decorators for each line lineToNamesMap.forEach((names, line) => { const contentText = names.sort((first, second) => { - const content = this.editor.getModel().getLineContent(line); + const content = model.getLineContent(line); return content.indexOf(first) - content.indexOf(second); }).map(name => `${name} = ${nameValueMap.get(name)}`).join(', '); decorations.push(this.createInlineValueDecoration(line, contentText)); @@ -738,7 +749,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.wordToLineNumbersMap.set(word, []); } - this.wordToLineNumbersMap.get(word).push(new Position(lineNumber, tokenStartOffset)); + this.wordToLineNumbersMap.get(word)!.push(new Position(lineNumber, tokenStartOffset)); } } } diff --git a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts b/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts similarity index 85% rename from src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts rename to src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts index 9bf0858803..1360355d9a 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugEditorModelManager.ts @@ -8,10 +8,10 @@ import { Constants } from 'vs/editor/common/core/uint'; import { Range } from 'vs/editor/common/core/range'; import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/model'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IDebugService, IBreakpoint, State, IBreakpointUpdateData } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, IBreakpoint, State, IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug'; import { IModelService } from 'vs/editor/common/services/modelService'; import { MarkdownString } from 'vs/base/common/htmlContent'; -import { getBreakpointMessageAndClassName } from 'vs/workbench/parts/debug/browser/breakpointsView'; +import { getBreakpointMessageAndClassName } from 'vs/workbench/contrib/debug/browser/breakpointsView'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; @@ -27,7 +27,7 @@ interface IDebugEditorModelData { toDispose: lifecycle.IDisposable[]; breakpointDecorations: IBreakpointDecoration[]; currentStackDecorations: string[]; - topStackFrameRange: Range; + topStackFrameRange: Range | undefined; } export class DebugEditorModelManager implements IWorkbenchContribution { @@ -93,8 +93,9 @@ export class DebugEditorModelManager implements IWorkbenchContribution { private onModelRemoved(model: ITextModel): void { const modelUriStr = model.uri.toString(); - if (this.modelDataMap.has(modelUriStr)) { - lifecycle.dispose(this.modelDataMap.get(modelUriStr).toDispose); + const data = this.modelDataMap.get(modelUriStr); + if (data) { + lifecycle.dispose(data.toDispose); this.modelDataMap.delete(modelUriStr); } } @@ -127,27 +128,20 @@ export class DebugEditorModelManager implements IWorkbenchContribution { range }); - if (stackFrame.thread.stoppedDetails && stackFrame.thread.stoppedDetails.reason === 'exception') { - result.push({ - options: DebugEditorModelManager.TOP_STACK_FRAME_EXCEPTION_DECORATION, - range: columnUntilEOLRange - }); - } else { - result.push({ - options: DebugEditorModelManager.TOP_STACK_FRAME_DECORATION, - range: columnUntilEOLRange - }); + result.push({ + options: DebugEditorModelManager.TOP_STACK_FRAME_DECORATION, + range: columnUntilEOLRange + }); - if (this.modelDataMap.has(modelUriStr)) { - const modelData = this.modelDataMap.get(modelUriStr); - if (modelData.topStackFrameRange && modelData.topStackFrameRange.startLineNumber === stackFrame.range.startLineNumber && modelData.topStackFrameRange.startColumn !== stackFrame.range.startColumn) { - result.push({ - options: DebugEditorModelManager.TOP_STACK_FRAME_INLINE_DECORATION, - range: columnUntilEOLRange - }); - } - modelData.topStackFrameRange = columnUntilEOLRange; + const modelData = this.modelDataMap.get(modelUriStr); + if (modelData) { + if (modelData.topStackFrameRange && modelData.topStackFrameRange.startLineNumber === stackFrame.range.startLineNumber && modelData.topStackFrameRange.startColumn !== stackFrame.range.startColumn) { + result.push({ + options: DebugEditorModelManager.TOP_STACK_FRAME_INLINE_DECORATION, + range: columnUntilEOLRange + }); } + modelData.topStackFrameRange = columnUntilEOLRange; } } else { result.push({ @@ -167,7 +161,7 @@ export class DebugEditorModelManager implements IWorkbenchContribution { // breakpoints management. Represent data coming from the debug service and also send data back. private onModelDecorationsChanged(modelUrlStr: string): void { const modelData = this.modelDataMap.get(modelUrlStr); - if (modelData.breakpointDecorations.length === 0 || this.ignoreDecorationsChangedEvent) { + if (!modelData || modelData.breakpointDecorations.length === 0 || this.ignoreDecorationsChangedEvent) { // I have no decorations return; } @@ -212,16 +206,18 @@ export class DebugEditorModelManager implements IWorkbenchContribution { const breakpointsMap = new Map(); this.debugService.getModel().getBreakpoints().forEach(bp => { const uriStr = bp.uri.toString(); - if (breakpointsMap.has(uriStr)) { - breakpointsMap.get(uriStr).push(bp); + const breakpoints = breakpointsMap.get(uriStr); + if (breakpoints) { + breakpoints.push(bp); } else { breakpointsMap.set(uriStr, [bp]); } }); breakpointsMap.forEach((bps, uri) => { - if (this.modelDataMap.has(uri)) { - this.updateBreakpoints(this.modelDataMap.get(uri), breakpointsMap.get(uri)); + const data = this.modelDataMap.get(uri); + if (data) { + this.updateBreakpoints(data, breakpointsMap.get(uri)!); } }); this.modelDataMap.forEach((modelData, uri) => { @@ -233,16 +229,17 @@ export class DebugEditorModelManager implements IWorkbenchContribution { private updateBreakpoints(modelData: IDebugEditorModelData, newBreakpoints: IBreakpoint[]): void { const desiredDecorations = this.createBreakpointDecorations(modelData.model, newBreakpoints); - let breakpointDecorationIds: string[]; try { this.ignoreDecorationsChangedEvent = true; - breakpointDecorationIds = modelData.model.deltaDecorations(modelData.breakpointDecorations.map(bpd => bpd.decorationId), desiredDecorations); + const breakpointDecorationIds = modelData.model.deltaDecorations(modelData.breakpointDecorations.map(bpd => bpd.decorationId), desiredDecorations); + modelData.breakpointDecorations = breakpointDecorationIds.map((decorationId, index) => ({ + decorationId, + modelId: newBreakpoints[index].getId(), + range: desiredDecorations[index].range + })); } finally { this.ignoreDecorationsChangedEvent = false; } - - modelData.breakpointDecorations = breakpointDecorationIds.map((decorationId, index) => - ({ decorationId, modelId: newBreakpoints[index].getId(), range: desiredDecorations[index].range })); } private createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArray): { range: Range; options: IModelDecorationOptions; }[] { @@ -267,7 +264,7 @@ export class DebugEditorModelManager implements IWorkbenchContribution { private getBreakpointDecorationOptions(breakpoint: IBreakpoint): IModelDecorationOptions { const { className, message } = getBreakpointMessageAndClassName(this.debugService, breakpoint); - let glyphMarginHoverMessage: MarkdownString; + let glyphMarginHoverMessage: MarkdownString | undefined; if (message) { if (breakpoint.condition || breakpoint.hitCondition) { @@ -307,13 +304,6 @@ export class DebugEditorModelManager implements IWorkbenchContribution { stickiness: DebugEditorModelManager.STICKINESS }; - private static TOP_STACK_FRAME_EXCEPTION_DECORATION: IModelDecorationOptions = { - isWholeLine: true, - inlineClassName: 'debug-remove-token-colors', - className: 'debug-top-stack-frame-exception-line', - stickiness: DebugEditorModelManager.STICKINESS - }; - private static TOP_STACK_FRAME_INLINE_DECORATION: IModelDecorationOptions = { beforeContentClassName: 'debug-top-stack-frame-column' }; diff --git a/src/vs/workbench/parts/debug/electron-browser/debugHover.ts b/src/vs/workbench/contrib/debug/browser/debugHover.ts similarity index 86% rename from src/vs/workbench/parts/debug/electron-browser/debugHover.ts rename to src/vs/workbench/contrib/debug/browser/debugHover.ts index e869a6e44e..174438adf9 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugHover.ts +++ b/src/vs/workbench/contrib/debug/browser/debugHover.ts @@ -14,25 +14,22 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IContentWidget, ICodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IDebugService, IExpression, IExpressionContainer } from 'vs/workbench/parts/debug/common/debug'; -import { Expression } from 'vs/workbench/parts/debug/common/debugModel'; -import { renderExpressionValue } from 'vs/workbench/parts/debug/browser/baseDebugView'; -import { VariablesRenderer } from 'vs/workbench/parts/debug/electron-browser/variablesView'; +import { IDebugService, IExpression, IExpressionContainer } from 'vs/workbench/contrib/debug/common/debug'; +import { Expression } from 'vs/workbench/contrib/debug/common/debugModel'; +import { renderExpressionValue } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; -import { getExactExpressionStartAndEnd } from 'vs/workbench/parts/debug/common/debugUtils'; +import { getExactExpressionStartAndEnd } from 'vs/workbench/contrib/debug/common/debugUtils'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { WorkbenchAsyncDataTree, IListService } from 'vs/platform/list/browser/listService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { coalesce } from 'vs/base/common/arrays'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; +import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; const $ = dom.$; const MAX_TREE_HEIGHT = 324; @@ -46,7 +43,7 @@ export class DebugHoverWidget implements IContentWidget { private _isVisible: boolean; private domNode: HTMLElement; private tree: AsyncDataTree; - private showAtPosition: Position; + private showAtPosition: Position | null; private highlightDecorations: string[]; private complexValueContainer: HTMLElement; private complexValueTitle: HTMLElement; @@ -61,10 +58,6 @@ export class DebugHoverWidget implements IContentWidget { @IDebugService private readonly debugService: IDebugService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IThemeService private readonly themeService: IThemeService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IListService private readonly listService: IListService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IKeybindingService private readonly keybindingService: IKeybindingService ) { this.toDispose = []; @@ -81,13 +74,13 @@ export class DebugHoverWidget implements IContentWidget { this.treeContainer.setAttribute('role', 'tree'); this.dataSource = new DebugHoverDataSource(); - this.tree = new WorkbenchAsyncDataTree(this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer)], + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer)], this.dataSource, { ariaLabel: nls.localize('treeAriaLabel', "Debug Hover"), accessibilityProvider: new DebugHoverAccessibilityProvider(), mouseSupport: false, horizontalScrolling: true - }, this.contextKeyService, this.listService, this.themeService, this.configurationService, this.keybindingService); + }) as any as AsyncDataTree; this.valueContainer = $('.value'); this.valueContainer.tabIndex = 0; @@ -145,15 +138,19 @@ export class DebugHoverWidget implements IContentWidget { const pos = range.getStartPosition(); const session = this.debugService.getViewModel().focusedSession; + if (!this.editor.hasModel()) { + return Promise.resolve(this.hide()); + } + const lineContent = this.editor.getModel().getLineContent(pos.lineNumber); const { start, end } = getExactExpressionStartAndEnd(lineContent, range.startColumn, range.endColumn); // use regex to extract the sub-expression #9821 const matchingExpression = lineContent.substring(start - 1, end); - if (!matchingExpression) { + if (!matchingExpression || !session) { return Promise.resolve(this.hide()); } - let promise: Promise; + let promise: Promise; if (session.capabilities.supportsEvaluateForHovers) { const result = new Expression(matchingExpression); promise = result.evaluate(session, this.debugService.getViewModel().focusedStackFrame, 'hover').then(() => result); @@ -200,13 +197,13 @@ export class DebugHoverWidget implements IContentWidget { }); } - private findExpressionInStackFrame(namesToFind: string[]): Promise { - return this.debugService.getViewModel().focusedStackFrame.getScopes() + private findExpressionInStackFrame(namesToFind: string[]): Promise { + return this.debugService.getViewModel().focusedStackFrame!.getScopes() .then(scopes => scopes.filter(s => !s.expensive)) .then(scopes => Promise.all(scopes.map(scope => this.doFindExpression(scope, namesToFind)))) .then(coalesce) // only show if all expressions found have the same value - .then(expressions => (expressions.length > 0 && expressions.every(e => e.value === expressions[0].value)) ? expressions[0] : null); + .then(expressions => (expressions.length > 0 && expressions.every(e => e.value === expressions[0].value)) ? expressions[0] : undefined); } private doShow(position: Position, expression: IExpression, focus: boolean, forceValueHover = false): Promise { @@ -253,7 +250,7 @@ export class DebugHoverWidget implements IContentWidget { } private layoutTreeAndContainer(): void { - const treeHeight = Math.min(MAX_TREE_HEIGHT, this.tree.visibleNodeCount * 18); + const treeHeight = Math.min(MAX_TREE_HEIGHT, this.tree.contentHeight); this.treeContainer.style.height = `${treeHeight}px`; this.tree.layout(treeHeight, 324); } @@ -270,7 +267,7 @@ export class DebugHoverWidget implements IContentWidget { this.editor.focus(); } - getPosition(): IContentWidgetPosition { + getPosition(): IContentWidgetPosition | null { return this._isVisible ? { position: this.showAtPosition, preference: [ diff --git a/src/vs/workbench/parts/debug/browser/debugQuickOpen.ts b/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts similarity index 75% rename from src/vs/workbench/parts/debug/browser/debugQuickOpen.ts rename to src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts index d6d8473570..1eace446f5 100644 --- a/src/vs/workbench/parts/debug/browser/debugQuickOpen.ts +++ b/src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts @@ -4,20 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as Filters from 'vs/base/common/filters'; -import * as Quickopen from 'vs/workbench/browser/quickopen'; -import * as QuickOpen from 'vs/base/parts/quickopen/common/quickOpen'; -import * as Model from 'vs/base/parts/quickopen/browser/quickOpenModel'; -import { IDebugService, ILaunch } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, ILaunch } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { StartAction } from 'vs/workbench/parts/debug/browser/debugActions'; +import { StartAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup, IHighlight } from 'vs/base/parts/quickopen/browser/quickOpenModel'; +import { Mode, IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; +import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; +import { matchesFuzzy } from 'vs/base/common/filters'; -class AddConfigEntry extends Model.QuickOpenEntry { +class AddConfigEntry extends QuickOpenEntry { - constructor(private label: string, private launch: ILaunch, private commandService: ICommandService, private contextService: IWorkspaceContextService, highlights: Model.IHighlight[] = []) { + constructor(private label: string, private launch: ILaunch, private commandService: ICommandService, private contextService: IWorkspaceContextService, highlights: IHighlight[] = []) { super(highlights); } @@ -33,8 +33,8 @@ class AddConfigEntry extends Model.QuickOpenEntry { return nls.localize('entryAriaLabel', "{0}, debug", this.getLabel()); } - public run(mode: QuickOpen.Mode, context: Model.IContext): boolean { - if (mode === QuickOpen.Mode.PREVIEW) { + public run(mode: Mode): boolean { + if (mode === Mode.PREVIEW) { return false; } this.commandService.executeCommand('debug.addConfiguration', this.launch.uri.toString()); @@ -43,9 +43,9 @@ class AddConfigEntry extends Model.QuickOpenEntry { } } -class StartDebugEntry extends Model.QuickOpenEntry { +class StartDebugEntry extends QuickOpenEntry { - constructor(private debugService: IDebugService, private contextService: IWorkspaceContextService, private notificationService: INotificationService, private launch: ILaunch, private configurationName: string, highlights: Model.IHighlight[] = []) { + constructor(private debugService: IDebugService, private contextService: IWorkspaceContextService, private notificationService: INotificationService, private launch: ILaunch, private configurationName: string, highlights: IHighlight[] = []) { super(highlights); } @@ -61,8 +61,8 @@ class StartDebugEntry extends Model.QuickOpenEntry { return nls.localize('entryAriaLabel', "{0}, debug", this.getLabel()); } - public run(mode: QuickOpen.Mode, context: Model.IContext): boolean { - if (mode === QuickOpen.Mode.PREVIEW || !StartAction.isEnabled(this.debugService)) { + public run(mode: Mode): boolean { + if (mode === Mode.PREVIEW || !StartAction.isEnabled(this.debugService)) { return false; } // Run selected debug configuration @@ -73,7 +73,7 @@ class StartDebugEntry extends Model.QuickOpenEntry { } } -export class DebugQuickOpenHandler extends Quickopen.QuickOpenHandler { +export class DebugQuickOpenHandler extends QuickOpenHandler { public static readonly ID = 'workbench.picker.launch'; @@ -92,13 +92,13 @@ export class DebugQuickOpenHandler extends Quickopen.QuickOpenHandler { return nls.localize('debugAriaLabel', "Type a name of a launch configuration to run."); } - public getResults(input: string, token: CancellationToken): Promise { - const configurations: Model.QuickOpenEntry[] = []; + public getResults(input: string, token: CancellationToken): Promise { + const configurations: QuickOpenEntry[] = []; const configManager = this.debugService.getConfigurationManager(); const launches = configManager.getLaunches(); for (let launch of launches) { - launch.getConfigurationNames().map(config => ({ config: config, highlights: Filters.matchesFuzzy(input, config, true) })) + launch.getConfigurationNames().map(config => ({ config: config, highlights: matchesFuzzy(input, config, true) || undefined })) .filter(({ highlights }) => !!highlights) .forEach(({ config, highlights }) => { if (launch === configManager.selectedConfiguration.launch && config === configManager.selectedConfiguration.name) { @@ -110,19 +110,19 @@ export class DebugQuickOpenHandler extends Quickopen.QuickOpenHandler { launches.filter(l => !l.hidden).forEach((l, index) => { const label = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration..."); - const entry = new AddConfigEntry(label, l, this.commandService, this.contextService, Filters.matchesFuzzy(input, label, true)); + const entry = new AddConfigEntry(label, l, this.commandService, this.contextService, matchesFuzzy(input, label, true) || undefined); if (index === 0) { - configurations.push(new Model.QuickOpenEntryGroup(entry, undefined, true)); + configurations.push(new QuickOpenEntryGroup(entry, undefined, true)); } else { configurations.push(entry); } }); - return Promise.resolve(new Model.QuickOpenModel(configurations)); + return Promise.resolve(new QuickOpenModel(configurations)); } - public getAutoFocus(input: string): QuickOpen.IAutoFocus { + public getAutoFocus(input: string): IAutoFocus { return { autoFocusFirstEntry: !!input, autoFocusIndex: this.autoFocusIndex diff --git a/src/vs/workbench/parts/debug/browser/debugStatus.ts b/src/vs/workbench/contrib/debug/browser/debugStatus.ts similarity index 94% rename from src/vs/workbench/parts/debug/browser/debugStatus.ts rename to src/vs/workbench/contrib/debug/browser/debugStatus.ts index 06b687a33d..ac6489513f 100644 --- a/src/vs/workbench/parts/debug/browser/debugStatus.ts +++ b/src/vs/workbench/contrib/debug/browser/debugStatus.ts @@ -9,10 +9,10 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; -import { IDebugService, State, IDebugConfiguration } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, State, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; import { Themable, STATUS_BAR_FOREGROUND } from 'vs/workbench/common/theme'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { STATUS_BAR_DEBUGGING_FOREGROUND, isStatusbarInDebugMode } from 'vs/workbench/parts/debug/browser/statusbarColorProvider'; +import { STATUS_BAR_DEBUGGING_FOREGROUND, isStatusbarInDebugMode } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; const $ = dom.$; @@ -89,11 +89,11 @@ export class DebugStatus extends Themable implements IStatusbarItem { private setLabel(): void { if (this.label && this.statusBarItem) { const manager = this.debugService.getConfigurationManager(); - const name = manager.selectedConfiguration.name; + const name = manager.selectedConfiguration.name || ''; const nameAndLaunchPresent = name && manager.selectedConfiguration.launch; dom.toggleClass(this.statusBarItem, 'hidden', this.showInStatusBar === 'never' || !nameAndLaunchPresent); if (nameAndLaunchPresent) { - this.label.textContent = manager.getLaunches().length > 1 ? `${name} (${manager.selectedConfiguration.launch.name})` : name; + this.label.textContent = manager.getLaunches().length > 1 ? `${name} (${manager.selectedConfiguration.launch!.name})` : name; } } } diff --git a/src/vs/workbench/parts/debug/browser/debugToolbar.ts b/src/vs/workbench/contrib/debug/browser/debugToolbar.ts similarity index 68% rename from src/vs/workbench/parts/debug/browser/debugToolbar.ts rename to src/vs/workbench/contrib/debug/browser/debugToolbar.ts index 18db504dee..eff448f75d 100644 --- a/src/vs/workbench/parts/debug/browser/debugToolbar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolbar.ts @@ -10,12 +10,11 @@ import * as dom from 'vs/base/browser/dom'; import * as arrays from 'vs/base/common/arrays'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IAction, IRunEvent } from 'vs/base/common/actions'; -import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { ActionBar, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IDebugConfiguration, IDebugService, State } from 'vs/workbench/parts/debug/common/debug'; -import { AbstractDebugAction, PauseAction, ContinueAction, StepBackAction, ReverseContinueAction, StopAction, DisconnectAction, StepOverAction, StepIntoAction, StepOutAction, RestartAction, FocusSessionAction } from 'vs/workbench/parts/debug/browser/debugActions'; -import { FocusSessionActionItem } from 'vs/workbench/parts/debug/browser/debugActionItems'; +import { IDebugConfiguration, IDebugService, State } from 'vs/workbench/contrib/debug/common/debug'; +import { FocusSessionActionItem } from 'vs/workbench/contrib/debug/browser/debugActionItems'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -24,12 +23,14 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { registerColor, contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { isExtensionHostDebugging } from 'vs/workbench/parts/debug/common/debugUtils'; +import { fillInActionBarActions, MenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; +import { IMenu, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions'; const DEBUG_TOOLBAR_POSITION_KEY = 'debug.actionswidgetposition'; const DEBUG_TOOLBAR_Y_KEY = 'debug.actionswidgety'; @@ -50,9 +51,9 @@ export class DebugToolbar extends Themable implements IWorkbenchContribution { private $el: HTMLElement; private dragArea: HTMLElement; private actionBar: ActionBar; - private allActions: AbstractDebugAction[] = []; - private activeActions: AbstractDebugAction[]; + private activeActions: IAction[]; private updateScheduler: RunOnceScheduler; + private debugToolbarMenu: IMenu; private isVisible: boolean; private isBuilt: boolean; @@ -61,22 +62,27 @@ export class DebugToolbar extends Themable implements IWorkbenchContribution { @INotificationService private readonly notificationService: INotificationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IDebugService private readonly debugService: IDebugService, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IStorageService private readonly storageService: IStorageService, @IConfigurationService private readonly configurationService: IConfigurationService, @IThemeService themeService: IThemeService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IContextViewService contextViewService: IContextViewService, - @IInstantiationService private readonly instantiationService: IInstantiationService + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IMenuService menuService: IMenuService, + @IContextMenuService contextMenuService: IContextMenuService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(themeService); this.$el = dom.$('div.debug-toolbar'); - this.$el.style.top = `${partService.getTitleBarOffset()}px`; + this.$el.style.top = `${layoutService.getTitleBarOffset()}px`; this.dragArea = dom.append(this.$el, dom.$('div.drag-area')); const actionBarContainer = dom.append(this.$el, dom.$('div.action-bar-container')); + this.debugToolbarMenu = menuService.createMenu(MenuId.DebugToolbar, contextKeyService); + this.toDispose.push(this.debugToolbarMenu); this.activeActions = []; this.actionBar = this._register(new ActionBar(actionBarContainer, { @@ -85,6 +91,9 @@ export class DebugToolbar extends Themable implements IWorkbenchContribution { if (action.id === FocusSessionAction.ID) { return new FocusSessionActionItem(action, this.debugService, this.themeService, contextViewService); } + if (action instanceof MenuItemAction) { + return new MenuItemActionItem(action, this.keybindingService, this.notificationService, contextMenuService); + } return null; } @@ -97,7 +106,7 @@ export class DebugToolbar extends Themable implements IWorkbenchContribution { return this.hide(); } - const actions = DebugToolbar.getActions(this.allActions, this.toDispose, this.debugService, this.keybindingService, this.instantiationService); + const actions = DebugToolbar.getActions(this.debugToolbarMenu, this.debugService, this.instantiationService); if (!arrays.equals(actions, this.activeActions, (first, second) => first.id === second.id)) { this.actionBar.clear(); this.actionBar.push(actions, { icon: true, label: false }); @@ -155,7 +164,7 @@ export class DebugToolbar extends Themable implements IWorkbenchContribution { // Prevent default to stop editor selecting text #8524 mouseMoveEvent.preventDefault(); // Reduce x by width of drag handle to reduce jarring #16604 - this.setCoordinates(mouseMoveEvent.posx - 14, mouseMoveEvent.posy - this.partService.getTitleBarOffset()); + this.setCoordinates(mouseMoveEvent.posx - 14, mouseMoveEvent.posy - this.layoutService.getTitleBarOffset()); }); const mouseUpListener = dom.addDisposableListener(window, 'mouseup', (e: MouseEvent) => { @@ -167,13 +176,16 @@ export class DebugToolbar extends Themable implements IWorkbenchContribution { }); })); - this._register(this.partService.onTitleBarVisibilityChange(() => this.setYCoordinate())); + this._register(this.layoutService.onTitleBarVisibilityChange(() => this.setYCoordinate())); this._register(browser.onDidChangeZoomLevel(() => this.setYCoordinate())); } private storePosition(): void { - const position = parseFloat(dom.getComputedStyle(this.$el).left) / window.innerWidth; - this.storageService.store(DEBUG_TOOLBAR_POSITION_KEY, position, StorageScope.GLOBAL); + const left = dom.getComputedStyle(this.$el).left; + if (left) { + const position = parseFloat(left) / window.innerWidth; + this.storageService.store(DEBUG_TOOLBAR_POSITION_KEY, position, StorageScope.GLOBAL); + } } protected updateStyles(): void { @@ -198,7 +210,7 @@ export class DebugToolbar extends Themable implements IWorkbenchContribution { } private setYCoordinate(y = 0): void { - const titlebarOffset = this.partService.getTitleBarOffset(); + const titlebarOffset = this.layoutService.getTitleBarOffset(); this.$el.style.top = `${titlebarOffset + y}px`; } @@ -216,7 +228,7 @@ export class DebugToolbar extends Themable implements IWorkbenchContribution { this.$el.style.left = `${x}px`; if (y === undefined) { - y = this.storageService.getInteger(DEBUG_TOOLBAR_Y_KEY, StorageScope.GLOBAL, 0); + y = this.storageService.getNumber(DEBUG_TOOLBAR_Y_KEY, StorageScope.GLOBAL, 0); } const titleAreaHeight = 35; if ((y < titleAreaHeight / 2) || (y > titleAreaHeight + titleAreaHeight / 2)) { @@ -239,7 +251,7 @@ export class DebugToolbar extends Themable implements IWorkbenchContribution { } if (!this.isBuilt) { this.isBuilt = true; - this.partService.getWorkbenchElement().appendChild(this.$el); + this.layoutService.getWorkbenchElement().appendChild(this.$el); } this.isVisible = true; @@ -252,51 +264,14 @@ export class DebugToolbar extends Themable implements IWorkbenchContribution { dom.hide(this.$el); } - public static getActions(allActions: AbstractDebugAction[], toDispose: IDisposable[], debugService: IDebugService, keybindingService: IKeybindingService, instantiationService: IInstantiationService): AbstractDebugAction[] { - if (allActions.length === 0) { - allActions.push(new ContinueAction(ContinueAction.ID, ContinueAction.LABEL, debugService, keybindingService)); - allActions.push(new PauseAction(PauseAction.ID, PauseAction.LABEL, debugService, keybindingService)); - allActions.push(new StopAction(StopAction.ID, StopAction.LABEL, debugService, keybindingService)); - allActions.push(new DisconnectAction(DisconnectAction.ID, DisconnectAction.LABEL, debugService, keybindingService)); - allActions.push(new StepOverAction(StepOverAction.ID, StepOverAction.LABEL, debugService, keybindingService)); - allActions.push(new StepIntoAction(StepIntoAction.ID, StepIntoAction.LABEL, debugService, keybindingService)); - allActions.push(new StepOutAction(StepOutAction.ID, StepOutAction.LABEL, debugService, keybindingService)); - allActions.push(instantiationService.createInstance(RestartAction, RestartAction.ID, RestartAction.LABEL)); - allActions.push(new StepBackAction(StepBackAction.ID, StepBackAction.LABEL, debugService, keybindingService)); - allActions.push(new ReverseContinueAction(ReverseContinueAction.ID, ReverseContinueAction.LABEL, debugService, keybindingService)); - allActions.push(instantiationService.createInstance(FocusSessionAction, FocusSessionAction.ID, FocusSessionAction.LABEL)); - allActions.forEach(a => toDispose.push(a)); + public static getActions(menu: IMenu, debugService: IDebugService, instantiationService: IInstantiationService): IAction[] { + const actions: IAction[] = []; + fillInActionBarActions(menu, undefined, actions, () => false); + if (debugService.getViewModel().isMultiSessionView()) { + actions.push(instantiationService.createInstance(FocusSessionAction, FocusSessionAction.ID, FocusSessionAction.LABEL)); } - const state = debugService.state; - const session = debugService.getViewModel().focusedSession; - const attached = session && session.configuration.request === 'attach' && !isExtensionHostDebugging(session.configuration); - - return allActions.filter(a => { - if (a.id === ContinueAction.ID) { - return state !== State.Running; - } - if (a.id === PauseAction.ID) { - return state === State.Running; - } - if (a.id === StepBackAction.ID) { - return session && session.capabilities.supportsStepBack; - } - if (a.id === ReverseContinueAction.ID) { - return session && session.capabilities.supportsStepBack; - } - if (a.id === DisconnectAction.ID) { - return attached; - } - if (a.id === StopAction.ID) { - return !attached; - } - if (a.id === FocusSessionAction.ID) { - return debugService.getViewModel().isMultiSessionView(); - } - - return true; - }).sort((first, second) => first.weight - second.weight); + return actions.filter(a => !(a instanceof Separator)); // do not render separators for now } public dispose(): void { diff --git a/src/vs/workbench/parts/debug/browser/debugViewlet.ts b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts similarity index 79% rename from src/vs/workbench/parts/debug/browser/debugViewlet.ts rename to src/vs/workbench/contrib/debug/browser/debugViewlet.ts index cb11dc6e7b..b31f47a638 100644 --- a/src/vs/workbench/parts/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/contrib/debug/browser/debugViewlet.ts @@ -9,9 +9,9 @@ import { IAction } from 'vs/base/common/actions'; import * as DOM from 'vs/base/browser/dom'; import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration } from 'vs/workbench/parts/debug/common/debug'; -import { StartAction, ToggleReplAction, ConfigureAction, AbstractDebugAction, SelectAndStartAction, FocusSessionAction } from 'vs/workbench/parts/debug/browser/debugActions'; -import { StartDebugActionItem, FocusSessionActionItem } from 'vs/workbench/parts/debug/browser/debugActionItems'; +import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; +import { StartAction, ToggleReplAction, ConfigureAction, SelectAndStartAction, FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions'; +import { StartDebugActionItem, FocusSessionActionItem } from 'vs/workbench/contrib/debug/browser/debugActionItems'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress'; @@ -21,12 +21,16 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { memoize } from 'vs/base/common/decorators'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { DebugToolbar } from 'vs/workbench/parts/debug/browser/debugToolbar'; +import { DebugToolbar } from 'vs/workbench/contrib/debug/browser/debugToolbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IMenu, MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { MenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export class DebugViewlet extends ViewContainerViewlet { @@ -34,10 +38,10 @@ export class DebugViewlet extends ViewContainerViewlet { private progressRunner: IProgressRunner; private breakpointView: ViewletPanel; private panelListeners = new Map(); - private allActions: AbstractDebugAction[] = []; + private debugToolbarMenu: IMenu; constructor( - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, @IProgressService private readonly progressService: IProgressService, @IDebugService private readonly debugService: IDebugService, @@ -50,10 +54,11 @@ export class DebugViewlet extends ViewContainerViewlet { @IConfigurationService configurationService: IConfigurationService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IContextViewService private readonly contextViewService: IContextViewService, + @IMenuService private readonly menuService: IMenuService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @INotificationService private readonly notificationService: INotificationService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, false, configurationService, partService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); - - this.progressRunner = null; + super(VIEWLET_ID, `${VIEWLET_ID}.state`, false, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); this._register(this.debugService.onDidChangeState(state => this.onDebugServiceStateChange(state))); this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateTitleArea())); @@ -102,7 +107,11 @@ export class DebugViewlet extends ViewContainerViewlet { return [this.startAction, this.configureAction, this.toggleReplAction]; } - return DebugToolbar.getActions(this.allActions, this.toDispose, this.debugService, this.keybindingService, this.instantiationService); + if (!this.debugToolbarMenu) { + this.debugToolbarMenu = this.menuService.createMenu(MenuId.DebugToolbar, this.contextKeyService); + this.toDispose.push(this.debugToolbarMenu); + } + return DebugToolbar.getActions(this.debugToolbarMenu, this.debugService, this.instantiationService); } get showInitialDebugActions(): boolean { @@ -118,7 +127,7 @@ export class DebugViewlet extends ViewContainerViewlet { return [this.selectAndStartAction, this.configureAction, this.toggleReplAction]; } - getActionItem(action: IAction): IActionItem { + getActionItem(action: IAction): IActionItem | null { if (action.id === StartAction.ID) { this.startDebugActionItem = this.instantiationService.createInstance(StartDebugActionItem, null, action); return this.startDebugActionItem; @@ -126,6 +135,9 @@ export class DebugViewlet extends ViewContainerViewlet { if (action.id === FocusSessionAction.ID) { return new FocusSessionActionItem(action, this.debugService, this.themeService, this.contextViewService); } + if (action instanceof MenuItemAction) { + return new MenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + } return null; } @@ -144,8 +156,6 @@ export class DebugViewlet extends ViewContainerViewlet { if (state === State.Initializing) { this.progressRunner = this.progressService.show(true); - } else { - this.progressRunner = null; } if (this.configurationService.getValue('debug').toolBarLocation === 'docked') { diff --git a/src/vs/workbench/parts/debug/browser/exceptionWidget.ts b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts similarity index 90% rename from src/vs/workbench/parts/debug/browser/exceptionWidget.ts rename to src/vs/workbench/contrib/debug/browser/exceptionWidget.ts index 999c10ab2f..90b08a3442 100644 --- a/src/vs/workbench/parts/debug/browser/exceptionWidget.ts +++ b/src/vs/workbench/contrib/debug/browser/exceptionWidget.ts @@ -3,18 +3,18 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!../browser/media/exceptionWidget'; +import 'vs/css!./media/exceptionWidget'; import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IExceptionInfo } from 'vs/workbench/parts/debug/common/debug'; +import { IExceptionInfo } from 'vs/workbench/contrib/debug/common/debug'; import { RunOnceScheduler } from 'vs/base/common/async'; import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { LinkDetector } from 'vs/workbench/parts/debug/browser/linkDetector'; +import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; const $ = dom.$; // theming @@ -24,7 +24,7 @@ export const debugExceptionWidgetBackground = registerColor('debugExceptionWidge export class ExceptionWidget extends ZoneWidget { - private _backgroundColor: Color; + private _backgroundColor?: Color; constructor(editor: ICodeEditor, private exceptionInfo: IExceptionInfo, @IThemeService themeService: IThemeService, @@ -55,7 +55,7 @@ export class ExceptionWidget extends ZoneWidget { protected _applyStyles(): void { if (this.container) { - this.container.style.backgroundColor = this._backgroundColor.toString(); + this.container.style.backgroundColor = this._backgroundColor ? this._backgroundColor.toString() : ''; } super._applyStyles(); } @@ -86,7 +86,7 @@ export class ExceptionWidget extends ZoneWidget { } } - protected _doLayout(heightInPixel: number, widthInPixel: number): void { + protected _doLayout(_heightInPixel: number | undefined, _widthInPixel: number | undefined): void { // Reload the height with respect to the exception text content and relayout it to match the line count. this.container.style.height = 'initial'; diff --git a/src/vs/workbench/parts/debug/browser/linkDetector.ts b/src/vs/workbench/contrib/debug/browser/linkDetector.ts similarity index 99% rename from src/vs/workbench/parts/debug/browser/linkDetector.ts rename to src/vs/workbench/contrib/debug/browser/linkDetector.ts index f50a02d315..a46f39169f 100644 --- a/src/vs/workbench/parts/debug/browser/linkDetector.ts +++ b/src/vs/workbench/contrib/debug/browser/linkDetector.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import strings = require('vs/base/common/strings'); -import { isAbsolute } from 'vs/base/common/paths'; +import { isAbsolute } from 'vs/base/common/path'; import { URI as uri } from 'vs/base/common/uri'; import { isMacintosh } from 'vs/base/common/platform'; import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; diff --git a/src/vs/workbench/parts/debug/browser/loadedScriptsView.ts b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts similarity index 88% rename from src/vs/workbench/parts/debug/browser/loadedScriptsView.ts rename to src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts index df3c403bd3..2e475abbe6 100644 --- a/src/vs/workbench/parts/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts @@ -6,15 +6,15 @@ import * as nls from 'vs/nls'; import * as dom from 'vs/base/browser/dom'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { normalize, isAbsolute, sep } from 'vs/base/common/paths'; +import { normalize, isAbsolute, posix } from 'vs/base/common/path'; import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { renderViewTree } from 'vs/workbench/parts/debug/browser/baseDebugView'; -import { IDebugSession, IDebugService, IDebugModel, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from 'vs/workbench/parts/debug/common/debug'; -import { Source } from 'vs/workbench/parts/debug/common/debugSource'; +import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { IDebugSession, IDebugService, IDebugModel, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from 'vs/workbench/contrib/debug/common/debug'; +import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -29,11 +29,10 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { WorkbenchAsyncDataTree, IListService, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { DebugContentProvider } from 'vs/workbench/parts/debug/browser/debugContentProvider'; +import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; import { dispose } from 'vs/base/common/lifecycle'; import { createMatches, FuzzyScore } from 'vs/base/common/filters'; +import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; const SMART = true; @@ -45,7 +44,7 @@ class BaseTreeItem { private _children: { [key: string]: BaseTreeItem; }; private _source: Source; - constructor(private _parent: BaseTreeItem, private _label: string) { + constructor(private _parent: BaseTreeItem | undefined, private _label: string) { this._children = {}; this._showedMoreThanOne = false; } @@ -54,7 +53,7 @@ class BaseTreeItem { return Object.keys(this._children).length === 0; } - getSession(): IDebugSession { + getSession(): IDebugSession | undefined { if (this._parent) { return this._parent.getSession(); } @@ -66,10 +65,12 @@ class BaseTreeItem { this._children = {}; if (source.raw && source.raw.sources) { for (const src of source.raw.sources) { - const s = new BaseTreeItem(this, src.name); - this._children[src.path] = s; - const ss = session.getSource(src); - s.setSource(session, ss); + if (src.name && src.path) { + const s = new BaseTreeItem(this, src.name); + this._children[src.path] = s; + const ss = session.getSource(src); + s.setSource(session, ss); + } } } } @@ -111,7 +112,7 @@ class BaseTreeItem { } // skips intermediate single-child nodes - getParent(): BaseTreeItem { + getParent(): BaseTreeItem | undefined { if (this._parent) { if (this._parent.isSkipped()) { return this._parent.getParent(); @@ -154,14 +155,14 @@ class BaseTreeItem { getLabel(separateRootFolder = true): string { const child = this.oneChild(); if (child) { - const sep = (this instanceof RootFolderTreeItem && separateRootFolder) ? ' • ' : '/'; + const sep = (this instanceof RootFolderTreeItem && separateRootFolder) ? ' • ' : posix.sep; return `${this._label}${sep}${child.getLabel()}`; } return this._label; } // skips intermediate single-child nodes - getHoverLabel(): string { + getHoverLabel(): string | undefined { if (this._source && this._parent && this._parent._source) { return this._source.raw.path || this._source.raw.name; } @@ -192,7 +193,7 @@ class BaseTreeItem { return 0; } - private oneChild(): BaseTreeItem { + private oneChild(): BaseTreeItem | undefined { if (SMART && !this._source && !this._showedMoreThanOne && !(this instanceof RootFolderTreeItem) && !(this instanceof SessionTreeItem)) { const keys = Object.keys(this._children); if (keys.length === 1) { @@ -251,7 +252,7 @@ class SessionTreeItem extends BaseTreeItem { return this._session; } - getHoverLabel(): string { + getHoverLabel(): string | undefined { return undefined; } @@ -300,10 +301,13 @@ class SessionTreeItem extends BaseTreeItem { addPath(source: Source): void { - let folder: IWorkspaceFolder; + let folder: IWorkspaceFolder | null; let url: string; let path = source.raw.path; + if (!path) { + return; + } const match = SessionTreeItem.URL_REGEXP.exec(path); if (match && match.length === 3) { @@ -317,17 +321,17 @@ class SessionTreeItem extends BaseTreeItem { folder = this.rootProvider ? this.rootProvider.getWorkspaceFolder(resource) : null; if (folder) { // strip off the root folder path - path = normalize(ltrim(resource.path.substr(folder.uri.path.length), sep), true); + path = normalize(ltrim(resource.path.substr(folder.uri.path.length), posix.sep)); const hasMultipleRoots = this.rootProvider.getWorkspace().folders.length > 1; if (hasMultipleRoots) { - path = '/' + path; + path = posix.sep + path; } else { // don't show root folder - folder = undefined; + folder = null; } } else { // on unix try to tildify absolute paths - path = normalize(path, true); + path = normalize(path); if (!isWindows) { path = tildify(path, this._environmentService.userHome); } @@ -338,7 +342,8 @@ class SessionTreeItem extends BaseTreeItem { let leaf: BaseTreeItem = this; path.split(/[\/\\]/).forEach((segment, i) => { if (i === 0 && folder) { - leaf = leaf.createIfNeeded(folder.name, parent => new RootFolderTreeItem(parent, folder)); + const f = folder; + leaf = leaf.createIfNeeded(folder.name, parent => new RootFolderTreeItem(parent, f)); } else if (i === 0 && url) { leaf = leaf.createIfNeeded(url, parent => new BaseTreeItem(parent, url)); } else { @@ -347,14 +352,18 @@ class SessionTreeItem extends BaseTreeItem { }); leaf.setSource(this._session, source); - this._map.set(source.raw.path, leaf); + if (source.raw.path) { + this._map.set(source.raw.path, leaf); + } } removePath(source: Source): boolean { - const leaf = this._map.get(source.raw.path); - if (leaf) { - leaf.removeFromParent(); - return true; + if (source.raw.path) { + const leaf = this._map.get(source.raw.path); + if (leaf) { + leaf.removeFromParent(); + return true; + } } return false; } @@ -377,12 +386,10 @@ export class LoadedScriptsView extends ViewletPanel { @IInstantiationService private readonly instantiationService: IInstantiationService, @IConfigurationService configurationService: IConfigurationService, @IEditorService private readonly editorService: IEditorService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IContextKeyService readonly contextKeyService: IContextKeyService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IDebugService private readonly debugService: IDebugService, - @IListService private readonly listService: IListService, - @IThemeService private readonly themeService: IThemeService ) { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService); this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService); @@ -401,22 +408,21 @@ export class LoadedScriptsView extends ViewletPanel { this.treeLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility } as IResourceLabelsContainer); this.disposables.push(this.treeLabels); - this.tree = new WorkbenchAsyncDataTree(this.treeContainer, new LoadedScriptsDelegate(), + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, this.treeContainer, new LoadedScriptsDelegate(), [new LoadedScriptsRenderer(this.treeLabels)], new LoadedScriptsDataSource(), { identityProvider: { - getId: element => element.getId() + getId: element => (element).getId() }, keyboardNavigationLabelProvider: { - getKeyboardNavigationLabel: element => element.getLabel() + getKeyboardNavigationLabel: element => (element).getLabel() }, filter: this.filter, accessibilityProvider: new LoadedSciptsAccessibilityProvider(), ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'loadedScriptsAriaLabel' }, "Debug Loaded Scripts"), - }, - this.contextKeyService, this.listService, this.themeService, this.configurationService, this.keybindingService - ); + } + ) as WorkbenchAsyncDataTree; this.tree.setInput(root); @@ -519,10 +525,7 @@ class LoadedScriptsDelegate implements IListVirtualDelegate { } getTemplateId(element: LoadedScriptsItem): string { - if (element instanceof BaseTreeItem) { - return LoadedScriptsRenderer.ID; - } - return undefined; + return LoadedScriptsRenderer.ID; } } @@ -555,9 +558,8 @@ class LoadedScriptsRenderer implements ITreeRenderer, index: number, data: ILoadedScriptsItemTemplateData): void { @@ -612,15 +614,11 @@ class LoadedSciptsAccessibilityProvider implements IAccessibilityProvider .monaco-workbench .monaco-list-row .expression { +.monaco-workbench.mac .debug-viewlet .monaco-list-row .expression, +.monaco-workbench.mac .debug-hover-widget .monaco-list-row .expression { font-size: 11px; } -.windows > .monaco-workbench .monaco-list-row .expression, -.linux > .monaco-workbench .monaco-list-row .expression { - font-size: 13px; -} - .monaco-workbench .monaco-list-row .expression .value { margin-left: 6px; } diff --git a/src/vs/workbench/parts/debug/browser/media/debugHover.css b/src/vs/workbench/contrib/debug/browser/media/debugHover.css similarity index 98% rename from src/vs/workbench/parts/debug/browser/media/debugHover.css rename to src/vs/workbench/contrib/debug/browser/media/debugHover.css index 98cb4464b8..45bc2bebef 100644 --- a/src/vs/workbench/parts/debug/browser/media/debugHover.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugHover.css @@ -56,6 +56,7 @@ white-space: pre-wrap; color: rgba(108, 108, 108, 0.8); overflow: auto; + font-family: var(--monaco-monospace-font); max-height: 500px; } diff --git a/src/vs/workbench/contrib/debug/browser/media/debugToolbar.css b/src/vs/workbench/contrib/debug/browser/media/debugToolbar.css new file mode 100644 index 0000000000..70a4dcc1c4 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/media/debugToolbar.css @@ -0,0 +1,41 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .debug-toolbar { + position: absolute; + z-index: 200; + height: 32px; + display: flex; + padding-left: 7px; +} + +.monaco-workbench .debug-toolbar .monaco-action-bar .action-item { + height: 32px; +} + +.monaco-workbench .debug-toolbar .monaco-action-bar .action-item.select-container { + margin-right: 7px; +} + +.monaco-workbench .debug-toolbar .drag-area { + cursor: -webkit-grab; + height: 32px; + width: 16px; + background: url('drag.svg') center center no-repeat; + background-size: 16px 16px; +} + +.monaco-workbench .debug-toolbar .drag-area.dragged { + cursor: -webkit-grabbing; +} + +.monaco-workbench .debug-toolbar .monaco-action-bar .action-item > .action-label { + width: 32px; + height: 32px; + margin-right: 0; + background-size: 16px; + background-position: center center; + background-repeat: no-repeat; +} diff --git a/src/vs/workbench/parts/debug/browser/media/debugViewlet.css b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css similarity index 97% rename from src/vs/workbench/parts/debug/browser/media/debugViewlet.css rename to src/vs/workbench/contrib/debug/browser/media/debugViewlet.css index dbaa0c7937..df38cbc163 100644 --- a/src/vs/workbench/parts/debug/browser/media/debugViewlet.css +++ b/src/vs/workbench/contrib/debug/browser/media/debugViewlet.css @@ -64,7 +64,7 @@ margin-top: 7px; } -.mac > .monaco-workbench .part > .title > .title-actions .start-debug-action-item { +.monaco-workbench.mac .part > .title > .title-actions .start-debug-action-item { border-radius: 4px; } @@ -296,7 +296,7 @@ } .debug-viewlet .debug-watch .monaco-inputbox { - font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); } .debug-viewlet .monaco-inputbox > .wrapper { diff --git a/src/vs/workbench/parts/debug/browser/media/disconnect-inverse.svg b/src/vs/workbench/contrib/debug/browser/media/disconnect-inverse.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/disconnect-inverse.svg rename to src/vs/workbench/contrib/debug/browser/media/disconnect-inverse.svg diff --git a/src/vs/workbench/parts/debug/browser/media/disconnect.svg b/src/vs/workbench/contrib/debug/browser/media/disconnect.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/disconnect.svg rename to src/vs/workbench/contrib/debug/browser/media/disconnect.svg diff --git a/src/vs/workbench/parts/debug/browser/media/drag.svg b/src/vs/workbench/contrib/debug/browser/media/drag.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/drag.svg rename to src/vs/workbench/contrib/debug/browser/media/drag.svg diff --git a/src/vs/workbench/parts/debug/browser/media/exceptionWidget.css b/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css similarity index 73% rename from src/vs/workbench/parts/debug/browser/media/exceptionWidget.css rename to src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css index 42f73d231f..beed8695a0 100644 --- a/src/vs/workbench/parts/debug/browser/media/exceptionWidget.css +++ b/src/vs/workbench/contrib/debug/browser/media/exceptionWidget.css @@ -19,11 +19,12 @@ .monaco-editor .zone-widget .zone-widget-container.exception-widget .description, .monaco-editor .zone-widget .zone-widget-container.exception-widget .stack-trace { - font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); } .monaco-editor .zone-widget .zone-widget-container.exception-widget .stack-trace { margin-top: 0.5em; + max-height: 500px; } .monaco-editor .zone-widget .zone-widget-container.exception-widget a { @@ -31,13 +32,11 @@ cursor: pointer; } -/* High Contrast Theming */ - -.mac > .monaco-workbench .zone-widget .zone-widget-container.exception-widget { +.monaco-workbench.mac .zone-widget .zone-widget-container.exception-widget { font-size: 11px; } -.windows > .monaco-workbench .zone-widget .zone-widget-container.exception-widget, -.linux > .monaco-workbench .zone-widget .zone-widget-container.exception-widget { +.monaco-workbench.windows .zone-widget .zone-widget-container.exception-widget, +.monaco-workbench.linux .zone-widget .zone-widget-container.exception-widget { font-size: 13px; } diff --git a/src/vs/workbench/parts/debug/browser/media/pause-inverse.svg b/src/vs/workbench/contrib/debug/browser/media/pause-inverse.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/pause-inverse.svg rename to src/vs/workbench/contrib/debug/browser/media/pause-inverse.svg diff --git a/src/vs/workbench/parts/debug/browser/media/pause.svg b/src/vs/workbench/contrib/debug/browser/media/pause.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/pause.svg rename to src/vs/workbench/contrib/debug/browser/media/pause.svg diff --git a/src/vs/workbench/parts/debug/browser/media/remove-all-inverse.svg b/src/vs/workbench/contrib/debug/browser/media/remove-all-inverse.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/remove-all-inverse.svg rename to src/vs/workbench/contrib/debug/browser/media/remove-all-inverse.svg diff --git a/src/vs/workbench/parts/debug/browser/media/remove-all.svg b/src/vs/workbench/contrib/debug/browser/media/remove-all.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/remove-all.svg rename to src/vs/workbench/contrib/debug/browser/media/remove-all.svg diff --git a/src/vs/workbench/parts/debug/browser/media/repl-inverse.svg b/src/vs/workbench/contrib/debug/browser/media/repl-inverse.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/repl-inverse.svg rename to src/vs/workbench/contrib/debug/browser/media/repl-inverse.svg diff --git a/src/vs/workbench/parts/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css similarity index 91% rename from src/vs/workbench/parts/debug/browser/media/repl.css rename to src/vs/workbench/contrib/debug/browser/media/repl.css index daecfd0ea9..38c5d28b1d 100644 --- a/src/vs/workbench/parts/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -11,23 +11,7 @@ overflow: hidden; } -.repl .surveyor { - font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback"; - position: absolute; - display: inline-block; - width : auto; - top: 0; - left: 0; - visibility: hidden; -} - -/* Align twisite to last line - for input output pair expressions. */ -.repl .repl-tree .monaco-tl-twistie { - background-position-y: calc(100% - 2px); -} - .repl .repl-tree .monaco-tl-contents { - line-height: 18px; user-select: text; /* Wrap words but also do not trim whitespace #6275 */ word-wrap: break-word; @@ -36,15 +20,11 @@ word-break: break-all; } -.mac > .monaco-workbench .repl .repl-tree .monaco-tl-twistie.collapsible + .monaco-tl-contents, -.mac > .monaco-workbench .repl .repl-tree .monaco-tl-twistie { +.monaco-workbench.mac .repl .repl-tree .monaco-tl-twistie.collapsible + .monaco-tl-contents, +.monaco-workbench.mac .repl .repl-tree .monaco-tl-twistie { cursor: pointer; } -.mac > .monaco-workbench .repl .repl-tree .input.expression, -.mac > .monaco-workbench .repl .repl-tree .output.expression { - font-size: 12px; -} .repl .repl-tree .output.expression.value-and-source { display: flex; @@ -65,13 +45,6 @@ cursor: text; } -.windows > .monaco-workbench .repl .repl-tree .monaco-list-row .input.expression, -.windows > .monaco-workbench .repl .repl-tree .monaco-list-row .output.expression, -.linux > .monaco-workbench .repl .repl-tree .monaco-list-row .input.expression, -.linux > .monaco-workbench .repl .repl-tree .monaco-list-row .output.expression { - font-size: 14px; -} - .repl .repl-tree .output.expression > .value { margin-left: 0px; } @@ -106,7 +79,7 @@ line-height: 18px; } -.linux > .monaco-workbench .repl .repl-input-wrapper:before { +.monaco-workbench.linux .repl .repl-input-wrapper:before { font-size: 9px; } @@ -151,6 +124,7 @@ /* ANSI Codes */ .monaco-workbench .repl .repl-tree .output.expression .code-bold { font-weight: bold; } +.monaco-workbench .repl .repl-tree .output.expression .code-italic { font-style: italic; } .monaco-workbench .repl .repl-tree .output.expression .code-underline { text-decoration: underline; } /* Regular and bright color codes are currently treated the same. */ diff --git a/src/vs/workbench/parts/debug/browser/media/repl.svg b/src/vs/workbench/contrib/debug/browser/media/repl.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/repl.svg rename to src/vs/workbench/contrib/debug/browser/media/repl.svg diff --git a/src/vs/workbench/parts/debug/browser/media/restart-inverse.svg b/src/vs/workbench/contrib/debug/browser/media/restart-inverse.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/restart-inverse.svg rename to src/vs/workbench/contrib/debug/browser/media/restart-inverse.svg diff --git a/src/vs/workbench/parts/debug/browser/media/restart.svg b/src/vs/workbench/contrib/debug/browser/media/restart.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/restart.svg rename to src/vs/workbench/contrib/debug/browser/media/restart.svg diff --git a/src/vs/workbench/contrib/debug/browser/media/reverse-continue-inverse.svg b/src/vs/workbench/contrib/debug/browser/media/reverse-continue-inverse.svg new file mode 100644 index 0000000000..5eb56cfd7b --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/media/reverse-continue-inverse.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/contrib/debug/browser/media/reverse-continue.svg b/src/vs/workbench/contrib/debug/browser/media/reverse-continue.svg new file mode 100644 index 0000000000..8bcbde7db0 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/media/reverse-continue.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/vs/workbench/parts/debug/browser/media/stackframe-and-breakpoint.svg b/src/vs/workbench/contrib/debug/browser/media/stackframe-and-breakpoint.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/stackframe-and-breakpoint.svg rename to src/vs/workbench/contrib/debug/browser/media/stackframe-and-breakpoint.svg diff --git a/src/vs/workbench/parts/debug/browser/media/stackframe-arrow.svg b/src/vs/workbench/contrib/debug/browser/media/stackframe-arrow.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/stackframe-arrow.svg rename to src/vs/workbench/contrib/debug/browser/media/stackframe-arrow.svg diff --git a/src/vs/workbench/parts/debug/browser/media/start-inverse.svg b/src/vs/workbench/contrib/debug/browser/media/start-inverse.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/start-inverse.svg rename to src/vs/workbench/contrib/debug/browser/media/start-inverse.svg diff --git a/src/vs/workbench/parts/debug/browser/media/start.svg b/src/vs/workbench/contrib/debug/browser/media/start.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/start.svg rename to src/vs/workbench/contrib/debug/browser/media/start.svg diff --git a/src/vs/workbench/contrib/debug/browser/media/step-back-inverse.svg b/src/vs/workbench/contrib/debug/browser/media/step-back-inverse.svg new file mode 100644 index 0000000000..7cb61c8436 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/media/step-back-inverse.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/vs/workbench/contrib/debug/browser/media/step-back.svg b/src/vs/workbench/contrib/debug/browser/media/step-back.svg new file mode 100644 index 0000000000..fffc2313a3 --- /dev/null +++ b/src/vs/workbench/contrib/debug/browser/media/step-back.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/src/vs/workbench/parts/debug/browser/media/step-into-inverse.svg b/src/vs/workbench/contrib/debug/browser/media/step-into-inverse.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/step-into-inverse.svg rename to src/vs/workbench/contrib/debug/browser/media/step-into-inverse.svg diff --git a/src/vs/workbench/parts/debug/browser/media/step-into.svg b/src/vs/workbench/contrib/debug/browser/media/step-into.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/step-into.svg rename to src/vs/workbench/contrib/debug/browser/media/step-into.svg diff --git a/src/vs/workbench/parts/debug/browser/media/step-out-inverse.svg b/src/vs/workbench/contrib/debug/browser/media/step-out-inverse.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/step-out-inverse.svg rename to src/vs/workbench/contrib/debug/browser/media/step-out-inverse.svg diff --git a/src/vs/workbench/parts/debug/browser/media/step-out.svg b/src/vs/workbench/contrib/debug/browser/media/step-out.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/step-out.svg rename to src/vs/workbench/contrib/debug/browser/media/step-out.svg diff --git a/src/vs/workbench/parts/debug/browser/media/step-over-inverse.svg b/src/vs/workbench/contrib/debug/browser/media/step-over-inverse.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/step-over-inverse.svg rename to src/vs/workbench/contrib/debug/browser/media/step-over-inverse.svg diff --git a/src/vs/workbench/parts/debug/browser/media/step-over.svg b/src/vs/workbench/contrib/debug/browser/media/step-over.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/step-over.svg rename to src/vs/workbench/contrib/debug/browser/media/step-over.svg diff --git a/src/vs/workbench/parts/debug/browser/media/stop-inverse.svg b/src/vs/workbench/contrib/debug/browser/media/stop-inverse.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/stop-inverse.svg rename to src/vs/workbench/contrib/debug/browser/media/stop-inverse.svg diff --git a/src/vs/workbench/parts/debug/browser/media/stop.svg b/src/vs/workbench/contrib/debug/browser/media/stop.svg similarity index 100% rename from src/vs/workbench/parts/debug/browser/media/stop.svg rename to src/vs/workbench/contrib/debug/browser/media/stop.svg diff --git a/src/vs/workbench/parts/debug/electron-browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts similarity index 80% rename from src/vs/workbench/parts/debug/electron-browser/repl.ts rename to src/vs/workbench/contrib/debug/browser/repl.ts index 8a5b7eef28..a87ec8f2b6 100644 --- a/src/vs/workbench/parts/debug/electron-browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -3,14 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!vs/workbench/parts/debug/browser/media/repl'; +import 'vs/css!vs/workbench/contrib/debug/browser/media/repl'; import * as nls from 'vs/nls'; import { URI as uri } from 'vs/base/common/uri'; import * as errors from 'vs/base/common/errors'; import { IAction, IActionItem, Action } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; import * as aria from 'vs/base/browser/ui/aria/aria'; -import { isMacintosh } from 'vs/base/common/platform'; import { CancellationToken } from 'vs/base/common/cancellation'; import { KeyCode } from 'vs/base/common/keyCodes'; import severity from 'vs/base/common/severity'; @@ -26,48 +25,45 @@ import { IInstantiationService, createDecorator } from 'vs/platform/instantiatio import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Panel } from 'vs/workbench/browser/panel'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { clipboard } from 'electron'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { memoize } from 'vs/base/common/decorators'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; -import { IDebugService, REPL_ID, DEBUG_SCHEME, CONTEXT_IN_DEBUG_REPL, IDebugSession, State, IReplElement, IExpressionContainer, IExpression, IReplElementSource } from 'vs/workbench/parts/debug/common/debug'; +import { IDebugService, REPL_ID, DEBUG_SCHEME, CONTEXT_IN_DEBUG_REPL, IDebugSession, State, IReplElement, IExpressionContainer, IExpression, IReplElementSource, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug'; import { HistoryNavigator } from 'vs/base/common/history'; import { IHistoryNavigationWidget } from 'vs/base/browser/history'; -import { createAndBindHistoryNavigationWidgetScopedContextKeyService } from 'vs/platform/widget/browser/contextScopedHistoryWidget'; +import { createAndBindHistoryNavigationWidgetScopedContextKeyService } from 'vs/platform/browser/contextScopedHistoryWidget'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { getSimpleCodeEditorWidgetOptions } from 'vs/workbench/parts/codeEditor/electron-browser/simpleEditorOptions'; -import { getSimpleEditorOptions } from 'vs/workbench/parts/codeEditor/browser/simpleEditorOptions'; +import { getSimpleEditorOptions, getSimpleCodeEditorWidgetOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions'; import { IDecorationOptions } from 'vs/editor/common/editorCommon'; import { transparent, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { FocusSessionActionItem } from 'vs/workbench/parts/debug/browser/debugActionItems'; +import { FocusSessionActionItem } from 'vs/workbench/contrib/debug/browser/debugActionItems'; import { CompletionContext, CompletionList, CompletionProviderRegistry } from 'vs/editor/common/modes'; import { first } from 'vs/base/common/arrays'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { Variable, Expression, SimpleReplElement, RawObjectReplElement } from 'vs/workbench/parts/debug/common/debugModel'; +import { Variable, Expression, SimpleReplElement, RawObjectReplElement } from 'vs/workbench/contrib/debug/common/debugModel'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { VariablesRenderer } from 'vs/workbench/parts/debug/electron-browser/variablesView'; import { ITreeRenderer, ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { renderExpressionValue } from 'vs/workbench/parts/debug/browser/baseDebugView'; -import { handleANSIOutput } from 'vs/workbench/parts/debug/browser/debugANSIHandling'; +import { renderExpressionValue } from 'vs/workbench/contrib/debug/browser/baseDebugView'; +import { handleANSIOutput } from 'vs/workbench/contrib/debug/browser/debugANSIHandling'; import { ILabelService } from 'vs/platform/label/common/label'; -import { LinkDetector } from 'vs/workbench/parts/debug/browser/linkDetector'; -import { CopyAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions'; -import { ReplCollapseAllAction } from 'vs/workbench/parts/debug/browser/debugActions'; +import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; +import { ReplCollapseAllAction, CopyAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { removeAnsiEscapeCodes, isFullWidthCharacter, endsWith } from 'vs/base/common/strings'; -import { WorkbenchAsyncDataTree, IListService } from 'vs/platform/list/browser/listService'; +import { removeAnsiEscapeCodes } from 'vs/base/common/strings'; +import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; const $ = dom.$; @@ -91,7 +87,6 @@ const sessionsToIgnore = new Set(); export class Repl extends Panel implements IPrivateReplService, IHistoryNavigationWidget { _serviceBrand: any; - private static readonly HALF_WIDTH_TYPICAL = 'n'; private static readonly REFRESH_DELAY = 100; // delay in ms to refresh the repl for new elements to show private static readonly REPL_INPUT_INITIAL_HEIGHT = 19; private static readonly REPL_INPUT_MAX_HEIGHT = 170; @@ -100,7 +95,6 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati private tree: WorkbenchAsyncDataTree; private replDelegate: ReplDelegate; private container: HTMLElement; - private treeContainer: HTMLElement; private replInput: CodeEditorWidget; private replInputContainer: HTMLElement; private dimension: dom.Dimension; @@ -109,6 +103,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati private historyNavigationEnablement: IContextKey; private scopedInstantiationService: IInstantiationService; private replElementsChangeListener: IDisposable; + private styleElement: HTMLStyleElement; constructor( @IDebugService private readonly debugService: IDebugService, @@ -120,10 +115,9 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati @IContextKeyService private readonly contextKeyService: IContextKeyService, @ICodeEditorService codeEditorService: ICodeEditorService, @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IListService private readonly listService: IListService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService, - @IKeybindingService private readonly keybindingService: IKeybindingService + @IClipboardService private readonly clipboardService: IClipboardService ) { super(REPL_ID, telemetryService, themeService, storageService); @@ -135,7 +129,9 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati private registerListeners(): void { this._register(this.debugService.getViewModel().onDidFocusSession(session => { - sessionsToIgnore.delete(session); + if (session) { + sessionsToIgnore.delete(session); + } this.selectSession(); })); this._register(this.debugService.onWillNewSession(() => { @@ -161,6 +157,11 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.refreshReplElements(true); } })); + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('debug.console.lineHeight') || e.affectsConfiguration('debug.console.fontSize') || e.affectsConfiguration('debug.console.fontFamily')) { + this.onDidFontChange(); + } + })); } get isReadonly(): boolean { @@ -181,6 +182,33 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.navigateHistory(false); } + private onDidFontChange(): void { + if (this.styleElement) { + const debugConsole = this.configurationService.getValue('debug').console; + const fontSize = debugConsole.fontSize; + const fontFamily = debugConsole.fontFamily === 'default' ? 'var(--monaco-monospace-font)' : debugConsole.fontFamily; + const lineHeight = debugConsole.lineHeight ? `${debugConsole.lineHeight}px` : '1.4em'; + + // Set the font size, font family, line height and align the twistie to be centered + this.styleElement.innerHTML = ` + .repl .repl-tree .expression { + font-size: ${fontSize}px; + font-family: ${fontFamily}; + } + + .repl .repl-tree .expression { + line-height: ${lineHeight}; + } + + .repl .repl-tree .monaco-tl-twistie { + background-position-y: calc(100% - ${fontSize * 1.4 / 2 - 8}px); + } + `; + + this.tree.rerender(); + } + } + private navigateHistory(previous: boolean): void { const historyInput = previous ? this.history.previous() : this.history.next(); if (historyInput) { @@ -193,13 +221,14 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati } selectSession(session?: IDebugSession): void { + const treeInput = this.tree.getInput(); if (!session) { const focusedSession = this.debugService.getViewModel().focusedSession; // If there is a focusedSession focus on that one, otherwise just show any other not ignored session if (focusedSession) { session = focusedSession; - } else if (!this.tree.getInput() || sessionsToIgnore.has(this.tree.getInput())) { - session = first(this.debugService.getModel().getSessions(true), s => !sessionsToIgnore.has(s)); + } else if (!treeInput || sessionsToIgnore.has(treeInput)) { + session = first(this.debugService.getModel().getSessions(true), s => !sessionsToIgnore.has(s)) || undefined; } } if (session) { @@ -207,10 +236,10 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.replElementsChangeListener.dispose(); } this.replElementsChangeListener = session.onDidChangeReplElements(() => { - this.refreshReplElements(session.getReplElements().length === 0); + this.refreshReplElements(session!.getReplElements().length === 0); }); - if (this.tree && this.tree.getInput() !== session) { + if (this.tree && treeInput !== session) { this.tree.setInput(session).then(() => revealLastElement(this.tree)).then(undefined, errors.onUnexpectedError); } } @@ -268,9 +297,8 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati layout(dimension: dom.Dimension): void { this.dimension = dimension; if (this.tree) { - this.replDelegate.setWidth(dimension.width - 25, this.characterWidth); const treeHeight = dimension.height - this.replInputHeight; - this.treeContainer.style.height = `${treeHeight}px`; + this.tree.getHTMLElement().style.height = `${treeHeight}px`; this.tree.layout(treeHeight, dimension.width); } this.replInputContainer.style.height = `${this.replInputHeight}px`; @@ -282,12 +310,12 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.replInput.focus(); } - getActionItem(action: IAction): IActionItem { + getActionItem(action: IAction): IActionItem | null { if (action.id === SelectReplAction.ID) { return this.instantiationService.createInstance(SelectReplActionItem, this.selectReplAction); } - return undefined; + return null; } getActions(): IAction[] { @@ -303,18 +331,6 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati } // --- Cached locals - @memoize - private get characterWidth(): number { - const characterWidthSurveyor = dom.append(this.container, $('.surveyor')); - characterWidthSurveyor.textContent = Repl.HALF_WIDTH_TYPICAL; - for (let i = 0; i < 10; i++) { - characterWidthSurveyor.textContent += characterWidthSurveyor.textContent; - } - characterWidthSurveyor.style.fontSize = isMacintosh ? '12px' : '14px'; - - return characterWidthSurveyor.clientWidth / characterWidthSurveyor.textContent.length; - } - @memoize private get selectReplAction(): SelectReplAction { return this.scopedInstantiationService.createInstance(SelectReplAction, SelectReplAction.ID, SelectReplAction.LABEL); @@ -346,11 +362,11 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati create(parent: HTMLElement): void { super.create(parent); this.container = dom.append(parent, $('.repl')); - this.treeContainer = dom.append(this.container, $('.repl-tree')); + const treeContainer = dom.append(this.container, $('.repl-tree')); this.createReplInput(this.container); - this.replDelegate = new ReplDelegate(); - this.tree = new WorkbenchAsyncDataTree(this.treeContainer, this.replDelegate, [ + this.replDelegate = new ReplDelegate(this.configurationService); + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, treeContainer, this.replDelegate, [ this.instantiationService.createInstance(VariablesRenderer), this.instantiationService.createInstance(ReplSimpleElementsRenderer), new ReplExpressionsRenderer(), @@ -358,15 +374,19 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati ], new ReplDataSource(), { ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel"), accessibilityProvider: new ReplAccessibilityProvider(), - identityProvider: { getId: element => element.getId() }, + identityProvider: { getId: element => (element).getId() }, mouseSupport: false, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: e => e }, - horizontalScrolling: false - }, this.contextKeyService, this.listService, this.themeService, this.configurationService, this.keybindingService); + horizontalScrolling: false, + setRowLineHeight: false, + supportDynamicHeights: true + }) as WorkbenchAsyncDataTree; this.toDispose.push(this.tree.onContextMenu(e => this.onContextMenu(e))); // Make sure to select the session if debugging is already active this.selectSession(); + this.styleElement = dom.createStyleSheet(this.container); + this.onDidFontChange(); } private createReplInput(container: HTMLElement): void { @@ -392,17 +412,20 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati const focusedSession = this.debugService.getViewModel().focusedSession; if (focusedSession && focusedSession.capabilities.supportsCompletionsRequest) { - const word = this.replInput.getModel().getWordAtPosition(position); - const overwriteBefore = word ? word.word.length : 0; - const text = this.replInput.getModel().getLineContent(position.lineNumber); - const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; - const frameId = focusedStackFrame ? focusedStackFrame.frameId : undefined; + const model = this.replInput.getModel(); + if (model) { + const word = model.getWordAtPosition(position); + const overwriteBefore = word ? word.word.length : 0; + const text = model.getLineContent(position.lineNumber); + const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame; + const frameId = focusedStackFrame ? focusedStackFrame.frameId : undefined; - return focusedSession.completions(frameId, text, position, overwriteBefore).then(suggestions => { - return { suggestions }; - }, err => { - return { suggestions: [] }; - }); + return focusedSession.completions(frameId, text, position, overwriteBefore).then(suggestions => { + return { suggestions }; + }, err => { + return { suggestions: [] }; + }); + } } return Promise.resolve({ suggestions: [] }); } @@ -416,7 +439,8 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati this.layout(this.dimension); })); this._register(this.replInput.onDidChangeModelContent(() => { - this.historyNavigationEnablement.set(this.replInput.getModel().getValue() === ''); + const model = this.replInput.getModel(); + this.historyNavigationEnablement.set(!!model && model.getValue() === ''); })); // We add the input decoration only when the focus is in the input #61126 this._register(this.replInput.onDidFocusEditorText(() => this.updateInputDecoration())); @@ -428,9 +452,9 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati private onContextMenu(e: ITreeContextMenuEvent): void { const actions: IAction[] = []; - actions.push(new CopyAction(CopyAction.ID, CopyAction.LABEL)); + actions.push(new CopyAction(CopyAction.ID, CopyAction.LABEL, this.clipboardService)); actions.push(new Action('workbench.debug.action.copyAll', nls.localize('copyAll', "Copy All"), undefined, true, () => { - clipboard.writeText(this.getVisibleContent()); + this.clipboardService.writeText(this.getVisibleContent()); return Promise.resolve(undefined); })); actions.push(new ReplCollapseAllAction(this.tree, this.replInput)); @@ -463,6 +487,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati const decorations: IDecorationOptions[] = []; if (this.isReadonly && this.replInput.hasTextFocus() && !this.replInput.getValue()) { + const transparentForeground = transparent(editorForeground, 0.4)(this.themeService.getTheme()); decorations.push({ range: { startLineNumber: 0, @@ -473,7 +498,7 @@ export class Repl extends Panel implements IPrivateReplService, IHistoryNavigati renderOptions: { after: { contentText: nls.localize('startDebugFirst', "Please start a debug session to evaluate expressions"), - color: transparent(editorForeground, 0.4)(this.themeService.getTheme()).toString() + color: transparentForeground ? transparentForeground.toString() : undefined } } }); @@ -516,9 +541,8 @@ interface ISimpleReplElementTemplateData { container: HTMLElement; value: HTMLElement; source: HTMLElement; - getReplElementSource(): IReplElementSource; + getReplElementSource(): IReplElementSource | undefined; toDispose: IDisposable[]; - label: HighlightedLabel; } interface IRawObjectReplTemplateData { @@ -538,15 +562,14 @@ class ReplExpressionsRenderer implements ITreeRenderer, index: number, templateData: IExpressionTemplateData): void { @@ -639,17 +662,15 @@ class ReplRawObjectsRenderer implements ITreeRenderer, index: number, templateData: IRawObjectReplTemplateData): void { @@ -685,25 +706,16 @@ class ReplRawObjectsRenderer implements ITreeRenderer { - private static readonly LINE_HEIGHT_PX = 18; - - private width: number; - private characterWidth: number; + constructor(private configurationService: IConfigurationService) { } getHeight(element: IReplElement): number { - if (element instanceof Variable && (element.hasChildren || (element.name !== null))) { - return ReplDelegate.LINE_HEIGHT_PX; - } + // Give approximate heights. Repl has dynamic height so the tree will measure the actual height on its own. + const fontSize = this.configurationService.getValue('debug').console.fontSize; if (element instanceof Expression && element.hasChildren) { - return 2 * ReplDelegate.LINE_HEIGHT_PX; + return Math.ceil(2 * 1.4 * fontSize); } - let availableWidth = this.width; - if (element instanceof SimpleReplElement && element.sourceData) { - availableWidth -= Math.ceil(`${element.sourceData.source.name}:${element.sourceData.lineNumber}`.length * this.characterWidth + 12); - } - - return this.getHeightForString((element).value, availableWidth) + (element instanceof Expression ? this.getHeightForString(element.name, availableWidth) : 0); + return Math.ceil(1.4 * fontSize); } getTemplateId(element: IReplElement): string { @@ -717,43 +729,12 @@ class ReplDelegate implements IListVirtualDelegate { // Variable with no name is a top level variable which should be rendered like a repl element #17404 return ReplSimpleElementsRenderer.ID; } - if (element instanceof RawObjectReplElement) { - return ReplRawObjectsRenderer.ID; - } - return undefined; + return ReplRawObjectsRenderer.ID; } hasDynamicHeight?(element: IReplElement): boolean { - // todo@isidor - return false; - } - - private getHeightForString(s: string, availableWidth: number): number { - if (!s || !s.length || !availableWidth || availableWidth <= 0 || !this.characterWidth || this.characterWidth <= 0) { - return ReplDelegate.LINE_HEIGHT_PX; - } - - // Last new line should be ignored since the repl elements are by design split by rows - if (endsWith(s, '\n')) { - s = s.substr(0, s.length - 1); - } - const lines = removeAnsiEscapeCodes(s).split('\n'); - const numLines = lines.reduce((lineCount: number, line: string) => { - let lineLength = 0; - for (let i = 0; i < line.length; i++) { - lineLength += isFullWidthCharacter(line.charCodeAt(i)) ? 2 : 1; - } - - return lineCount + Math.floor(lineLength * this.characterWidth / availableWidth); - }, lines.length); - - return ReplDelegate.LINE_HEIGHT_PX * numLines; - } - - setWidth(fullWidth: number, characterWidth: number): void { - this.width = fullWidth; - this.characterWidth = characterWidth; + return true; } } @@ -798,7 +779,7 @@ class ReplAccessibilityProvider implements IAccessibilityProvider return nls.localize('replRawObjectAriaLabel', "Repl variable {0} has value {1}, read eval print loop, debug", element.name, element.value); } - return null; + return ''; } } @@ -839,7 +820,8 @@ class ReplCopyAllAction extends EditorAction { } run(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise { - clipboard.writeText(accessor.get(IPrivateReplService).getVisibleContent()); + const clipboardService = accessor.get(IClipboardService); + clipboardService.writeText(accessor.get(IPrivateReplService).getVisibleContent()); } } diff --git a/src/vs/workbench/parts/debug/browser/statusbarColorProvider.ts b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts similarity index 93% rename from src/vs/workbench/parts/debug/browser/statusbarColorProvider.ts rename to src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts index d8644690dc..79cbd0b3d6 100644 --- a/src/vs/workbench/parts/debug/browser/statusbarColorProvider.ts +++ b/src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts @@ -7,8 +7,8 @@ import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } import { localize } from 'vs/nls'; import { registerColor, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; -import { IDebugService, State } from 'vs/workbench/parts/debug/common/debug'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { IDebugService, State } from 'vs/workbench/contrib/debug/common/debug'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_BACKGROUND, Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_BORDER } from 'vs/workbench/common/theme'; import { addClass, removeClass, createStyleSheet } from 'vs/base/browser/dom'; @@ -40,7 +40,7 @@ export class StatusBarColorProvider extends Themable implements IWorkbenchContri @IThemeService themeService: IThemeService, @IDebugService private readonly debugService: IDebugService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IPartService private readonly partService: IPartService + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { super(themeService); @@ -56,7 +56,7 @@ export class StatusBarColorProvider extends Themable implements IWorkbenchContri protected updateStyles(): void { super.updateStyles(); - const container = this.partService.getContainer(Parts.STATUSBAR_PART); + const container = this.layoutService.getContainer(Parts.STATUSBAR_PART); if (isStatusbarInDebugMode(this.debugService)) { addClass(container, 'debugging'); } else { diff --git a/src/vs/workbench/parts/debug/electron-browser/variablesView.ts b/src/vs/workbench/contrib/debug/browser/variablesView.ts similarity index 80% rename from src/vs/workbench/parts/debug/electron-browser/variablesView.ts rename to src/vs/workbench/contrib/debug/browser/variablesView.ts index bed694f6ed..15bd550a03 100644 --- a/src/vs/workbench/parts/debug/electron-browser/variablesView.ts +++ b/src/vs/workbench/contrib/debug/browser/variablesView.ts @@ -6,16 +6,15 @@ import * as nls from 'vs/nls'; import { RunOnceScheduler } from 'vs/base/common/async'; import * as dom from 'vs/base/browser/dom'; -import { CollapseAction2 } from 'vs/workbench/browser/viewlet'; +import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IViewModel } from 'vs/workbench/parts/debug/common/debug'; -import { Variable, Scope } from 'vs/workbench/parts/debug/common/debugModel'; +import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IViewModel } from 'vs/workbench/contrib/debug/common/debug'; +import { Variable, Scope } from 'vs/workbench/contrib/debug/common/debugModel'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/parts/debug/browser/baseDebugView'; +import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IAction } from 'vs/base/common/actions'; -import { SetValueAction, AddToWatchExpressionsAction } from 'vs/workbench/parts/debug/browser/debugActions'; -import { CopyValueAction, CopyEvaluatePathAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions'; +import { SetValueAction, AddToWatchExpressionsAction, CopyValueAction, CopyEvaluatePathAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; @@ -24,9 +23,7 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { ITreeRenderer, ITreeNode, ITreeMouseEvent, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Emitter } from 'vs/base/common/event'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { WorkbenchAsyncDataTree, IListService } from 'vs/platform/list/browser/listService'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { onUnexpectedError } from 'vs/base/common/errors'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; @@ -39,7 +36,7 @@ export class VariablesView extends ViewletPanel { private onFocusStackFrameScheduler: RunOnceScheduler; private needsRefresh: boolean; - private tree: WorkbenchAsyncDataTree; + private tree: WorkbenchAsyncDataTree; constructor( options: IViewletViewOptions, @@ -48,9 +45,6 @@ export class VariablesView extends ViewletPanel { @IKeybindingService keybindingService: IKeybindingService, @IConfigurationService configurationService: IConfigurationService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IListService private readonly listService: IListService, - @IThemeService private readonly themeService: IThemeService ) { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService); @@ -75,20 +69,20 @@ export class VariablesView extends ViewletPanel { dom.addClass(container, 'debug-variables'); const treeContainer = renderViewTree(container); - this.tree = new WorkbenchAsyncDataTree(treeContainer, new VariablesDelegate(), + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, treeContainer, new VariablesDelegate(), [this.instantiationService.createInstance(VariablesRenderer), new ScopesRenderer()], new VariablesDataSource(), { ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"), accessibilityProvider: new VariablesAccessibilityProvider(), - identityProvider: { getId: element => element.getId() }, + identityProvider: { getId: element => (element).getId() }, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: e => e } - }, this.contextKeyService, this.listService, this.themeService, this.configurationService, this.keybindingService); + }) as WorkbenchAsyncDataTree; this.tree.setInput(this.debugService.getViewModel()).then(null, onUnexpectedError); - CONTEXT_VARIABLES_FOCUSED.bindTo(this.contextKeyService.createScoped(treeContainer)); + CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService); - const collapseAction = new CollapseAction2(this.tree, true, 'explorer-action collapse-explorer'); + const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer'); this.toolbar.setActions([collapseAction])(); this.tree.updateChildren(); @@ -114,7 +108,7 @@ export class VariablesView extends ViewletPanel { })); this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(e => { if (e instanceof Variable) { - this.tree.refresh(e); + this.tree.rerender(e); } })); } @@ -129,7 +123,7 @@ export class VariablesView extends ViewletPanel { private onMouseDblClick(e: ITreeMouseEvent): void { const session = this.debugService.getViewModel().focusedSession; - if (e.element instanceof Variable && session.capabilities.supportsSetVariable) { + if (session && e.element instanceof Variable && session.capabilities.supportsSetVariable) { this.debugService.getViewModel().setSelectedExpression(e.element); } } @@ -138,10 +132,10 @@ export class VariablesView extends ViewletPanel { const element = e.element; if (element instanceof Variable && !!element.value) { const actions: IAction[] = []; - const variable = element; + const variable = element as Variable; actions.push(new SetValueAction(SetValueAction.ID, SetValueAction.LABEL, variable, this.debugService, this.keybindingService)); - actions.push(new CopyValueAction(CopyValueAction.ID, CopyValueAction.LABEL, variable, this.debugService)); - actions.push(new CopyEvaluatePathAction(CopyEvaluatePathAction.ID, CopyEvaluatePathAction.LABEL, variable)); + actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'variables')); + actions.push(this.instantiationService.createInstance(CopyEvaluatePathAction, CopyEvaluatePathAction.ID, CopyEvaluatePathAction.LABEL, variable)); actions.push(new Separator()); actions.push(new AddToWatchExpressionsAction(AddToWatchExpressionsAction.ID, AddToWatchExpressionsAction.LABEL, variable, this.debugService, this.keybindingService)); @@ -193,11 +187,8 @@ class VariablesDelegate implements IListVirtualDelegate { if (element instanceof Scope) { return ScopesRenderer.ID; } - if (element instanceof Variable) { - return VariablesRenderer.ID; - } - return null; + return VariablesRenderer.ID; } } @@ -210,11 +201,10 @@ class ScopesRenderer implements ITreeRenderer, index: number, templateData: IScopeTemplateData): void { @@ -247,7 +237,7 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { validation: () => variable.errorMessage ? ({ content: variable.errorMessage }) : null }, onFinish: (value: string, success: boolean) => { - variable.errorMessage = null; + variable.errorMessage = undefined; if (success && variable.value !== value) { variable.setVariable(value) // Need to force watch expressions and variables to update since a variable change can have an effect on both @@ -259,7 +249,7 @@ export class VariablesRenderer extends AbstractExpressionsRenderer { } class VariablesAccessibilityProvider implements IAccessibilityProvider { - getAriaLabel(element: IExpression | IScope): string { + getAriaLabel(element: IExpression | IScope): string | null { if (element instanceof Scope) { return nls.localize('variableScopeAriaLabel', "Scope {0}, variables, debug", element.name); } diff --git a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts similarity index 82% rename from src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts rename to src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts index 5369a51591..db9266c0e5 100644 --- a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts +++ b/src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts @@ -6,32 +6,29 @@ import * as nls from 'vs/nls'; import { RunOnceScheduler } from 'vs/base/common/async'; import * as dom from 'vs/base/browser/dom'; -import { CollapseAction2 } from 'vs/workbench/browser/viewlet'; +import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IDebugService, IExpression, CONTEXT_WATCH_EXPRESSIONS_FOCUSED } from 'vs/workbench/parts/debug/common/debug'; -import { Expression, Variable } from 'vs/workbench/parts/debug/common/debugModel'; -import { AddWatchExpressionAction, RemoveAllWatchExpressionsAction, EditWatchExpressionAction, RemoveWatchExpressionAction } from 'vs/workbench/parts/debug/browser/debugActions'; +import { IDebugService, IExpression, CONTEXT_WATCH_EXPRESSIONS_FOCUSED } from 'vs/workbench/contrib/debug/common/debug'; +import { Expression, Variable } from 'vs/workbench/contrib/debug/common/debugModel'; +import { AddWatchExpressionAction, RemoveAllWatchExpressionsAction, EditWatchExpressionAction, RemoveWatchExpressionAction, CopyValueAction } from 'vs/workbench/contrib/debug/browser/debugActions'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IAction } from 'vs/base/common/actions'; -import { CopyValueAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/parts/debug/browser/baseDebugView'; +import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { VariablesRenderer, variableSetEmitter } from 'vs/workbench/parts/debug/electron-browser/variablesView'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { WorkbenchAsyncDataTree, IListService } from 'vs/platform/list/browser/listService'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; import { IAsyncDataSource, ITreeMouseEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction } from 'vs/base/browser/ui/tree/tree'; import { IDragAndDropData } from 'vs/base/browser/dnd'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { FuzzyScore } from 'vs/base/common/filters'; import { IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; +import { variableSetEmitter, VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView'; const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024; @@ -39,7 +36,7 @@ export class WatchExpressionsView extends ViewletPanel { private onWatchExpressionsUpdatedScheduler: RunOnceScheduler; private needsRefresh: boolean; - private tree: WorkbenchAsyncDataTree; + private tree: WorkbenchAsyncDataTree; constructor( options: IViewletViewOptions, @@ -48,9 +45,6 @@ export class WatchExpressionsView extends ViewletPanel { @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IConfigurationService configurationService: IConfigurationService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - @IListService private readonly listService: IListService, - @IThemeService private readonly themeService: IThemeService ) { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService); @@ -63,22 +57,22 @@ export class WatchExpressionsView extends ViewletPanel { renderBody(container: HTMLElement): void { dom.addClass(container, 'debug-watch'); const treeContainer = renderViewTree(container); - CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.contextKeyService.createScoped(treeContainer)); const expressionsRenderer = this.instantiationService.createInstance(WatchExpressionsRenderer); - this.tree = new WorkbenchAsyncDataTree(treeContainer, new WatchExpressionsDelegate(), [expressionsRenderer, this.instantiationService.createInstance(VariablesRenderer)], + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, treeContainer, new WatchExpressionsDelegate(), [expressionsRenderer, this.instantiationService.createInstance(VariablesRenderer)], new WatchExpressionsDataSource(), { ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"), accessibilityProvider: new WatchExpressionsAccessibilityProvider(), - identityProvider: { getId: element => element.getId() }, + identityProvider: { getId: element => (element).getId() }, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: e => e }, dnd: new WatchExpressionsDragAndDrop(this.debugService), - }, this.contextKeyService, this.listService, this.themeService, this.configurationService, this.keybindingService); + }) as WorkbenchAsyncDataTree; this.tree.setInput(this.debugService).then(undefined, onUnexpectedError); + CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.tree.contextKeyService); const addWatchExpressionAction = new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService); - const collapseAction = new CollapseAction2(this.tree, true, 'explorer-action collapse-explorer'); + const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer'); const removeAllWatchExpressionsAction = new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService); this.toolbar.setActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction])(); @@ -110,7 +104,7 @@ export class WatchExpressionsView extends ViewletPanel { })); this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(e => { if (e instanceof Expression && e.name) { - this.tree.refresh(e); + this.tree.rerender(e); } })); } @@ -141,6 +135,10 @@ export class WatchExpressionsView extends ViewletPanel { private onContextMenu(e: ITreeContextMenuEvent): void { const element = e.element; + const anchor = e.anchor; + if (!anchor) { + return; + } const actions: IAction[] = []; if (element instanceof Expression) { @@ -148,7 +146,7 @@ export class WatchExpressionsView extends ViewletPanel { actions.push(new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService)); actions.push(new EditWatchExpressionAction(EditWatchExpressionAction.ID, EditWatchExpressionAction.LABEL, this.debugService, this.keybindingService)); if (!expression.hasChildren) { - actions.push(new CopyValueAction(CopyValueAction.ID, CopyValueAction.LABEL, expression.value, this.debugService)); + actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value, 'watch', this.debugService)); } actions.push(new Separator()); @@ -157,9 +155,9 @@ export class WatchExpressionsView extends ViewletPanel { } else { actions.push(new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService)); if (element instanceof Variable) { - const variable = element; + const variable = element as Variable; if (!variable.hasChildren) { - actions.push(new CopyValueAction(CopyValueAction.ID, CopyValueAction.LABEL, variable, this.debugService)); + actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'watch', this.debugService)); } actions.push(new Separator()); } @@ -167,7 +165,7 @@ export class WatchExpressionsView extends ViewletPanel { } this.contextMenuService.showContextMenu({ - getAnchor: () => e.anchor, + getAnchor: () => anchor, getActions: () => actions, getActionsContext: () => element }); @@ -184,11 +182,9 @@ class WatchExpressionsDelegate implements IListVirtualDelegate { if (element instanceof Expression) { return WatchExpressionsRenderer.ID; } - if (element instanceof Variable) { - return VariablesRenderer.ID; - } - return undefined; + // Variable + return VariablesRenderer.ID; } } @@ -198,7 +194,7 @@ function isDebugService(element: any): element is IDebugService { class WatchExpressionsDataSource implements IAsyncDataSource { - hasChildren(element: IExpression | null): boolean { + hasChildren(element: IExpression | IDebugService): boolean { return isDebugService(element) || element.hasChildren; } @@ -208,7 +204,7 @@ class WatchExpressionsDataSource implements IAsyncDataSource !!we.name - ? we.evaluate(viewModel.focusedSession, viewModel.focusedStackFrame, 'watch').then(() => we) + ? we.evaluate(viewModel.focusedSession!, viewModel.focusedStackFrame!, 'watch').then(() => we) : Promise.resolve(we))); } @@ -258,11 +254,9 @@ class WatchExpressionsAccessibilityProvider implements IAccessibilityProviderelement).name, (element).value); } - if (element instanceof Variable) { - return nls.localize('watchVariableAriaLabel', "{0} value {1}, watch, debug", (element).name, (element).value); - } - return null; + // Variable + return nls.localize('watchVariableAriaLabel', "{0} value {1}, watch, debug", (element).name, (element).value); } } diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts similarity index 84% rename from src/vs/workbench/parts/debug/common/debug.ts rename to src/vs/workbench/contrib/debug/common/debug.ts index b40842d1b3..4ece65c5d6 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -14,7 +14,7 @@ import { ITextModel as EditorIModel } from 'vs/editor/common/model'; import { IEditor } from 'vs/workbench/common/editor'; import { Position } from 'vs/editor/common/core/position'; import { CompletionItem } from 'vs/editor/common/modes'; -import { Source } from 'vs/workbench/parts/debug/common/debugSource'; +import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { Range, IRange } from 'vs/editor/common/core/range'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -22,9 +22,9 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IDisposable } from 'vs/base/common/lifecycle'; import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; import { Registry } from 'vs/platform/registry/common/platform'; -import { TaskIdentifier } from 'vs/workbench/parts/tasks/common/tasks'; +import { TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import { IOutputService } from 'vs/workbench/parts/output/common/output'; +import { IOutputService } from 'vs/workbench/contrib/output/common/output'; export const VIEWLET_ID = 'workbench.view.debug'; export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); @@ -51,6 +51,8 @@ export const CONTEXT_BREAKPOINT_SELECTED = new RawContextKey('breakpoin export const CONTEXT_CALLSTACK_ITEM_TYPE = new RawContextKey('callStackItemType', undefined); export const CONTEXT_LOADED_SCRIPTS_SUPPORTED = new RawContextKey('loadedScriptsSupported', false); export const CONTEXT_LOADED_SCRIPTS_ITEM_TYPE = new RawContextKey('loadedScriptsItemType', undefined); +export const CONTEXT_FOCUSED_SESSION_IS_ATTACH = new RawContextKey('focusedSessionIsAttach', false); +export const CONTEXT_STEP_BACK_SUPPORTED = new RawContextKey('stepBackSupported', false); export const EDITOR_CONTRIBUTION_ID = 'editor.contrib.debug'; export const DEBUG_SCHEME = 'debug'; @@ -63,15 +65,13 @@ export const INTERNAL_CONSOLE_OPTIONS_SCHEMA = { // raw export interface IRawModelUpdate { - threadId: number; sessionId: string; - thread?: DebugProtocol.Thread; - callStack?: DebugProtocol.StackFrame[]; + threads: DebugProtocol.Thread[]; stoppedDetails?: IRawStoppedDetails; } export interface IRawStoppedDetails { - reason: string; + reason?: string; description?: string; threadId?: number; text?: string; @@ -105,14 +105,14 @@ export interface IExpressionContainer extends ITreeElement { export interface IExpression extends IReplElement, IExpressionContainer { name: string; readonly value: string; - readonly valueChanged?: boolean; + valueChanged?: boolean; readonly type?: string; } export interface IDebugger { createDebugAdapter(session: IDebugSession, outputService: IOutputService): Promise; runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise; - getCustomTelemetryService(): Promise; + getCustomTelemetryService(): Promise; } export const enum State { @@ -145,26 +145,26 @@ export interface LoadedSourceEvent { export interface IDebugSession extends ITreeElement { readonly configuration: IConfig; - readonly unresolvedConfiguration: IConfig; + readonly unresolvedConfiguration: IConfig | undefined; readonly state: State; readonly root: IWorkspaceFolder; getLabel(): string; - getSourceForUri(modelUri: uri): Source; - getSource(raw: DebugProtocol.Source): Source; + getSourceForUri(modelUri: uri): Source | undefined; + getSource(raw?: DebugProtocol.Source): Source; - setConfiguration(configuration: { resolved: IConfig, unresolved: IConfig }): void; + setConfiguration(configuration: { resolved: IConfig, unresolved: IConfig | undefined }): void; rawUpdate(data: IRawModelUpdate): void; - getThread(threadId: number): IThread; + getThread(threadId: number): IThread | undefined; getAllThreads(): IThread[]; clearThreads(removeThreads: boolean, reference?: number): void; getReplElements(): IReplElement[]; removeReplExpressions(): void; - addReplExpression(stackFrame: IStackFrame, name: string): Promise; + addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise; appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void; logToRepl(sev: severity, args: any[], frame?: { uri: uri, line: number, column: number }); @@ -197,9 +197,9 @@ export interface IDebugSession extends ITreeElement { sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise; stackTrace(threadId: number, startFrame: number, levels: number): Promise; - exceptionInfo(threadId: number): Promise; + exceptionInfo(threadId: number): Promise; scopes(frameId: number): Promise; - variables(variablesReference: number, filter: 'indexed' | 'named', start: number, count: number): Promise; + variables(variablesReference: number, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise; evaluate(expression: string, frameId?: number, context?: string): Promise; customRequest(request: string, args: any): Promise; @@ -213,8 +213,8 @@ export interface IDebugSession extends ITreeElement { pause(threadId: number): Promise; terminateThreads(threadIds: number[]): Promise; - completions(frameId: number, text: string, position: Position, overwriteBefore: number): Promise; - setVariable(variablesReference: number, name: string, value: string): Promise; + completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number): Promise; + setVariable(variablesReference: number | undefined, name: string, value: string): Promise; loadSource(resource: uri): Promise; getLoadedSources(): Promise; } @@ -237,18 +237,20 @@ export interface IThread extends ITreeElement { readonly name: string; /** - * Information about the current thread stop event. Null if thread is not stopped. + * Information about the current thread stop event. Undefined if thread is not stopped. */ - readonly stoppedDetails: IRawStoppedDetails; + readonly stoppedDetails: IRawStoppedDetails | undefined; /** - * Information about the exception if an 'exception' stopped event raised and DA supports the 'exceptionInfo' request, otherwise null. + * Information about the exception if an 'exception' stopped event raised and DA supports the 'exceptionInfo' request, otherwise undefined. */ - readonly exceptionInfo: Promise; + readonly exceptionInfo: Promise; + + readonly stateLabel: string; /** * Gets the callstack if it has already been received from the debug - * adapter, otherwise it returns null. + * adapter. */ getCallStack(): ReadonlyArray; @@ -282,7 +284,7 @@ export interface IScope extends IExpressionContainer { export interface IStackFrame extends ITreeElement { readonly thread: IThread; readonly name: string; - readonly presentationHint: string; + readonly presentationHint: string | undefined; readonly frameId: number; readonly range: IRange; readonly source: Source; @@ -317,20 +319,20 @@ export interface IBreakpointUpdateData { } export interface IBaseBreakpoint extends IEnablement { - readonly condition: string; - readonly hitCondition: string; - readonly logMessage: string; + readonly condition?: string; + readonly hitCondition?: string; + readonly logMessage?: string; readonly verified: boolean; - readonly idFromAdapter: number; + readonly idFromAdapter: number | undefined; } export interface IBreakpoint extends IBaseBreakpoint { readonly uri: uri; readonly lineNumber: number; readonly endLineNumber?: number; - readonly column: number; + readonly column?: number; readonly endColumn?: number; - readonly message: string; + readonly message?: string; readonly adapterData: any; } @@ -346,7 +348,7 @@ export interface IExceptionBreakpoint extends IEnablement { export interface IExceptionInfo { readonly id?: string; readonly description?: string; - readonly breakMode: string; + readonly breakMode: string | null; readonly details?: DebugProtocol.ExceptionDetails; } @@ -354,30 +356,30 @@ export interface IExceptionInfo { export interface IViewModel extends ITreeElement { /** - * Returns the focused debug session or null if no session is stopped. + * Returns the focused debug session or undefined if no session is stopped. */ - readonly focusedSession: IDebugSession; + readonly focusedSession: IDebugSession | undefined; /** - * Returns the focused thread or null if no thread is stopped. + * Returns the focused thread or undefined if no thread is stopped. */ - readonly focusedThread: IThread; + readonly focusedThread: IThread | undefined; /** - * Returns the focused stack frame or null if there are no stack frames. + * Returns the focused stack frame or undefined if there are no stack frames. */ - readonly focusedStackFrame: IStackFrame; + readonly focusedStackFrame: IStackFrame | undefined; - getSelectedExpression(): IExpression; - getSelectedFunctionBreakpoint(): IFunctionBreakpoint; - setSelectedExpression(expression: IExpression): void; - setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint): void; + getSelectedExpression(): IExpression | undefined; + getSelectedFunctionBreakpoint(): IFunctionBreakpoint | undefined; + setSelectedExpression(expression: IExpression | undefined): void; + setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint | undefined): void; isMultiSessionView(): boolean; onDidFocusSession: Event; - onDidFocusStackFrame: Event<{ stackFrame: IStackFrame, explicit: boolean }>; - onDidSelectExpression: Event; + onDidFocusStackFrame: Event<{ stackFrame: IStackFrame | undefined, explicit: boolean }>; + onDidSelectExpression: Event; } export interface IEvaluate { @@ -392,9 +394,9 @@ export interface IDebugModel extends ITreeElement { getExceptionBreakpoints(): ReadonlyArray; getWatchExpressions(): ReadonlyArray; - onDidChangeBreakpoints: Event; + onDidChangeBreakpoints: Event; onDidChangeCallStack: Event; - onDidChangeWatchExpressions: Event; + onDidChangeWatchExpressions: Event; } /** @@ -419,6 +421,11 @@ export interface IDebugConfiguration { internalConsoleOptions: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart'; extensionHostDebugAdapter: boolean; enableAllHovers: boolean; + console: { + fontSize: number; + fontFamily: string; + lineHeight: number; + }; } export interface IGlobalConfig { @@ -461,7 +468,7 @@ export interface ICompound { export interface IDebugAdapter extends IDisposable { readonly onError: Event; - readonly onExit: Event; + readonly onExit: Event; onRequest(callback: (request: DebugProtocol.Request) => void); onEvent(callback: (event: DebugProtocol.Event) => void); startSession(): Promise; @@ -473,7 +480,7 @@ export interface IDebugAdapter extends IDisposable { export interface IDebugAdapterFactory extends ITerminalLauncher { createDebugAdapter(session: IDebugSession): IDebugAdapter; - substituteVariables(folder: IWorkspaceFolder, config: IConfig): Promise; + substituteVariables(folder: IWorkspaceFolder | undefined, config: IConfig): Promise; } export interface IDebugAdapterExecutableOptions { @@ -509,7 +516,7 @@ export interface IPlatformSpecificAdapterContribution { } export interface IDebuggerContribution extends IPlatformSpecificAdapterContribution { - type?: string; + type: string; label?: string; // debug adapter executable adapterExecutableCommand?: string; @@ -535,7 +542,7 @@ export interface IDebuggerContribution extends IPlatformSpecificAdapterContribut export interface IDebugConfigurationProvider { readonly type: string; - resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig): Promise; + resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig): Promise; provideDebugConfigurations?(folderUri: uri | undefined): Promise; debugAdapterExecutable?(folderUri: uri | undefined): Promise; // TODO@AW legacy } @@ -578,15 +585,15 @@ export interface IConfigurationManager { * Returns an object containing the selected launch configuration and the selected configuration name. Both these fields can be null (no folder workspace). */ readonly selectedConfiguration: { - launch: ILaunch; - name: string; + launch: ILaunch | undefined; + name: string | undefined; }; - selectConfiguration(launch: ILaunch, name?: string, debugStarted?: boolean): void; + selectConfiguration(launch: ILaunch | undefined, name?: string, debugStarted?: boolean): void; getLaunches(): ReadonlyArray; - getLaunch(workspaceUri: uri): ILaunch | undefined; + getLaunch(workspaceUri: uri | undefined): ILaunch | undefined; /** * Allows to register on change of selected debug configuration. @@ -611,9 +618,9 @@ export interface IConfigurationManager { getDebugAdapterDescriptor(session: IDebugSession): Promise; registerDebugAdapterFactory(debugTypes: string[], debugAdapterFactory: IDebugAdapterFactory): IDisposable; - createDebugAdapter(session: IDebugSession): IDebugAdapter; + createDebugAdapter(session: IDebugSession): IDebugAdapter | undefined; - substituteVariables(debugType: string, folder: IWorkspaceFolder, config: IConfig): Promise; + substituteVariables(debugType: string, folder: IWorkspaceFolder | undefined, config: IConfig): Promise; runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise; } @@ -630,9 +637,9 @@ export interface ILaunch { readonly name: string; /** - * Workspace of the launch. Can be null. + * Workspace of the launch. Can be undefined. */ - readonly workspace: IWorkspaceFolder; + readonly workspace: IWorkspaceFolder | undefined; /** * Should this launch be shown in the debug dropdown. @@ -641,15 +648,15 @@ export interface ILaunch { /** * Returns a configuration with the specified name. - * Returns null if there is no configuration with the specified name. + * Returns undefined if there is no configuration with the specified name. */ - getConfiguration(name: string): IConfig; + getConfiguration(name: string): IConfig | undefined; /** * Returns a compound with the specified name. - * Returns null if there is no compound with the specified name. + * Returns undefined if there is no compound with the specified name. */ - getCompound(name: string): ICompound; + getCompound(name: string): ICompound | undefined; /** * Returns the names of all configurations and compounds. @@ -660,7 +667,7 @@ export interface ILaunch { /** * Opens the launch.json file. Creates if it does not exist. */ - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor, created: boolean }>; + openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }>; } // Debug service interfaces @@ -703,7 +710,7 @@ export interface IDebugService { /** * Sets the focused stack frame and evaluates all expressions against the newly focused stack frame, */ - focusStackFrame(focusedStackFrame: IStackFrame, thread?: IThread, session?: IDebugSession, explicit?: boolean): void; + focusStackFrame(focusedStackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): void; /** * Adds new breakpoints to the model for the file specified with the uri. Notifies debug adapter of breakpoint changes. @@ -784,7 +791,7 @@ export interface IDebugService { * Returns true if the start debugging was successfull. For compound launches, all configurations have to start successfuly for it to return success. * On errors the startDebugging will throw an error, however some error and cancelations are handled and in that case will simply return false. */ - startDebugging(launch: ILaunch, configOrName?: IConfig | string, noDebug?: boolean): Promise; + startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, noDebug?: boolean, parentSession?: IDebugSession): Promise; /** * Restarts a session or creates a new one if there is no active session. @@ -794,7 +801,7 @@ export interface IDebugService { /** * Stops the session. If the session does not exist then stops all sessions. */ - stopSession(session: IDebugSession): Promise; + stopSession(session: IDebugSession | undefined): Promise; /** * Makes unavailable all sources with the passed uri. Source will appear as grayed out in callstack view. @@ -821,7 +828,7 @@ export const enum BreakpointWidgetContext { export interface IDebugEditorContribution extends IEditorContribution { showHover(range: Range, focus: boolean): Promise; - showBreakpointWidget(lineNumber: number, column: number, context?: BreakpointWidgetContext): void; + showBreakpointWidget(lineNumber: number, column: number | undefined, context?: BreakpointWidgetContext): void; closeBreakpointWidget(): void; addLaunchConfiguration(): Promise; } diff --git a/src/vs/workbench/parts/debug/browser/debugContentProvider.ts b/src/vs/workbench/contrib/debug/common/debugContentProvider.ts similarity index 93% rename from src/vs/workbench/parts/debug/browser/debugContentProvider.ts rename to src/vs/workbench/contrib/debug/common/debugContentProvider.ts index 3e64b0e910..f7c8b02087 100644 --- a/src/vs/workbench/parts/debug/browser/debugContentProvider.ts +++ b/src/vs/workbench/contrib/debug/common/debugContentProvider.ts @@ -11,8 +11,8 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { DEBUG_SCHEME, IDebugService, IDebugSession } from 'vs/workbench/parts/debug/common/debug'; -import { Source } from 'vs/workbench/parts/debug/common/debugSource'; +import { DEBUG_SCHEME, IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; @@ -52,7 +52,7 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC this.pendingUpdates.forEach(cancellationSource => cancellationSource.dispose()); } - provideTextContent(resource: uri): Promise { + provideTextContent(resource: uri): Promise | null { return this.createOrUpdateContentModel(resource, true); } @@ -69,15 +69,15 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC /** * Create or reload the model content of the given resource. */ - private createOrUpdateContentModel(resource: uri, createIfNotExists: boolean): Promise { + private createOrUpdateContentModel(resource: uri, createIfNotExists: boolean): Promise | null { const model = this.modelService.getModel(resource); if (!model && !createIfNotExists) { // nothing to do - return undefined; + return null; } - let session: IDebugSession; + let session: IDebugSession | undefined; if (resource.query) { const data = Source.getEncodedDebugData(resource); @@ -125,7 +125,7 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC // remove token this.pendingUpdates.delete(model.id); - if (!myToken.token.isCancellationRequested && edits.length > 0) { + if (!myToken.token.isCancellationRequested && edits && edits.length > 0) { // use the evil-edit as these models show in readonly-editor only model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text))); } diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts similarity index 83% rename from src/vs/workbench/parts/debug/common/debugModel.ts rename to src/vs/workbench/contrib/debug/common/debugModel.ts index 8af1f18656..0d076f5e16 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -17,10 +17,10 @@ import { Range, IRange } from 'vs/editor/common/core/range'; import { ITreeElement, IExpression, IExpressionContainer, IDebugSession, IStackFrame, IExceptionBreakpoint, IBreakpoint, IFunctionBreakpoint, IDebugModel, IReplElementSource, IThread, IRawModelUpdate, IScope, IRawStoppedDetails, IEnablement, IBreakpointData, IExceptionInfo, IReplElement, IBreakpointsChangeEvent, IBreakpointUpdateData, IBaseBreakpoint, State -} from 'vs/workbench/parts/debug/common/debug'; -import { Source } from 'vs/workbench/parts/debug/common/debugSource'; +} from 'vs/workbench/contrib/debug/common/debug'; +import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { commonSuffixLength } from 'vs/base/common/strings'; -import { sep } from 'vs/base/common/paths'; +import { posix } from 'vs/base/common/path'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; @@ -29,7 +29,7 @@ export class SimpleReplElement implements IReplElement { private id: string, public value: string, public severity: severity, - public sourceData: IReplElementSource, + public sourceData?: IReplElementSource, ) { } toString(): string { @@ -95,22 +95,22 @@ export class ExpressionContainer implements IExpressionContainer { public valueChanged: boolean; private _value: string; - protected children: Promise; + protected children?: Promise; constructor( - protected session: IDebugSession, - private _reference: number, + protected session: IDebugSession | undefined, + private _reference: number | undefined, private id: string, - public namedVariables = 0, - public indexedVariables = 0, - private startOfVariables = 0 + public namedVariables: number | undefined = 0, + public indexedVariables: number | undefined = 0, + private startOfVariables: number | undefined = 0 ) { } - get reference(): number { + get reference(): number | undefined { return this._reference; } - set reference(value: number) { + set reference(value: number | undefined) { this._reference = value; this.children = undefined; // invalidate children cache } @@ -137,17 +137,17 @@ export class ExpressionContainer implements IExpressionContainer { return childrenThenable.then(childrenArray => { // Use a dynamic chunk size based on the number of elements #9774 let chunkSize = ExpressionContainer.BASE_CHUNK_SIZE; - while (this.indexedVariables > chunkSize * ExpressionContainer.BASE_CHUNK_SIZE) { + while (!!this.indexedVariables && this.indexedVariables > chunkSize * ExpressionContainer.BASE_CHUNK_SIZE) { chunkSize *= ExpressionContainer.BASE_CHUNK_SIZE; } - if (this.indexedVariables > chunkSize) { + if (!!this.indexedVariables && this.indexedVariables > chunkSize) { // There are a lot of children, create fake intermediate values that represent chunks #9537 const numberOfChunks = Math.ceil(this.indexedVariables / chunkSize); for (let i = 0; i < numberOfChunks; i++) { - const start = this.startOfVariables + i * chunkSize; + const start = (this.startOfVariables || 0) + i * chunkSize; const count = Math.min(chunkSize, this.indexedVariables - i * chunkSize); - childrenArray.push(new Variable(this.session, this, this.reference, `[${start}..${start + count - 1}]`, '', '', null, count, { kind: 'virtual' }, null, true, start)); + childrenArray.push(new Variable(this.session, this, this.reference, `[${start}..${start + count - 1}]`, '', '', undefined, count, { kind: 'virtual' }, undefined, true, start)); } return childrenArray; @@ -168,16 +168,16 @@ export class ExpressionContainer implements IExpressionContainer { get hasChildren(): boolean { // only variables with reference > 0 have children. - return this.reference > 0; + return !!this.reference && this.reference > 0; } - private fetchVariables(start: number, count: number, filter: 'indexed' | 'named'): Promise { - return this.session.variables(this.reference, filter, start, count).then(response => { + private fetchVariables(start: number | undefined, count: number | undefined, filter: 'indexed' | 'named' | undefined): Promise { + return this.session!.variables(this.reference || 0, filter, start, count).then(response => { return response && response.body && response.body.variables - ? distinct(response.body.variables.filter(v => !!v && isString(v.name)), v => v.name).map( - v => new Variable(this.session, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.presentationHint, v.type)) + ? distinct(response.body.variables.filter(v => !!v && isString(v.name)), (v: DebugProtocol.Variable) => v.name).map((v: DebugProtocol.Variable) => + new Variable(this.session, this, v.variablesReference, v.name, v.evaluateName, v.value, v.namedVariables, v.indexedVariables, v.presentationHint, v.type)) : []; - }, (e: Error) => [new Variable(this.session, this, 0, e.message, e.message, '', 0, 0, { kind: 'virtual' }, null, false)]); + }, (e: Error) => [new Variable(this.session, this, 0, e.message, e.message, '', 0, 0, { kind: 'virtual' }, undefined, false)]); } // The adapter explicitly sents the children count of an expression only if there are lots of children which should be chunked. @@ -187,7 +187,7 @@ export class ExpressionContainer implements IExpressionContainer { set value(value: string) { this._value = value; - this.valueChanged = ExpressionContainer.allValues.get(this.getId()) && + this.valueChanged = !!ExpressionContainer.allValues.get(this.getId()) && ExpressionContainer.allValues.get(this.getId()) !== Expression.DEFAULT_VALUE && ExpressionContainer.allValues.get(this.getId()) !== value; ExpressionContainer.allValues.set(this.getId(), value); } @@ -204,7 +204,7 @@ export class Expression extends ExpressionContainer implements IExpression { public type: string; constructor(public name: string, id = generateUuid()) { - super(null, 0, id); + super(undefined, 0, id); this.available = false; // name is not set if the expression is just being added // in that case do not set default value to prevent flashing #14499 @@ -213,7 +213,7 @@ export class Expression extends ExpressionContainer implements IExpression { } } - evaluate(session: IDebugSession, stackFrame: IStackFrame, context: string): Promise { + evaluate(session: IDebugSession | undefined, stackFrame: IStackFrame | undefined, context: string): Promise { if (!session || (!stackFrame && context !== 'repl')) { this.value = context === 'repl' ? nls.localize('startDebugFirst', "Please start a debug session to evaluate expressions") : Expression.DEFAULT_VALUE; this.available = false; @@ -230,7 +230,7 @@ export class Expression extends ExpressionContainer implements IExpression { this.reference = response.body.variablesReference; this.namedVariables = response.body.namedVariables; this.indexedVariables = response.body.indexedVariables; - this.type = response.body.type; + this.type = response.body.type || this.type; } }, err => { this.value = err.message; @@ -247,19 +247,19 @@ export class Expression extends ExpressionContainer implements IExpression { export class Variable extends ExpressionContainer implements IExpression { // Used to show the error message coming from the adapter when setting the value #7807 - public errorMessage: string; + public errorMessage: string | undefined; constructor( - session: IDebugSession, + session: IDebugSession | undefined, public parent: IExpressionContainer, - reference: number, + reference: number | undefined, public name: string, - public evaluateName: string, + public evaluateName: string | undefined, value: string, - namedVariables: number, - indexedVariables: number, - public presentationHint: DebugProtocol.VariablePresentationHint, - public type: string | null = null, + namedVariables: number | undefined, + indexedVariables: number | undefined, + public presentationHint: DebugProtocol.VariablePresentationHint | undefined, + public type: string | undefined = undefined, public available = true, startOfVariables = 0 ) { @@ -268,6 +268,10 @@ export class Variable extends ExpressionContainer implements IExpression { } setVariable(value: string): Promise { + if (!this.session) { + return Promise.resolve(undefined); + } + return this.session.setVariable((this.parent).reference, this.name, value).then(response => { if (response && response.body) { this.value = response.body.value; @@ -294,8 +298,8 @@ export class Scope extends ExpressionContainer implements IScope { public name: string, reference: number, public expensive: boolean, - namedVariables: number, - indexedVariables: number, + namedVariables?: number, + indexedVariables?: number, public range?: IRange ) { super(stackFrame.thread.session, reference, `scope:${stackFrame.getId()}:${name}:${index}`, namedVariables, indexedVariables); @@ -308,14 +312,14 @@ export class Scope extends ExpressionContainer implements IScope { export class StackFrame implements IStackFrame { - private scopes: Promise; + private scopes: Promise | null; constructor( public thread: IThread, public frameId: number, public source: Source, public name: string, - public presentationHint: string, + public presentationHint: string | undefined, public range: IRange, private index: number ) { @@ -331,7 +335,7 @@ export class StackFrame implements IStackFrame { this.scopes = this.thread.session.scopes(this.frameId).then(response => { return response && response.body && response.body.scopes ? response.body.scopes.map((rs, index) => new Scope(this, index, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables, - rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : null)) : []; + rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : undefined)) : []; }, err => []); } @@ -354,7 +358,7 @@ export class StackFrame implements IStackFrame { return this.source.name; } - const from = Math.max(0, this.source.uri.path.lastIndexOf(sep, this.source.uri.path.length - suffixLength - 1)); + const from = Math.max(0, this.source.uri.path.lastIndexOf(posix.sep, this.source.uri.path.length - suffixLength - 1)); return (from > 0 ? '...' : '') + this.source.uri.path.substr(from); } @@ -367,7 +371,7 @@ export class StackFrame implements IStackFrame { } const scopesContainingRange = scopes.filter(scope => scope.range && Range.containsRange(scope.range, range)) - .sort((first, second) => (first.range.endLineNumber - first.range.startLineNumber) - (second.range.endLineNumber - second.range.startLineNumber)); + .sort((first, second) => (first.range!.endLineNumber - first.range!.startLineNumber) - (second.range!.endLineNumber - second.range!.startLineNumber)); return scopesContainingRange.length ? scopesContainingRange : scopes; }); } @@ -389,11 +393,10 @@ export class StackFrame implements IStackFrame { export class Thread implements IThread { private callStack: IStackFrame[]; private staleCallStack: IStackFrame[]; - public stoppedDetails: IRawStoppedDetails; + public stoppedDetails: IRawStoppedDetails | undefined; public stopped: boolean; constructor(public session: IDebugSession, public name: string, public threadId: number) { - this.stoppedDetails = null; this.callStack = []; this.staleCallStack = []; this.stopped = false; @@ -418,6 +421,15 @@ export class Thread implements IThread { return this.staleCallStack; } + get stateLabel(): string { + if (this.stoppedDetails) { + return this.stoppedDetails.description || + this.stoppedDetails.reason ? nls.localize({ key: 'pausedOn', comment: ['indicates reason for program being paused'] }, "Paused on {0}", this.stoppedDetails.reason) : nls.localize('paused', "Paused"); + } + + return nls.localize({ key: 'running', comment: ['indicates state'] }, "Running"); + } + /** * Queries the debug adapter for the callstack and returns a promise * which completes once the call stack has been retrieved. @@ -456,8 +468,8 @@ export class Thread implements IThread { return new StackFrame(this, rsf.id, source, rsf.name, rsf.presentationHint, new Range( rsf.line, rsf.column, - rsf.endLine, - rsf.endColumn + rsf.endLine || rsf.line, + rsf.endColumn || rsf.column ), startFrame + index); }); }, (err: Error) => { @@ -470,9 +482,9 @@ export class Thread implements IThread { } /** - * Returns exception info promise if the exception was thrown, otherwise null + * Returns exception info promise if the exception was thrown, otherwise undefined */ - get exceptionInfo(): Promise { + get exceptionInfo(): Promise { if (this.stoppedDetails && this.stoppedDetails.reason === 'exception') { if (this.session.capabilities.supportsExceptionInfoRequest) { return this.session.exceptionInfo(this.threadId); @@ -482,7 +494,7 @@ export class Thread implements IThread { breakMode: null }); } - return Promise.resolve(null); + return Promise.resolve(undefined); } next(): Promise { @@ -532,13 +544,13 @@ export class Enablement implements IEnablement { export class BaseBreakpoint extends Enablement implements IBaseBreakpoint { private sessionData = new Map(); - private sessionId: string; + private sessionId: string | undefined; constructor( enabled: boolean, - public hitCondition: string, - public condition: string, - public logMessage: string, + public hitCondition: string | undefined, + public condition: string | undefined, + public logMessage: string | undefined, id: string ) { super(enabled, id); @@ -547,15 +559,15 @@ export class BaseBreakpoint extends Enablement implements IBaseBreakpoint { } } - protected getSessionData() { - return this.sessionData.get(this.sessionId); + protected getSessionData(): DebugProtocol.Breakpoint | undefined { + return this.sessionId ? this.sessionData.get(this.sessionId) : undefined; } setSessionData(sessionId: string, data: DebugProtocol.Breakpoint): void { this.sessionData.set(sessionId, data); } - setSessionId(sessionId: string): void { + setSessionId(sessionId: string | undefined): void { this.sessionId = sessionId; } @@ -564,7 +576,7 @@ export class BaseBreakpoint extends Enablement implements IBaseBreakpoint { return data ? data.verified : true; } - get idFromAdapter(): number { + get idFromAdapter(): number | undefined { const data = this.getSessionData(); return data ? data.id : undefined; } @@ -585,11 +597,11 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { constructor( public uri: uri, private _lineNumber: number, - private _column: number, + private _column: number | undefined, enabled: boolean, - condition: string, - hitCondition: string, - logMessage: string, + condition: string | undefined, + hitCondition: string | undefined, + logMessage: string | undefined, private _adapterData: any, private textFileService: ITextFileService, id = generateUuid() @@ -611,13 +623,13 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { return true; } - get column(): number { + get column(): number | undefined { const data = this.getSessionData(); // Only respect the column if the user explictly set the column to have an inline breakpoint return data && typeof data.column === 'number' && typeof this._column === 'number' ? data.column : this._column; } - get message(): string { + get message(): string | undefined { const data = this.getSessionData(); if (!data) { return undefined; @@ -634,12 +646,12 @@ export class Breakpoint extends BaseBreakpoint implements IBreakpoint { return data && data.source && data.source.adapterData ? data.source.adapterData : this._adapterData; } - get endLineNumber(): number { + get endLineNumber(): number | undefined { const data = this.getSessionData(); return data ? data.endLine : undefined; } - get endColumn(): number { + get endColumn(): number | undefined { const data = this.getSessionData(); return data ? data.endColumn : undefined; } @@ -689,9 +701,9 @@ export class FunctionBreakpoint extends BaseBreakpoint implements IFunctionBreak constructor( public name: string, enabled: boolean, - hitCondition: string, - condition: string, - logMessage: string, + hitCondition: string | undefined, + condition: string | undefined, + logMessage: string | undefined, id = generateUuid() ) { super(enabled, hitCondition, condition, logMessage, id); @@ -742,10 +754,10 @@ export class DebugModel implements IDebugModel { private sessions: IDebugSession[]; private toDispose: lifecycle.IDisposable[]; private schedulers = new Map(); - private breakpointsSessionId: string; - private readonly _onDidChangeBreakpoints: Emitter; + private breakpointsSessionId: string | undefined; + private readonly _onDidChangeBreakpoints: Emitter; private readonly _onDidChangeCallStack: Emitter; - private readonly _onDidChangeWatchExpressions: Emitter; + private readonly _onDidChangeWatchExpressions: Emitter; constructor( private breakpoints: Breakpoint[], @@ -789,7 +801,7 @@ export class DebugModel implements IDebugModel { this._onDidChangeCallStack.fire(undefined); } - get onDidChangeBreakpoints(): Event { + get onDidChangeBreakpoints(): Event { return this._onDidChangeBreakpoints.event; } @@ -797,7 +809,7 @@ export class DebugModel implements IDebugModel { return this._onDidChangeCallStack.event; } - get onDidChangeWatchExpressions(): Event { + get onDidChangeWatchExpressions(): Event { return this._onDidChangeWatchExpressions.event; } @@ -820,22 +832,31 @@ export class DebugModel implements IDebugModel { } } - fetchCallStack(thread: Thread): Promise { + fetchCallStack(thread: Thread): { topCallStack: Promise, wholeCallStack: Promise } { if (thread.session.capabilities.supportsDelayedStackTraceLoading) { // For improved performance load the first stack frame and then load the rest async. - return thread.fetchCallStack(1).then(() => { - if (!this.schedulers.has(thread.getId())) { - this.schedulers.set(thread.getId(), new RunOnceScheduler(() => { - thread.fetchCallStack(19).then(() => this._onDidChangeCallStack.fire()); - }, 420)); - } + let topCallStack = Promise.resolve(); + const wholeCallStack = new Promise((c, e) => { + topCallStack = thread.fetchCallStack(1).then(() => { + if (!this.schedulers.has(thread.getId())) { + this.schedulers.set(thread.getId(), new RunOnceScheduler(() => { + thread.fetchCallStack(19).then(() => { + this._onDidChangeCallStack.fire(); + c(); + }); + }, 420)); + } - this.schedulers.get(thread.getId()).schedule(); + this.schedulers.get(thread.getId())!.schedule(); + }); this._onDidChangeCallStack.fire(); }); + + return { topCallStack, wholeCallStack }; } - return thread.fetchCallStack(); + const wholeCallStack = thread.fetchCallStack(); + return { wholeCallStack, topCallStack: wholeCallStack }; } getBreakpoints(filter?: { uri?: uri, lineNumber?: number, column?: number, enabledOnly?: boolean }): IBreakpoint[] { @@ -879,7 +900,7 @@ export class DebugModel implements IDebugModel { this.exceptionBreakpoints = data.map(d => { const ebp = this.exceptionBreakpoints.filter(ebp => ebp.filter === d.filter).pop(); - return new ExceptionBreakpoint(d.filter, d.label, ebp ? ebp.enabled : d.default); + return new ExceptionBreakpoint(d.filter, d.label, ebp ? ebp.enabled : !!d.default); }); this._onDidChangeBreakpoints.fire(undefined); } @@ -895,7 +916,7 @@ export class DebugModel implements IDebugModel { } addBreakpoints(uri: uri, rawData: IBreakpointData[], fireEvent = true): IBreakpoint[] { - const newBreakpoints = rawData.map(rawBp => new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled, rawBp.condition, rawBp.hitCondition, rawBp.logMessage, undefined, this.textFileService, rawBp.id)); + const newBreakpoints = rawData.map(rawBp => new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled === false ? false : true, rawBp.condition, rawBp.hitCondition, rawBp.logMessage, undefined, this.textFileService, rawBp.id)); newBreakpoints.forEach(bp => bp.setSessionId(this.breakpointsSessionId)); this.breakpoints = this.breakpoints.concat(newBreakpoints); this.breakpointsActivated = true; @@ -945,7 +966,7 @@ export class DebugModel implements IDebugModel { }); } - setBreakpointsSessionId(sessionId: string): void { + setBreakpointsSessionId(sessionId: string | undefined): void { this.breakpointsSessionId = sessionId; this.breakpoints.forEach(bp => bp.setSessionId(sessionId)); this.functionBreakpoints.forEach(fbp => fbp.setSessionId(sessionId)); @@ -961,7 +982,10 @@ export class DebugModel implements IDebugModel { return resources.basenameOrAuthority(first.uri).localeCompare(resources.basenameOrAuthority(second.uri)); } if (first.lineNumber === second.lineNumber) { - return first.column - second.column; + if (first.column && second.column) { + return first.column - second.column; + } + return -1; } return first.lineNumber - second.lineNumber; @@ -1001,7 +1025,7 @@ export class DebugModel implements IDebugModel { this._onDidChangeBreakpoints.fire({ changed: changed }); } - addFunctionBreakpoint(functionName: string, id: string): IFunctionBreakpoint { + addFunctionBreakpoint(functionName: string, id?: string): IFunctionBreakpoint { const newFunctionBreakpoint = new FunctionBreakpoint(functionName, true, undefined, undefined, undefined, id); this.functionBreakpoints.push(newFunctionBreakpoint); this._onDidChangeBreakpoints.fire({ added: [newFunctionBreakpoint] }); @@ -1019,7 +1043,7 @@ export class DebugModel implements IDebugModel { removeFunctionBreakpoints(id?: string): void { - let removed: IFunctionBreakpoint[]; + let removed: FunctionBreakpoint[]; if (id) { removed = this.functionBreakpoints.filter(fbp => fbp.getId() === id); this.functionBreakpoints = this.functionBreakpoints.filter(fbp => fbp.getId() !== id); @@ -1057,10 +1081,11 @@ export class DebugModel implements IDebugModel { moveWatchExpression(id: string, position: number): void { const we = this.watchExpressions.filter(we => we.getId() === id).pop(); - this.watchExpressions = this.watchExpressions.filter(we => we.getId() !== id); - this.watchExpressions = this.watchExpressions.slice(0, position).concat(we, this.watchExpressions.slice(position)); - - this._onDidChangeWatchExpressions.fire(undefined); + if (we) { + this.watchExpressions = this.watchExpressions.filter(we => we.getId() !== id); + this.watchExpressions = this.watchExpressions.slice(0, position).concat(we, this.watchExpressions.slice(position)); + this._onDidChangeWatchExpressions.fire(undefined); + } } sourceIsNotAvailable(uri: uri): void { diff --git a/src/vs/workbench/parts/debug/common/debugProtocol.d.ts b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts similarity index 94% rename from src/vs/workbench/parts/debug/common/debugProtocol.d.ts rename to src/vs/workbench/contrib/debug/common/debugProtocol.d.ts index e38ca27945..8920ff9a24 100644 --- a/src/vs/workbench/parts/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/contrib/debug/common/debugProtocol.d.ts @@ -83,7 +83,7 @@ declare module DebugProtocol { body: { /** The reason for the event. For backward compatibility this string is shown in the UI if the 'description' attribute is missing (but it must not be translated). - Values: 'step', 'breakpoint', 'exception', 'pause', 'entry', 'goto', etc. + Values: 'step', 'breakpoint', 'exception', 'pause', 'entry', 'goto', 'function breakpoint', 'data breakpoint', etc. */ reason: string; /** The full reason for the event, e.g. 'Paused on exception'. This string is shown in the UI as is and must be translated. */ @@ -487,9 +487,9 @@ declare module DebugProtocol { } /** SetFunctionBreakpoints request; value of command field is 'setFunctionBreakpoints'. - Sets multiple function breakpoints and clears all previous function breakpoints. - To clear all function breakpoint, specify an empty array. - When a function breakpoint is hit, a 'stopped' event (event type 'function breakpoint') is generated. + Replaces all existing function breakpoints with new function breakpoints. + To clear all function breakpoints, specify an empty array. + When a function breakpoint is hit, a 'stopped' event (with reason 'function breakpoint') is generated. */ export interface SetFunctionBreakpointsRequest extends Request { // command: 'setFunctionBreakpoints'; @@ -532,6 +532,62 @@ declare module DebugProtocol { export interface SetExceptionBreakpointsResponse extends Response { } + /** DataBreakpointInfo request; value of command field is 'dataBreakpointInfo'. + Obtains information on a possible data breakpoint that could be set on an expression or variable. + */ + export interface DataBreakpointInfoRequest extends Request { + // command: 'dataBreakpointInfo'; + arguments: DataBreakpointInfoArguments; + } + + /** Arguments for 'dataBreakpointInfo' request. */ + export interface DataBreakpointInfoArguments { + /** Reference to the Variable container if the data breakpoint is requested for a child of the container. */ + variablesReference?: number; + /** The name of the Variable's child to obtain data breakpoint information for. If variableReference isn’t provided, this can be an expression. */ + name: string; + } + + /** Response to 'dataBreakpointInfo' request. */ + export interface DataBreakpointInfoResponse extends Response { + body: { + /** An identifier for the data on which a data breakpoint can be registered with the setDataBreakpoints request or null if no data breakpoint is available. */ + dataId: string | null; + /** UI string that describes on what data the breakpoint is set on or why a data breakpoint is not available. */ + description: string; + /** Optional attribute listing the available access types for a potential data breakpoint. A UI frontend could surface this information. */ + accessTypes?: DataBreakpointAccessType[]; + /** Optional attribute indicating that a potential data breakpoint could be persisted across sessions. */ + canPersist?: boolean; + }; + } + + /** SetDataBreakpoints request; value of command field is 'setDataBreakpoints'. + Replaces all existing data breakpoints with new data breakpoints. + To clear all data breakpoints, specify an empty array. + When a data breakpoint is hit, a 'stopped' event (with reason 'data breakpoint') is generated. + */ + export interface SetDataBreakpointsRequest extends Request { + // command: 'setDataBreakpoints'; + arguments: SetDataBreakpointsArguments; + } + + /** Arguments for 'setDataBreakpoints' request. */ + export interface SetDataBreakpointsArguments { + /** The contents of this array replaces all existing data breakpoints. An empty array clears all data breakpoints. */ + breakpoints: DataBreakpoint[]; + } + + /** Response to 'setDataBreakpoints' request. + Returned is information about each breakpoint created by this request. + */ + export interface SetDataBreakpointsResponse extends Response { + body: { + /** Information about the data breakpoints. The array elements correspond to the elements of the input argument 'breakpoints' array. */ + breakpoints: Breakpoint[]; + }; + } + /** Continue request; value of command field is 'continue'. The request starts the debuggee to run again. */ @@ -812,7 +868,7 @@ declare module DebugProtocol { export interface SetVariableArguments { /** The reference of the variable container. */ variablesReference: number; - /** The name of the variable. */ + /** The name of the variable in the container. */ name: string; /** The value of the variable. */ value: string; @@ -1200,6 +1256,8 @@ declare module DebugProtocol { supportsSetExpression?: boolean; /** The debug adapter supports the 'terminate' request. */ supportsTerminateRequest?: boolean; + /** The debug adapter supports data breakpoints. */ + supportsDataBreakpoints?: boolean; } /** An ExceptionBreakpointsFilter is shown in the UI as an option for configuring how exceptions are dealt with. */ @@ -1413,6 +1471,7 @@ declare module DebugProtocol { 'interface': Indicates that the object is an interface. 'mostDerivedClass': Indicates that the object is the most derived class. 'virtual': Indicates that the object is virtual, that means it is a synthetic object introduced by the adapter for rendering purposes, e.g. an index range for large arrays. + 'dataBreakpoint': Indicates that a data breakpoint is registered for the object. etc. */ kind?: string; @@ -1458,6 +1517,21 @@ declare module DebugProtocol { hitCondition?: string; } + /** This enumeration defines all possible access types for data breakpoints. */ + export type DataBreakpointAccessType = 'read' | 'write' | 'readWrite'; + + /** Properties of a data breakpoint passed to the setDataBreakpoints request. */ + export interface DataBreakpoint { + /** An id representing the data. This id is returned from the dataBreakpointInfo request. */ + dataId: string; + /** The access type of the data. */ + accessType?: DataBreakpointAccessType; + /** An optional expression for conditional breakpoints. */ + condition?: string; + /** An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. */ + hitCondition?: string; + } + /** Information about a Breakpoint created in setBreakpoints or setFunctionBreakpoints. */ export interface Breakpoint { /** An optional identifier for the breakpoint. It is needed if breakpoint events are used to update or remove breakpoints. */ diff --git a/src/vs/workbench/parts/debug/common/debugSchemas.ts b/src/vs/workbench/contrib/debug/common/debugSchemas.ts similarity index 97% rename from src/vs/workbench/parts/debug/common/debugSchemas.ts rename to src/vs/workbench/contrib/debug/common/debugSchemas.ts index f10a16f704..34bf295134 100644 --- a/src/vs/workbench/parts/debug/common/debugSchemas.ts +++ b/src/vs/workbench/contrib/debug/common/debugSchemas.ts @@ -5,7 +5,7 @@ import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry'; import * as nls from 'vs/nls'; -import { IDebuggerContribution, ICompound } from 'vs/workbench/parts/debug/common/debug'; +import { IDebuggerContribution, ICompound } from 'vs/workbench/contrib/debug/common/debug'; import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { inputsSchema } from 'vs/workbench/services/configurationResolver/common/configurationResolverSchema'; @@ -13,7 +13,7 @@ import { inputsSchema } from 'vs/workbench/services/configurationResolver/common // debuggers extension point export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'debuggers', - isDynamic: true, + defaultExtensionKind: 'workspace', jsonSchema: { description: nls.localize('vscode.extension.contributes.debuggers', 'Contributes debug adapters.'), type: 'array', @@ -112,7 +112,6 @@ export interface IRawBreakpointContribution { // breakpoints extension point #9037 export const breakpointsExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'breakpoints', - isDynamic: true, jsonSchema: { description: nls.localize('vscode.extension.contributes.breakpoints', 'Contributes breakpoints.'), type: 'array', @@ -195,6 +194,6 @@ export const launchSchema: IJSONSchema = { defaultCompound ] }, - inputs: inputsSchema.definitions.inputs + inputs: inputsSchema.definitions!.inputs } }; diff --git a/src/vs/workbench/parts/debug/common/debugSource.ts b/src/vs/workbench/contrib/debug/common/debugSource.ts similarity index 84% rename from src/vs/workbench/parts/debug/common/debugSource.ts rename to src/vs/workbench/contrib/debug/common/debugSource.ts index e8503e3a36..00140127da 100644 --- a/src/vs/workbench/parts/debug/common/debugSource.ts +++ b/src/vs/workbench/contrib/debug/common/debugSource.ts @@ -5,13 +5,13 @@ import * as nls from 'vs/nls'; import { URI as uri } from 'vs/base/common/uri'; -import * as paths from 'vs/base/common/paths'; +import { normalize, isAbsolute } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; -import { DEBUG_SCHEME } from 'vs/workbench/parts/debug/common/debug'; +import { DEBUG_SCHEME } from 'vs/workbench/contrib/debug/common/debug'; import { IRange } from 'vs/editor/common/core/range'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { Schemas } from 'vs/base/common/network'; -import { isUri } from 'vs/workbench/parts/debug/common/debugUtils'; +import { isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source"); @@ -33,11 +33,13 @@ export class Source { public readonly uri: uri; public available: boolean; + public raw: DebugProtocol.Source; - constructor(public raw: DebugProtocol.Source, sessionId: string) { + constructor(raw_: DebugProtocol.Source | undefined, sessionId: string) { let path: string; - if (raw) { - path = this.raw.path || this.raw.name; + if (raw_) { + this.raw = raw_; + path = this.raw.path || this.raw.name || ''; this.available = true; } else { this.raw = { name: UNKNOWN_SOURCE_LABEL }; @@ -45,14 +47,14 @@ export class Source { path = `${DEBUG_SCHEME}:${UNKNOWN_SOURCE_LABEL}`; } - if (this.raw.sourceReference > 0) { + if (typeof this.raw.sourceReference === 'number' && this.raw.sourceReference > 0) { this.uri = uri.parse(`${DEBUG_SCHEME}:${encodeURIComponent(path)}?session=${encodeURIComponent(sessionId)}&ref=${this.raw.sourceReference}`); } else { if (isUri(path)) { // path looks like a uri this.uri = uri.parse(path); } else { // assume a filesystem path - if (paths.isAbsolute_posix(path) || paths.isAbsolute_win32(path)) { + if (isAbsolute(path)) { this.uri = uri.file(path); } else { // path is relative: since VS Code cannot deal with this by itself @@ -97,14 +99,14 @@ export class Source { }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP); } - static getEncodedDebugData(modelUri: uri): { name: string, path: string, sessionId: string, sourceReference: number } { + static getEncodedDebugData(modelUri: uri): { name: string, path: string, sessionId?: string, sourceReference?: number } { let path: string; - let sourceReference: number; - let sessionId: string; + let sourceReference: number | undefined; + let sessionId: string | undefined; switch (modelUri.scheme) { case Schemas.file: - path = paths.normalize(modelUri.fsPath, true); + path = normalize(modelUri.fsPath); break; case DEBUG_SCHEME: path = modelUri.path; diff --git a/src/vs/workbench/parts/debug/common/debugUtils.ts b/src/vs/workbench/contrib/debug/common/debugUtils.ts similarity index 89% rename from src/vs/workbench/parts/debug/common/debugUtils.ts rename to src/vs/workbench/contrib/debug/common/debugUtils.ts index d1099a88aa..2233ef90db 100644 --- a/src/vs/workbench/parts/debug/common/debugUtils.ts +++ b/src/vs/workbench/contrib/debug/common/debugUtils.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import { equalsIgnoreCase } from 'vs/base/common/strings'; -import { IConfig } from 'vs/workbench/parts/debug/common/debug'; +import { IConfig, IDebuggerContribution } from 'vs/workbench/contrib/debug/common/debug'; import { URI as uri } from 'vs/base/common/uri'; -import { isAbsolute_posix, isAbsolute_win32 } from 'vs/base/common/paths'; +import { isAbsolute } from 'vs/base/common/path'; import { deepClone } from 'vs/base/common/objects'; const _formatPIIRegexp = /{([^}]+)}/g; @@ -27,6 +27,11 @@ export function isExtensionHostDebugging(config: IConfig) { return config.type && equalsIgnoreCase(config.type === 'vslsShare' ? (config).adapterProxy.configuration.type : config.type, 'extensionhost'); } +// only a debugger contributions with a label, program, or runtime attribute is considered a "defining" or "main" debugger contribution +export function isDebuggerMainContribution(dbg: IDebuggerContribution) { + return dbg.type && (dbg.label || dbg.program || dbg.runtime); +} + export function getExactExpressionStartAndEnd(lineContent: string, looseStart: number, looseEnd: number): { start: number, end: number } { let matchingExpression: string | undefined = undefined; let startOffset = 0; @@ -34,7 +39,7 @@ export function getExactExpressionStartAndEnd(lineContent: string, looseStart: n // Some example supported expressions: myVar.prop, a.b.c.d, myVar?.prop, myVar->prop, MyClass::StaticProp, *myVar // Match any character except a set of characters which often break interesting sub-expressions let expression: RegExp = /([^()\[\]{}<>\s+\-/%~#^;=|,`!]|\->)+/g; - let result: RegExpExecArray | undefined = undefined; + let result: RegExpExecArray | null = null; // First find the full expression under the cursor while (result = expression.exec(lineContent)) { @@ -52,7 +57,7 @@ export function getExactExpressionStartAndEnd(lineContent: string, looseStart: n // For example in expression 'a.b.c.d', if the focus was under 'b', 'a.b' would be evaluated. if (matchingExpression) { let subExpression: RegExp = /\w+/g; - let subExpressionResult: RegExpExecArray | undefined = undefined; + let subExpressionResult: RegExpExecArray | null = null; while (subExpressionResult = subExpression.exec(matchingExpression)) { let subEnd = subExpressionResult.index + 1 + startOffset + subExpressionResult[0].length; if (subEnd >= looseEnd) { @@ -73,10 +78,10 @@ export function getExactExpressionStartAndEnd(lineContent: string, looseStart: n // RFC 2396, Appendix A: https://www.ietf.org/rfc/rfc2396.txt const _schemePattern = /^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/; -export function isUri(s: string) { +export function isUri(s: string | undefined): boolean { // heuristics: a valid uri starts with a scheme and // the scheme has at least 2 characters so that it doesn't look like a drive letter. - return s && s.match(_schemePattern); + return !!(s && s.match(_schemePattern)); } function stringToUri(path: string): string { @@ -85,7 +90,7 @@ function stringToUri(path: string): string { return uri.parse(path); } else { // assume path - if (isAbsolute_posix(path) || isAbsolute_win32(path)) { + if (isAbsolute(path)) { return uri.file(path); } else { // leave relative path as is @@ -122,7 +127,7 @@ export function convertToDAPaths(message: DebugProtocol.ProtocolMessage, toUri: convertPaths(msg, (toDA: boolean, source: PathContainer | undefined) => { if (toDA && source) { - source.path = fixPath(source.path); + source.path = source.path ? fixPath(source.path) : undefined; } }); return msg; @@ -137,7 +142,7 @@ export function convertToVSCPaths(message: DebugProtocol.ProtocolMessage, toUri: convertPaths(msg, (toDA: boolean, source: PathContainer | undefined) => { if (!toDA && source) { - source.path = fixPath(source.path); + source.path = source.path ? fixPath(source.path) : undefined; } }); return msg; diff --git a/src/vs/workbench/parts/debug/common/debugViewModel.ts b/src/vs/workbench/contrib/debug/common/debugViewModel.ts similarity index 59% rename from src/vs/workbench/parts/debug/common/debugViewModel.ts rename to src/vs/workbench/contrib/debug/common/debugViewModel.ts index c1de8105fb..81c03bf7b8 100644 --- a/src/vs/workbench/parts/debug/common/debugViewModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugViewModel.ts @@ -4,25 +4,28 @@ *--------------------------------------------------------------------------------------------*/ import { Event, Emitter } from 'vs/base/common/event'; -import { CONTEXT_EXPRESSION_SELECTED, IViewModel, IStackFrame, IDebugSession, IThread, IExpression, IFunctionBreakpoint, CONTEXT_BREAKPOINT_SELECTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED } from 'vs/workbench/parts/debug/common/debug'; +import { CONTEXT_EXPRESSION_SELECTED, IViewModel, IStackFrame, IDebugSession, IThread, IExpression, IFunctionBreakpoint, CONTEXT_BREAKPOINT_SELECTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH } from 'vs/workbench/contrib/debug/common/debug'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { isExtensionHostDebugging } from 'vs/workbench/contrib/debug/common/debugUtils'; export class ViewModel implements IViewModel { firstSessionStart = true; - private _focusedStackFrame: IStackFrame; - private _focusedSession: IDebugSession; - private _focusedThread: IThread; - private selectedExpression: IExpression; - private selectedFunctionBreakpoint: IFunctionBreakpoint; + private _focusedStackFrame: IStackFrame | undefined; + private _focusedSession: IDebugSession | undefined; + private _focusedThread: IThread | undefined; + private selectedExpression: IExpression | undefined; + private selectedFunctionBreakpoint: IFunctionBreakpoint | undefined; private readonly _onDidFocusSession: Emitter; - private readonly _onDidFocusStackFrame: Emitter<{ stackFrame: IStackFrame, explicit: boolean }>; - private readonly _onDidSelectExpression: Emitter; + private readonly _onDidFocusStackFrame: Emitter<{ stackFrame: IStackFrame | undefined, explicit: boolean }>; + private readonly _onDidSelectExpression: Emitter; private multiSessionView: boolean; private expressionSelectedContextKey: IContextKey; private breakpointSelectedContextKey: IContextKey; private loadedScriptsSupportedContextKey: IContextKey; + private stepBackSupportedContextKey: IContextKey; + private focusedSessionIsAttach: IContextKey; constructor(contextKeyService: IContextKeyService) { this._onDidFocusSession = new Emitter(); @@ -32,25 +35,27 @@ export class ViewModel implements IViewModel { this.expressionSelectedContextKey = CONTEXT_EXPRESSION_SELECTED.bindTo(contextKeyService); this.breakpointSelectedContextKey = CONTEXT_BREAKPOINT_SELECTED.bindTo(contextKeyService); this.loadedScriptsSupportedContextKey = CONTEXT_LOADED_SCRIPTS_SUPPORTED.bindTo(contextKeyService); + this.stepBackSupportedContextKey = CONTEXT_STEP_BACK_SUPPORTED.bindTo(contextKeyService); + this.focusedSessionIsAttach = CONTEXT_FOCUSED_SESSION_IS_ATTACH.bindTo(contextKeyService); } getId(): string { return 'root'; } - get focusedSession(): IDebugSession { + get focusedSession(): IDebugSession | undefined { return this._focusedSession; } - get focusedThread(): IThread { + get focusedThread(): IThread | undefined { return this._focusedThread; } - get focusedStackFrame(): IStackFrame { + get focusedStackFrame(): IStackFrame | undefined { return this._focusedStackFrame; } - setFocus(stackFrame: IStackFrame, thread: IThread, session: IDebugSession, explicit: boolean): void { + setFocus(stackFrame: IStackFrame | undefined, thread: IThread | undefined, session: IDebugSession | undefined, explicit: boolean): void { const shouldEmitForStackFrame = this._focusedStackFrame !== stackFrame; const shouldEmitForSession = this._focusedSession !== session; @@ -58,7 +63,10 @@ export class ViewModel implements IViewModel { this._focusedThread = thread; this._focusedSession = session; - this.loadedScriptsSupportedContextKey.set(session && session.capabilities.supportsLoadedSourcesRequest); + this.loadedScriptsSupportedContextKey.set(session ? !!session.capabilities.supportsLoadedSourcesRequest : false); + this.stepBackSupportedContextKey.set(session ? !!session.capabilities.supportsStepBack : false); + const attach = !!session && session.configuration.request === 'attach' && !isExtensionHostDebugging(session.configuration); + this.focusedSessionIsAttach.set(attach); if (shouldEmitForSession) { this._onDidFocusSession.fire(session); @@ -68,33 +76,33 @@ export class ViewModel implements IViewModel { } } - get onDidFocusSession(): Event { + get onDidFocusSession(): Event { return this._onDidFocusSession.event; } - get onDidFocusStackFrame(): Event<{ stackFrame: IStackFrame, explicit: boolean }> { + get onDidFocusStackFrame(): Event<{ stackFrame: IStackFrame | undefined, explicit: boolean }> { return this._onDidFocusStackFrame.event; } - getSelectedExpression(): IExpression { + getSelectedExpression(): IExpression | undefined { return this.selectedExpression; } - setSelectedExpression(expression: IExpression) { + setSelectedExpression(expression: IExpression | undefined) { this.selectedExpression = expression; this.expressionSelectedContextKey.set(!!expression); this._onDidSelectExpression.fire(expression); } - get onDidSelectExpression(): Event { + get onDidSelectExpression(): Event { return this._onDidSelectExpression.event; } - getSelectedFunctionBreakpoint(): IFunctionBreakpoint { + getSelectedFunctionBreakpoint(): IFunctionBreakpoint | undefined { return this.selectedFunctionBreakpoint; } - setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint): void { + setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint | undefined): void { this.selectedFunctionBreakpoint = functionBreakpoint; this.breakpointSelectedContextKey.set(!!functionBreakpoint); } diff --git a/src/vs/workbench/parts/debug/common/replModel.ts b/src/vs/workbench/contrib/debug/common/replModel.ts similarity index 94% rename from src/vs/workbench/parts/debug/common/replModel.ts rename to src/vs/workbench/contrib/debug/common/replModel.ts index dfdad89abb..024b8bfdb0 100644 --- a/src/vs/workbench/parts/debug/common/replModel.ts +++ b/src/vs/workbench/contrib/debug/common/replModel.ts @@ -5,8 +5,8 @@ import * as nls from 'vs/nls'; import severity from 'vs/base/common/severity'; -import { IReplElement, IStackFrame, IExpression, IReplElementSource, IDebugSession } from 'vs/workbench/parts/debug/common/debug'; -import { Expression, SimpleReplElement, RawObjectReplElement } from 'vs/workbench/parts/debug/common/debugModel'; +import { IReplElement, IStackFrame, IExpression, IReplElementSource, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { Expression, SimpleReplElement, RawObjectReplElement } from 'vs/workbench/contrib/debug/common/debugModel'; import { isUndefinedOrNull, isObject } from 'vs/base/common/types'; import { basenameOrAuthority } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; @@ -23,7 +23,7 @@ export class ReplModel { return this.replElements; } - addReplExpression(stackFrame: IStackFrame, name: string): Promise { + addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise { const expression = new Expression(name); this.addReplElements([expression]); return expression.evaluate(this.session, stackFrame, 'repl'); @@ -46,7 +46,7 @@ export class ReplModel { // remove potential empty lines between different repl types this.replElements.pop(); } else if (previousElement instanceof SimpleReplElement && sev === previousElement.severity && toAdd.length && toAdd[0].sourceData === previousElement.sourceData) { - previousElement.value += toAdd.shift().value; + previousElement.value += toAdd.shift()!.value; } this.addReplElements(toAdd); } else { @@ -66,7 +66,7 @@ export class ReplModel { logToRepl(sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) { - let source: IReplElementSource; + let source: IReplElementSource | undefined; if (frame) { source = { column: frame.column, diff --git a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/electron-browser/debug.contribution.ts similarity index 77% rename from src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts rename to src/vs/workbench/contrib/debug/electron-browser/debug.contribution.ts index 28905ee812..2d1c88b9d4 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debug.contribution.ts @@ -17,43 +17,42 @@ import { ShowViewletAction, Extensions as ViewletExtensions, ViewletRegistry, Vi import { TogglePanelAction, Extensions as PanelExtensions, PanelRegistry, PanelDescriptor } from 'vs/workbench/browser/panel'; import { StatusbarItemDescriptor, IStatusbarRegistry, Extensions as StatusExtensions } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; -import { VariablesView } from 'vs/workbench/parts/debug/electron-browser/variablesView'; -import { BreakpointsView } from 'vs/workbench/parts/debug/browser/breakpointsView'; -import { WatchExpressionsView } from 'vs/workbench/parts/debug/electron-browser/watchExpressionsView'; -import { CallStackView } from 'vs/workbench/parts/debug/electron-browser/callStackView'; +import { BreakpointsView } from 'vs/workbench/contrib/debug/browser/breakpointsView'; +import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IDebugService, VIEWLET_ID, REPL_ID, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA, - CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, VIEW_CONTAINER, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_IN_DEBUG_REPL -} from 'vs/workbench/parts/debug/common/debug'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; + CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, VIEW_CONTAINER, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED, +} from 'vs/workbench/contrib/debug/common/debug'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { DebugEditorModelManager } from 'vs/workbench/parts/debug/browser/debugEditorModelManager'; +import { DebugEditorModelManager } from 'vs/workbench/contrib/debug/browser/debugEditorModelManager'; import { StepOverAction, FocusReplAction, StepIntoAction, StepOutAction, StartAction, RestartAction, ContinueAction, StopAction, DisconnectAction, PauseAction, AddFunctionBreakpointAction, - ConfigureAction, DisableAllBreakpointsAction, EnableAllBreakpointsAction, RemoveAllBreakpointsAction, RunAction, ReapplyBreakpointsAction, SelectAndStartAction, TerminateThreadAction -} from 'vs/workbench/parts/debug/browser/debugActions'; -import { DebugToolbar } from 'vs/workbench/parts/debug/browser/debugToolbar'; -import * as service from 'vs/workbench/parts/debug/electron-browser/debugService'; -import { DebugContentProvider } from 'vs/workbench/parts/debug/browser/debugContentProvider'; -import 'vs/workbench/parts/debug/electron-browser/debugEditorContribution'; + ConfigureAction, DisableAllBreakpointsAction, EnableAllBreakpointsAction, RemoveAllBreakpointsAction, RunAction, ReapplyBreakpointsAction, SelectAndStartAction, TerminateThreadAction, StepBackAction, ReverseContinueAction, +} from 'vs/workbench/contrib/debug/browser/debugActions'; +import { DebugToolbar } from 'vs/workbench/contrib/debug/browser/debugToolbar'; +import * as service from 'vs/workbench/contrib/debug/electron-browser/debugService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { registerCommands, ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID } from 'vs/workbench/parts/debug/browser/debugCommands'; +import { registerCommands, ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID } from 'vs/workbench/contrib/debug/browser/debugCommands'; import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; -import { StatusBarColorProvider } from 'vs/workbench/parts/debug/browser/statusbarColorProvider'; -import { ViewsRegistry } from 'vs/workbench/common/views'; +import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; +import { IViewsRegistry, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { isMacintosh } from 'vs/base/common/platform'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { URI } from 'vs/base/common/uri'; -import { DebugViewlet } from 'vs/workbench/parts/debug/browser/debugViewlet'; -import { Repl, ClearReplAction } from 'vs/workbench/parts/debug/electron-browser/repl'; -import { DebugQuickOpenHandler } from 'vs/workbench/parts/debug/browser/debugQuickOpen'; -import { DebugStatus } from 'vs/workbench/parts/debug/browser/debugStatus'; +import { DebugViewlet } from 'vs/workbench/contrib/debug/browser/debugViewlet'; +import { DebugQuickOpenHandler } from 'vs/workbench/contrib/debug/browser/debugQuickOpen'; +import { DebugStatus } from 'vs/workbench/contrib/debug/browser/debugStatus'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; -import { LoadedScriptsView } from 'vs/workbench/parts/debug/browser/loadedScriptsView'; -import { TOGGLE_LOG_POINT_ID, TOGGLE_CONDITIONAL_BREAKPOINT_ID, TOGGLE_BREAKPOINT_ID } from 'vs/workbench/parts/debug/browser/debugEditorActions'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { LoadedScriptsView } from 'vs/workbench/contrib/debug/browser/loadedScriptsView'; +import { TOGGLE_LOG_POINT_ID, TOGGLE_CONDITIONAL_BREAKPOINT_ID, TOGGLE_BREAKPOINT_ID } from 'vs/workbench/contrib/debug/browser/debugEditorActions'; +import { WatchExpressionsView } from 'vs/workbench/contrib/debug/browser/watchExpressionsView'; +import { VariablesView } from 'vs/workbench/contrib/debug/browser/variablesView'; +import { ClearReplAction, Repl } from 'vs/workbench/contrib/debug/browser/repl'; +import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider'; class OpenDebugViewletAction extends ShowViewletAction { public static readonly ID = VIEWLET_ID; @@ -64,9 +63,9 @@ class OpenDebugViewletAction extends ShowViewletAction { label: string, @IViewletService viewletService: IViewletService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IPartService partService: IPartService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(id, label, VIEWLET_ID, viewletService, editorGroupService, partService); + super(id, label, VIEWLET_ID, viewletService, editorGroupService, layoutService); } } @@ -78,9 +77,9 @@ class OpenDebugPanelAction extends TogglePanelAction { id: string, label: string, @IPanelService panelService: IPanelService, - @IPartService partService: IPartService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(id, label, REPL_ID, panelService, partService); + super(id, label, REPL_ID, panelService, layoutService); } } @@ -113,11 +112,12 @@ Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescri Registry.as(PanelExtensions.Panels).setDefaultPanelId(REPL_ID); // Register default debug views -ViewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctor: VariablesView, order: 10, weight: 40, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' } }], VIEW_CONTAINER); -ViewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctor: WatchExpressionsView, order: 20, weight: 10, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' } }], VIEW_CONTAINER); -ViewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctor: CallStackView, order: 30, weight: 30, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' } }], VIEW_CONTAINER); -ViewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctor: BreakpointsView, order: 40, weight: 20, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' } }], VIEW_CONTAINER); -ViewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctor: LoadedScriptsView, order: 35, weight: 5, canToggleVisibility: true, collapsed: true, when: CONTEXT_LOADED_SCRIPTS_SUPPORTED }], VIEW_CONTAINER); +const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); +viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctorDescriptor: { ctor: VariablesView }, order: 10, weight: 40, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' } }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctorDescriptor: { ctor: WatchExpressionsView }, order: 20, weight: 10, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' } }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctorDescriptor: { ctor: CallStackView }, order: 30, weight: 30, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' } }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctorDescriptor: { ctor: BreakpointsView }, order: 40, weight: 20, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' } }], VIEW_CONTAINER); +viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctorDescriptor: { ctor: LoadedScriptsView }, order: 35, weight: 5, canToggleVisibility: true, collapsed: true, when: CONTEXT_LOADED_SCRIPTS_SUPPORTED }], VIEW_CONTAINER); // register action to open viewlet const registry = Registry.as(WorkbenchActionRegistryExtensions.WorkbenchActions); @@ -150,7 +150,7 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(EnableAllBreakpointsAc registry.registerWorkbenchAction(new SyncActionDescriptor(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL), 'Debug: Disable All Breakpoints', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(FocusReplAction, FocusReplAction.ID, FocusReplAction.LABEL), 'Debug: Focus on Debug Console View', debugCategory); registry.registerWorkbenchAction(new SyncActionDescriptor(SelectAndStartAction, SelectAndStartAction.ID, SelectAndStartAction.LABEL), 'Debug: Select and Start Debugging', debugCategory); -registry.registerWorkbenchAction(new SyncActionDescriptor(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL, { primary: KeyMod.WinCtrl | KeyCode.KEY_L }, CONTEXT_IN_DEBUG_REPL), 'Debug: Clear Console', debugCategory); +registry.registerWorkbenchAction(new SyncActionDescriptor(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL), 'Debug: Clear Console', debugCategory); // Register Quick Open (Registry.as(QuickOpenExtensions.Quickopen)).registerQuickOpenHandler( @@ -211,6 +211,21 @@ configurationRegistry.registerConfiguration({ description: nls.localize({ comment: ['This is the description for a setting'], key: 'enableAllHovers' }, "Controls whether the non-debug hovers should be enabled while debugging. When enabled the hover providers will be called to provide a hover. Regular hovers will not be shown even if this setting is enabled."), default: false }, + 'debug.console.fontSize': { + type: 'number', + description: nls.localize('debug.console.fontSize', "Controls the font size in pixels in the debug console."), + default: isMacintosh ? 12 : 14, + }, + 'debug.console.fontFamily': { + type: 'string', + description: nls.localize('debug.console.fontFamily', "Controls the font family in the debug console."), + default: 'default' + }, + 'debug.console.lineHeight': { + type: 'number', + description: nls.localize('debug.console.lineHeight', "Controls the line height in pixels in the debug console. Use 0 to compute the line height from the font size."), + default: 0 + }, 'launch': { type: 'object', description: nls.localize({ comment: ['This is the description for a setting'], key: 'launch' }, "Global debug launch configuration. Should be used as an alternative to 'launch.json' that is shared across workspaces."), @@ -226,6 +241,36 @@ registerCommands(); const statusBar = Registry.as(StatusExtensions.Statusbar); statusBar.registerStatusbarItem(new StatusbarItemDescriptor(DebugStatus, StatusbarAlignment.LEFT, 30 /* Low Priority */)); +// Debug toolbar + +const registerDebugToolbarItem = (id: string, title: string, icon: string, order: number, when?: ContextKeyExpr, precondition?: ContextKeyExpr) => { + MenuRegistry.appendMenuItem(MenuId.DebugToolbar, { + group: 'navigation', + when, + order, + command: { + id, + title, + iconLocation: { + light: URI.parse(require.toUrl(`vs/workbench/contrib/debug/browser/media/${icon}.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/debug/browser/media/${icon}-inverse.svg`)) + }, + precondition + } + }); +}; + +registerDebugToolbarItem(ContinueAction.ID, ContinueAction.LABEL, 'continue', 10, CONTEXT_DEBUG_STATE.notEqualsTo('running')); +registerDebugToolbarItem(PauseAction.ID, PauseAction.LABEL, 'pause', 10, CONTEXT_DEBUG_STATE.isEqualTo('running')); +registerDebugToolbarItem(StopAction.ID, StopAction.LABEL, 'stop', 70, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated()); +registerDebugToolbarItem(DisconnectAction.ID, DisconnectAction.LABEL, 'disconnect', 70, CONTEXT_FOCUSED_SESSION_IS_ATTACH); +registerDebugToolbarItem(StepOverAction.ID, StepOverAction.LABEL, 'step-over', 20, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolbarItem(StepIntoAction.ID, StepIntoAction.LABEL, 'step-into', 30, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolbarItem(StepOutAction.ID, StepOutAction.LABEL, 'step-out', 40, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolbarItem(RestartAction.ID, RestartAction.LABEL, 'restart', 60); +registerDebugToolbarItem(StepBackAction.ID, StepBackAction.LABEL, 'step-back', 50, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); +registerDebugToolbarItem(ReverseContinueAction.ID, ReverseContinueAction.LABEL, 'reverse-continue', 60, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped')); + // View menu // {{SQL CARBON EDIT}} - Disable unused menu item @@ -447,7 +492,7 @@ if (isMacintosh) { const registerTouchBarEntry = (id: string, title: string, order, when: ContextKeyExpr, icon: string) => { MenuRegistry.appendMenuItem(MenuId.TouchBarContext, { command: { - id, title, iconLocation: { dark: URI.parse(require.toUrl(`vs/workbench/parts/debug/electron-browser/media/${icon}`)) } + id, title, iconLocation: { dark: URI.parse(require.toUrl(`vs/workbench/contrib/debug/electron-browser/media/${icon}`)) } }, when, group: '9_debug', diff --git a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/contrib/debug/electron-browser/debugConfigurationManager.ts similarity index 74% rename from src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts rename to src/vs/workbench/contrib/debug/electron-browser/debugConfigurationManager.ts index a92b45ff9f..0e968d2b74 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugConfigurationManager.ts @@ -21,16 +21,16 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, ITerminalSettings, ITerminalLauncher, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugAdapterTrackerFactory, IDebugService } from 'vs/workbench/parts/debug/common/debug'; -import { Debugger } from 'vs/workbench/parts/debug/node/debugger'; +import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, ITerminalSettings, ITerminalLauncher, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugAdapterTrackerFactory, IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { Debugger } from 'vs/workbench/contrib/debug/node/debugger'; import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { TerminalLauncher } from 'vs/workbench/parts/debug/electron-browser/terminalSupport'; +import { TerminalLauncher } from 'vs/workbench/contrib/debug/electron-browser/terminalSupport'; import { Registry } from 'vs/platform/registry/common/platform'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; -import { launchSchema, debuggersExtPoint, breakpointsExtPoint } from 'vs/workbench/parts/debug/common/debugSchemas'; +import { launchSchema, debuggersExtPoint, breakpointsExtPoint } from 'vs/workbench/contrib/debug/common/debugSchemas'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -45,8 +45,8 @@ export class ConfigurationManager implements IConfigurationManager { private debuggers: Debugger[]; private breakpointModeIdsSet = new Set(); private launches: ILaunch[]; - private selectedName: string; - private selectedLaunch: ILaunch; + private selectedName: string | undefined; + private selectedLaunch: ILaunch | undefined; private toDispose: IDisposable[]; private _onDidSelectConfigurationName = new Emitter(); private configProviders: IDebugConfigurationProvider[]; @@ -87,7 +87,7 @@ export class ConfigurationManager implements IConfigurationManager { // debuggers - public registerDebugAdapterFactory(debugTypes: string[], debugAdapterLauncher: IDebugAdapterFactory): IDisposable { + registerDebugAdapterFactory(debugTypes: string[], debugAdapterLauncher: IDebugAdapterFactory): IDisposable { debugTypes.forEach(debugType => this.debugAdapterFactories.set(debugType, debugAdapterLauncher)); return { dispose: () => { @@ -96,7 +96,7 @@ export class ConfigurationManager implements IConfigurationManager { }; } - public createDebugAdapter(session: IDebugSession): IDebugAdapter { + createDebugAdapter(session: IDebugSession): IDebugAdapter | undefined { let dap = this.debugAdapterFactories.get(session.configuration.type); if (dap) { return dap.createDebugAdapter(session); @@ -104,7 +104,7 @@ export class ConfigurationManager implements IConfigurationManager { return undefined; } - public substituteVariables(debugType: string, folder: IWorkspaceFolder, config: IConfig): Promise { + substituteVariables(debugType: string, folder: IWorkspaceFolder | undefined, config: IConfig): Promise { let dap = this.debugAdapterFactories.get(debugType); if (dap) { return dap.substituteVariables(folder, config); @@ -112,8 +112,8 @@ export class ConfigurationManager implements IConfigurationManager { return Promise.resolve(config); } - public runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise { - let tl: ITerminalLauncher = this.debugAdapterFactories.get(debugType); + runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise { + let tl: ITerminalLauncher | undefined = this.debugAdapterFactories.get(debugType); if (!tl) { if (!this.terminalLauncher) { this.terminalLauncher = this.instantiationService.createInstance(TerminalLauncher); @@ -125,7 +125,7 @@ export class ConfigurationManager implements IConfigurationManager { // debug adapter - public registerDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): IDisposable { + registerDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): IDisposable { this.adapterDescriptorFactories.push(debugAdapterProvider); return { dispose: () => { @@ -134,20 +134,20 @@ export class ConfigurationManager implements IConfigurationManager { }; } - public unregisterDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): void { + unregisterDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): void { const ix = this.adapterDescriptorFactories.indexOf(debugAdapterProvider); if (ix >= 0) { this.adapterDescriptorFactories.splice(ix, 1); } } - public getDebugAdapterDescriptor(session: IDebugSession): Promise { + getDebugAdapterDescriptor(session: IDebugSession): Promise { const config = session.configuration; // first try legacy proposed API: DebugConfigurationProvider.debugAdapterExecutable const providers0 = this.configProviders.filter(p => p.type === config.type && p.debugAdapterExecutable); - if (providers0.length === 1) { + if (providers0.length === 1 && providers0[0].debugAdapterExecutable) { return providers0[0].debugAdapterExecutable(session.root ? session.root.uri : undefined); } else { // TODO@AW handle n > 1 case @@ -165,7 +165,7 @@ export class ConfigurationManager implements IConfigurationManager { // debug adapter trackers - public registerDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): IDisposable { + registerDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): IDisposable { this.adapterTrackerFactories.push(debugAdapterTrackerFactory); return { dispose: () => { @@ -174,7 +174,7 @@ export class ConfigurationManager implements IConfigurationManager { }; } - public unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): void { + unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): void { const ix = this.adapterTrackerFactories.indexOf(debugAdapterTrackerFactory); if (ix >= 0) { this.adapterTrackerFactories.splice(ix, 1); @@ -183,7 +183,7 @@ export class ConfigurationManager implements IConfigurationManager { // debug configurations - public registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable { + registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable { this.configProviders.push(debugConfigurationProvider); return { dispose: () => { @@ -192,36 +192,36 @@ export class ConfigurationManager implements IConfigurationManager { }; } - public unregisterDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): void { + unregisterDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): void { const ix = this.configProviders.indexOf(debugConfigurationProvider); if (ix >= 0) { this.configProviders.splice(ix, 1); } } - public hasDebugConfigurationProvider(debugType: string): boolean { + hasDebugConfigurationProvider(debugType: string): boolean { // check if there are providers for the given type that contribute a provideDebugConfigurations method const providers = this.configProviders.filter(p => p.provideDebugConfigurations && (p.type === debugType)); return providers.length > 0; } - public needsToRunInExtHost(debugType: string): boolean { + needsToRunInExtHost(debugType: string): boolean { // if the given debugType matches any registered tracker factory we need to run the DA in the EH const providers = this.adapterTrackerFactories.filter(p => p.type === debugType || p.type === '*'); return providers.length > 0; } - public resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig): Promise { + resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig): Promise { return this.activateDebuggers('onDebugResolve', type).then(() => { - // pipe the config through the promises sequentially. append at the end the '*' types + // pipe the config through the promises sequentially. Append at the end the '*' types const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfiguration) .concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfiguration)); return providers.reduce((promise, provider) => { return promise.then(config => { if (config) { - return provider.resolveDebugConfiguration(folderUri, config); + return provider.resolveDebugConfiguration!(folderUri, config); } else { return Promise.resolve(config); } @@ -230,9 +230,9 @@ export class ConfigurationManager implements IConfigurationManager { }); } - public provideDebugConfigurations(folderUri: uri | undefined, type: string): Promise { + provideDebugConfigurations(folderUri: uri | undefined, type: string): Promise { return this.activateDebuggers('onDebugInitialConfigurations') - .then(() => Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations(folderUri))) + .then(() => Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri))) .then(results => results.reduce((first, second) => first.concat(second), []))); } @@ -249,14 +249,26 @@ export class ConfigurationManager implements IConfigurationManager { }); } - const duplicate = this.getDebugger(rawAdapter.type); - if (duplicate) { - duplicate.merge(rawAdapter, added.description); - } else { - this.debuggers.push(this.instantiationService.createInstance(Debugger, this, rawAdapter, added.description)); + if (rawAdapter.type !== '*') { + const existing = this.getDebugger(rawAdapter.type); + if (existing) { + existing.merge(rawAdapter, added.description); + } else { + this.debuggers.push(this.instantiationService.createInstance(Debugger, this, rawAdapter, added.description)); + } } }); }); + + // take care of all wildcard contributions + extensions.forEach(extension => { + extension.value.forEach(rawAdapter => { + if (rawAdapter.type === '*') { + this.debuggers.forEach(dbg => dbg.merge(rawAdapter, extension.description)); + } + }); + }); + delta.removed.forEach(removed => { const removedTypes = removed.value.map(rawAdapter => rawAdapter.type); this.debuggers = this.debuggers.filter(d => removedTypes.indexOf(d.type) === -1); @@ -270,13 +282,13 @@ export class ConfigurationManager implements IConfigurationManager { // update the schema to include all attributes, snippets and types from extensions. this.debuggers.forEach(adapter => { - const items = (launchSchema.properties['configurations'].items); + const items = (launchSchema.properties!['configurations'].items); const schemaAttributes = adapter.getSchemaAttributes(); - if (schemaAttributes) { + if (schemaAttributes && items.oneOf) { items.oneOf.push(...schemaAttributes); } const configurationSnippets = adapter.configurationSnippets; - if (configurationSnippets) { + if (configurationSnippets && items.defaultSnippets) { items.defaultSnippets.push(...configurationSnippets); } }); @@ -311,33 +323,33 @@ export class ConfigurationManager implements IConfigurationManager { private initLaunches(): void { this.launches = this.contextService.getWorkspace().folders.map(folder => this.instantiationService.createInstance(Launch, this, folder)); if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { - this.launches.push(this.instantiationService.createInstance(WorkspaceLaunch, this)); + this.launches.push(this.instantiationService.createInstance(WorkspaceLaunch)); } - this.launches.push(this.instantiationService.createInstance(UserLaunch, this)); + this.launches.push(this.instantiationService.createInstance(UserLaunch)); - if (this.launches.indexOf(this.selectedLaunch) === -1) { + if (this.selectedLaunch && this.launches.indexOf(this.selectedLaunch) === -1) { this.selectedLaunch = undefined; } } private setCompoundSchemaValues(): void { - const compoundConfigurationsSchema = (launchSchema.properties['compounds'].items).properties['configurations']; + const compoundConfigurationsSchema = (launchSchema.properties!['compounds'].items).properties!['configurations']; const launchNames = this.launches.map(l => l.getConfigurationNames(false)).reduce((first, second) => first.concat(second), []); - (compoundConfigurationsSchema.items).oneOf[0].enum = launchNames; - (compoundConfigurationsSchema.items).oneOf[1].properties.name.enum = launchNames; + (compoundConfigurationsSchema.items).oneOf![0].enum = launchNames; + (compoundConfigurationsSchema.items).oneOf![1].properties!.name.enum = launchNames; const folderNames = this.contextService.getWorkspace().folders.map(f => f.name); - (compoundConfigurationsSchema.items).oneOf[1].properties.folder.enum = folderNames; + (compoundConfigurationsSchema.items).oneOf![1].properties!.folder.enum = folderNames; jsonRegistry.registerSchema(launchSchemaId, launchSchema); } - public getLaunches(): ILaunch[] { + getLaunches(): ILaunch[] { return this.launches; } - public getLaunch(workspaceUri: uri): ILaunch { + getLaunch(workspaceUri: uri | undefined): ILaunch | undefined { if (!uri.isUri(workspaceUri)) { return undefined; } @@ -345,18 +357,18 @@ export class ConfigurationManager implements IConfigurationManager { return this.launches.filter(l => l.workspace && l.workspace.uri.toString() === workspaceUri.toString()).pop(); } - public get selectedConfiguration(): { launch: ILaunch, name: string } { + get selectedConfiguration(): { launch: ILaunch | undefined, name: string | undefined } { return { launch: this.selectedLaunch, name: this.selectedName }; } - public get onDidSelectConfiguration(): Event { + get onDidSelectConfiguration(): Event { return this._onDidSelectConfigurationName.event; } - public getWorkspaceLaunch(): ILaunch { + getWorkspaceLaunch(): ILaunch | undefined { if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { return this.launches[this.launches.length - 1]; } @@ -364,7 +376,7 @@ export class ConfigurationManager implements IConfigurationManager { return undefined; } - public selectConfiguration(launch: ILaunch, name?: string): void { + selectConfiguration(launch: ILaunch | undefined, name?: string): void { const previousLaunch = this.selectedLaunch; const previousName = this.selectedName; @@ -373,12 +385,13 @@ export class ConfigurationManager implements IConfigurationManager { if (name && names.indexOf(name) >= 0) { this.selectedName = name; } - if (names.indexOf(this.selectedName) === -1) { + if (!this.selectedName || names.indexOf(this.selectedName) === -1) { this.selectedName = names.length ? names[0] : undefined; } - if (this.selectedLaunch && this.selectedName) { - const configuration = this.selectedLaunch.getConfiguration(this.selectedName); - this.debugConfigurationTypeContext.set(configuration ? configuration.type : undefined); + + const configuration = this.selectedLaunch && this.selectedName ? this.selectedLaunch.getConfiguration(this.selectedName) : undefined; + if (configuration) { + this.debugConfigurationTypeContext.set(configuration.type); } else { this.debugConfigurationTypeContext.reset(); } @@ -388,8 +401,8 @@ export class ConfigurationManager implements IConfigurationManager { } } - public canSetBreakpointsIn(model: ITextModel): boolean { - const modeId = model ? model.getLanguageIdentifier().language : null; + canSetBreakpointsIn(model: ITextModel): boolean { + const modeId = model.getLanguageIdentifier().language; if (!modeId || modeId === 'jsonc' || modeId === 'log') { // do not allow breakpoints in our settings files and output return false; @@ -401,22 +414,22 @@ export class ConfigurationManager implements IConfigurationManager { return this.breakpointModeIdsSet.has(modeId); } - public getDebugger(type: string): Debugger { + getDebugger(type: string): Debugger | undefined { return this.debuggers.filter(dbg => strings.equalsIgnoreCase(dbg.type, type)).pop(); } - public guessDebugger(type?: string): Promise { + guessDebugger(type?: string): Promise { if (type) { const adapter = this.getDebugger(type); return Promise.resolve(adapter); } const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - let candidates: Promise; + let candidates: Promise | undefined; if (isCodeEditor(activeTextEditorWidget)) { const model = activeTextEditorWidget.getModel(); const language = model ? model.getLanguageIdentifier().language : undefined; - const adapters = this.debuggers.filter(a => a.languages && a.languages.indexOf(language) >= 0); + const adapters = this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0); if (adapters.length === 1) { return Promise.resolve(adapters[0]); } @@ -432,7 +445,7 @@ export class ConfigurationManager implements IConfigurationManager { return candidates.then(debuggers => { debuggers.sort((first, second) => first.label.localeCompare(second.label)); const picks = debuggers.map(c => ({ label: c.label, debugger: c })); - return this.quickInputService.pick<(typeof picks)[0]>([...picks, { type: 'separator' }, { label: 'More...', debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") }) + return this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: 'More...', debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") }) .then(picked => { if (picked && picked.debugger) { return picked.debugger; @@ -445,7 +458,7 @@ export class ConfigurationManager implements IConfigurationManager { }); } - public activateDebuggers(activationEvent: string, debugType?: string): Promise { + activateDebuggers(activationEvent: string, debugType?: string): Promise { const thenables: Promise[] = [ this.extensionService.activateByEvent(activationEvent), this.extensionService.activateByEvent('onDebug') @@ -459,56 +472,32 @@ export class ConfigurationManager implements IConfigurationManager { } private saveState(): void { - this.storageService.store(DEBUG_SELECTED_CONFIG_NAME_KEY, this.selectedName, StorageScope.WORKSPACE); + if (this.selectedName) { + this.storageService.store(DEBUG_SELECTED_CONFIG_NAME_KEY, this.selectedName, StorageScope.WORKSPACE); + } if (this.selectedLaunch) { this.storageService.store(DEBUG_SELECTED_ROOT, this.selectedLaunch.uri.toString(), StorageScope.WORKSPACE); } } - public dispose(): void { + dispose(): void { this.toDispose = dispose(this.toDispose); } } -class Launch implements ILaunch { +abstract class AbstractLaunch { + protected abstract getConfig(): IGlobalConfig | undefined; - constructor( - private configurationManager: ConfigurationManager, - public workspace: IWorkspaceFolder, - @IFileService private readonly fileService: IFileService, - @IEditorService protected editorService: IEditorService, - @IConfigurationService protected configurationService: IConfigurationService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - ) { - // noop - } - - public get uri(): uri { - return resources.joinPath(this.workspace.uri, '/.vscode/launch.json'); - } - - public get name(): string { - return this.workspace.name; - } - - public get hidden(): boolean { - return false; - } - - protected getConfig(): IGlobalConfig { - return this.configurationService.inspect('launch', { resource: this.workspace.uri }).workspaceFolder; - } - - public getCompound(name: string): ICompound { + getCompound(name: string): ICompound | undefined { const config = this.getConfig(); if (!config || !config.compounds) { - return null; + return undefined; } return config.compounds.filter(compound => compound.name === name).pop(); } - public getConfigurationNames(includeCompounds = true): string[] { + getConfigurationNames(includeCompounds = true): string[] { const config = this.getConfig(); if (!config || !config.configurations || !Array.isArray(config.configurations)) { return []; @@ -525,17 +514,46 @@ class Launch implements ILaunch { } } - public getConfiguration(name: string): IConfig { + getConfiguration(name: string): IConfig | undefined { // We need to clone the configuration in order to be able to make changes to it #42198 const config = objects.deepClone(this.getConfig()); if (!config || !config.configurations) { - return null; + return undefined; } return config.configurations.filter(config => config && config.name === name).shift(); } - public openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor, created: boolean }> { + get hidden(): boolean { + return false; + } +} + +class Launch extends AbstractLaunch implements ILaunch { + + constructor( + private configurationManager: ConfigurationManager, + public workspace: IWorkspaceFolder, + @IFileService private readonly fileService: IFileService, + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super(); + } + + get uri(): uri { + return resources.joinPath(this.workspace.uri, '/.vscode/launch.json'); + } + + get name(): string { + return this.workspace.name; + } + + protected getConfig(): IGlobalConfig | undefined { + return this.configurationService.inspect('launch', { resource: this.workspace.uri }).workspaceFolder; + } + + openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> { const resource = this.uri; let created = false; @@ -547,12 +565,12 @@ class Launch implements ILaunch { return adapter.getInitialConfigurationContent(initialConfigs); }); } else { - return undefined; + return ''; } }).then(content => { if (!content) { - return undefined; + return ''; } created = true; // pin only if config file is created #8727 @@ -563,7 +581,7 @@ class Launch implements ILaunch { }); }).then(content => { if (!content) { - return { editor: undefined, created: false }; + return { editor: null, created: false }; } const index = content.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`); let startLineNumber = 1; @@ -589,49 +607,50 @@ class Launch implements ILaunch { } } -class WorkspaceLaunch extends Launch implements ILaunch { - +class WorkspaceLaunch extends AbstractLaunch implements ILaunch { constructor( - configurationManager: ConfigurationManager, - @IFileService fileService: IFileService, - @IEditorService editorService: IEditorService, - @IConfigurationService configurationService: IConfigurationService, - @IWorkspaceContextService contextService: IWorkspaceContextService, + @IEditorService private readonly editorService: IEditorService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService ) { - super(configurationManager, undefined, fileService, editorService, configurationService, contextService); + super(); + } + + get workspace(): undefined { + return undefined; } get uri(): uri { - return this.contextService.getWorkspace().configuration; + return this.contextService.getWorkspace().configuration!; } get name(): string { return nls.localize('workspace', "workspace"); } - protected getConfig(): IGlobalConfig { + protected getConfig(): IGlobalConfig | undefined { return this.configurationService.inspect('launch').workspace; } - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor, created: boolean }> { + openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> { return this.editorService.openEditor({ - resource: this.contextService.getWorkspace().configuration, + resource: this.contextService.getWorkspace().configuration!, options: { preserveFocus } }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor, created: false })); } } -class UserLaunch extends Launch implements ILaunch { +class UserLaunch extends AbstractLaunch implements ILaunch { constructor( - configurationManager: ConfigurationManager, - @IFileService fileService: IFileService, - @IEditorService editorService: IEditorService, - @IConfigurationService configurationService: IConfigurationService, - @IPreferencesService private readonly preferencesService: IPreferencesService, - @IWorkspaceContextService contextService: IWorkspaceContextService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IPreferencesService private readonly preferencesService: IPreferencesService ) { - super(configurationManager, undefined, fileService, editorService, configurationService, contextService); + super(); + } + + get workspace(): undefined { + return undefined; } get uri(): uri { @@ -642,15 +661,15 @@ class UserLaunch extends Launch implements ILaunch { return nls.localize('user settings', "user settings"); } - public get hidden(): boolean { + get hidden(): boolean { return true; } - protected getConfig(): IGlobalConfig { + protected getConfig(): IGlobalConfig | undefined { return this.configurationService.inspect('launch').user; } - openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor, created: boolean }> { + openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> { return this.preferencesService.openGlobalSettings(false, { preserveFocus }).then(editor => ({ editor, created: false })); } } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/contrib/debug/electron-browser/debugService.ts similarity index 89% rename from src/vs/workbench/parts/debug/electron-browser/debugService.ts rename to src/vs/workbench/contrib/debug/electron-browser/debugService.ts index 3744704704..2b6ebd3cdb 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugService.ts @@ -18,33 +18,33 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { DebugModel, ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, Expression } from 'vs/workbench/parts/debug/common/debugModel'; -import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel'; -import * as debugactions from 'vs/workbench/parts/debug/browser/debugActions'; -import { ConfigurationManager } from 'vs/workbench/parts/debug/electron-browser/debugConfigurationManager'; -import Constants from 'vs/workbench/parts/markers/electron-browser/constants'; -import { ITaskService, ITaskSummary } from 'vs/workbench/parts/tasks/common/taskService'; -import { TaskError } from 'vs/workbench/parts/tasks/common/taskSystem'; -import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from 'vs/workbench/parts/files/common/files'; +import { DebugModel, ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, Expression } from 'vs/workbench/contrib/debug/common/debugModel'; +import { ViewModel } from 'vs/workbench/contrib/debug/common/debugViewModel'; +import * as debugactions from 'vs/workbench/contrib/debug/browser/debugActions'; +import { ConfigurationManager } from 'vs/workbench/contrib/debug/electron-browser/debugConfigurationManager'; +import Constants from 'vs/workbench/contrib/markers/browser/constants'; +import { ITaskService, ITaskSummary } from 'vs/workbench/contrib/tasks/common/taskService'; +import { TaskError } from 'vs/workbench/contrib/tasks/common/taskSystem'; +import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from 'vs/workbench/contrib/files/common/files'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_ATTACH_BROADCAST_CHANNEL, EXTENSION_TERMINATE_BROADCAST_CHANNEL, EXTENSION_RELOAD_BROADCAST_CHANNEL, EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost'; -import { IBroadcastService } from 'vs/platform/broadcast/electron-browser/broadcastService'; +import { IBroadcastService } from 'vs/workbench/services/broadcast/electron-browser/broadcastService'; import { IRemoteConsoleLog, parse, getFirstFrame } from 'vs/base/node/console'; -import { TaskEvent, TaskEventKind, TaskIdentifier } from 'vs/workbench/parts/tasks/common/tasks'; +import { TaskEvent, TaskEventKind, TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IAction, Action } from 'vs/base/common/actions'; import { deepClone, equals } from 'vs/base/common/objects'; -import { DebugSession } from 'vs/workbench/parts/debug/electron-browser/debugSession'; +import { DebugSession } from 'vs/workbench/contrib/debug/electron-browser/debugSession'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel } from 'vs/workbench/parts/debug/common/debug'; -import { isExtensionHostDebugging } from 'vs/workbench/parts/debug/common/debugUtils'; +import { IDebugService, State, IDebugSession, CONTEXT_DEBUG_TYPE, CONTEXT_DEBUG_STATE, CONTEXT_IN_DEBUG_MODE, IThread, IDebugConfiguration, VIEWLET_ID, REPL_ID, IConfig, ILaunch, IViewModel, IConfigurationManager, IDebugModel, IEnablement, IBreakpoint, IBreakpointData, ICompound, IGlobalConfig, IStackFrame, AdapterEndEvent, getStateLabel } from 'vs/workbench/contrib/debug/common/debug'; +import { isExtensionHostDebugging } from 'vs/workbench/contrib/debug/common/debugUtils'; import { isErrorWithActions, createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -97,7 +97,7 @@ export class DebugService implements IDebugService { @IPanelService private readonly panelService: IPanelService, @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IBroadcastService private readonly broadcastService: IBroadcastService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -158,10 +158,7 @@ export class DebugService implements IDebugService { const extensionOutput = broadcast.payload.logEntry; const sev = extensionOutput.severity === 'warn' ? severity.Warning : extensionOutput.severity === 'error' ? severity.Error : severity.Info; const { args, stack } = parse(extensionOutput); - let frame = undefined; - if (stack) { - frame = getFirstFrame(stack); - } + const frame = !!stack ? getFirstFrame(stack) : undefined; session.logToRepl(sev, args, frame); break; } @@ -255,7 +252,7 @@ export class DebugService implements IDebugService { * main entry point * properly manages compounds, checks for errors and handles the initializing state. */ - startDebugging(launch: ILaunch, configOrName?: IConfig | string, noDebug = false, unresolvedConfig?: IConfig, ): Promise { + startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, noDebug = false, parentSession?: IDebugSession): Promise { this.startInitializingState(); // make sure to save all files and that the configuration is up to date @@ -263,7 +260,8 @@ export class DebugService implements IDebugService { return this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration(launch ? launch.workspace : undefined).then(() => { return this.extensionService.whenInstalledExtensionsRegistered().then(() => { - let config: IConfig, compound: ICompound; + let config: IConfig | undefined; + let compound: ICompound | undefined; if (!configOrName) { configOrName = this.configurationManager.selectedConfiguration.name; } @@ -276,7 +274,7 @@ export class DebugService implements IDebugService { if (sessions.some(s => s.configuration.name === configOrName && (!launch || !launch.workspace || !s.root || s.root.uri.toString() === launch.workspace.uri.toString()))) { return Promise.reject(new Error(alreadyRunningMessage)); } - if (compound && compound.configurations && sessions.some(p => compound.configurations.indexOf(p.configuration.name) !== -1)) { + if (compound && compound.configurations && sessions.some(p => compound!.configurations.indexOf(p.configuration.name) !== -1)) { return Promise.reject(new Error(alreadyRunningMessage)); } } else if (typeof configOrName !== 'string') { @@ -292,16 +290,16 @@ export class DebugService implements IDebugService { return Promise.all(compound.configurations.map(configData => { const name = typeof configData === 'string' ? configData : configData.name; - if (name === compound.name) { + if (name === compound!.name) { return Promise.resolve(false); } - let launchForName: ILaunch; + let launchForName: ILaunch | undefined; if (typeof configData === 'string') { const launchesContainingName = this.configurationManager.getLaunches().filter(l => !!l.getConfiguration(name)); if (launchesContainingName.length === 1) { launchForName = launchesContainingName[0]; - } else if (launchesContainingName.length > 1 && launchesContainingName.indexOf(launch) >= 0) { + } else if (launch && launchesContainingName.length > 1 && launchesContainingName.indexOf(launch) >= 0) { // If there are multiple launches containing the configuration give priority to the configuration in the current launch launchForName = launch; } else { @@ -313,11 +311,11 @@ export class DebugService implements IDebugService { if (launchesMatchingConfigData.length === 1) { launchForName = launchesMatchingConfigData[0]; } else { - return Promise.reject(new Error(nls.localize('noFolderWithName', "Can not find folder with name '{0}' for configuration '{1}' in compound '{2}'.", configData.folder, configData.name, compound.name))); + return Promise.reject(new Error(nls.localize('noFolderWithName', "Can not find folder with name '{0}' for configuration '{1}' in compound '{2}'.", configData.folder, configData.name, compound!.name))); } } - return this.createSession(launchForName, launchForName.getConfiguration(name), unresolvedConfig, noDebug); + return this.createSession(launchForName, launchForName!.getConfiguration(name), noDebug); })).then(values => values.every(success => !!success)); // Compound launch is a success only if each configuration launched successfully } @@ -327,7 +325,7 @@ export class DebugService implements IDebugService { return Promise.reject(new Error(message)); } - return this.createSession(launch, config, unresolvedConfig, noDebug); + return this.createSession(launch, config, noDebug); }); })); }).then(success => { @@ -343,25 +341,25 @@ export class DebugService implements IDebugService { /** * gets the debugger for the type, resolves configurations by providers, substitutes variables and runs prelaunch tasks */ - private createSession(launch: ILaunch, config: IConfig, unresolvedConfig: IConfig, noDebug: boolean): Promise { + private createSession(launch: ILaunch | undefined, config: IConfig | undefined, noDebug: boolean): Promise { // We keep the debug type in a separate variable 'type' so that a no-folder config has no attributes. // Storing the type in the config would break extensions that assume that the no-folder case is indicated by an empty config. - let type: string; + let type: string | undefined; if (config) { type = config.type; } else { // a no-folder workspace has no launch.config config = Object.create(null); } - unresolvedConfig = unresolvedConfig || deepClone(config); + const unresolvedConfig = deepClone(config); if (noDebug) { - config.noDebug = true; + config!.noDebug = true; } - const debuggerThenable: Promise = type ? Promise.resolve(null) : this.configurationManager.guessDebugger().then(dbgr => { type = dbgr && dbgr.type; }); + const debuggerThenable: Promise = type ? Promise.resolve() : this.configurationManager.guessDebugger().then(dbgr => { type = dbgr && dbgr.type; }); return debuggerThenable.then(() => - this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config).then(config => { + this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config!).then(config => { // a falsy config indicates an aborted launch if (config && config.type) { return this.substituteVariables(launch, config).then(resolvedConfig => { @@ -417,7 +415,7 @@ export class DebugService implements IDebugService { /** * instantiates the new session, initializes the session, registers session listeners and reports telemetry */ - private doCreateSession(root: IWorkspaceFolder, configuration: { resolved: IConfig, unresolved: IConfig }): Promise { + private doCreateSession(root: IWorkspaceFolder | undefined, configuration: { resolved: IConfig, unresolved: IConfig | undefined }): Promise { const session = this.instantiationService.createInstance(DebugSession, configuration, root, this.model); this.model.addSession(session); @@ -455,7 +453,7 @@ export class DebugService implements IDebugService { if (errors.isPromiseCanceledError(error)) { // don't show 'canceled' error messages to the user #7906 - return Promise.resolve(undefined); + return Promise.resolve(false); } // Show the repl if some error got logged there #5870 @@ -465,7 +463,7 @@ export class DebugService implements IDebugService { if (session.configuration && session.configuration.request === 'attach' && session.configuration.__autoAttach) { // ignore attach timeouts in auto attach mode - return Promise.resolve(undefined); + return Promise.resolve(false); } const errorMessage = error instanceof Error ? error.message : error; @@ -476,7 +474,7 @@ export class DebugService implements IDebugService { private launchOrAttachToSession(session: IDebugSession, focus = true): Promise { const dbgr = this.configurationManager.getDebugger(session.configuration.type); - return session.initialize(dbgr).then(() => { + return session.initialize(dbgr!).then(() => { return session.launchOrAttach(session.configuration).then(() => { if (focus) { this.focusStackFrame(undefined, undefined, session); @@ -536,7 +534,7 @@ export class DebugService implements IDebugService { if (this.model.getSessions().length === 0) { this.viewModel.setMultiSessionView(false); - if (this.partService.isVisible(Parts.SIDEBAR_PART) && this.configurationService.getValue('debug').openExplorerOnEnd) { + if (this.layoutService.isVisible(Parts.SIDEBAR_PART) && this.configurationService.getValue('debug').openExplorerOnEnd) { this.viewletService.openViewlet(EXPLORER_VIEWLET_ID); } } @@ -581,7 +579,7 @@ export class DebugService implements IDebugService { // Read the configuration again if a launch.json has been changed, if not just use the inmemory configuration let needsToSubstitute = false; - let unresolved: IConfig; + let unresolved: IConfig | undefined; const launch = session.root ? this.configurationManager.getLaunch(session.root.uri) : undefined; if (launch) { unresolved = launch.getConfiguration(session.configuration.name); @@ -593,12 +591,28 @@ export class DebugService implements IDebugService { } } - let substitutionThenable: Promise = Promise.resolve(session.configuration); - if (needsToSubstitute) { + let substitutionThenable: Promise = Promise.resolve(session.configuration); + if (launch && needsToSubstitute && unresolved) { substitutionThenable = this.configurationManager.resolveConfigurationByProviders(launch.workspace ? launch.workspace.uri : undefined, unresolved.type, unresolved) - .then(resolved => this.substituteVariables(launch, resolved)); + .then(resolved => { + if (resolved) { + // start debugging + return this.substituteVariables(launch, resolved); + } else if (resolved === null) { + // abort debugging silently and open launch.json + return Promise.resolve(null); + } else { + // abort debugging silently + return Promise.resolve(undefined); + } + }); } substitutionThenable.then(resolved => { + + if (!resolved) { + return c(undefined); + } + session.setConfiguration({ resolved, unresolved }); session.configuration.__restart = restartData; @@ -628,7 +642,7 @@ export class DebugService implements IDebugService { return Promise.all(sessions.map(s => s.terminate())); } - private substituteVariables(launch: ILaunch | undefined, config: IConfig): Promise { + private substituteVariables(launch: ILaunch | undefined, config: IConfig): Promise { const dbg = this.configurationManager.getDebugger(config.type); if (dbg) { let folder: IWorkspaceFolder | undefined = undefined; @@ -664,7 +678,7 @@ export class DebugService implements IDebugService { //---- task management - private runTaskAndCheckErrors(root: IWorkspaceFolder, taskId: string | TaskIdentifier): Promise { + private runTaskAndCheckErrors(root: IWorkspaceFolder | undefined, taskId: string | TaskIdentifier | undefined): Promise { const debugAnywayAction = new Action('debug.debugAnyway', nls.localize('debugAnyway', "Debug Anyway"), undefined, true, () => Promise.resolve(TaskRunResult.Success)); return this.runTask(root, taskId).then((taskSummary: ITaskSummary) => { @@ -675,7 +689,7 @@ export class DebugService implements IDebugService { return TaskRunResult.Success; } - const taskLabel = typeof taskId === 'string' ? taskId : taskId.name; + const taskLabel = typeof taskId === 'string' ? taskId : taskId ? taskId.name : ''; const message = errorCount > 1 ? nls.localize('preLaunchTaskErrors', "Errors exist after running preLaunchTask '{0}'.", taskLabel) : errorCount === 1 @@ -693,7 +707,7 @@ export class DebugService implements IDebugService { }); } - private runTask(root: IWorkspaceFolder, taskId: string | TaskIdentifier): Promise { + private runTask(root: IWorkspaceFolder | undefined, taskId: string | TaskIdentifier | undefined): Promise { if (!taskId) { return Promise.resolve(null); } @@ -752,10 +766,10 @@ export class DebugService implements IDebugService { //---- focus management - focusStackFrame(stackFrame: IStackFrame, thread?: IThread, session?: IDebugSession, explicit?: boolean): void { + focusStackFrame(stackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): void { if (!session) { if (stackFrame || thread) { - session = stackFrame ? stackFrame.thread.session : thread.session; + session = stackFrame ? stackFrame.thread.session : thread!.session; } else { const sessions = this.model.getSessions(); const stoppedSession = sessions.filter(s => s.state === State.Stopped).shift(); @@ -776,13 +790,13 @@ export class DebugService implements IDebugService { if (!stackFrame) { if (thread) { const callStack = thread.getCallStack(); - stackFrame = first(callStack, sf => sf.source && sf.source.available && sf.source.presentationHint !== 'deemphasize', undefined); + stackFrame = first(callStack, sf => !!(sf && sf.source && sf.source.available && sf.source.presentationHint !== 'deemphasize'), undefined); } } if (stackFrame) { stackFrame.openInEditor(this.editorService, true); - aria.alert(nls.localize('debuggingPaused', "Debugging paused {0}, {1} {2}", thread.stoppedDetails ? `, reason ${thread.stoppedDetails.reason}` : '', stackFrame.source ? stackFrame.source.name : '', stackFrame.range.startLineNumber)); + aria.alert(nls.localize('debuggingPaused', "Debugging paused {0}, {1} {2}", thread && thread.stoppedDetails ? `, reason ${thread.stoppedDetails.reason}` : '', stackFrame.source ? stackFrame.source.name : '', stackFrame.range.startLineNumber)); } if (session) { this.debugType.set(session.configuration.type); @@ -790,7 +804,7 @@ export class DebugService implements IDebugService { this.debugType.reset(); } - this.viewModel.setFocus(stackFrame, thread, session, explicit); + this.viewModel.setFocus(stackFrame, thread, session, !!explicit); } //---- watches @@ -885,7 +899,6 @@ export class DebugService implements IDebugService { } private sendBreakpoints(modelUri: uri, sourceModified = false, session?: IDebugSession): Promise { - const breakpointsToSend = this.model.getBreakpoints({ uri: modelUri, enabledOnly: true }); return this.sendToOneOrAllSessions(session, s => @@ -894,7 +907,6 @@ export class DebugService implements IDebugService { } private sendFunctionBreakpoints(session?: IDebugSession): Promise { - const breakpointsToSend = this.model.getFunctionBreakpoints().filter(fbp => fbp.enabled && this.model.areBreakpointsActivated()); return this.sendToOneOrAllSessions(session, s => { @@ -903,7 +915,6 @@ export class DebugService implements IDebugService { } private sendExceptionBreakpoints(session?: IDebugSession): Promise { - const enabledExceptionBps = this.model.getExceptionBreakpoints().filter(exb => exb.enabled); return this.sendToOneOrAllSessions(session, s => { @@ -911,7 +922,7 @@ export class DebugService implements IDebugService { }); } - private sendToOneOrAllSessions(session: IDebugSession, send: (session: IDebugSession) => Promise): Promise { + private sendToOneOrAllSessions(session: IDebugSession | undefined, send: (session: IDebugSession) => Promise): Promise { if (session) { return send(session); } @@ -934,7 +945,7 @@ export class DebugService implements IDebugService { } private loadBreakpoints(): Breakpoint[] { - let result: Breakpoint[]; + let result: Breakpoint[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((breakpoint: any) => { return new Breakpoint(uri.parse(breakpoint.uri.external || breakpoint.source.uri.external), breakpoint.lineNumber, breakpoint.column, breakpoint.enabled, breakpoint.condition, breakpoint.hitCondition, breakpoint.logMessage, breakpoint.adapterData, this.textFileService); @@ -945,7 +956,7 @@ export class DebugService implements IDebugService { } private loadFunctionBreakpoints(): FunctionBreakpoint[] { - let result: FunctionBreakpoint[]; + let result: FunctionBreakpoint[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_FUNCTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((fb: any) => { return new FunctionBreakpoint(fb.name, fb.enabled, fb.hitCondition, fb.condition, fb.logMessage); @@ -956,7 +967,7 @@ export class DebugService implements IDebugService { } private loadExceptionBreakpoints(): ExceptionBreakpoint[] { - let result: ExceptionBreakpoint[]; + let result: ExceptionBreakpoint[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_EXCEPTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((exBreakpoint: any) => { return new ExceptionBreakpoint(exBreakpoint.filter, exBreakpoint.label, exBreakpoint.enabled); @@ -967,7 +978,7 @@ export class DebugService implements IDebugService { } private loadWatchExpressions(): Expression[] { - let result: Expression[]; + let result: Expression[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_WATCH_EXPRESSIONS_KEY, StorageScope.WORKSPACE, '[]')).map((watchStoredData: { name: string, id: string }) => { return new Expression(watchStoredData.name, watchStoredData.id); @@ -1015,8 +1026,13 @@ export class DebugService implements IDebugService { //---- telemetry - private telemetryDebugSessionStart(root: IWorkspaceFolder, type: string): Promise { - const extension = this.configurationManager.getDebugger(type).extensionDescription; + private telemetryDebugSessionStart(root: IWorkspaceFolder | undefined, type: string): Promise { + const dbgr = this.configurationManager.getDebugger(type); + if (!dbgr) { + return Promise.resolve(); + } + + const extension = dbgr.getMainExtensionDescriptor(); /* __GDPR__ "debugSessionStart" : { "type": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, @@ -1061,7 +1077,7 @@ export class DebugService implements IDebugService { }); } - private telemetryDebugMisconfiguration(debugType: string, message: string): Promise { + private telemetryDebugMisconfiguration(debugType: string | undefined, message: string): Promise { /* __GDPR__ "debugMisconfiguration" : { "type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, diff --git a/src/vs/workbench/parts/debug/electron-browser/debugSession.ts b/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts similarity index 86% rename from src/vs/workbench/parts/debug/electron-browser/debugSession.ts rename to src/vs/workbench/contrib/debug/electron-browser/debugSession.ts index 6ff080503e..18568c251a 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/debugSession.ts @@ -9,15 +9,15 @@ import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; import severity from 'vs/base/common/severity'; import { Event, Emitter } from 'vs/base/common/event'; -import { CompletionItem, completionKindFromLegacyString } from 'vs/editor/common/modes'; +import { CompletionItem, completionKindFromString } from 'vs/editor/common/modes'; import { Position } from 'vs/editor/common/core/position'; import * as aria from 'vs/base/browser/ui/aria/aria'; -import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource } from 'vs/workbench/parts/debug/common/debug'; -import { Source } from 'vs/workbench/parts/debug/common/debugSource'; +import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource } from 'vs/workbench/contrib/debug/common/debug'; +import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { mixin } from 'vs/base/common/objects'; -import { Thread, ExpressionContainer, DebugModel } from 'vs/workbench/parts/debug/common/debugModel'; -import { RawDebugSession } from 'vs/workbench/parts/debug/electron-browser/rawDebugSession'; -import product from 'vs/platform/node/product'; +import { Thread, ExpressionContainer, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel'; +import { RawDebugSession } from 'vs/workbench/contrib/debug/electron-browser/rawDebugSession'; +import product from 'vs/platform/product/node/product'; import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -25,17 +25,19 @@ import { generateUuid } from 'vs/base/common/uuid'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { normalizeDriveLetter } from 'vs/base/common/labels'; -import { IOutputService } from 'vs/workbench/parts/output/common/output'; +import { IOutputService } from 'vs/workbench/contrib/output/common/output'; import { Range } from 'vs/editor/common/core/range'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { ReplModel } from 'vs/workbench/parts/debug/common/replModel'; +import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { INotificationService } from 'vs/platform/notification/common/notification'; export class DebugSession implements IDebugSession { private id: string; - private raw: RawDebugSession; + private raw: RawDebugSession | undefined; + private initialized = false; private sources = new Map(); private threads = new Map(); @@ -52,7 +54,7 @@ export class DebugSession implements IDebugSession { private readonly _onDidChangeREPLElements = new Emitter(); constructor( - private _configuration: { resolved: IConfig, unresolved: IConfig }, + private _configuration: { resolved: IConfig, unresolved: IConfig | undefined }, public root: IWorkspaceFolder, private model: DebugModel, @IDebugService private readonly debugService: IDebugService, @@ -62,7 +64,8 @@ export class DebugSession implements IDebugSession { @IConfigurationService private readonly configurationService: IConfigurationService, @IViewletService private readonly viewletService: IViewletService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @INotificationService private readonly notificationService: INotificationService ) { this.id = generateUuid(); this.repl = new ReplModel(this); @@ -76,11 +79,11 @@ export class DebugSession implements IDebugSession { return this._configuration.resolved; } - get unresolvedConfiguration(): IConfig { + get unresolvedConfiguration(): IConfig | undefined { return this._configuration.unresolved; } - setConfiguration(configuration: { resolved: IConfig, unresolved: IConfig }) { + setConfiguration(configuration: { resolved: IConfig, unresolved: IConfig | undefined }) { this._configuration = configuration; } @@ -90,6 +93,9 @@ export class DebugSession implements IDebugSession { } get state(): State { + if (!this.initialized) { + return State.Initializing; + } if (!this.raw) { return State.Inactive; } @@ -150,11 +156,11 @@ export class DebugSession implements IDebugSession { this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.environmentService); - return this.raw.start().then(() => { + return this.raw!.start().then(() => { this.registerListeners(); - return this.raw.initialize({ + return this.raw!.initialize({ clientID: 'vscode', clientName: product.nameLong, adapterID: this.configuration.type, @@ -166,8 +172,9 @@ export class DebugSession implements IDebugSession { supportsRunInTerminalRequest: true, // #10574 locale: platform.locale }).then(() => { + this.initialized = true; this._onDidChangeState.fire(); - this.model.setExceptionBreakpoints(this.raw.capabilities.exceptionBreakpointFilters); + this.model.setExceptionBreakpoints(this.raw!.capabilities.exceptionBreakpointFilters || []); }); }); }); @@ -252,7 +259,9 @@ export class DebugSession implements IDebugSession { rawSource.adapterData = breakpointsToSend[0].adapterData; } // Normalize all drive letters going out from vscode to debug adapters so we are consistent with our resolving #43959 - rawSource.path = normalizeDriveLetter(rawSource.path); + if (rawSource.path) { + rawSource.path = normalizeDriveLetter(rawSource.path); + } return this.raw.setBreakpoints({ source: rawSource, @@ -315,7 +324,7 @@ export class DebugSession implements IDebugSession { return Promise.reject(new Error('no debug adapter')); } - exceptionInfo(threadId: number): Promise { + exceptionInfo(threadId: number): Promise { if (this.raw) { return this.raw.exceptionInfo({ threadId }).then(response => { if (response) { @@ -326,7 +335,7 @@ export class DebugSession implements IDebugSession { details: response.body.details }; } - return null; + return undefined; }); } return Promise.reject(new Error('no debug adapter')); @@ -339,11 +348,11 @@ export class DebugSession implements IDebugSession { return Promise.reject(new Error('no debug adapter')); } - variables(variablesReference: number, filter: 'indexed' | 'named', start: number, count: number): Promise { + variables(variablesReference: number, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise { if (this.raw) { return this.raw.variables({ variablesReference, filter, start, count }); } - return Promise.resolve(undefined); + return Promise.reject(new Error('no debug adapter')); } evaluate(expression: string, frameId: number, context?: string): Promise { @@ -436,7 +445,7 @@ export class DebugSession implements IDebugSession { } else { // create a Source - let sourceRef: number; + let sourceRef: number | undefined; if (resource.query) { const data = Source.getEncodedDebugData(resource); sourceRef = data.sourceReference; @@ -448,7 +457,7 @@ export class DebugSession implements IDebugSession { }; } - return this.raw.source({ sourceReference: rawSource.sourceReference, source: rawSource }); + return this.raw.source({ sourceReference: rawSource.sourceReference || 0, source: rawSource }); } getLoadedSources(): Promise { @@ -466,7 +475,7 @@ export class DebugSession implements IDebugSession { return Promise.reject(new Error('no debug adapter')); } - completions(frameId: number, text: string, position: Position, overwriteBefore: number): Promise { + completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number): Promise { if (this.raw) { return this.raw.completions({ frameId, @@ -482,8 +491,8 @@ export class DebugSession implements IDebugSession { result.push({ label: item.label, insertText: item.text || item.label, - kind: completionKindFromLegacyString(item.type), - filterText: item.start && item.length && text.substr(item.start, item.length).concat(item.label), + kind: completionKindFromString(item.type || 'property'), + filterText: (item.start && item.length) ? text.substr(item.start, item.length).concat(item.label) : undefined, range: Range.fromPositions(position.delta(0, -(item.length || overwriteBefore)), position) }); } @@ -498,7 +507,7 @@ export class DebugSession implements IDebugSession { //---- threads - getThread(threadId: number): Thread { + getThread(threadId: number): Thread | undefined { return this.threads.get(threadId); } @@ -510,8 +519,8 @@ export class DebugSession implements IDebugSession { clearThreads(removeThreads: boolean, reference: number | undefined = undefined): void { if (reference !== undefined && reference !== null) { - if (this.threads.has(reference)) { - const thread = this.threads.get(reference); + const thread = this.threads.get(reference); + if (thread) { thread.clearCallStack(); thread.stoppedDetails = undefined; thread.stopped = false; @@ -535,30 +544,37 @@ export class DebugSession implements IDebugSession { } rawUpdate(data: IRawModelUpdate): void { + data.threads.forEach(thread => { + if (!this.threads.has(thread.id)) { + // A new thread came in, initialize it. + this.threads.set(thread.id, new Thread(this, thread.name, thread.id)); + } else if (thread.name) { + // Just the thread name got updated #18244 + const oldThread = this.threads.get(thread.id); + if (oldThread) { + oldThread.name = thread.name; + } + } + }); - if (data.thread && !this.threads.has(data.threadId)) { - // A new thread came in, initialize it. - this.threads.set(data.threadId, new Thread(this, data.thread.name, data.thread.id)); - } else if (data.thread && data.thread.name) { - // Just the thread name got updated #18244 - this.threads.get(data.threadId).name = data.thread.name; - } - - if (data.stoppedDetails) { + const stoppedDetails = data.stoppedDetails; + if (stoppedDetails) { // Set the availability of the threads' callstacks depending on // whether the thread is stopped or not - if (data.stoppedDetails.allThreadsStopped) { + if (stoppedDetails.allThreadsStopped) { this.threads.forEach(thread => { - thread.stoppedDetails = thread.threadId === data.threadId ? data.stoppedDetails : { reason: undefined }; + thread.stoppedDetails = thread.threadId === stoppedDetails.threadId ? stoppedDetails : { reason: undefined }; thread.stopped = true; thread.clearCallStack(); }); - } else if (this.threads.has(data.threadId)) { - // One thread is stopped, only update that thread. - const thread = this.threads.get(data.threadId); - thread.stoppedDetails = data.stoppedDetails; - thread.clearCallStack(); - thread.stopped = true; + } else { + const thread = typeof stoppedDetails.threadId === 'number' ? this.threads.get(stoppedDetails.threadId) : undefined; + if (thread) { + // One thread is stopped, only update that thread. + thread.stoppedDetails = stoppedDetails; + thread.clearCallStack(); + thread.stopped = true; + } } } } @@ -566,13 +582,10 @@ export class DebugSession implements IDebugSession { private fetchThreads(stoppedDetails?: IRawStoppedDetails): Promise { return this.raw ? this.raw.threads().then(response => { if (response && response.body && response.body.threads) { - response.body.threads.forEach(thread => { - this.model.rawUpdate({ - sessionId: this.getId(), - threadId: thread.id, - thread, - stoppedDetails: stoppedDetails && thread.id === stoppedDetails.threadId ? stoppedDetails : undefined - }); + this.model.rawUpdate({ + sessionId: this.getId(), + threads: response.body.threads, + stoppedDetails }); } }) : Promise.resolve(undefined); @@ -581,6 +594,10 @@ export class DebugSession implements IDebugSession { //---- private private registerListeners(): void { + if (!this.raw) { + return; + } + this.rawListeners.push(this.raw.onDidInitialize(() => { aria.status(nls.localize('debuggingStarted', "Debugging started.")); const sendConfigurationDone = () => { @@ -591,7 +608,7 @@ export class DebugSession implements IDebugSession { this.raw.disconnect(); } if (e.command !== 'canceled' && e.message !== 'canceled') { - onUnexpectedError(e); + this.notificationService.error(e); } }); } @@ -606,11 +623,12 @@ export class DebugSession implements IDebugSession { this.rawListeners.push(this.raw.onDidStop(event => { this.fetchThreads(event.body).then(() => { - const thread = this.getThread(event.body.threadId); + const thread = typeof event.body.threadId === 'number' ? this.getThread(event.body.threadId) : undefined; if (thread) { // Call fetch call stack twice, the first only return the top stack frame. // Second retrieves the rest of the call stack. For performance reasons #25605 - this.model.fetchCallStack(thread).then(() => { + const promises = this.model.fetchCallStack(thread); + const focus = () => { if (!event.body.preserveFocusHint && thread.getCallStack().length) { this.debugService.focusStackFrame(undefined, thread); if (thread.stoppedDetails) { @@ -620,6 +638,14 @@ export class DebugSession implements IDebugSession { this.windowService.focusWindow(); } } + }; + + promises.topCallStack.then(focus); + promises.wholeCallStack.then(() => { + if (!this.debugService.getViewModel().focusedStackFrame) { + // The top stack frame can be deemphesized so try to focus again #68616 + focus(); + } }); } }).then(() => this._onDidChangeState.fire()); @@ -646,7 +672,7 @@ export class DebugSession implements IDebugSession { aria.status(nls.localize('debuggingStopped', "Debugging stopped.")); if (event.body && event.body.restart) { this.debugService.restartSession(this, event.body.restart).then(undefined, onUnexpectedError); - } else { + } else if (this.raw) { this.raw.disconnect(); } })); @@ -659,7 +685,7 @@ export class DebugSession implements IDebugSession { let outpuPromises: Promise[] = []; this.rawListeners.push(this.raw.onDidOutput(event => { - if (!event.body) { + if (!event.body || !this.raw) { return; } @@ -677,7 +703,7 @@ export class DebugSession implements IDebugSession { // Make sure to append output in the correct order by properly waiting on preivous promises #33822 const waitFor = outpuPromises.slice(); - const source = event.body.source ? { + const source = event.body.source && event.body.line ? { lineNumber: event.body.line, column: event.body.column ? event.body.column : 1, source: this.getSource(event.body.source) @@ -687,7 +713,7 @@ export class DebugSession implements IDebugSession { outpuPromises.push(container.getChildren().then(children => { return Promise.all(waitFor).then(() => children.forEach(child => { // Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names) - child.name = null; + (child).name = null; this.appendToRepl(child, outputSeverity, source); })); })); @@ -702,7 +728,7 @@ export class DebugSession implements IDebugSession { const breakpoint = this.model.getBreakpoints().filter(bp => bp.idFromAdapter === id).pop(); const functionBreakpoint = this.model.getFunctionBreakpoints().filter(bp => bp.idFromAdapter === id).pop(); - if (event.body.reason === 'new' && event.body.breakpoint.source) { + if (event.body.reason === 'new' && event.body.breakpoint.source && event.body.breakpoint.line) { const source = this.getSource(event.body.breakpoint.source); const bps = this.model.addBreakpoints(source.uri, [{ column: event.body.breakpoint.column, @@ -754,7 +780,6 @@ export class DebugSession implements IDebugSession { shutdown(): void { dispose(this.rawListeners); - this.fetchThreadsScheduler = undefined; if (this.raw) { this.raw.disconnect(); } @@ -765,11 +790,11 @@ export class DebugSession implements IDebugSession { //---- sources - getSourceForUri(uri: URI): Source { + getSourceForUri(uri: URI): Source | undefined { return this.sources.get(this.getUriKey(uri)); } - getSource(raw: DebugProtocol.Source): Source { + getSource(raw?: DebugProtocol.Source): Source { let source = new Source(raw, this.getId()); const uriKey = this.getUriKey(source.uri); const found = this.sources.get(uriKey); @@ -804,7 +829,7 @@ export class DebugSession implements IDebugSession { this._onDidChangeREPLElements.fire(); } - addReplExpression(stackFrame: IStackFrame, name: string): Promise { + addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise { const viewModel = this.debugService.getViewModel(); return this.repl.addReplExpression(stackFrame, name) .then(() => this._onDidChangeREPLElements.fire()) diff --git a/src/vs/workbench/parts/debug/electron-browser/media/continue-tb.png b/src/vs/workbench/contrib/debug/electron-browser/media/continue-tb.png similarity index 100% rename from src/vs/workbench/parts/debug/electron-browser/media/continue-tb.png rename to src/vs/workbench/contrib/debug/electron-browser/media/continue-tb.png diff --git a/src/vs/workbench/parts/debug/electron-browser/media/continue-without-debugging-tb.png b/src/vs/workbench/contrib/debug/electron-browser/media/continue-without-debugging-tb.png similarity index 100% rename from src/vs/workbench/parts/debug/electron-browser/media/continue-without-debugging-tb.png rename to src/vs/workbench/contrib/debug/electron-browser/media/continue-without-debugging-tb.png diff --git a/src/vs/workbench/parts/debug/electron-browser/media/pause-tb.png b/src/vs/workbench/contrib/debug/electron-browser/media/pause-tb.png similarity index 100% rename from src/vs/workbench/parts/debug/electron-browser/media/pause-tb.png rename to src/vs/workbench/contrib/debug/electron-browser/media/pause-tb.png diff --git a/src/vs/workbench/parts/debug/electron-browser/media/restart-tb.png b/src/vs/workbench/contrib/debug/electron-browser/media/restart-tb.png similarity index 100% rename from src/vs/workbench/parts/debug/electron-browser/media/restart-tb.png rename to src/vs/workbench/contrib/debug/electron-browser/media/restart-tb.png diff --git a/src/vs/workbench/parts/debug/electron-browser/media/stepinto-tb.png b/src/vs/workbench/contrib/debug/electron-browser/media/stepinto-tb.png similarity index 100% rename from src/vs/workbench/parts/debug/electron-browser/media/stepinto-tb.png rename to src/vs/workbench/contrib/debug/electron-browser/media/stepinto-tb.png diff --git a/src/vs/workbench/parts/debug/electron-browser/media/stepout-tb.png b/src/vs/workbench/contrib/debug/electron-browser/media/stepout-tb.png similarity index 100% rename from src/vs/workbench/parts/debug/electron-browser/media/stepout-tb.png rename to src/vs/workbench/contrib/debug/electron-browser/media/stepout-tb.png diff --git a/src/vs/workbench/parts/debug/electron-browser/media/stepover-tb.png b/src/vs/workbench/contrib/debug/electron-browser/media/stepover-tb.png similarity index 100% rename from src/vs/workbench/parts/debug/electron-browser/media/stepover-tb.png rename to src/vs/workbench/contrib/debug/electron-browser/media/stepover-tb.png diff --git a/src/vs/workbench/parts/debug/electron-browser/media/stop-tb.png b/src/vs/workbench/contrib/debug/electron-browser/media/stop-tb.png similarity index 100% rename from src/vs/workbench/parts/debug/electron-browser/media/stop-tb.png rename to src/vs/workbench/contrib/debug/electron-browser/media/stop-tb.png diff --git a/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/electron-browser/rawDebugSession.ts similarity index 96% rename from src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts rename to src/vs/workbench/contrib/debug/electron-browser/rawDebugSession.ts index 3ac49b2034..a85a35ac17 100644 --- a/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/rawDebugSession.ts @@ -9,8 +9,8 @@ import * as objects from 'vs/base/common/objects'; import { Action } from 'vs/base/common/actions'; import * as errors from 'vs/base/common/errors'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { formatPII, isUri } from 'vs/workbench/parts/debug/common/debugUtils'; -import { IDebugAdapter, IConfig, AdapterEndEvent, IDebugger } from 'vs/workbench/parts/debug/common/debug'; +import { formatPII, isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; +import { IDebugAdapter, IConfig, AdapterEndEvent, IDebugger } from 'vs/workbench/contrib/debug/common/debug'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import * as cp from 'child_process'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -64,14 +64,16 @@ export class RawDebugSession { // DA events private readonly _onDidExitAdapter: Emitter; + private debugAdapter: IDebugAdapter | null; constructor( - private debugAdapter: IDebugAdapter, + debugAdapter: IDebugAdapter, dbgr: IDebugger, private telemetryService: ITelemetryService, - public customTelemetryService: ITelemetryService, + public customTelemetryService: ITelemetryService | undefined, private environmentService: IEnvironmentService ) { + this.debugAdapter = debugAdapter; this._capabilities = Object.create(null); this._readyForBreakpoints = false; this.inShutdown = false; @@ -224,6 +226,9 @@ export class RawDebugSession { * Starts the underlying debug adapter and tracks the session time for telemetry. */ public start(): Promise { + if (!this.debugAdapter) { + return Promise.reject(new Error('no debug adapter')); + } return this.debugAdapter.startSession().then(() => { this.startTime = new Date().getTime(); }, err => { @@ -560,7 +565,7 @@ export class RawDebugSession { let spawnArgs = vscodeArgs.args.map(a => { if ((a.prefix === '--file-uri=' || a.prefix === '--folder-uri=') && !isUri(a.path)) { - return a.path; + return (a.path || ''); } return (a.prefix || '') + (a.path || ''); }); @@ -576,8 +581,8 @@ export class RawDebugSession { // guess the VS Code workspace path from the executable const vscodeWorkspacePath = runtimeExecutable.substr(0, electronIdx); - // only add path if user hasn't already added that path - const x = spawnArgs.filter(a => a.indexOf(vscodeWorkspacePath) >= 0); + // only add VS Code workspace path if user hasn't already added that path as a (folder) argument + const x = spawnArgs.filter(a => a.indexOf(vscodeWorkspacePath) === 0); if (x.length === 0) { spawnArgs.unshift(vscodeWorkspacePath); } @@ -622,6 +627,10 @@ export class RawDebugSession { private send(command: string, args: any, timeout?: number): Promise { return new Promise((completeDispatch, errorDispatch) => { + if (!this.debugAdapter) { + errorDispatch(new Error('no debug adapter found')); + return; + } this.debugAdapter.sendRequest(command, args, (response: R) => { if (response.success) { completeDispatch(response); @@ -639,7 +648,7 @@ export class RawDebugSession { } const error = errorResponse && errorResponse.body ? errorResponse.body.error : null; - const errorMessage = errorResponse ? errorResponse.message : ''; + const errorMessage = errorResponse ? errorResponse.message || '' : ''; if (error && error.sendTelemetry) { const telemetryMessage = error ? formatPII(error.format, true, error.variables) : errorMessage; @@ -650,7 +659,7 @@ export class RawDebugSession { if (error && error.url) { const label = error.urlLabel ? error.urlLabel : nls.localize('moreInfo', "More Info"); return createErrorWithActions(userMessage, { - actions: [new Action('debug.moreInfo', label, null, true, () => { + actions: [new Action('debug.moreInfo', label, undefined, true, () => { window.open(error.url); return Promise.resolve(null); })] @@ -660,7 +669,7 @@ export class RawDebugSession { return new Error(userMessage); } - private mergeCapabilities(capabilities: DebugProtocol.Capabilities): void { + private mergeCapabilities(capabilities: DebugProtocol.Capabilities | undefined): void { if (capabilities) { this._capabilities = objects.mixin(this._capabilities, capabilities); } @@ -674,11 +683,11 @@ export class RawDebugSession { threadId, allThreadsContinued }, - seq: undefined + seq: undefined! }); } - private telemetryDebugProtocolErrorResponse(telemetryMessage: string) { + private telemetryDebugProtocolErrorResponse(telemetryMessage: string | undefined) { /* __GDPR__ "debugProtocolErrorResponse" : { "error" : { "classification": "CallstackOrException", "purpose": "FeatureInsight" } diff --git a/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts b/src/vs/workbench/contrib/debug/electron-browser/terminalSupport.ts similarity index 71% rename from src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts rename to src/vs/workbench/contrib/debug/electron-browser/terminalSupport.ts index a120124a23..71756e0815 100644 --- a/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/terminalSupport.ts @@ -5,39 +5,40 @@ import * as nls from 'vs/nls'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { ITerminalService, ITerminalInstance } from 'vs/workbench/parts/terminal/common/terminal'; -import { ITerminalService as IExternalTerminalService } from 'vs/workbench/parts/execution/common/execution'; -import { ITerminalLauncher, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug'; -import { hasChildProcesses, prepareCommand } from 'vs/workbench/parts/debug/node/terminals'; +import { ITerminalService, ITerminalInstance } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal'; +import { ITerminalLauncher, ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug'; +import { hasChildProcesses, prepareCommand } from 'vs/workbench/contrib/debug/node/terminals'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export class TerminalLauncher implements ITerminalLauncher { - private integratedTerminalInstance: ITerminalInstance; + private integratedTerminalInstance: ITerminalInstance | undefined; private terminalDisposedListener: IDisposable; constructor( @ITerminalService private readonly terminalService: ITerminalService, - @IExternalTerminalService private readonly nativeTerminalService: IExternalTerminalService + @IExternalTerminalService private readonly externalTerminalService: IExternalTerminalService ) { } runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise { if (args.kind === 'external') { - return this.nativeTerminalService.runInTerminal(args.title, args.cwd, args.args, args.env || {}); + return this.externalTerminalService.runInTerminal(args.title || '', args.cwd, args.args, args.env || {}); } if (!this.terminalDisposedListener) { // React on terminal disposed and check if that is the debug terminal #12956 this.terminalDisposedListener = this.terminalService.onInstanceDisposed(terminal => { if (this.integratedTerminalInstance && this.integratedTerminalInstance.id === terminal.id) { - this.integratedTerminalInstance = null; + this.integratedTerminalInstance = undefined; } }); } let t = this.integratedTerminalInstance; - if ((t && hasChildProcesses(t.processId)) || !t) { + if ((t && (typeof t.processId === 'number') && hasChildProcesses(t.processId)) || !t) { t = this.terminalService.createTerminal({ name: args.title || nls.localize('debug.terminal.title', "debuggee") }); this.integratedTerminalInstance = t; } @@ -45,14 +46,13 @@ export class TerminalLauncher implements ITerminalLauncher { this.terminalService.showPanel(true); return new Promise((resolve, error) => { - - if (typeof t.processId === 'number') { + if (t && typeof t.processId === 'number') { // no need to wait resolve(t.processId); } // shell not ready: wait for ready event - const toDispose = t.onProcessIdReady(t => { + const toDispose = t!.onProcessIdReady(t => { toDispose.dispose(); resolve(t.processId); }); @@ -65,7 +65,7 @@ export class TerminalLauncher implements ITerminalLauncher { }).then(shellProcessId => { const command = prepareCommand(args, config); - t.sendText(command, true); + t!.sendText(command, true); return shellProcessId; }); diff --git a/src/vs/workbench/parts/debug/node/debugAdapter.ts b/src/vs/workbench/contrib/debug/node/debugAdapter.ts similarity index 79% rename from src/vs/workbench/parts/debug/node/debugAdapter.ts rename to src/vs/workbench/contrib/debug/node/debugAdapter.ts index 2da839e9af..9c767aae3c 100644 --- a/src/vs/workbench/parts/debug/node/debugAdapter.ts +++ b/src/vs/workbench/contrib/debug/node/debugAdapter.ts @@ -3,20 +3,20 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; +import { exists } from 'vs/base/node/pfs'; import * as cp from 'child_process'; import * as stream from 'stream'; import * as nls from 'vs/nls'; import * as net from 'net'; -import * as paths from 'vs/base/common/paths'; +import * as path from 'vs/base/common/path'; import * as strings from 'vs/base/common/strings'; import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; import { ExtensionsChannelId } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import { IOutputService } from 'vs/workbench/parts/output/common/output'; -import { IDebugAdapter, IDebugAdapterExecutable, IDebuggerContribution, IPlatformSpecificAdapterContribution, IDebugAdapterServer } from 'vs/workbench/parts/debug/common/debug'; +import { IOutputService } from 'vs/workbench/contrib/output/common/output'; +import { IDebugAdapter, IDebugAdapterExecutable, IDebuggerContribution, IPlatformSpecificAdapterContribution, IDebugAdapterServer } from 'vs/workbench/contrib/debug/common/debug'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; /** * Abstract implementation of the low level API for a debug adapter. @@ -31,7 +31,7 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { private messageCallback: (message: DebugProtocol.ProtocolMessage) => void; protected readonly _onError: Emitter; - protected readonly _onExit: Emitter; + protected readonly _onExit: Emitter; constructor() { this.sequence = 1; @@ -50,7 +50,7 @@ export abstract class AbstractDebugAdapter implements IDebugAdapter { return this._onError.event; } - get onExit(): Event { + get onExit(): Event { return this._onExit.event; } @@ -255,7 +255,7 @@ export abstract class StreamDebugAdapter extends AbstractDebugAdapter { */ export class SocketDebugAdapter extends StreamDebugAdapter { - private socket: net.Socket; + private socket?: net.Socket; constructor(private adapterServer: IDebugAdapterServer) { super(); @@ -265,8 +265,8 @@ export class SocketDebugAdapter extends StreamDebugAdapter { return new Promise((resolve, reject) => { let connected = false; this.socket = net.createConnection(this.adapterServer.port, this.adapterServer.host || '127.0.0.1', () => { - this.connect(this.socket, this.socket); - resolve(null); + this.connect(this.socket!, this.socket!); + resolve(); connected = true; }); this.socket.on('close', () => { @@ -306,69 +306,71 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { private serverProcess: cp.ChildProcess; - constructor(private adapterExecutable: IDebugAdapterExecutable, private debugType: string, private outputService?: IOutputService) { + constructor(private adapterExecutable: IDebugAdapterExecutable, private debugType: string, private readonly outputService?: IOutputService) { super(); } - startSession(): Promise { + async startSession(): Promise { - return new Promise((resolve, reject) => { + const command = this.adapterExecutable.command; + const args = this.adapterExecutable.args; + const options = this.adapterExecutable.options || {}; - // verify executables - if (this.adapterExecutable.command) { - if (paths.isAbsolute(this.adapterExecutable.command)) { - if (!fs.existsSync(this.adapterExecutable.command)) { - reject(new Error(nls.localize('debugAdapterBinNotFound', "Debug adapter executable '{0}' does not exist.", this.adapterExecutable.command))); + try { + // verify executables asynchronously + if (command) { + if (path.isAbsolute(command)) { + const commandExists = await exists(command); + if (!commandExists) { + throw new Error(nls.localize('debugAdapterBinNotFound', "Debug adapter executable '{0}' does not exist.", command)); } } else { // relative path - if (this.adapterExecutable.command.indexOf('/') < 0 && this.adapterExecutable.command.indexOf('\\') < 0) { + if (command.indexOf('/') < 0 && command.indexOf('\\') < 0) { // no separators: command looks like a runtime name like 'node' or 'mono' // TODO: check that the runtime is available on PATH } } } else { - reject(new Error(nls.localize({ key: 'debugAdapterCannotDetermineExecutable', comment: ['Adapter executable file not found'] }, - "Cannot determine executable for debug adapter '{0}'.", this.debugType))); + throw new Error(nls.localize({ key: 'debugAdapterCannotDetermineExecutable', comment: ['Adapter executable file not found'] }, + "Cannot determine executable for debug adapter '{0}'.", this.debugType)); } let env = objects.mixin({}, process.env); - if (this.adapterExecutable.options && this.adapterExecutable.options.env) { - env = objects.mixin(env, this.adapterExecutable.options.env); + if (options.env) { + env = objects.mixin(env, options.env); } delete env.VSCODE_PREVENT_FOREIGN_INSPECT; - if (this.adapterExecutable.command === 'node') { - if (Array.isArray(this.adapterExecutable.args) && this.adapterExecutable.args.length > 0) { + if (command === 'node') { + if (Array.isArray(args) && args.length > 0) { const isElectron = !!process.env['ELECTRON_RUN_AS_NODE'] || !!process.versions['electron']; - const options: cp.ForkOptions = { + const forkOptions: cp.ForkOptions = { env: env, execArgv: isElectron ? ['-e', 'delete process.env.ELECTRON_RUN_AS_NODE;require(process.argv[1])'] : [], silent: true }; - if (this.adapterExecutable.options && this.adapterExecutable.options.cwd) { - options.cwd = this.adapterExecutable.options.cwd; + if (options.cwd) { + forkOptions.cwd = options.cwd; } - const child = cp.fork(this.adapterExecutable.args[0], this.adapterExecutable.args.slice(1), options); + const child = cp.fork(args[0], args.slice(1), forkOptions); if (!child.pid) { - reject(new Error(nls.localize('unableToLaunchDebugAdapter', "Unable to launch debug adapter from '{0}'.", this.adapterExecutable.args[0]))); + throw new Error(nls.localize('unableToLaunchDebugAdapter', "Unable to launch debug adapter from '{0}'.", args[0])); } this.serverProcess = child; - resolve(null); } else { - reject(new Error(nls.localize('unableToLaunchDebugAdapterNoArgs', "Unable to launch debug adapter."))); + throw new Error(nls.localize('unableToLaunchDebugAdapterNoArgs', "Unable to launch debug adapter.")); } } else { - const options: cp.SpawnOptions = { + const spawnOptions: cp.SpawnOptions = { env: env }; - if (this.adapterExecutable.options && this.adapterExecutable.options.cwd) { - options.cwd = this.adapterExecutable.options.cwd; + if (options.cwd) { + spawnOptions.cwd = options.cwd; } - this.serverProcess = cp.spawn(this.adapterExecutable.command, this.adapterExecutable.args, options); - resolve(null); + this.serverProcess = cp.spawn(command, args, spawnOptions); } - }).then(_ => { + this.serverProcess.on('error', err => { this._onError.fire(err); }); @@ -387,20 +389,26 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { this._onError.fire(error); }); - if (this.outputService) { + const outputService = this.outputService; + if (outputService) { const sanitize = (s: string) => s.toString().replace(/\r?\n$/mg, ''); // this.serverProcess.stdout.on('data', (data: string) => { // console.log('%c' + sanitize(data), 'background: #ddd; font-style: italic;'); // }); this.serverProcess.stderr.on('data', (data: string) => { - this.outputService.getChannel(ExtensionsChannelId).append(sanitize(data)); + const channel = outputService.getChannel(ExtensionsChannelId); + if (channel) { + channel.append(sanitize(data)); + } }); } + // finally connect to the DA this.connect(this.serverProcess.stdout, this.serverProcess.stdin); - }, (err: Error) => { + + } catch (err) { this._onError.fire(err); - }); + } } stopSession(): Promise { @@ -431,33 +439,35 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { } } - private static extract(contribution: IDebuggerContribution, extensionFolderPath: string): IDebuggerContribution { - if (!contribution) { + private static extract(platformContribution: IPlatformSpecificAdapterContribution, extensionFolderPath: string): IDebuggerContribution | undefined { + if (!platformContribution) { return undefined; } const result: IDebuggerContribution = Object.create(null); - if (contribution.runtime) { - if (contribution.runtime.indexOf('./') === 0) { // TODO - result.runtime = paths.join(extensionFolderPath, contribution.runtime); + if (platformContribution.runtime) { + if (platformContribution.runtime.indexOf('./') === 0) { // TODO + result.runtime = path.join(extensionFolderPath, platformContribution.runtime); } else { - result.runtime = contribution.runtime; + result.runtime = platformContribution.runtime; } } - if (contribution.runtimeArgs) { - result.runtimeArgs = contribution.runtimeArgs; + if (platformContribution.runtimeArgs) { + result.runtimeArgs = platformContribution.runtimeArgs; } - if (contribution.program) { - if (!paths.isAbsolute(contribution.program)) { - result.program = paths.join(extensionFolderPath, contribution.program); + if (platformContribution.program) { + if (!path.isAbsolute(platformContribution.program)) { + result.program = path.join(extensionFolderPath, platformContribution.program); } else { - result.program = contribution.program; + result.program = platformContribution.program; } } - if (contribution.args) { - result.args = contribution.args; + if (platformContribution.args) { + result.args = platformContribution.args; } + const contribution = platformContribution as IDebuggerContribution; + if (contribution.win) { result.win = ExecutableDebugAdapter.extract(contribution.win, extensionFolderPath); } @@ -485,8 +495,8 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { if (ed.contributes) { const debuggers = ed.contributes['debuggers']; if (debuggers && debuggers.length > 0) { - debuggers.filter(dbg => strings.equalsIgnoreCase(dbg.type, debugType)).forEach(dbg => { - // extract relevant attributes and make then absolute where needed + debuggers.filter(dbg => typeof dbg.type === 'string' && strings.equalsIgnoreCase(dbg.type, debugType)).forEach(dbg => { + // extract relevant attributes and make them absolute where needed const extractedDbg = ExecutableDebugAdapter.extract(dbg, ed.extensionLocation.fsPath); // merge @@ -497,7 +507,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { } // select the right platform - let platformInfo: IPlatformSpecificAdapterContribution; + let platformInfo: IPlatformSpecificAdapterContribution | undefined; if (platform.isWindows && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432')) { platformInfo = result.winx86 || result.win || result.windows; } else if (platform.isWindows) { @@ -519,7 +529,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { return { type: 'executable', command: runtime, - args: (runtimeArgs || []).concat([program]).concat(args || []) + args: (runtimeArgs || []).concat(typeof program === 'string' ? [program] : []).concat(args || []) }; } else if (program) { return { diff --git a/src/vs/workbench/parts/debug/node/debugger.ts b/src/vs/workbench/contrib/debug/node/debugger.ts similarity index 74% rename from src/vs/workbench/parts/debug/node/debugger.ts rename to src/vs/workbench/contrib/debug/node/debugger.ts index b86d37dc3e..1e10b033ff 100644 --- a/src/vs/workbench/parts/debug/node/debugger.ts +++ b/src/vs/workbench/contrib/debug/node/debugger.ts @@ -7,38 +7,94 @@ import * as nls from 'vs/nls'; import { Client as TelemetryClient } from 'vs/base/parts/ipc/node/ipc.cp'; import * as strings from 'vs/base/common/strings'; import * as objects from 'vs/base/common/objects'; +import { isObject } from 'vs/base/common/types'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IConfig, IDebuggerContribution, IDebugAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, IDebugConfiguration, ITerminalSettings, IDebugger, IDebugSession, IAdapterDescriptor, IDebugAdapterServer } from 'vs/workbench/parts/debug/common/debug'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { IConfig, IDebuggerContribution, IDebugAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager, IDebugAdapter, ITerminalSettings, IDebugger, IDebugSession, IAdapterDescriptor, IDebugAdapterServer } from 'vs/workbench/contrib/debug/common/debug'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IOutputService } from 'vs/workbench/parts/output/common/output'; -import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter'; +import { IOutputService } from 'vs/workbench/contrib/output/common/output'; +import { ExecutableDebugAdapter, SocketDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import * as ConfigurationResolverUtils from 'vs/workbench/services/configurationResolver/common/configurationResolverUtils'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { memoize } from 'vs/base/common/decorators'; -import { TaskDefinitionRegistry } from 'vs/workbench/parts/tasks/common/taskDefinitionRegistry'; +import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; +import { isDebuggerMainContribution } from 'vs/workbench/contrib/debug/common/debugUtils'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export class Debugger implements IDebugger { - private mergedExtensionDescriptions: IExtensionDescription[]; + private debuggerContribution: IDebuggerContribution; + private mergedExtensionDescriptions: IExtensionDescription[] = []; + private mainExtensionDescription: IExtensionDescription | undefined; - constructor(private configurationManager: IConfigurationManager, private debuggerContribution: IDebuggerContribution, public extensionDescription: IExtensionDescription, + constructor(private configurationManager: IConfigurationManager, dbgContribution: IDebuggerContribution, extensionDescription: IExtensionDescription, @IConfigurationService private readonly configurationService: IConfigurationService, @ITextResourcePropertiesService private readonly resourcePropertiesService: ITextResourcePropertiesService, @ICommandService private readonly commandService: ICommandService, @IConfigurationResolverService private readonly configurationResolverService: IConfigurationResolverService, @ITelemetryService private readonly telemetryService: ITelemetryService, ) { - this.mergedExtensionDescriptions = [extensionDescription]; + this.debuggerContribution = { type: dbgContribution.type }; + this.merge(dbgContribution, extensionDescription); + } + + public merge(otherDebuggerContribution: IDebuggerContribution, extensionDescription: IExtensionDescription): void { + + /** + * Copies all properties of source into destination. The optional parameter "overwrite" allows to control + * if existing non-structured properties on the destination should be overwritten or not. Defaults to true (overwrite). + */ + function mixin(destination: any, source: any, overwrite: boolean, level = 0): any { + + if (!isObject(destination)) { + return source; + } + + if (isObject(source)) { + Object.keys(source).forEach(key => { + if (isObject(destination[key]) && isObject(source[key])) { + mixin(destination[key], source[key], overwrite, level + 1); + } else { + if (key in destination) { + if (overwrite) { + if (level === 0 && key === 'type') { + // don't merge the 'type' property + } else { + destination[key] = source[key]; + } + } + } else { + destination[key] = source[key]; + } + } + }); + } + + return destination; + } + + // only if not already merged + if (this.mergedExtensionDescriptions.indexOf(extensionDescription) < 0) { + + // remember all extensions that have been merged for this debugger + this.mergedExtensionDescriptions.push(extensionDescription); + + // merge new debugger contribution into existing contributions (and don't overwrite values in built-in extensions) + mixin(this.debuggerContribution, otherDebuggerContribution, extensionDescription.isBuiltin); + + // remember the extension that is considered the "main" debugger contribution + if (isDebuggerMainContribution(otherDebuggerContribution)) { + this.mainExtensionDescription = extensionDescription; + } + } } public createDebugAdapter(session: IDebugSession, outputService: IOutputService): Promise { @@ -94,12 +150,15 @@ export class Debugger implements IDebugger { if (this.debuggerContribution.adapterExecutableCommand) { console.info('debugAdapterExecutable attribute in package.json is deprecated and support for it will be removed soon; please use DebugAdapterDescriptorFactory.createDebugAdapterDescriptor instead.'); const rootFolder = session.root ? session.root.uri.toString() : undefined; - return this.commandService.executeCommand(this.debuggerContribution.adapterExecutableCommand, rootFolder).then((ae: { command: string, args: string[] }) => { - return { - type: 'executable', - command: ae.command, - args: ae.args || [] - }; + return this.commandService.executeCommand(this.debuggerContribution.adapterExecutableCommand, rootFolder).then(ae => { + if (ae) { + return { + type: 'executable', + command: ae.command, + args: ae.args || [] + }; + } + throw new Error('command adapterExecutableCommand did not return proper command.'); }); } @@ -112,7 +171,7 @@ export class Debugger implements IDebugger { }); } - substituteVariables(folder: IWorkspaceFolder, config: IConfig): Promise { + substituteVariables(folder: IWorkspaceFolder | undefined, config: IConfig): Promise { if (this.inExtHost()) { return this.configurationManager.substituteVariables(this.type, folder, config).then(config => { return this.configurationResolverService.resolveWithInteractionReplace(folder, config, 'launch', this.variables); @@ -128,8 +187,13 @@ export class Debugger implements IDebugger { } private inExtHost(): boolean { + /* const debugConfigs = this.configurationService.getValue('debug'); - return debugConfigs.extensionHostDebugAdapter || this.configurationManager.needsToRunInExtHost(this.type) || this.extensionDescription.extensionLocation.scheme !== 'file'; + return !!debugConfigs.extensionHostDebugAdapter + || this.configurationManager.needsToRunInExtHost(this.type) + || (!!this.mainExtensionDescription && this.mainExtensionDescription.extensionLocation.scheme !== 'file'); + */ + return true; } get label(): string { @@ -140,30 +204,18 @@ export class Debugger implements IDebugger { return this.debuggerContribution.type; } - get variables(): { [key: string]: string } { + get variables(): { [key: string]: string } | undefined { return this.debuggerContribution.variables; } - get configurationSnippets(): IJSONSchemaSnippet[] { + get configurationSnippets(): IJSONSchemaSnippet[] | undefined { return this.debuggerContribution.configurationSnippets; } - get languages(): string[] { + get languages(): string[] | undefined { return this.debuggerContribution.languages; } - merge(secondRawAdapter: IDebuggerContribution, extensionDescription: IExtensionDescription): void { - - // remember all ext descriptions that are the source of this debugger - this.mergedExtensionDescriptions.push(extensionDescription); - - // Give priority to built in debug adapters - if (extensionDescription.isBuiltin) { - this.extensionDescription = extensionDescription; - } - objects.mixin(this.debuggerContribution, secondRawAdapter, extensionDescription.isBuiltin); - } - hasInitialConfiguration(): boolean { return !!this.debuggerContribution.initialConfigurations; } @@ -204,9 +256,16 @@ export class Debugger implements IDebugger { return Promise.resolve(content); } + public getMainExtensionDescriptor(): IExtensionDescription { + return this.mainExtensionDescription || this.mergedExtensionDescriptions[0]; + } + @memoize - getCustomTelemetryService(): Promise { - if (!this.debuggerContribution.aiKey) { + getCustomTelemetryService(): Promise { + + const aiKey = this.debuggerContribution.aiKey; + + if (!aiKey) { return Promise.resolve(undefined); } @@ -221,11 +280,11 @@ export class Debugger implements IDebugger { { serverName: 'Debug Telemetry', timeout: 1000 * 60 * 5, - args: [`${this.extensionDescription.publisher}.${this.type}`, JSON.stringify(data), this.debuggerContribution.aiKey], + args: [`${this.getMainExtensionDescriptor().publisher}.${this.type}`, JSON.stringify(data), aiKey], env: { ELECTRON_RUN_AS_NODE: 1, PIPE_LOGGING: 'true', - AMD_ENTRYPOINT: 'vs/workbench/parts/debug/node/telemetryApp' + AMD_ENTRYPOINT: 'vs/workbench/contrib/debug/node/telemetryApp' } } ); @@ -237,10 +296,12 @@ export class Debugger implements IDebugger { }); } - getSchemaAttributes(): IJSONSchema[] { + getSchemaAttributes(): IJSONSchema[] | null { + if (!this.debuggerContribution.configurationAttributes) { return null; } + // fill in the default configuration attributes shared by all adapters. const taskSchema = TaskDefinitionRegistry.getJsonSchema(); return Object.keys(this.debuggerContribution.configurationAttributes).map(request => { @@ -290,9 +351,9 @@ export class Debugger implements IDebugger { }; properties['internalConsoleOptions'] = INTERNAL_CONSOLE_OPTIONS_SCHEMA; // Clear out windows, linux and osx fields to not have cycles inside the properties object - properties['windows'] = undefined; - properties['osx'] = undefined; - properties['linux'] = undefined; + delete properties['windows']; + delete properties['osx']; + delete properties['linux']; const osProperties = objects.deepClone(properties); properties['windows'] = { @@ -310,11 +371,10 @@ export class Debugger implements IDebugger { description: nls.localize('debugLinuxConfiguration', "Linux specific launch configuration attributes."), properties: osProperties }; - Object.keys(attributes.properties).forEach(name => { + Object.keys(properties).forEach(name => { // Use schema allOf property to get independent error reporting #21113 - ConfigurationResolverUtils.applyDeprecatedVariableMessage(attributes.properties[name]); + ConfigurationResolverUtils.applyDeprecatedVariableMessage(properties[name]); }); - return attributes; }); } diff --git a/src/vs/workbench/parts/debug/node/telemetryApp.ts b/src/vs/workbench/contrib/debug/node/telemetryApp.ts similarity index 100% rename from src/vs/workbench/parts/debug/node/telemetryApp.ts rename to src/vs/workbench/contrib/debug/node/telemetryApp.ts diff --git a/src/vs/workbench/parts/debug/node/terminals.ts b/src/vs/workbench/contrib/debug/node/terminals.ts similarity index 89% rename from src/vs/workbench/parts/debug/node/terminals.ts rename to src/vs/workbench/contrib/debug/node/terminals.ts index 59b47cbd87..1b70f21230 100644 --- a/src/vs/workbench/parts/debug/node/terminals.ts +++ b/src/vs/workbench/contrib/debug/node/terminals.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import * as env from 'vs/base/common/platform'; import * as pfs from 'vs/base/node/pfs'; import { assign } from 'vs/base/common/objects'; -import { ITerminalLauncher, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug'; +import { ITerminalLauncher, ITerminalSettings } from 'vs/workbench/contrib/debug/common/debug'; import { getPathFromAmdModule } from 'vs/base/common/amd'; const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console"); @@ -33,7 +33,7 @@ export function getDefaultTerminalLinuxReady(): Promise { if (!_DEFAULT_TERMINAL_LINUX_READY) { _DEFAULT_TERMINAL_LINUX_READY = new Promise(c => { if (env.isLinux) { - Promise.all([pfs.exists('/etc/debian_version'), process.lazyEnv]).then(([isDebian]) => { + Promise.all([pfs.exists('/etc/debian_version'), process.lazyEnv]).then(([isDebian]) => { if (isDebian) { c('x-terminal-emulator'); } else if (process.env.DESKTOP_SESSION === 'gnome' || process.env.DESKTOP_SESSION === 'gnome-classic') { @@ -67,19 +67,18 @@ export function getDefaultTerminalWindows(): string { } abstract class TerminalLauncher implements ITerminalLauncher { - public runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise { - return this.runInTerminal0(args.title, args.cwd, args.args, args.env || {}, config); - } - runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, config): Promise { - return undefined; + runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise { + return this.runInTerminal0(args.title!, args.cwd, args.args, args.env || {}, config); } + + abstract runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment | {}, config): Promise; } class WinTerminalService extends TerminalLauncher { private static readonly CMD = 'cmd.exe'; - public runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, configuration: ITerminalSettings): Promise { + runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, configuration: ITerminalSettings): Promise { const exec = configuration.external.windowsExec || getDefaultTerminalWindows(); @@ -117,7 +116,7 @@ class MacTerminalService extends TerminalLauncher { private static readonly DEFAULT_TERMINAL_OSX = 'Terminal.app'; private static readonly OSASCRIPT = '/usr/bin/osascript'; // osascript is the AppleScript interpreter on OS X - public runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, configuration: ITerminalSettings): Promise { + runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, configuration: ITerminalSettings): Promise { const terminalApp = configuration.external.osxExec || MacTerminalService.DEFAULT_TERMINAL_OSX; @@ -129,7 +128,7 @@ class MacTerminalService extends TerminalLauncher { // and then launches the program inside that window. const script = terminalApp === MacTerminalService.DEFAULT_TERMINAL_OSX ? 'TerminalHelper' : 'iTermHelper'; - const scriptpath = getPathFromAmdModule(require, `vs/workbench/parts/execution/electron-browser/${script}.scpt`); + const scriptpath = getPathFromAmdModule(require, `vs/workbench/contrib/externalTerminal/electron-browser/${script}.scpt`); const osaArgs = [ scriptpath, @@ -184,7 +183,7 @@ class LinuxTerminalService extends TerminalLauncher { private static readonly WAIT_MESSAGE = nls.localize('press.any.key', "Press any key to continue..."); - public runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, configuration: ITerminalSettings): Promise { + runInTerminal0(title: string, dir: string, args: string[], envVars: env.IProcessEnvironment, configuration: ITerminalSettings): Promise { const terminalConfig = configuration.external; const execThenable: Promise = terminalConfig.linuxExec ? Promise.resolve(terminalConfig.linuxExec) : getDefaultTerminalLinuxReady(); @@ -305,6 +304,8 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments } else if (env.isMacintosh) { shell = shell_config.osx; shellType = ShellType.bash; + } else { + throw new Error('Unknown platform'); } // try to determine the shell type @@ -346,7 +347,7 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments } } if (args.args && args.args.length > 0) { - const cmd = quote(args.args.shift()); + const cmd = quote(args.args.shift()!); command += (cmd[0] === '\'') ? `& ${cmd} ` : `${cmd} `; for (let a of args.args) { command += `${quote(a)} `; @@ -391,6 +392,10 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments return (s.indexOf(' ') >= 0 || s.indexOf('\\') >= 0) ? `"${s}"` : s; }; + const hardQuote = (s: string) => { + return /[^\w@%\/+=,.:^-]/.test(s) ? `'${s.replace(/'/g, '\'\\\'\'')}'` : s; + }; + if (args.cwd) { command += `cd ${quote(args.cwd)} ; `; } @@ -399,9 +404,9 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments for (let key in args.env) { const value = args.env[key]; if (value === null) { - command += ` -u "${key}"`; + command += ` -u ${hardQuote(key)}`; } else { - command += ` "${key}=${value}"`; + command += ` ${hardQuote(`${key}=${value}`)}`; } } command += ' '; diff --git a/src/vs/workbench/parts/debug/test/browser/baseDebugView.test.ts b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts similarity index 81% rename from src/vs/workbench/parts/debug/test/browser/baseDebugView.test.ts rename to src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts index bc5dde2ab2..ff7df6f28f 100644 --- a/src/vs/workbench/parts/debug/test/browser/baseDebugView.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/baseDebugView.test.ts @@ -4,10 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { replaceWhitespace, renderExpressionValue, renderVariable } from 'vs/workbench/parts/debug/browser/baseDebugView'; +import { replaceWhitespace, renderExpressionValue, renderVariable } from 'vs/workbench/contrib/debug/browser/baseDebugView'; import * as dom from 'vs/base/browser/dom'; -import { Expression, Variable, Scope, StackFrame, Thread } from 'vs/workbench/parts/debug/common/debugModel'; -import { MockSession } from 'vs/workbench/parts/debug/test/common/mockDebug'; +import { MockSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; const $ = dom.$; diff --git a/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts new file mode 100644 index 0000000000..5a38d590b6 --- /dev/null +++ b/src/vs/workbench/contrib/debug/test/browser/debugANSIHandling.test.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as dom from 'vs/base/browser/dom'; +import { generateUuid } from 'vs/base/common/uuid'; +import { appendStylizedStringToContainer, handleANSIOutput } from 'vs/workbench/contrib/debug/browser/debugANSIHandling'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; +import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; + +suite('Debug - ANSI Handling', () => { + test('appendStylizedStringToContainer', () => { + assert.equal('', ''); + }); +}); diff --git a/src/vs/workbench/parts/debug/test/browser/linkDetector.test.ts b/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts similarity index 96% rename from src/vs/workbench/parts/debug/test/browser/linkDetector.test.ts rename to src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts index 11e43b6022..267ffbe4b4 100644 --- a/src/vs/workbench/parts/debug/test/browser/linkDetector.test.ts +++ b/src/vs/workbench/contrib/debug/test/browser/linkDetector.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; -import { LinkDetector } from 'vs/workbench/parts/debug/browser/linkDetector'; +import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector'; import { isWindows } from 'vs/base/common/platform'; suite('Debug - Link Detector', () => { @@ -58,10 +58,10 @@ suite('Debug - Link Detector', () => { assert.equal(1, output.children.length); assert.equal('SPAN', output.tagName); - assert.equal('A', output.firstElementChild.tagName); + assert.equal('A', output.firstElementChild!.tagName); assert(expectedOutput.test(output.outerHTML)); - assertElementIsLink(output.firstElementChild); - assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.firstElementChild.textContent); + assertElementIsLink(output.firstElementChild!); + assert.equal(isWindows ? 'C:/foo/bar.js:12:34' : '/Users/foo/bar.js:12:34', output.firstElementChild!.textContent); }); test('relativeLink', () => { diff --git a/src/vs/workbench/parts/debug/test/common/debugSource.test.ts b/src/vs/workbench/contrib/debug/test/common/debugSource.test.ts similarity index 87% rename from src/vs/workbench/parts/debug/test/common/debugSource.test.ts rename to src/vs/workbench/contrib/debug/test/common/debugSource.test.ts index 1b1fe31848..eaed6ef761 100644 --- a/src/vs/workbench/parts/debug/test/common/debugSource.test.ts +++ b/src/vs/workbench/contrib/debug/test/common/debugSource.test.ts @@ -5,8 +5,8 @@ import * as assert from 'assert'; import { URI as uri } from 'vs/base/common/uri'; -import { Source } from 'vs/workbench/parts/debug/common/debugSource'; -import { normalize } from 'vs/base/common/paths'; +import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; +import { isWindows } from 'vs/base/common/platform'; suite('Debug - Source', () => { @@ -48,8 +48,8 @@ suite('Debug - Source', () => { assert.equal(sessionId, expectedSessionId); }; - checkData(uri.file('a/b/c/d'), 'd', normalize('/a/b/c/d', true), undefined, undefined); - checkData(uri.from({ scheme: 'file', path: '/my/path/test.js', query: 'ref=1&session=2' }), 'test.js', normalize('/my/path/test.js', true), undefined, undefined); + checkData(uri.file('a/b/c/d'), 'd', isWindows ? '\\a\\b\\c\\d' : '/a/b/c/d', undefined, undefined); + checkData(uri.from({ scheme: 'file', path: '/my/path/test.js', query: 'ref=1&session=2' }), 'test.js', isWindows ? '\\my\\path\\test.js' : '/my/path/test.js', undefined, undefined); checkData(uri.from({ scheme: 'http', authority: 'www.msft.com', path: '/my/path' }), 'path', 'http://www.msft.com/my/path', undefined, undefined); checkData(uri.from({ scheme: 'debug', authority: 'www.msft.com', path: '/my/path', query: 'ref=100' }), 'path', '/my/path', 100, undefined); diff --git a/src/vs/workbench/parts/debug/test/common/debugUtils.test.ts b/src/vs/workbench/contrib/debug/test/common/debugUtils.test.ts similarity index 98% rename from src/vs/workbench/parts/debug/test/common/debugUtils.test.ts rename to src/vs/workbench/contrib/debug/test/common/debugUtils.test.ts index acb8dfa17c..e141ef7009 100644 --- a/src/vs/workbench/parts/debug/test/common/debugUtils.test.ts +++ b/src/vs/workbench/contrib/debug/test/common/debugUtils.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { formatPII, getExactExpressionStartAndEnd } from 'vs/workbench/parts/debug/common/debugUtils'; +import { formatPII, getExactExpressionStartAndEnd } from 'vs/workbench/contrib/debug/common/debugUtils'; suite('Debug - Utils', () => { test('formatPII', () => { diff --git a/src/vs/workbench/parts/debug/test/common/debugViewModel.test.ts b/src/vs/workbench/contrib/debug/test/common/debugViewModel.test.ts similarity index 68% rename from src/vs/workbench/parts/debug/test/common/debugViewModel.test.ts rename to src/vs/workbench/contrib/debug/test/common/debugViewModel.test.ts index 9786bbf649..cad2439cd2 100644 --- a/src/vs/workbench/parts/debug/test/common/debugViewModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/common/debugViewModel.test.ts @@ -4,9 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel'; -import { StackFrame, Expression, Thread } from 'vs/workbench/parts/debug/common/debugModel'; -import { MockSession } from 'vs/workbench/parts/debug/test/common/mockDebug'; +import { ViewModel } from 'vs/workbench/contrib/debug/common/debugViewModel'; +import { StackFrame, Expression, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; +import { MockSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; suite('Debug - View Model', () => { @@ -16,21 +16,17 @@ suite('Debug - View Model', () => { model = new ViewModel(new MockContextKeyService()); }); - teardown(() => { - model = null; - }); - test('focused stack frame', () => { assert.equal(model.focusedStackFrame, null); assert.equal(model.focusedThread, null); const session = new MockSession(); const thread = new Thread(session, 'myThread', 1); - const frame = new StackFrame(thread, 1, null, 'app.js', 'normal', { startColumn: 1, startLineNumber: 1, endColumn: undefined, endLineNumber: undefined }, 0); + const frame = new StackFrame(thread, 1, undefined!, 'app.js', 'normal', { startColumn: 1, startLineNumber: 1, endColumn: 1, endLineNumber: 1 }, 0); model.setFocus(frame, thread, session, false); - assert.equal(model.focusedStackFrame.getId(), frame.getId()); - assert.equal(model.focusedThread.threadId, 1); - assert.equal(model.focusedSession.getId(), session.getId()); + assert.equal(model.focusedStackFrame!.getId(), frame.getId()); + assert.equal(model.focusedThread!.threadId, 1); + assert.equal(model.focusedSession!.getId(), session.getId()); }); test('selected expression', () => { diff --git a/src/vs/workbench/parts/debug/test/common/mockDebug.ts b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts similarity index 83% rename from src/vs/workbench/parts/debug/test/common/mockDebug.ts rename to src/vs/workbench/contrib/debug/test/common/mockDebug.ts index 728a893ea4..cfc08d64a1 100644 --- a/src/vs/workbench/parts/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/contrib/debug/test/common/mockDebug.ts @@ -7,8 +7,8 @@ import { URI as uri } from 'vs/base/common/uri'; import { Event } from 'vs/base/common/event'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { Position } from 'vs/editor/common/core/position'; -import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource } from 'vs/workbench/parts/debug/common/debug'; -import { Source } from 'vs/workbench/parts/debug/common/debugSource'; +import { ILaunch, IDebugService, State, IDebugSession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IDebugModel, IViewModel, IBreakpoint, LoadedSourceEvent, IThread, IRawModelUpdate, IFunctionBreakpoint, IExceptionBreakpoint, IDebugger, IExceptionInfo, AdapterEndEvent, IReplElement, IExpression, IReplElementSource } from 'vs/workbench/contrib/debug/common/debug'; +import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; import { CompletionItem } from 'vs/editor/common/modes'; import Severity from 'vs/base/common/severity'; @@ -17,52 +17,52 @@ export class MockDebugService implements IDebugService { public _serviceBrand: any; public get state(): State { - return null; + throw new Error('not implemented'); } public get onWillNewSession(): Event { - return null; + throw new Error('not implemented'); } public get onDidNewSession(): Event { - return null; + throw new Error('not implemented'); } public get onDidEndSession(): Event { - return null; + throw new Error('not implemented'); } public get onDidChangeState(): Event { - return null; + throw new Error('not implemented'); } public getConfigurationManager(): IConfigurationManager { - return null; + throw new Error('not implemented'); } public focusStackFrame(focusedStackFrame: IStackFrame): void { } sendAllBreakpoints(session?: IDebugSession): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public addBreakpoints(uri: uri, rawBreakpoints: IBreakpointData[]): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public updateBreakpoints(uri: uri, data: { [id: string]: IBreakpointUpdateData }, sendOnResourceSaved: boolean): void { } public enableOrDisableBreakpoints(enabled: boolean): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public setBreakpointsActivated(): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public removeBreakpoints(): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public addFunctionBreakpoint(): void { } @@ -70,25 +70,25 @@ export class MockDebugService implements IDebugService { public moveWatchExpression(id: string, position: number): void { } public renameFunctionBreakpoint(id: string, newFunctionName: string): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public removeFunctionBreakpoints(id?: string): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public addReplExpression(name: string): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public removeReplExpressions(): void { } public addWatchExpression(name?: string): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public renameWatchExpression(id: string, newName: string): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public removeWatchExpressions(id?: string): void { } @@ -98,19 +98,19 @@ export class MockDebugService implements IDebugService { } public restartSession(): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public stopSession(): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public getModel(): IDebugModel { - return null; + throw new Error('not implemented'); } public getViewModel(): IViewModel { - return null; + throw new Error('not implemented'); } public logToRepl(session: IDebugSession, value: string): void { } @@ -118,7 +118,7 @@ export class MockDebugService implements IDebugService { public sourceIsNotAvailable(uri: uri): void { } public tryToAutoFocusStackFrame(thread: IThread): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } } @@ -129,7 +129,7 @@ export class MockSession implements IDebugSession { removeReplExpressions(): void { } get onDidChangeReplElements(): Event { - return null; + throw new Error('not implemented'); } addReplExpression(stackFrame: IStackFrame, name: string): Promise { @@ -154,27 +154,27 @@ export class MockSession implements IDebugSession { } getSourceForUri(modelUri: uri): Source { - return null; + throw new Error('not implemented'); } getThread(threadId: number): IThread { - return null; + throw new Error('not implemented'); } get onDidCustomEvent(): Event { - return null; + throw new Error('not implemented'); } get onDidLoadedSource(): Event { - return null; + throw new Error('not implemented'); } get onDidChangeState(): Event { - return null; + throw new Error('not implemented'); } get onDidEndAdapter(): Event { - return null; + throw new Error('not implemented'); } setConfiguration(configuration: { resolved: IConfig, unresolved: IConfig }) { } @@ -184,7 +184,7 @@ export class MockSession implements IDebugSession { } getSource(raw: DebugProtocol.Source): Source { - return undefined; + throw new Error('not implemented'); } getLoadedSources(): Promise { @@ -311,104 +311,104 @@ export class MockRawSession { } public exceptionInfo(args: DebugProtocol.ExceptionInfoArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public launchOrAttach(args: IConfig): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public scopes(args: DebugProtocol.ScopesArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public variables(args: DebugProtocol.VariablesArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } evaluate(args: DebugProtocol.EvaluateArguments): Promise { - return Promise.resolve(null); + return Promise.resolve(null!); } public custom(request: string, args: any): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public terminate(restart = false): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public disconnect(restart?: boolean): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public threads(): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public stepIn(args: DebugProtocol.StepInArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public stepOut(args: DebugProtocol.StepOutArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public stepBack(args: DebugProtocol.StepBackArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public continue(args: DebugProtocol.ContinueArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public reverseContinue(args: DebugProtocol.ReverseContinueArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public pause(args: DebugProtocol.PauseArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public terminateThreads(args: DebugProtocol.TerminateThreadsArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public setVariable(args: DebugProtocol.SetVariableArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public restartFrame(args: DebugProtocol.RestartFrameArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public completions(args: DebugProtocol.CompletionsArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public next(args: DebugProtocol.NextArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public source(args: DebugProtocol.SourceArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public loadedSources(args: DebugProtocol.LoadedSourcesArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public setBreakpoints(args: DebugProtocol.SetBreakpointsArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public setFunctionBreakpoints(args: DebugProtocol.SetFunctionBreakpointsArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } - public readonly onDidStop: Event = null; + public readonly onDidStop: Event = null!; } diff --git a/src/vs/workbench/parts/debug/test/electron-browser/debugModel.test.ts b/src/vs/workbench/contrib/debug/test/electron-browser/debugModel.test.ts similarity index 85% rename from src/vs/workbench/parts/debug/test/electron-browser/debugModel.test.ts rename to src/vs/workbench/contrib/debug/test/electron-browser/debugModel.test.ts index 09a43fa22f..6057b3d650 100644 --- a/src/vs/workbench/parts/debug/test/electron-browser/debugModel.test.ts +++ b/src/vs/workbench/contrib/debug/test/electron-browser/debugModel.test.ts @@ -6,12 +6,12 @@ import * as assert from 'assert'; import { URI as uri } from 'vs/base/common/uri'; import severity from 'vs/base/common/severity'; -import { SimpleReplElement, DebugModel, Expression, RawObjectReplElement, StackFrame, Thread } from 'vs/workbench/parts/debug/common/debugModel'; +import { SimpleReplElement, DebugModel, Expression, RawObjectReplElement, StackFrame, Thread } from 'vs/workbench/contrib/debug/common/debugModel'; import * as sinon from 'sinon'; -import { MockRawSession } from 'vs/workbench/parts/debug/test/common/mockDebug'; -import { Source } from 'vs/workbench/parts/debug/common/debugSource'; -import { DebugSession } from 'vs/workbench/parts/debug/electron-browser/debugSession'; -import { ReplModel } from 'vs/workbench/parts/debug/common/replModel'; +import { MockRawSession } from 'vs/workbench/contrib/debug/test/common/mockDebug'; +import { Source } from 'vs/workbench/contrib/debug/common/debugSource'; +import { DebugSession } from 'vs/workbench/contrib/debug/electron-browser/debugSession'; +import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel'; suite('Debug - Model', () => { let model: DebugModel; @@ -22,10 +22,6 @@ suite('Debug - Model', () => { rawSession = new MockRawSession(); }); - teardown(() => { - model = null; - }); - // Breakpoints test('breakpoints simple', () => { @@ -43,7 +39,10 @@ suite('Debug - Model', () => { model.addBreakpoints(modelUri, [{ lineNumber: 5, enabled: true }, { lineNumber: 10, enabled: false }]); model.addBreakpoints(modelUri, [{ lineNumber: 12, enabled: true, condition: 'fake condition' }]); assert.equal(model.getBreakpoints().length, 3); - model.removeBreakpoints([model.getBreakpoints().pop()]); + const bp = model.getBreakpoints().pop(); + if (bp) { + model.removeBreakpoints([bp]); + } assert.equal(model.getBreakpoints().length, 2); model.setBreakpointsActivated(false); @@ -110,23 +109,22 @@ suite('Debug - Model', () => { test('threads simple', () => { const threadId = 1; const threadName = 'firstThread'; - const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined); + const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); model.addSession(session); assert.equal(model.getSessions(true).length, 1); model.rawUpdate({ sessionId: session.getId(), - threadId: threadId, - thread: { + threads: [{ id: threadId, name: threadName - } + }] }); - assert.equal(session.getThread(threadId).name, threadName); + assert.equal(session.getThread(threadId)!.name, threadName); model.clearThreads(session.getId(), true); - assert.equal(session.getThread(threadId), null); + assert.equal(session.getThread(threadId), undefined); assert.equal(model.getSessions(true).length, 1); }); @@ -138,33 +136,34 @@ suite('Debug - Model', () => { const stoppedReason = 'breakpoint'; // Add the threads - const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined); + const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); model.addSession(session); session['raw'] = rawSession; model.rawUpdate({ sessionId: session.getId(), - threadId: threadId1, - thread: { + threads: [{ id: threadId1, name: threadName1 - } + }] }); model.rawUpdate({ sessionId: session.getId(), - threadId: threadId2, - thread: { + threads: [{ id: threadId2, name: threadName2 - } + }] }); // Stopped event with all threads stopped model.rawUpdate({ sessionId: session.getId(), - threadId: threadId1, + threads: [{ + id: threadId1, + name: threadName1 + }], stoppedDetails: { reason: stoppedReason, threadId: 1, @@ -172,19 +171,19 @@ suite('Debug - Model', () => { }, }); - const thread1 = session.getThread(threadId1); - const thread2 = session.getThread(threadId2); + const thread1 = session.getThread(threadId1)!; + const thread2 = session.getThread(threadId2)!; // at the beginning, callstacks are obtainable but not available assert.equal(session.getAllThreads().length, 2); assert.equal(thread1.name, threadName1); assert.equal(thread1.stopped, true); assert.equal(thread1.getCallStack().length, 0); - assert.equal(thread1.stoppedDetails.reason, stoppedReason); + assert.equal(thread1.stoppedDetails!.reason, stoppedReason); assert.equal(thread2.name, threadName2); assert.equal(thread2.stopped, true); assert.equal(thread2.getCallStack().length, 0); - assert.equal(thread2.stoppedDetails.reason, undefined); + assert.equal(thread2.stoppedDetails!.reason, undefined); // after calling getCallStack, the callstack becomes available // and results in a request for the callstack in the debug adapter @@ -212,8 +211,8 @@ suite('Debug - Model', () => { assert.equal(thread2.getCallStack().length, 0); model.clearThreads(session.getId(), true); - assert.equal(session.getThread(threadId1), null); - assert.equal(session.getThread(threadId2), null); + assert.equal(session.getThread(threadId1), undefined); + assert.equal(session.getThread(threadId2), undefined); assert.equal(session.getAllThreads().length, 0); }); @@ -225,7 +224,7 @@ suite('Debug - Model', () => { const runningThreadId = 2; const runningThreadName = 'runningThread'; const stoppedReason = 'breakpoint'; - const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined); + const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); model.addSession(session); session['raw'] = rawSession; @@ -233,26 +232,27 @@ suite('Debug - Model', () => { // Add the threads model.rawUpdate({ sessionId: session.getId(), - threadId: stoppedThreadId, - thread: { + threads: [{ id: stoppedThreadId, name: stoppedThreadName - } + }] }); model.rawUpdate({ sessionId: session.getId(), - threadId: runningThreadId, - thread: { + threads: [{ id: runningThreadId, name: runningThreadName - } + }] }); // Stopped event with only one thread stopped model.rawUpdate({ sessionId: session.getId(), - threadId: stoppedThreadId, + threads: [{ + id: 1, + name: stoppedThreadName + }], stoppedDetails: { reason: stoppedReason, threadId: 1, @@ -260,8 +260,8 @@ suite('Debug - Model', () => { } }); - const stoppedThread = session.getThread(stoppedThreadId); - const runningThread = session.getThread(runningThreadId); + const stoppedThread = session.getThread(stoppedThreadId)!; + const runningThread = session.getThread(runningThreadId)!; // the callstack for the stopped thread is obtainable but not available // the callstack for the running thread is not obtainable nor available @@ -269,7 +269,7 @@ suite('Debug - Model', () => { assert.equal(stoppedThread.stopped, true); assert.equal(session.getAllThreads().length, 2); assert.equal(stoppedThread.getCallStack().length, 0); - assert.equal(stoppedThread.stoppedDetails.reason, stoppedReason); + assert.equal(stoppedThread.stoppedDetails!.reason, stoppedReason); assert.equal(runningThread.name, runningThreadName); assert.equal(runningThread.stopped, false); assert.equal(runningThread.getCallStack().length, 0); @@ -297,8 +297,8 @@ suite('Debug - Model', () => { assert.equal(stoppedThread.getCallStack().length, 0); model.clearThreads(session.getId(), true); - assert.equal(session.getThread(stoppedThreadId), null); - assert.equal(session.getThread(runningThreadId), null); + assert.equal(session.getThread(stoppedThreadId), undefined); + assert.equal(session.getThread(runningThreadId), undefined); assert.equal(session.getAllThreads().length, 0); }); @@ -338,13 +338,13 @@ suite('Debug - Model', () => { }); test('repl expressions', () => { - const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined); + const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); assert.equal(session.getReplElements().length, 0); model.addSession(session); session['raw'] = rawSession; const thread = new Thread(session, 'mockthread', 1); - const stackFrame = new StackFrame(thread, 1, null, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1); + const stackFrame = new StackFrame(thread, 1, undefined, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 10 }, 1); const replModel = new ReplModel(session); replModel.addReplExpression(stackFrame, 'myVariable').then(); replModel.addReplExpression(stackFrame, 'myVariable').then(); @@ -362,7 +362,7 @@ suite('Debug - Model', () => { }); test('stack frame get specific source name', () => { - const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined); + const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); model.addSession(session); let firstStackFrame: StackFrame; @@ -393,7 +393,7 @@ suite('Debug - Model', () => { // Repl output test('repl output', () => { - const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined, model, undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined); + const session = new DebugSession({ resolved: { name: 'mockSession', type: 'node', request: 'launch' }, unresolved: undefined }, undefined!, model, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!, undefined!); const repl = new ReplModel(session); repl.appendToRepl('first line\n', severity.Error); repl.appendToRepl('second line', severity.Error); @@ -418,7 +418,7 @@ suite('Debug - Model', () => { assert.equal(elements[4].severity, severity.Warning); const keyValueObject = { 'key1': 2, 'key2': 'value' }; - repl.appendToRepl(new RawObjectReplElement('fakeid', 'fake', keyValueObject), null); + repl.appendToRepl(new RawObjectReplElement('fakeid', 'fake', keyValueObject), severity.Info); const element = repl.getReplElements()[5]; assert.equal(element.value, 'Object'); assert.deepEqual(element.valueObj, keyValueObject); diff --git a/src/vs/workbench/parts/debug/test/node/debugger.test.ts b/src/vs/workbench/contrib/debug/test/node/debugger.test.ts similarity index 89% rename from src/vs/workbench/parts/debug/test/node/debugger.test.ts rename to src/vs/workbench/contrib/debug/test/node/debugger.test.ts index 8a76a0b447..5ce25d4b0f 100644 --- a/src/vs/workbench/parts/debug/test/node/debugger.test.ts +++ b/src/vs/workbench/contrib/debug/test/node/debugger.test.ts @@ -4,16 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as paths from 'vs/base/common/paths'; +import { join, normalize } from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; -import { IDebugAdapterExecutable, IConfigurationManager, IConfig, IDebugSession } from 'vs/workbench/parts/debug/common/debug'; -import { Debugger } from 'vs/workbench/parts/debug/node/debugger'; +import { IDebugAdapterExecutable, IConfigurationManager, IConfig, IDebugSession } from 'vs/workbench/contrib/debug/common/debug'; +import { Debugger } from 'vs/workbench/contrib/debug/node/debugger'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { URI } from 'vs/base/common/uri'; -import { ExecutableDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter'; +import { ExecutableDebugAdapter } from 'vs/workbench/contrib/debug/node/debugAdapter'; import { TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; suite('Debug - Debugger', () => { @@ -144,12 +143,12 @@ suite('Debug - Debugger', () => { const ae = ExecutableDebugAdapter.platformAdapterExecutable([extensionDescriptor0], 'mock'); - assert.equal(ae!.command, paths.join(extensionFolderPath, debuggerContribution.program)); + assert.equal(ae!.command, join(extensionFolderPath, debuggerContribution.program)); assert.deepEqual(ae!.args, debuggerContribution.args); }); test('schema attributes', () => { - const schemaAttribute = _debugger.getSchemaAttributes()[0]; + const schemaAttribute = _debugger.getSchemaAttributes()![0]; assert.notDeepEqual(schemaAttribute, debuggerContribution.configurationAttributes); Object.keys(debuggerContribution.configurationAttributes.launch).forEach(key => { assert.deepEqual(schemaAttribute[key], debuggerContribution.configurationAttributes.launch[key]); @@ -166,7 +165,7 @@ suite('Debug - Debugger', () => { const ae = ExecutableDebugAdapter.platformAdapterExecutable([extensionDescriptor1, extensionDescriptor2], 'mock')!; assert.equal(ae.command, platform.isLinux ? 'linuxRuntime' : (platform.isMacintosh ? 'osxRuntime' : 'winRuntime')); const xprogram = platform.isLinux ? 'linuxProgram' : (platform.isMacintosh ? 'osxProgram' : 'winProgram'); - assert.deepEqual(ae.args, ['rarg', '/e2/b/c/' + xprogram, 'parg']); + assert.deepEqual(ae.args, ['rarg', normalize('/e2/b/c/') + xprogram, 'parg']); }); test('initial config file content', () => { diff --git a/src/vs/workbench/parts/emmet/electron-browser/actions/expandAbbreviation.ts b/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts similarity index 95% rename from src/vs/workbench/parts/emmet/electron-browser/actions/expandAbbreviation.ts rename to src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts index aa1c9d7626..72a6d0888a 100644 --- a/src/vs/workbench/parts/emmet/electron-browser/actions/expandAbbreviation.ts +++ b/src/vs/workbench/contrib/emmet/browser/actions/expandAbbreviation.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { EmmetEditorAction } from 'vs/workbench/parts/emmet/electron-browser/emmetActions'; +import { EmmetEditorAction } from 'vs/workbench/contrib/emmet/browser/emmetActions'; import { registerEditorAction } from 'vs/editor/browser/editorExtensions'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { KeyCode } from 'vs/base/common/keyCodes'; diff --git a/src/vs/workbench/parts/emmet/browser/actions/showEmmetCommands.ts b/src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts similarity index 100% rename from src/vs/workbench/parts/emmet/browser/actions/showEmmetCommands.ts rename to src/vs/workbench/contrib/emmet/browser/actions/showEmmetCommands.ts diff --git a/src/vs/workbench/parts/emmet/electron-browser/emmet.contribution.ts b/src/vs/workbench/contrib/emmet/browser/emmet.contribution.ts similarity index 91% rename from src/vs/workbench/parts/emmet/electron-browser/emmet.contribution.ts rename to src/vs/workbench/contrib/emmet/browser/emmet.contribution.ts index 0a144fb302..d8344f0e0a 100644 --- a/src/vs/workbench/parts/emmet/electron-browser/emmet.contribution.ts +++ b/src/vs/workbench/contrib/emmet/browser/emmet.contribution.ts @@ -3,5 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import './actions/showEmmetCommands'; import './actions/expandAbbreviation'; diff --git a/src/vs/workbench/parts/emmet/electron-browser/emmetActions.ts b/src/vs/workbench/contrib/emmet/browser/emmetActions.ts similarity index 98% rename from src/vs/workbench/parts/emmet/electron-browser/emmetActions.ts rename to src/vs/workbench/contrib/emmet/browser/emmetActions.ts index c58b7995ec..fe4d5e42e7 100644 --- a/src/vs/workbench/parts/emmet/electron-browser/emmetActions.ts +++ b/src/vs/workbench/contrib/emmet/browser/emmetActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { EditorAction, ServicesAccessor, IActionOptions } from 'vs/editor/browser/editorExtensions'; -import { grammarsExtPoint, ITMSyntaxExtensionPoint } from 'vs/workbench/services/textMate/electron-browser/TMGrammars'; +import { grammarsExtPoint, ITMSyntaxExtensionPoint } from 'vs/workbench/services/textMate/common/TMGrammars'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IExtensionService, ExtensionPointContribution } from 'vs/workbench/services/extensions/common/extensions'; import { ICommandService } from 'vs/platform/commands/common/commands'; diff --git a/src/vs/workbench/parts/emmet/test/electron-browser/emmetAction.test.ts b/src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts similarity index 97% rename from src/vs/workbench/parts/emmet/test/electron-browser/emmetAction.test.ts rename to src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts index 13eeb1efad..e49282f398 100644 --- a/src/vs/workbench/parts/emmet/test/electron-browser/emmetAction.test.ts +++ b/src/vs/workbench/contrib/emmet/test/browser/emmetAction.test.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IGrammarContributions, ILanguageIdentifierResolver, EmmetEditorAction } from 'vs/workbench/parts/emmet/electron-browser/emmetActions'; +import { IGrammarContributions, ILanguageIdentifierResolver, EmmetEditorAction } from 'vs/workbench/contrib/emmet/browser/emmetActions'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import * as assert from 'assert'; import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes'; diff --git a/src/vs/workbench/parts/experiments/electron-browser/experimentalPrompt.ts b/src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts similarity index 96% rename from src/vs/workbench/parts/experiments/electron-browser/experimentalPrompt.ts rename to src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts index 71a5f43a93..28ee86712a 100644 --- a/src/vs/workbench/parts/experiments/electron-browser/experimentalPrompt.ts +++ b/src/vs/workbench/contrib/experiments/electron-browser/experimentalPrompt.ts @@ -5,9 +5,9 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { INotificationService, Severity, IPromptChoice } from 'vs/platform/notification/common/notification'; -import { IExperimentService, IExperiment, ExperimentActionType, IExperimentActionPromptProperties, IExperimentActionPromptCommand, ExperimentState } from 'vs/workbench/parts/experiments/node/experimentService'; +import { IExperimentService, IExperiment, ExperimentActionType, IExperimentActionPromptProperties, IExperimentActionPromptCommand, ExperimentState } from 'vs/workbench/contrib/experiments/node/experimentService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IExtensionsViewlet } from 'vs/workbench/parts/extensions/common/extensions'; +import { IExtensionsViewlet } from 'vs/workbench/contrib/extensions/common/extensions'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; import { language } from 'vs/base/common/platform'; diff --git a/src/vs/workbench/parts/experiments/electron-browser/experiments.contribution.ts b/src/vs/workbench/contrib/experiments/electron-browser/experiments.contribution.ts similarity index 86% rename from src/vs/workbench/parts/experiments/electron-browser/experiments.contribution.ts rename to src/vs/workbench/contrib/experiments/electron-browser/experiments.contribution.ts index 1977fa84be..4257865ff6 100644 --- a/src/vs/workbench/parts/experiments/electron-browser/experiments.contribution.ts +++ b/src/vs/workbench/contrib/experiments/electron-browser/experiments.contribution.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IExperimentService, ExperimentService } from 'vs/workbench/parts/experiments/node/experimentService'; +import { IExperimentService, ExperimentService } from 'vs/workbench/contrib/experiments/node/experimentService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { ExperimentalPrompts } from 'vs/workbench/parts/experiments/electron-browser/experimentalPrompt'; +import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/electron-browser/experimentalPrompt'; registerSingleton(IExperimentService, ExperimentService, true); diff --git a/src/vs/workbench/parts/experiments/node/experimentService.ts b/src/vs/workbench/contrib/experiments/node/experimentService.ts similarity index 99% rename from src/vs/workbench/parts/experiments/node/experimentService.ts rename to src/vs/workbench/contrib/experiments/node/experimentService.ts index 41395070e9..d4fc6203c9 100644 --- a/src/vs/workbench/parts/experiments/node/experimentService.ts +++ b/src/vs/workbench/contrib/experiments/node/experimentService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -19,7 +19,7 @@ import { match } from 'vs/base/common/glob'; import { asJson } from 'vs/base/node/request'; import { Emitter, Event } from 'vs/base/common/event'; import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; -import { WorkspaceStats } from 'vs/workbench/parts/stats/node/workspaceStats'; +import { WorkspaceStats } from 'vs/workbench/contrib/stats/node/workspaceStats'; import { CancellationToken } from 'vs/base/common/cancellation'; import { distinct } from 'vs/base/common/arrays'; import { lastSessionDateStorageKey } from 'vs/platform/telemetry/node/workbenchCommonProperties'; diff --git a/src/vs/workbench/parts/experiments/test/electron-browser/experimentService.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts similarity index 99% rename from src/vs/workbench/parts/experiments/test/electron-browser/experimentService.test.ts rename to src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts index 605b81dd2f..45b807e6c8 100644 --- a/src/vs/workbench/parts/experiments/test/electron-browser/experimentService.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentService.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ExperimentService, ExperimentActionType, ExperimentState, IExperiment } from 'vs/workbench/parts/experiments/node/experimentService'; +import { ExperimentService, ExperimentActionType, ExperimentState, IExperiment } from 'vs/workbench/contrib/experiments/node/experimentService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices'; diff --git a/src/vs/workbench/parts/experiments/test/electron-browser/experimentalPrompts.test.ts b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts similarity index 95% rename from src/vs/workbench/parts/experiments/test/electron-browser/experimentalPrompts.test.ts rename to src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts index 3a38619403..436c168181 100644 --- a/src/vs/workbench/parts/experiments/test/electron-browser/experimentalPrompts.test.ts +++ b/src/vs/workbench/contrib/experiments/test/electron-browser/experimentalPrompts.test.ts @@ -12,9 +12,9 @@ import { TestNotificationService } from 'vs/platform/notification/test/common/te import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { ExperimentalPrompts } from 'vs/workbench/parts/experiments/electron-browser/experimentalPrompt'; -import { ExperimentActionType, ExperimentState, IExperiment, IExperimentActionPromptProperties, IExperimentService } from 'vs/workbench/parts/experiments/node/experimentService'; -import { TestExperimentService } from 'vs/workbench/parts/experiments/test/electron-browser/experimentService.test'; +import { ExperimentalPrompts } from 'vs/workbench/contrib/experiments/electron-browser/experimentalPrompt'; +import { ExperimentActionType, ExperimentState, IExperiment, IExperimentActionPromptProperties, IExperimentService } from 'vs/workbench/contrib/experiments/node/experimentService'; +import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; import { TestLifecycleService } from 'vs/workbench/test/workbenchTestServices'; suite('Experimental Prompts', () => { diff --git a/src/vs/workbench/parts/extensions/browser/extensionsQuickOpen.ts b/src/vs/workbench/contrib/extensions/browser/extensionsQuickOpen.ts similarity index 97% rename from src/vs/workbench/parts/extensions/browser/extensionsQuickOpen.ts rename to src/vs/workbench/contrib/extensions/browser/extensionsQuickOpen.ts index 22ff7d0f69..439f17b7c3 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsQuickOpen.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsQuickOpen.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { IAutoFocus, Mode, IModel } from 'vs/base/parts/quickopen/common/quickOpen'; import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; -import { IExtensionsViewlet, VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions'; +import { IExtensionsViewlet, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { INotificationService } from 'vs/platform/notification/common/notification'; diff --git a/src/vs/workbench/parts/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts similarity index 98% rename from src/vs/workbench/parts/extensions/browser/extensionsViewer.ts rename to src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index e794ac4f4c..e2b2e16a04 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -9,7 +9,7 @@ import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IDataSource, ITree, IRenderer } from 'vs/base/parts/tree/browser/tree'; import { Action } from 'vs/base/common/actions'; -import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/parts/extensions/common/extensions'; +import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; import { Event } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -35,8 +35,8 @@ export interface IUnknownExtensionTemplateData { export interface IExtensionData { extension: IExtension; hasChildren: boolean; - getChildren: () => Promise; - parent: IExtensionData; + getChildren: () => Promise; + parent: IExtensionData | null; } export class DataSource implements IDataSource { diff --git a/src/vs/workbench/parts/extensions/common/extensionQuery.ts b/src/vs/workbench/contrib/extensions/common/extensionQuery.ts similarity index 100% rename from src/vs/workbench/parts/extensions/common/extensionQuery.ts rename to src/vs/workbench/contrib/extensions/common/extensionQuery.ts diff --git a/src/vs/workbench/parts/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts similarity index 98% rename from src/vs/workbench/parts/extensions/common/extensions.ts rename to src/vs/workbench/contrib/extensions/common/extensions.ts index 665dac0f8a..53dd8d23e5 100644 --- a/src/vs/workbench/parts/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -18,6 +18,8 @@ import { IExtensionManifest, ExtensionType } from 'vs/platform/extensions/common export const VIEWLET_ID = 'workbench.view.extensions'; export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); +export const EXTENSIONS_CONFIG = '.azuredatastudio/extensions.json'; + export interface IExtensionsViewlet extends IViewlet { search(text: string): void; } @@ -153,4 +155,4 @@ export class ExtensionContainers extends Disposable { } } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/extensions/common/extensionsFileTemplate.ts b/src/vs/workbench/contrib/extensions/common/extensionsFileTemplate.ts similarity index 100% rename from src/vs/workbench/parts/extensions/common/extensionsFileTemplate.ts rename to src/vs/workbench/contrib/extensions/common/extensionsFileTemplate.ts diff --git a/src/vs/workbench/parts/extensions/common/extensionsInput.ts b/src/vs/workbench/contrib/extensions/common/extensionsInput.ts similarity index 94% rename from src/vs/workbench/parts/extensions/common/extensionsInput.ts rename to src/vs/workbench/contrib/extensions/common/extensionsInput.ts index 3a9b07c147..734bc6e5ca 100644 --- a/src/vs/workbench/parts/extensions/common/extensionsInput.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionsInput.ts @@ -5,7 +5,7 @@ import { localize } from 'vs/nls'; import { EditorInput } from 'vs/workbench/common/editor'; -import { IExtension } from 'vs/workbench/parts/extensions/common/extensions'; +import { IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; export class ExtensionsInput extends EditorInput { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts b/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts similarity index 96% rename from src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts rename to src/vs/workbench/contrib/extensions/common/extensionsUtils.ts index ef31b2239f..d3b08e20ce 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsUtils.ts +++ b/src/vs/workbench/contrib/extensions/common/extensionsUtils.ts @@ -15,7 +15,6 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; -import product from 'vs/platform/node/product'; export interface IExtensionStatus { identifier: IExtensionIdentifier; @@ -137,8 +136,3 @@ export function isKeymapExtension(tipsService: IExtensionTipsService, extension: const cats = extension.local.manifest.categories; return cats && cats.indexOf('Keymaps') !== -1 || tipsService.getKeymapRecommendations().some(({ extensionId }) => areSameExtensions({ id: extensionId }, extension.local.identifier)); } - -export function getKeywordsForExtension(extension: string): string[] { - const keywords = product.extensionKeywords || {}; - return keywords[extension] || []; -} \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts similarity index 67% rename from src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts index 6c4aff2cbe..891d30471d 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionEditor.ts @@ -23,18 +23,18 @@ import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiati import { IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionManifest, IKeyBinding, IView, IViewContainer, ExtensionType } from 'vs/platform/extensions/common/extensions'; import { ResolvedKeybinding, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; -import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, IExtensionDependencies, ExtensionContainers } from 'vs/workbench/parts/extensions/common/extensions'; -import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget } from 'vs/workbench/parts/extensions/electron-browser/extensionsWidgets'; +import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; +import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, IExtensionDependencies, ExtensionContainers } from 'vs/workbench/contrib/extensions/common/extensions'; +import { RatingsWidget, InstallCountWidget, RemoteBadgeWidget } from 'vs/workbench/contrib/extensions/electron-browser/extensionsWidgets'; import { EditorOptions } from 'vs/workbench/common/editor'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { CombinedInstallAction, UpdateAction, ExtensionEditorDropDownAction, ReloadAction, MaliciousStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction, EnableDropDownAction, DisableDropDownAction, StatusLabelAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; -import { WebviewElement } from 'vs/workbench/parts/webview/electron-browser/webviewElement'; +import { CombinedInstallAction, UpdateAction, ExtensionEditorDropDownAction, ReloadAction, MaliciousStatusLabelAction, IgnoreExtensionRecommendationAction, UndoIgnoreExtensionRecommendationAction, EnableDropDownAction, DisableDropDownAction, StatusLabelAction, SetFileIconThemeAction, SetColorThemeAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; +import { WebviewElement } from 'vs/workbench/contrib/webview/electron-browser/webviewElement'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; -import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -45,18 +45,18 @@ import { Color } from 'vs/base/common/color'; import { assign } from 'vs/base/common/objects'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { ExtensionsTree, IExtensionData } from 'vs/workbench/parts/extensions/browser/extensionsViewer'; -import { ShowCurrentReleaseNotesAction } from 'vs/workbench/parts/update/electron-browser/update'; - -// {{SQL CARBON EDIT}} -import { renderDashboardContributions } from 'sql/parts/extensions/sqlExtensionsHelper'; -// {{SQL CARBON EDIT}} - End - +import { ExtensionsTree, IExtensionData } from 'vs/workbench/contrib/extensions/browser/extensionsViewer'; +import { ShowCurrentReleaseNotesAction } from 'vs/workbench/contrib/update/electron-browser/update'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { getDefaultValue } from 'vs/platform/configuration/common/configurationRegistry'; import { isUndefined } from 'vs/base/common/types'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; + +// {{SQL CARBON EDIT}} +import { renderDashboardContributions } from 'sql/parts/extensions/sqlExtensionsHelper'; +// {{SQL CARBON EDIT}} - End function renderBody(body: string): string { const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-core-resource://'); @@ -79,8 +79,13 @@ function removeEmbeddedSVGs(documentContent: string): string { // remove all inline svgs const allSVGs = newDocument.documentElement.querySelectorAll('svg'); - for (let i = 0; i < allSVGs.length; i++) { - allSVGs[i].parentNode.removeChild(allSVGs[i]); + if (allSVGs) { + for (let i = 0; i < allSVGs.length; i++) { + const svg = allSVGs[i]; + if (svg.parentNode) { + svg.parentNode.removeChild(allSVGs[i]); + } + } } return newDocument.documentElement.outerHTML; @@ -88,8 +93,8 @@ function removeEmbeddedSVGs(documentContent: string): string { class NavBar { - private _onChange = new Emitter<{ id: string, focus: boolean }>(); - get onChange(): Event<{ id: string, focus: boolean }> { return this._onChange.event; } + private _onChange = new Emitter<{ id: string | null, focus: boolean }>(); + get onChange(): Event<{ id: string | null, focus: boolean }> { return this._onChange.event; } private currentId: string | null = null; private actions: Action[]; @@ -102,7 +107,7 @@ class NavBar { } push(id: string, label: string, tooltip: string): void { - const action = new Action(id, label, null, true, () => this._update(id, true)); + const action = new Action(id, label, undefined, true, () => this._update(id, true)); action.tooltip = tooltip; @@ -123,9 +128,9 @@ class NavBar { this._update(this.currentId); } - _update(id: string = this.currentId, focus?: boolean): Promise { + _update(id: string | null = this.currentId, focus?: boolean): Promise { this.currentId = id; - this._onChange.fire({ id, focus }); + this._onChange.fire({ id, focus: !!focus }); this.actions.forEach(a => a.enabled = a.id !== id); return Promise.resolve(undefined); } @@ -175,16 +180,16 @@ export class ExtensionEditor extends BaseEditor { private ignoreActionbar: ActionBar; private header: HTMLElement; - private extensionReadme: Cache; - private extensionChangelog: Cache; - private extensionManifest: Cache; - private extensionDependencies: Cache; + private extensionReadme: Cache | null; + private extensionChangelog: Cache | null; + private extensionManifest: Cache | null; + private extensionDependencies: Cache | null; private layoutParticipants: ILayoutParticipant[] = []; private contentDisposables: IDisposable[] = []; private transientDisposables: IDisposable[] = []; private disposables: IDisposable[]; - private activeElement: IActiveElement; + private activeElement: IActiveElement | null; private editorLoadComplete: boolean = false; constructor( @@ -196,10 +201,12 @@ export class ExtensionEditor extends BaseEditor { @IKeybindingService private readonly keybindingService: IKeybindingService, @INotificationService private readonly notificationService: INotificationService, @IOpenerService private readonly openerService: IOpenerService, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService, @IStorageService storageService: IStorageService, - @IExtensionService private readonly extensionService: IExtensionService + @IExtensionService private readonly extensionService: IExtensionService, + @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService + ) { super(ExtensionEditor.ID, telemetryService, themeService, storageService); this.disposables = []; @@ -278,147 +285,150 @@ export class ExtensionEditor extends BaseEditor { this.content = append(body, $('.content')); } - setInput(input: ExtensionsInput, options: EditorOptions, token: CancellationToken): Promise { - return this.extensionService.getExtensions() - .then(runningExtensions => { - this.activeElement = null; - this.editorLoadComplete = false; - const extension = input.extension; + async setInput(input: ExtensionsInput, options: EditorOptions, token: CancellationToken): Promise { + const runningExtensions = await this.extensionService.getExtensions(); + const colorThemes = await this.workbenchThemeService.getColorThemes(); + const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); - this.transientDisposables = dispose(this.transientDisposables); + this.activeElement = null; + this.editorLoadComplete = false; + const extension = input.extension; - this.extensionReadme = new Cache(() => createCancelablePromise(token => extension.getReadme(token))); - this.extensionChangelog = new Cache(() => createCancelablePromise(token => extension.getChangelog(token))); - this.extensionManifest = new Cache(() => createCancelablePromise(token => extension.getManifest(token))); - this.extensionDependencies = new Cache(() => createCancelablePromise(token => this.extensionsWorkbenchService.loadDependencies(extension, token))); + this.transientDisposables = dispose(this.transientDisposables); - const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, this.iconContainer); - const onError = Event.once(domEvent(this.icon, 'error')); - onError(() => this.icon.src = extension.iconUrlFallback, null, this.transientDisposables); - this.icon.src = extension.iconUrl; + this.extensionReadme = new Cache(() => createCancelablePromise(token => extension.getReadme(token))); + this.extensionChangelog = new Cache(() => createCancelablePromise(token => extension.getChangelog(token))); + this.extensionManifest = new Cache(() => createCancelablePromise(token => extension.getManifest(token))); + this.extensionDependencies = new Cache(() => createCancelablePromise(token => this.extensionsWorkbenchService.loadDependencies(extension, token))); - this.name.textContent = extension.displayName; - this.identifier.textContent = extension.identifier.id; - this.preview.style.display = extension.preview ? 'inherit' : 'none'; - this.builtin.style.display = extension.type === ExtensionType.System ? 'inherit' : 'none'; + const remoteBadge = this.instantiationService.createInstance(RemoteBadgeWidget, this.iconContainer); + const onError = Event.once(domEvent(this.icon, 'error')); + onError(() => this.icon.src = extension.iconUrlFallback, null, this.transientDisposables); + this.icon.src = extension.iconUrl; - this.publisher.textContent = extension.publisherDisplayName; - this.description.textContent = extension.description; + this.name.textContent = extension.displayName; + this.identifier.textContent = extension.identifier.id; + this.preview.style.display = extension.preview ? 'inherit' : 'none'; + this.builtin.style.display = extension.type === ExtensionType.System ? 'inherit' : 'none'; - const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); - let recommendationsData = {}; - if (extRecommendations[extension.identifier.id.toLowerCase()]) { - recommendationsData = { recommendationReason: extRecommendations[extension.identifier.id.toLowerCase()].reasonId }; - } + this.publisher.textContent = extension.publisherDisplayName; + this.description.textContent = extension.description; - /* __GDPR__ - "extensionGallery:openExtension" : { - "recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "${include}": [ - "${GalleryExtensionTelemetryData}" - ] - } - */ - this.telemetryService.publicLog('extensionGallery:openExtension', assign(extension.telemetryData, recommendationsData)); + const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason(); + let recommendationsData = {}; + if (extRecommendations[extension.identifier.id.toLowerCase()]) { + recommendationsData = { recommendationReason: extRecommendations[extension.identifier.id.toLowerCase()].reasonId }; + } - toggleClass(this.name, 'clickable', !!extension.url); - toggleClass(this.publisher, 'clickable', !!extension.url); - toggleClass(this.rating, 'clickable', !!extension.url); - if (extension.url) { - this.name.onclick = finalHandler(() => window.open(extension.url)); - this.rating.onclick = finalHandler(() => window.open(`${extension.url}#review-details`)); - this.publisher.onclick = finalHandler(() => { - this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => viewlet.search(`publisher:"${extension.publisherDisplayName}"`)); - }); + /* __GDPR__ + "extensionGallery:openExtension" : { + "recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "${include}": [ + "${GalleryExtensionTelemetryData}" + ] + } + */ + this.telemetryService.publicLog('extensionGallery:openExtension', assign(extension.telemetryData, recommendationsData)); - if (extension.licenseUrl) { - this.license.onclick = finalHandler(() => window.open(extension.licenseUrl)); - this.license.style.display = 'initial'; - } else { - this.license.onclick = null; - this.license.style.display = 'none'; - } - } else { - this.name.onclick = null; - this.rating.onclick = null; - this.publisher.onclick = null; - this.license.onclick = null; - this.license.style.display = 'none'; - } - - // {{SQL CARBON EDIT}} - if (extension.licenseUrl) { - this.license.onclick = finalHandler(() => window.open(extension.licenseUrl)); - this.license.style.display = 'initial'; - } else { - this.license.onclick = null; - this.license.style.display = 'none'; - } - // {{SQL CARBON EDIT}} - End - - if (extension.repository) { - this.repository.onclick = finalHandler(() => window.open(extension.repository)); - this.repository.style.display = 'initial'; - } - else { - this.repository.onclick = null; - this.repository.style.display = 'none'; - } - - const widgets = [ - remoteBadge, - // {{SQL CARBON EDIT}} - // this.instantiationService.createInstance(InstallCountWidget, this.installCount, false), - // this.instantiationService.createInstance(RatingsWidget, this.rating, false) - ]; - const reloadAction = this.instantiationService.createInstance(ReloadAction); - const actions = [ - reloadAction, - this.instantiationService.createInstance(StatusLabelAction), - this.instantiationService.createInstance(UpdateAction), - this.instantiationService.createInstance(EnableDropDownAction), - this.instantiationService.createInstance(DisableDropDownAction, runningExtensions), - this.instantiationService.createInstance(CombinedInstallAction), - this.instantiationService.createInstance(MaliciousStatusLabelAction, true), - ]; - const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets]); - extensionContainers.extension = extension; - - this.extensionActionBar.clear(); - this.extensionActionBar.push(actions, { icon: true, label: true }); - this.transientDisposables.push(...[...actions, ...widgets, extensionContainers]); - - this.setSubText(extension, reloadAction); - this.content.innerHTML = ''; // Clear content before setting navbar actions. - - this.navbar.clear(); - this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables); - - if (extension.hasReadme()) { - this.navbar.push(NavbarSection.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file")); - } - this.extensionManifest.get() - .promise - .then(manifest => { - if (extension.extensionPack.length) { - this.navbar.push(NavbarSection.ExtensionPack, localize('extensionPack', "Extension Pack"), localize('extensionsPack', "Set of extensions that can be installed together")); - } - if (manifest && manifest.contributes) { - this.navbar.push(NavbarSection.Contributions, localize('contributions', "Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension")); - } - if (extension.hasChangelog()) { - this.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file")); - } - if (extension.dependencies.length) { - this.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"), localize('dependenciestooltip', "Lists extensions this extension depends on")); - } - this.editorLoadComplete = true; - }); - - return super.setInput(input, options, token); + toggleClass(this.name, 'clickable', !!extension.url); + toggleClass(this.publisher, 'clickable', !!extension.url); + toggleClass(this.rating, 'clickable', !!extension.url); + if (extension.url) { + this.name.onclick = finalHandler(() => window.open(extension.url)); + this.rating.onclick = finalHandler(() => window.open(`${extension.url}#review-details`)); + this.publisher.onclick = finalHandler(() => { + this.viewletService.openViewlet(VIEWLET_ID, true) + .then(viewlet => viewlet as IExtensionsViewlet) + .then(viewlet => viewlet.search(`publisher:"${extension.publisherDisplayName}"`)); }); + + if (extension.licenseUrl) { + this.license.onclick = finalHandler(() => window.open(extension.licenseUrl)); + this.license.style.display = 'initial'; + } else { + this.license.onclick = null; + this.license.style.display = 'none'; + } + } else { + this.name.onclick = null; + this.rating.onclick = null; + this.publisher.onclick = null; + this.license.onclick = null; + this.license.style.display = 'none'; + } + + // {{SQL CARBON EDIT}} add license url + if (extension.licenseUrl) { + this.license.onclick = finalHandler(() => window.open(extension.licenseUrl)); + this.license.style.display = 'initial'; + } else { + this.license.onclick = null; + this.license.style.display = 'none'; + } + // {{SQL CARBON EDIT}} - End + + if (extension.repository) { + this.repository.onclick = finalHandler(() => window.open(extension.repository)); + this.repository.style.display = 'initial'; + } + else { + this.repository.onclick = null; + this.repository.style.display = 'none'; + } + + const widgets = [ + remoteBadge, + // {{SQL CARBON EDIT}} Remove the widgets + // this.instantiationService.createInstance(InstallCountWidget, this.installCount, false), + // this.instantiationService.createInstance(RatingsWidget, this.rating, false) + ]; + const reloadAction = this.instantiationService.createInstance(ReloadAction); + const actions = [ + reloadAction, + this.instantiationService.createInstance(StatusLabelAction), + this.instantiationService.createInstance(UpdateAction), + this.instantiationService.createInstance(SetColorThemeAction, colorThemes), + this.instantiationService.createInstance(SetFileIconThemeAction, fileIconThemes), + this.instantiationService.createInstance(EnableDropDownAction), + this.instantiationService.createInstance(DisableDropDownAction, runningExtensions), + this.instantiationService.createInstance(CombinedInstallAction), + this.instantiationService.createInstance(MaliciousStatusLabelAction, true), + ]; + const extensionContainers: ExtensionContainers = this.instantiationService.createInstance(ExtensionContainers, [...actions, ...widgets]); + extensionContainers.extension = extension; + + this.extensionActionBar.clear(); + this.extensionActionBar.push(actions, { icon: true, label: true }); + this.transientDisposables.push(...[...actions, ...widgets, extensionContainers]); + + this.setSubText(extension, reloadAction); + this.content.innerHTML = ''; // Clear content before setting navbar actions. + + this.navbar.clear(); + this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables); + + if (extension.hasReadme()) { + this.navbar.push(NavbarSection.Readme, localize('details', "Details"), localize('detailstooltip', "Extension details, rendered from the extension's 'README.md' file")); + } + this.extensionManifest.get() + .promise + .then(manifest => { + if (extension.extensionPack.length) { + this.navbar.push(NavbarSection.ExtensionPack, localize('extensionPack', "Extension Pack"), localize('extensionsPack', "Set of extensions that can be installed together")); + } + if (manifest && manifest.contributes) { + this.navbar.push(NavbarSection.Contributions, localize('contributions', "Contributions"), localize('contributionstooltip', "Lists contributions to VS Code by this extension")); + } + if (extension.hasChangelog()) { + this.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"), localize('changelogtooltip', "Extension update history, rendered from the extension's 'CHANGELOG.md' file")); + } + if (extension.dependencies.length) { + this.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"), localize('dependenciestooltip', "Lists extensions this extension depends on")); + } + this.editorLoadComplete = true; + }); + + return super.setInput(input, options, token); } private setSubText(extension: IExtension, reloadAction: ReloadAction): void { @@ -536,9 +546,14 @@ export class ExtensionEditor extends BaseEditor { .then(renderBody) .then(removeEmbeddedSVGs) .then(body => { - const allowedBadgeProviders = this.extensionsWorkbenchService.allowedBadgeProviders; - const webViewOptions = allowedBadgeProviders.length > 0 ? { allowScripts: false, allowSvgs: false, svgWhiteList: allowedBadgeProviders } : {}; - const wbeviewElement = this.instantiationService.createInstance(WebviewElement, this.partService.getContainer(Parts.EDITOR_PART), webViewOptions); + const wbeviewElement = this.instantiationService.createInstance(WebviewElement, + this.layoutService.getContainer(Parts.EDITOR_PART), + { + enableFindWidget: true, + }, + { + svgWhiteList: this.extensionsWorkbenchService.allowedBadgeProviders + }); wbeviewElement.mountTo(this.content); const removeLayoutParticipant = arrays.insert(this.layoutParticipants, wbeviewElement); this.contentDisposables.push(toDisposable(removeLayoutParticipant)); @@ -564,17 +579,21 @@ export class ExtensionEditor extends BaseEditor { } private openReadme(): Promise { - return this.openMarkdown(this.extensionReadme.get(), localize('noReadme', "No README available.")); + return this.openMarkdown(this.extensionReadme!.get(), localize('noReadme', "No README available.")); } private openChangelog(): Promise { - return this.openMarkdown(this.extensionChangelog.get(), localize('noChangelog', "No Changelog available.")); + return this.openMarkdown(this.extensionChangelog!.get(), localize('noChangelog', "No Changelog available.")); } private openContributions(): Promise { const content = $('div', { class: 'subcontent', tabindex: '0' }); - return this.loadContents(() => this.extensionManifest.get()) + return this.loadContents(() => this.extensionManifest!.get()) .then(manifest => { + if (!manifest) { + return content; + } + const scrollableContent = new DomScrollableElement(content, {}); const layout = () => scrollableContent.scanDomNode(); @@ -621,8 +640,8 @@ export class ExtensionEditor extends BaseEditor { return Promise.resolve(this.content); } - return this.loadContents(() => this.extensionDependencies.get()) - .then(extensionDependencies => { + return this.loadContents(() => this.extensionDependencies!.get()) + .then(extensionDependencies => { if (extensionDependencies) { const content = $('div', { class: 'subcontent' }); const scrollableContent = new DomScrollableElement(content, {}); @@ -665,7 +684,7 @@ export class ExtensionEditor extends BaseEditor { return this.extensionDependencies.extension; } - get parent(): IExtensionData { + get parent(): IExtensionData | null { return this.extensionDependencies.dependent ? new ExtensionData(this.extensionDependencies.dependent) : null; } @@ -673,8 +692,8 @@ export class ExtensionEditor extends BaseEditor { return this.extensionDependencies.hasDependencies; } - getChildren(): Promise { - return this.extensionDependencies.dependencies ? Promise.resolve(this.extensionDependencies.dependencies.map(d => new ExtensionData(d))) : null; + getChildren(): Promise { + return this.extensionDependencies.dependencies ? Promise.resolve(this.extensionDependencies.dependencies.map(d => new ExtensionData(d))) : Promise.resolve(null); } } @@ -706,11 +725,11 @@ export class ExtensionEditor extends BaseEditor { class ExtensionData implements IExtensionData { readonly extension: IExtension; - readonly parent: IExtensionData; + readonly parent: IExtensionData | null; constructor(extension: IExtension, parent?: IExtensionData) { this.extension = extension; - this.parent = parent; + this.parent = parent || null; } get hasChildren(): boolean { @@ -748,17 +767,17 @@ export class ExtensionEditor extends BaseEditor { } const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', null, localize('settings', "Settings ({0})", contrib.length)), - $('table', null, - $('tr', null, - $('th', null, localize('setting name', "Name")), - $('th', null, localize('description', "Description")), - $('th', null, localize('default', "Default")) + $('summary', undefined, localize('settings', "Settings ({0})", contrib.length)), + $('table', undefined, + $('tr', undefined, + $('th', undefined, localize('setting name', "Name")), + $('th', undefined, localize('description', "Description")), + $('th', undefined, localize('default', "Default")) ), - ...contrib.map(key => $('tr', null, - $('td', null, $('code', null, key)), - $('td', null, properties[key].description), - $('td', null, $('code', null, `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : properties[key].default}`)) + ...contrib.map(key => $('tr', undefined, + $('td', undefined, $('code', undefined, key)), + $('td', undefined, properties[key].description), + $('td', undefined, $('code', undefined, `${isUndefined(properties[key].default) ? getDefaultValue(properties[key].type) : properties[key].default}`)) )) ) ); @@ -776,15 +795,15 @@ export class ExtensionEditor extends BaseEditor { } const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', null, localize('debuggers', "Debuggers ({0})", contrib.length)), - $('table', null, - $('tr', null, - $('th', null, localize('debugger name', "Name")), - $('th', null, localize('debugger type', "Type")), + $('summary', undefined, localize('debuggers', "Debuggers ({0})", contrib.length)), + $('table', undefined, + $('tr', undefined, + $('th', undefined, localize('debugger name', "Name")), + $('th', undefined, localize('debugger type', "Type")), ), - ...contrib.map(d => $('tr', null, - $('td', null, d.label), - $('td', null, d.type))) + ...contrib.map(d => $('tr', undefined, + $('td', undefined, d.label!), + $('td', undefined, d.type))) ) ); @@ -796,21 +815,21 @@ export class ExtensionEditor extends BaseEditor { const contributes = manifest.contributes; const contrib = contributes && contributes.viewsContainers || {}; - let viewContainers = <{ id: string, title: string, location: string }[]>Object.keys(contrib).reduce((result, location) => { + let viewContainers = Object.keys(contrib).reduce((result, location) => { let viewContainersForLocation: IViewContainer[] = contrib[location]; result.push(...viewContainersForLocation.map(viewContainer => ({ ...viewContainer, location }))); return result; - }, []); + }, [] as Array<{ id: string, title: string, location: string }>); if (!viewContainers.length) { return false; } const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', null, localize('viewContainers', "View Containers ({0})", viewContainers.length)), - $('table', null, - $('tr', null, $('th', null, localize('view container id', "ID")), $('th', null, localize('view container title', "Title")), $('th', null, localize('view container location', "Where"))), - ...viewContainers.map(viewContainer => $('tr', null, $('td', null, viewContainer.id), $('td', null, viewContainer.title), $('td', null, viewContainer.location))) + $('summary', undefined, localize('viewContainers', "View Containers ({0})", viewContainers.length)), + $('table', undefined, + $('tr', undefined, $('th', undefined, localize('view container id', "ID")), $('th', undefined, localize('view container title', "Title")), $('th', undefined, localize('view container location', "Where"))), + ...viewContainers.map(viewContainer => $('tr', undefined, $('td', undefined, viewContainer.id), $('td', undefined, viewContainer.title), $('td', undefined, viewContainer.location))) ) ); @@ -822,21 +841,21 @@ export class ExtensionEditor extends BaseEditor { const contributes = manifest.contributes; const contrib = contributes && contributes.views || {}; - let views = <{ id: string, name: string, location: string }[]>Object.keys(contrib).reduce((result, location) => { + let views = Object.keys(contrib).reduce((result, location) => { let viewsForLocation: IView[] = contrib[location]; result.push(...viewsForLocation.map(view => ({ ...view, location }))); return result; - }, []); + }, [] as Array<{ id: string, name: string, location: string }>); if (!views.length) { return false; } const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', null, localize('views', "Views ({0})", views.length)), - $('table', null, - $('tr', null, $('th', null, localize('view id', "ID")), $('th', null, localize('view name', "Name")), $('th', null, localize('view location', "Where"))), - ...views.map(view => $('tr', null, $('td', null, view.id), $('td', null, view.name), $('td', null, view.location))) + $('summary', undefined, localize('views', "Views ({0})", views.length)), + $('table', undefined, + $('tr', undefined, $('th', undefined, localize('view id', "ID")), $('th', undefined, localize('view name', "Name")), $('th', undefined, localize('view location', "Where"))), + ...views.map(view => $('tr', undefined, $('td', undefined, view.id), $('td', undefined, view.name), $('td', undefined, view.location))) ) ); @@ -853,10 +872,10 @@ export class ExtensionEditor extends BaseEditor { } const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', null, localize('localizations', "Localizations ({0})", localizations.length)), - $('table', null, - $('tr', null, $('th', null, localize('localizations language id', "Language Id")), $('th', null, localize('localizations language name', "Language Name")), $('th', null, localize('localizations localized language name', "Language Name (Localized)"))), - ...localizations.map(localization => $('tr', null, $('td', null, localization.languageId), $('td', null, localization.languageName), $('td', null, localization.localizedLanguageName))) + $('summary', undefined, localize('localizations', "Localizations ({0})", localizations.length)), + $('table', undefined, + $('tr', undefined, $('th', undefined, localize('localizations language id', "Language Id")), $('th', undefined, localize('localizations language name', "Language Name")), $('th', undefined, localize('localizations localized language name', "Language Name (Localized)"))), + ...localizations.map(localization => $('tr', undefined, $('td', undefined, localization.languageId), $('td', undefined, localization.languageName || ''), $('td', undefined, localization.localizedLanguageName || ''))) ) ); @@ -873,8 +892,8 @@ export class ExtensionEditor extends BaseEditor { } const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', null, localize('colorThemes', "Color Themes ({0})", contrib.length)), - $('ul', null, ...contrib.map(theme => $('li', null, theme.label))) + $('summary', undefined, localize('colorThemes', "Color Themes ({0})", contrib.length)), + $('ul', undefined, ...contrib.map(theme => $('li', undefined, theme.label))) ); append(container, details); @@ -890,8 +909,8 @@ export class ExtensionEditor extends BaseEditor { } const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', null, localize('iconThemes', "Icon Themes ({0})", contrib.length)), - $('ul', null, ...contrib.map(theme => $('li', null, theme.label))) + $('summary', undefined, localize('iconThemes', "Icon Themes ({0})", contrib.length)), + $('ul', undefined, ...contrib.map(theme => $('li', undefined, theme.label))) ); append(container, details); @@ -914,26 +933,26 @@ export class ExtensionEditor extends BaseEditor { result.push($('span', { class: 'colorBox', style: 'background-color: ' + Color.Format.CSS.format(color) }, '')); } } - result.push($('code', null, colorReference)); + result.push($('code', undefined, colorReference)); return result; } const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', null, localize('colors', "Colors ({0})", colors.length)), - $('table', null, - $('tr', null, - $('th', null, localize('colorId', "Id")), - $('th', null, localize('description', "Description")), - $('th', null, localize('defaultDark', "Dark Default")), - $('th', null, localize('defaultLight', "Light Default")), - $('th', null, localize('defaultHC', "High Contrast Default")) + $('summary', undefined, localize('colors', "Colors ({0})", colors.length)), + $('table', undefined, + $('tr', undefined, + $('th', undefined, localize('colorId', "Id")), + $('th', undefined, localize('description', "Description")), + $('th', undefined, localize('defaultDark', "Dark Default")), + $('th', undefined, localize('defaultLight', "Light Default")), + $('th', undefined, localize('defaultHC', "High Contrast Default")) ), - ...colors.map(color => $('tr', null, - $('td', null, $('code', null, color.id)), - $('td', null, color.description), - $('td', null, ...colorPreview(color.defaults.dark)), - $('td', null, ...colorPreview(color.defaults.light)), - $('td', null, ...colorPreview(color.defaults.highContrast)) + ...colors.map(color => $('tr', undefined, + $('td', undefined, $('code', undefined, color.id)), + $('td', undefined, color.description), + $('td', undefined, ...colorPreview(color.defaults.dark)), + $('td', undefined, ...colorPreview(color.defaults.light)), + $('td', undefined, ...colorPreview(color.defaults.highContrast)) )) ) ); @@ -952,15 +971,15 @@ export class ExtensionEditor extends BaseEditor { } const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', null, localize('JSON Validation', "JSON Validation ({0})", contrib.length)), - $('table', null, - $('tr', null, - $('th', null, localize('fileMatch', "File Match")), - $('th', null, localize('schema', "Schema")) + $('summary', undefined, localize('JSON Validation', "JSON Validation ({0})", contrib.length)), + $('table', undefined, + $('tr', undefined, + $('th', undefined, localize('fileMatch', "File Match")), + $('th', undefined, localize('schema', "Schema")) ), - ...contrib.map(v => $('tr', null, - $('td', null, $('code', null, v.fileMatch)), - $('td', null, v.url) + ...contrib.map(v => $('tr', undefined, + $('td', undefined, $('code', undefined, v.fileMatch)), + $('td', undefined, v.url) )))); append(container, details); @@ -973,8 +992,8 @@ export class ExtensionEditor extends BaseEditor { const commands = rawCommands.map(c => ({ id: c.command, title: c.title, - keybindings: [], - menus: [] + keybindings: [] as ResolvedKeybinding[], + menus: [] as string[] })); const byId = arrays.index(commands, c => c.id); @@ -1026,19 +1045,19 @@ export class ExtensionEditor extends BaseEditor { }; const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', null, localize('commands', "Commands ({0})", commands.length)), - $('table', null, - $('tr', null, - $('th', null, localize('command name', "Name")), - $('th', null, localize('description', "Description")), - $('th', null, localize('keyboard shortcuts', "Keyboard Shortcuts")), - $('th', null, localize('menuContexts', "Menu Contexts")) + $('summary', undefined, localize('commands', "Commands ({0})", commands.length)), + $('table', undefined, + $('tr', undefined, + $('th', undefined, localize('command name', "Name")), + $('th', undefined, localize('description', "Description")), + $('th', undefined, localize('keyboard shortcuts', "Keyboard Shortcuts")), + $('th', undefined, localize('menuContexts', "Menu Contexts")) ), - ...commands.map(c => $('tr', null, - $('td', null, $('code', null, c.id)), - $('td', null, c.title), - $('td', null, ...c.keybindings.map(keybinding => renderKeybinding(keybinding))), - $('td', null, ...c.menus.map(context => $('code', null, context))) + ...commands.map(c => $('tr', undefined, + $('td', undefined, $('code', undefined, c.id)), + $('td', undefined, c.title), + $('td', undefined, ...c.keybindings.map(keybinding => renderKeybinding(keybinding))), + $('td', undefined, ...c.menus.map(context => $('code', undefined, context))) )) ) ); @@ -1093,21 +1112,21 @@ export class ExtensionEditor extends BaseEditor { } const details = $('details', { open: true, ontoggle: onDetailsToggle }, - $('summary', null, localize('languages', "Languages ({0})", languages.length)), - $('table', null, - $('tr', null, - $('th', null, localize('language id', "ID")), - $('th', null, localize('language name', "Name")), - $('th', null, localize('file extensions', "File Extensions")), - $('th', null, localize('grammar', "Grammar")), - $('th', null, localize('snippets', "Snippets")) + $('summary', undefined, localize('languages', "Languages ({0})", languages.length)), + $('table', undefined, + $('tr', undefined, + $('th', undefined, localize('language id', "ID")), + $('th', undefined, localize('language name', "Name")), + $('th', undefined, localize('file extensions', "File Extensions")), + $('th', undefined, localize('grammar', "Grammar")), + $('th', undefined, localize('snippets', "Snippets")) ), - ...languages.map(l => $('tr', null, - $('td', null, l.id), - $('td', null, l.name), - $('td', null, ...join(l.extensions.map(ext => $('code', null, ext)), ' ')), - $('td', null, document.createTextNode(l.hasGrammar ? '✔︎' : '—')), - $('td', null, document.createTextNode(l.hasSnippets ? '✔︎' : '—')) + ...languages.map(l => $('tr', undefined, + $('td', undefined, l.id), + $('td', undefined, l.name), + $('td', undefined, ...join(l.extensions.map(ext => $('code', undefined, ext)), ' ')), + $('td', undefined, document.createTextNode(l.hasGrammar ? '✔︎' : '—')), + $('td', undefined, document.createTextNode(l.hasSnippets ? '✔︎' : '—')) )) ) ); @@ -1116,8 +1135,8 @@ export class ExtensionEditor extends BaseEditor { return true; } - private resolveKeybinding(rawKeyBinding: IKeyBinding): ResolvedKeybinding { - let key: string; + private resolveKeybinding(rawKeyBinding: IKeyBinding): ResolvedKeybinding | null { + let key: string | undefined; switch (process.platform) { case 'win32': key = rawKeyBinding.win; break; @@ -1172,7 +1191,7 @@ class ShowExtensionEditorFindCommand extends Command { } } - private getExtensionEditor(accessor: ServicesAccessor): ExtensionEditor { + private getExtensionEditor(accessor: ServicesAccessor): ExtensionEditor | null { const activeControl = accessor.get(IEditorService).activeControl as ExtensionEditor; if (activeControl instanceof ExtensionEditor) { return activeControl; diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts similarity index 93% rename from src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts index 4683c9e5a3..2d04688d44 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionProfileService.ts @@ -13,13 +13,13 @@ import { append, $, addDisposableListener } from 'vs/base/browser/dom'; import { IStatusbarRegistry, StatusbarItemDescriptor, Extensions, IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IExtensionHostProfileService, ProfileSessionState } from 'vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor'; +import { IExtensionHostProfileService, ProfileSessionState } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { randomPort } from 'vs/base/node/ports'; -import product from 'vs/platform/node/product'; -import { RuntimeExtensionsInput } from 'vs/workbench/services/extensions/electron-browser/runtimeExtensionsInput'; +import product from 'vs/platform/product/node/product'; +import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export class ExtensionHostProfileService extends Disposable implements IExtensionHostProfileService { @@ -33,8 +33,8 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio public readonly onDidChangeLastProfile: Event = this._onDidChangeLastProfile.event; private readonly _unresponsiveProfiles = new Map(); - private _profile: IExtensionHostProfile; - private _profileSession: ProfileSession; + private _profile: IExtensionHostProfile | null; + private _profileSession: ProfileSession | null; private _state: ProfileSessionState; public get state() { return this._state; } @@ -71,7 +71,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio this._onDidChangeState.fire(undefined); } - public startProfiling(): Promise { + public startProfiling(): Promise | null { if (this._state !== ProfileSessionState.None) { return null; } @@ -102,7 +102,7 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio } public stopProfiling(): void { - if (this._state !== ProfileSessionState.Running) { + if (this._state !== ProfileSessionState.Running || !this._profileSession) { return; } @@ -142,7 +142,7 @@ export class ProfileExtHostStatusbarItem implements IStatusbarItem { private label: HTMLElement; private timeStarted: number; private labelUpdater: any; - private clickHandler: () => void; + private clickHandler: (() => void) | null; constructor() { ProfileExtHostStatusbarItem.instance = this; diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts similarity index 97% rename from src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts index 8f914ead62..8472c65b55 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionTipsService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import * as paths from 'vs/base/common/paths'; +import { join } from 'vs/base/common/path'; import { forEach } from 'vs/base/common/collections'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { match } from 'vs/base/common/glob'; @@ -16,14 +16,14 @@ import { import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextModel } from 'vs/editor/common/model'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ShowRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction, InstallRecommendedExtensionAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; +import { ShowRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction, InstallRecommendedExtensionAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; import Severity from 'vs/base/common/severity'; import { IWorkspaceContextService, IWorkspaceFolder, IWorkspace, IWorkspaceFoldersChangeEvent, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IFileService } from 'vs/platform/files/common/files'; // {{SQL CARBON EDIT}} -import { IExtensionsConfiguration, ConfigurationKey, ShowRecommendationsOnlyOnDemandKey, IExtensionsViewlet, IExtensionsWorkbenchService, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/parts/extensions/common/extensions'; +import { IExtensionsConfiguration, ConfigurationKey, ShowRecommendationsOnlyOnDemandKey, IExtensionsViewlet, IExtensionsWorkbenchService, EXTENSIONS_CONFIG, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/contrib/extensions/common/extensions'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import * as pfs from 'vs/base/node/pfs'; @@ -32,7 +32,7 @@ import { flatten, distinct, shuffle, coalesce } from 'vs/base/common/arrays'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { guessMimeTypes, MIME_UNKNOWN } from 'vs/base/common/mime'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { getHashedRemotesFromUri } from 'vs/workbench/parts/stats/node/workspaceStats'; +import { getHashedRemotesFromUri } from 'vs/workbench/contrib/stats/node/workspaceStats'; import { IRequestService } from 'vs/platform/request/node/request'; import { asJson } from 'vs/base/node/request'; import { isNumber } from 'vs/base/common/types'; @@ -42,10 +42,10 @@ import { Emitter, Event } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/workbench/parts/experiments/node/experimentService'; +import { IExperimentService, ExperimentActionType, ExperimentState } from 'vs/workbench/contrib/experiments/node/experimentService'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { getKeywordsForExtension } from 'vs/workbench/parts/extensions/electron-browser/extensionsUtils'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { extname } from 'vs/base/common/resources'; const milliSecondsInADay = 1000 * 60 * 60 * 24; const choiceNever = localize('neverShowAgain', "Don't Show Again"); @@ -347,8 +347,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe * Parse the extensions.json files for given workspace folder and return the recommendations */ private resolveWorkspaceFolderExtensionConfig(workspaceFolder: IWorkspaceFolder): Promise { - // {{SQL CARBON EDIT}} - const extensionsJsonUri = workspaceFolder.toResource(paths.join('.azuredatastudio', 'extensions.json')); + const extensionsJsonUri = workspaceFolder.toResource(EXTENSIONS_CONFIG); return Promise.resolve(this.fileService.resolveFile(extensionsJsonUri) .then(() => this.fileService.resolveContent(extensionsJsonUri)) @@ -603,7 +602,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return; } - let fileExtension = paths.extname(uri.path); + let fileExtension = extname(uri); if (fileExtension) { if (processedFileExtensions.indexOf(fileExtension) > -1) { return; @@ -775,7 +774,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return; } - const keywords = getKeywordsForExtension(fileExtension); + const lookup = product.extensionKeywords || {}; + const keywords = lookup[fileExtension] || []; this._galleryService.query({ text: `tag:"__ext_${fileExtension}" ${keywords.map(tag => `tag:"${tag}"`)}` }).then(pager => { if (!pager || !pager.firstPage || !pager.firstPage.length) { return; @@ -916,8 +916,8 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe .replace('%APPDATA%', process.env['APPDATA']!); promises.push(findExecutable(exeName, windowsPath)); } else { - promises.push(findExecutable(exeName, paths.join('/usr/local/bin', exeName))); - promises.push(findExecutable(exeName, paths.join(homeDir, exeName))); + promises.push(findExecutable(exeName, join('/usr/local/bin', exeName))); + promises.push(findExecutable(exeName, join(homeDir, exeName))); } }); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts similarity index 80% rename from src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index b9b5baaf8b..8fa045f09d 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -9,44 +9,46 @@ import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IExtensionTipsService, ExtensionsLabel, ExtensionsChannelId, PreferencesLabel } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionTipsService, ExtensionsLabel, ExtensionsChannelId, PreferencesLabel, IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; -import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/parts/output/common/output'; +import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/contrib/output/common/output'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; // {{SQL CARBON EDIT}} import { VIEWLET_ID, IExtensionsWorkbenchService, ExtensionsPolicy } from '../common/extensions'; -import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; +import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/node/extensionsWorkbenchService'; import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowDisabledExtensionsAction, ShowBuiltInExtensionsAction, UpdateAllAction, EnableAllAction, EnableAllWorkpsaceAction, DisableAllAction, DisableAllWorkpsaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, ShowAzureExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ConfigureRecommendedExtensionsCommandsContributor, OpenExtensionsFolderAction, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction -} from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; -import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; +} from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; +import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; -import { ExtensionEditor } from 'vs/workbench/parts/extensions/electron-browser/extensionEditor'; -import { StatusUpdater, ExtensionsViewlet, MaliciousExtensionChecker, ExtensionsViewletViewsContribution } from 'vs/workbench/parts/extensions/electron-browser/extensionsViewlet'; +import { ExtensionEditor } from 'vs/workbench/contrib/extensions/electron-browser/extensionEditor'; +import { StatusUpdater, ExtensionsViewlet, MaliciousExtensionChecker, ExtensionsViewletViewsContribution } from 'vs/workbench/contrib/extensions/electron-browser/extensionsViewlet'; import { IQuickOpenRegistry, Extensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import * as jsonContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; -import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/parts/extensions/common/extensionsFileTemplate'; +import { ExtensionsConfigurationSchema, ExtensionsConfigurationSchemaId } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { KeymapExtensions } from 'vs/workbench/parts/extensions/electron-browser/extensionsUtils'; +import { KeymapExtensions } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { GalleryExtensionsHandler, ExtensionsHandler } from 'vs/workbench/parts/extensions/browser/extensionsQuickOpen'; +import { GalleryExtensionsHandler, ExtensionsHandler } from 'vs/workbench/contrib/extensions/browser/extensionsQuickOpen'; import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { RuntimeExtensionsEditor, ShowRuntimeExtensionsAction, IExtensionHostProfileService, DebugExtensionHostAction, StartExtensionHostProfileAction, StopExtensionHostProfileAction, CONTEXT_PROFILE_SESSION_STATE, SaveExtensionHostProfileAction, CONTEXT_EXTENSION_HOST_PROFILE_RECORDED } from 'vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor'; +import { RuntimeExtensionsEditor, ShowRuntimeExtensionsAction, IExtensionHostProfileService, DebugExtensionHostAction, StartExtensionHostProfileAction, StopExtensionHostProfileAction, CONTEXT_PROFILE_SESSION_STATE, SaveExtensionHostProfileAction, CONTEXT_EXTENSION_HOST_PROFILE_RECORDED } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor'; import { EditorInput, IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions, ActiveEditorContext } from 'vs/workbench/common/editor'; -import { ExtensionHostProfileService } from 'vs/workbench/parts/extensions/electron-browser/extensionProfileService'; -import { RuntimeExtensionsInput } from 'vs/workbench/services/extensions/electron-browser/runtimeExtensionsInput'; -import { URI } from 'vs/base/common/uri'; +import { ExtensionHostProfileService } from 'vs/workbench/contrib/extensions/electron-browser/extensionProfileService'; +import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput'; +import { URI, UriComponents } from 'vs/base/common/uri'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { ExtensionActivationProgress } from 'vs/workbench/parts/extensions/electron-browser/extensionsActivationProgress'; -import { ExtensionsAutoProfiler } from 'vs/workbench/parts/extensions/electron-browser/extensionsAutoProfiler'; +import { ExtensionActivationProgress } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActivationProgress'; +import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { ExtensionDependencyChecker } from 'vs/workbench/contrib/extensions/electron-browser/extensionsDependencyChecker'; // Singletons registerSingleton(IExtensionsWorkbenchService, ExtensionsWorkbenchService); @@ -61,6 +63,7 @@ workbenchRegistry.registerWorkbenchContribution(KeymapExtensions, LifecyclePhase workbenchRegistry.registerWorkbenchContribution(ExtensionsViewletViewsContribution, LifecyclePhase.Starting); workbenchRegistry.registerWorkbenchContribution(ExtensionActivationProgress, LifecyclePhase.Eventually); workbenchRegistry.registerWorkbenchContribution(ExtensionsAutoProfiler, LifecyclePhase.Eventually); +workbenchRegistry.registerWorkbenchContribution(ExtensionDependencyChecker, LifecyclePhase.Eventually); Registry.as(OutputExtensions.OutputChannels) .registerChannel({ id: ExtensionsChannelId, label: ExtensionsLabel, log: false }); @@ -71,7 +74,7 @@ Registry.as(Extensions.Quickopen).registerQuickOpenHandler( ExtensionsHandler, ExtensionsHandler.ID, 'ext ', - null, + undefined, localize('extensionsCommands', "Manage Extensions"), true ) @@ -82,7 +85,7 @@ Registry.as(Extensions.Quickopen).registerQuickOpenHandler( GalleryExtensionsHandler, GalleryExtensionsHandler.ID, 'ext install ', - null, + undefined, localize('galleryExtensionsCommands', "Install Gallery Extensions"), true ) @@ -275,23 +278,23 @@ CommandsRegistry.registerCommand('extension.open', (accessor: ServicesAccessor, }); CommandsRegistry.registerCommand(DebugExtensionHostAction.ID, (accessor: ServicesAccessor) => { - const instantationService = accessor.get(IInstantiationService); - instantationService.createInstance(DebugExtensionHostAction).run(); + const instantiationService = accessor.get(IInstantiationService); + instantiationService.createInstance(DebugExtensionHostAction).run(); }); CommandsRegistry.registerCommand(StartExtensionHostProfileAction.ID, (accessor: ServicesAccessor) => { - const instantationService = accessor.get(IInstantiationService); - instantationService.createInstance(StartExtensionHostProfileAction, StartExtensionHostProfileAction.ID, StartExtensionHostProfileAction.LABEL).run(); + const instantiationService = accessor.get(IInstantiationService); + instantiationService.createInstance(StartExtensionHostProfileAction, StartExtensionHostProfileAction.ID, StartExtensionHostProfileAction.LABEL).run(); }); CommandsRegistry.registerCommand(StopExtensionHostProfileAction.ID, (accessor: ServicesAccessor) => { - const instantationService = accessor.get(IInstantiationService); - instantationService.createInstance(StopExtensionHostProfileAction, StopExtensionHostProfileAction.ID, StopExtensionHostProfileAction.LABEL).run(); + const instantiationService = accessor.get(IInstantiationService); + instantiationService.createInstance(StopExtensionHostProfileAction, StopExtensionHostProfileAction.ID, StopExtensionHostProfileAction.LABEL).run(); }); CommandsRegistry.registerCommand(SaveExtensionHostProfileAction.ID, (accessor: ServicesAccessor) => { - const instantationService = accessor.get(IInstantiationService); - instantationService.createInstance(SaveExtensionHostProfileAction, SaveExtensionHostProfileAction.ID, SaveExtensionHostProfileAction.LABEL).run(); + const instantiationService = accessor.get(IInstantiationService); + instantiationService.createInstance(SaveExtensionHostProfileAction, SaveExtensionHostProfileAction.ID, SaveExtensionHostProfileAction.LABEL).run(); }); // File menu registration @@ -335,8 +338,8 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: DebugExtensionHostAction.ID, title: DebugExtensionHostAction.LABEL, iconLocation: { - dark: URI.parse(require.toUrl(`vs/workbench/parts/extensions/electron-browser/media/start-inverse.svg`)), - light: URI.parse(require.toUrl(`vs/workbench/parts/extensions/electron-browser/media/start.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/start-inverse.svg`)), + light: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/start.svg`)), } }, group: 'navigation', @@ -348,8 +351,8 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: StartExtensionHostProfileAction.ID, title: StartExtensionHostProfileAction.LABEL, iconLocation: { - dark: URI.parse(require.toUrl(`vs/workbench/parts/extensions/electron-browser/media/profile-start-inverse.svg`)), - light: URI.parse(require.toUrl(`vs/workbench/parts/extensions/electron-browser/media/profile-start.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/profile-start-inverse.svg`)), + light: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/profile-start.svg`)), } }, group: 'navigation', @@ -361,8 +364,8 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: StopExtensionHostProfileAction.ID, title: StopExtensionHostProfileAction.LABEL, iconLocation: { - dark: URI.parse(require.toUrl(`vs/workbench/parts/extensions/electron-browser/media/profile-stop-inverse.svg`)), - light: URI.parse(require.toUrl(`vs/workbench/parts/extensions/electron-browser/media/profile-stop.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/profile-stop-inverse.svg`)), + light: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/profile-stop.svg`)), } }, group: 'navigation', @@ -374,11 +377,45 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: SaveExtensionHostProfileAction.ID, title: SaveExtensionHostProfileAction.LABEL, iconLocation: { - dark: URI.parse(require.toUrl(`vs/workbench/parts/extensions/electron-browser/media/save-inverse.svg`)), - light: URI.parse(require.toUrl(`vs/workbench/parts/extensions/electron-browser/media/save.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/save-inverse.svg`)), + light: URI.parse(require.toUrl(`vs/workbench/contrib/extensions/electron-browser/media/save.svg`)), }, precondition: CONTEXT_EXTENSION_HOST_PROFILE_RECORDED }, group: 'navigation', when: ContextKeyExpr.and(ActiveEditorContext.isEqualTo(RuntimeExtensionsEditor.ID)) }); + +CommandsRegistry.registerCommand({ + id: 'workbench.extensions.installExtension', + description: { + description: localize('workbench.extensions.installExtension.description', "Install the given extension"), + args: [ + { + name: localize('workbench.extensions.installExtension.arg.name', "Extension id or VSIX resource uri"), + schema: { + 'type': ['object', 'string'] + } + } + ] + }, + handler: async (accessor, arg: string | UriComponents) => { + const extensionManagementService = accessor.get(IExtensionManagementService); + const extensionGalleryService = accessor.get(IExtensionGalleryService); + try { + if (typeof arg === 'string') { + const extension = await extensionGalleryService.getCompatibleExtension({ id: arg }); + if (extension) { + await extensionManagementService.installFromGallery(extension); + } else { + throw new Error(localize('notFound', "Extension '{0}' not found.", arg)); + } + } else { + const vsix = URI.revive(arg); + await extensionManagementService.install(vsix); + } + } catch (e) { + onUnexpectedError(e); + } + } +}); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts similarity index 88% rename from src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts index 0f72d734cb..5dd49b44d2 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -6,31 +6,30 @@ import 'vs/css!./media/extensionActions'; import { localize } from 'vs/nls'; import { IAction, Action } from 'vs/base/common/actions'; -import { Throttler } from 'vs/base/common/async'; +import { Throttler, Delayer } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; -import * as paths from 'vs/base/common/paths'; import { Event } from 'vs/base/common/event'; import * as json from 'vs/base/common/json'; import { ActionItem, Separator, IActionItemOptions } from 'vs/base/browser/ui/actionbar/actionbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; // {{SQL CARBON EDIT}} -import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey, IExtensionContainer, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/parts/extensions/common/extensions'; -import { ExtensionsConfigurationInitialContent } from 'vs/workbench/parts/extensions/common/extensionsFileTemplate'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey, IExtensionContainer, EXTENSIONS_CONFIG, ExtensionsPolicy, ExtensionsPolicyKey } from 'vs/workbench/contrib/extensions/common/extensions'; +import { ExtensionsConfigurationInitialContent } from 'vs/workbench/contrib/extensions/common/extensionsFileTemplate'; import { IExtensionEnablementService, IExtensionTipsService, EnablementState, ExtensionsLabel, IExtensionRecommendation, IGalleryExtension, IExtensionsConfigContent, IExtensionGalleryService, INSTALL_ERROR_MALICIOUS, INSTALL_ERROR_INCOMPATIBLE, IGalleryExtensionVersion, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { ExtensionType, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ShowViewletAction } from 'vs/workbench/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { Query } from 'vs/workbench/parts/extensions/common/extensionQuery'; +import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { IFileService, IContent } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; -import { IExtensionService, IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { buttonBackground, buttonForeground, buttonHoverBackground, contrastBorder, registerColor, foreground } from 'vs/platform/theme/common/colorRegistry'; import { Color } from 'vs/base/common/color'; @@ -47,15 +46,17 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; -import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; -import product from 'vs/platform/node/product'; -import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; +import product from 'vs/platform/product/node/product'; +import { IQuickPickItem, IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; import { CancellationToken } from 'vs/base/common/cancellation'; import { clipboard } from 'electron'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { coalesce } from 'vs/base/common/arrays'; +import { IWorkbenchThemeService, COLOR_THEME_SETTING, ICON_THEME_SETTING, IFileIconTheme, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; + // {{SQL CARBON EDIT}} import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; @@ -148,7 +149,9 @@ export class InstallAction extends ExtensionAction { @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @IInstantiationService private readonly instantiationService: IInstantiationService, @INotificationService private readonly notificationService: INotificationService, - @IOpenerService private readonly openerService: IOpenerService + @IOpenerService private readonly openerService: IOpenerService, + @IExtensionService private readonly runtimeExtensionService: IExtensionService, + @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService ) { super(`extensions.install`, InstallAction.INSTALL_LABEL, InstallAction.Class, false); this.update(); @@ -175,31 +178,70 @@ export class InstallAction extends ExtensionAction { } } - run(): Promise { + async run(): Promise { this.extensionsWorkbenchService.open(this.extension); alert(localize('installExtensionStart', "Installing extension {0} started. An editor is now open with more details on this extension", this.extension.displayName)); - return this.install(this.extension); + const extension = await this.install(this.extension); + + if (extension.local) { + const runningExtension = await this.getRunningExtension(extension.local); + if (runningExtension) { + const colorThemes = await this.workbenchThemeService.getColorThemes(); + const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); + if (SetColorThemeAction.getColorThemes(colorThemes, this.extension).length) { + const action = this.instantiationService.createInstance(SetColorThemeAction, colorThemes); + action.extension = extension; + return action.run({ showCurrentTheme: true, ignoreFocusLost: true }); + } + if (SetFileIconThemeAction.getFileIconThemes(fileIconThemes, this.extension).length) { + const action = this.instantiationService.createInstance(SetFileIconThemeAction, fileIconThemes); + action.extension = extension; + return action.run({ showCurrentTheme: true, ignoreFocusLost: true }); + } + } + } + } - private install(extension: IExtension): Promise { - return this.extensionsWorkbenchService.install(extension).then(null, err => { - if (!extension.gallery) { - return this.notificationService.error(err); - } + private install(extension: IExtension): Promise { + return this.extensionsWorkbenchService.install(extension) + .then(null, err => { + // {{SQL CARBON EDIT}} + // Prompt the user that the current ADS version is not compatible with the extension, + // return here as in this scenario it doesn't make sense for the user to download manually. + if (err && err.code === INSTALL_ERROR_INCOMPATIBLE) { + return this.notificationService.error(err); + } - // {{SQL CARBON EDIT}} - // Prompt the user that the current ADS version is not compatible with the extension, - // return here as in this scenario it doesn't make sense for the user to download manually. - if(err && err.code === INSTALL_ERROR_INCOMPATIBLE) { - return this.notificationService.error(err); - } + if (!extension.gallery) { + return this.notificationService.error(err); + } - console.error(err); + console.error(err); - return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService); - }); + return promptDownloadManually(extension.gallery, localize('failedToInstall', "Failed to install \'{0}\'.", extension.identifier.id), err, this.instantiationService, this.notificationService, this.openerService); + }); + } + + private async getRunningExtension(extension: ILocalExtension): Promise { + const runningExtension = await this.runtimeExtensionService.getExtension(extension.identifier.id); + if (runningExtension) { + return runningExtension; + } + if (this.runtimeExtensionService.canAddExtension(toExtensionDescription(extension))) { + return new Promise((c, e) => { + const disposable = this.runtimeExtensionService.onDidChangeExtensions(async () => { + const runningExtension = await this.runtimeExtensionService.getExtension(extension.identifier.id); + if (runningExtension) { + disposable.dispose(); + c(runningExtension); + } + }); + }); + } + return null; } } @@ -329,7 +371,7 @@ export class CombinedInstallAction extends ExtensionAction { return this.uninstallAction.run(); } - return Promise.resolve(null); + return Promise.resolve(); } dispose(): void { @@ -392,7 +434,7 @@ export class UpdateAction extends ExtensionAction { // {{SQL CARBON EDIT}} // Prompt the user that the current ADS version is not compatible with the extension, // return here as in this scenario it doesn't make sense for the user to download manually. - if(err && err.code === INSTALL_ERROR_INCOMPATIBLE) { + if (err && err.code === INSTALL_ERROR_INCOMPATIBLE) { return this.notificationService.error(err); } @@ -468,7 +510,7 @@ export abstract class ExtensionDropDownAction extends ExtensionAction { if (this._actionItem) { this._actionItem.showMenu(actionGroups, disposeActionsOnHide); } - return Promise.resolve(null); + return Promise.resolve(); } dispose(): void { @@ -524,7 +566,8 @@ export class ManageExtensionAction extends ExtensionDropDownAction { constructor( @IInstantiationService instantiationService: IInstantiationService, - @IExtensionService private readonly extensionService: IExtensionService + @IExtensionService private readonly extensionService: IExtensionService, + @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService ) { super(ManageExtensionAction.ID, '', '', true, true, instantiationService); @@ -534,8 +577,22 @@ export class ManageExtensionAction extends ExtensionDropDownAction { this.update(); } - getActionGroups(runningExtensions: IExtensionDescription[]): IAction[][] { + getActionGroups(runningExtensions: IExtensionDescription[], colorThemes: IColorTheme[], fileIconThemes: IFileIconTheme[]): IAction[][] { const groups: ExtensionAction[][] = []; + if (this.extension) { + const extensionColorThemes = SetColorThemeAction.getColorThemes(colorThemes, this.extension); + const extensionFileIconThemes = SetFileIconThemeAction.getFileIconThemes(fileIconThemes, this.extension); + if (extensionColorThemes.length || extensionFileIconThemes.length) { + const themesGroup: ExtensionAction[] = []; + if (extensionColorThemes.length) { + themesGroup.push(this.instantiationService.createInstance(SetColorThemeAction, colorThemes)); + } + if (extensionFileIconThemes.length) { + themesGroup.push(this.instantiationService.createInstance(SetFileIconThemeAction, fileIconThemes)); + } + groups.push(themesGroup); + } + } groups.push([ this.instantiationService.createInstance(EnableGloballyAction), this.instantiationService.createInstance(EnableForWorkspaceAction) @@ -553,8 +610,11 @@ export class ManageExtensionAction extends ExtensionDropDownAction { return groups; } - run(): Promise { - return this.extensionService.getExtensions().then(runtimeExtensions => super.run({ actionGroups: this.getActionGroups(runtimeExtensions), disposeActionsOnHide: true })); + async run(): Promise { + const runtimeExtensions = await this.extensionService.getExtensions(); + const colorThemes = await this.workbenchThemeService.getColorThemes(); + const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); + return super.run({ actionGroups: this.getActionGroups(runtimeExtensions, colorThemes, fileIconThemes), disposeActionsOnHide: true }); } update(): void { @@ -648,7 +708,7 @@ export class ExtensionInfoAction extends ExtensionAction { const clipboardStr = `${name}\n${id}\n${description}\n${verision}\n${publisher}${link ? '\n' + link : ''}`; clipboard.writeText(clipboardStr); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -793,7 +853,7 @@ export abstract class ExtensionEditorDropDownAction extends ExtensionDropDownAct } else { return super.run({ actionGroups: [this.actions], disposeActionsOnHide: false }); } - return Promise.resolve(null); + return Promise.resolve(); } } @@ -958,7 +1018,7 @@ export class UpdateAllAction extends Action { return Promise.all(this.outdated.map(e => this.install(e))); } - private install(extension: IExtension): Promise { + private install(extension: IExtension): Promise { return this.extensionsWorkbenchService.install(extension).then(undefined, err => { if (!extension.gallery) { return this.notificationService.error(err); @@ -1002,16 +1062,16 @@ export class ReloadAction extends ExtensionAction { this.enabled = false; this.tooltip = ''; if (!this.extension) { - return Promise.resolve(); + return Promise.resolve(undefined); } const state = this.extension.state; if (state === ExtensionState.Installing || state === ExtensionState.Uninstalling) { - return Promise.resolve(); + return Promise.resolve(undefined); } const installed = this.extensionsWorkbenchService.local.filter(e => areSameExtensions(e.identifier, this.extension.identifier))[0]; const local = this.extension.local || (installed && installed.local); if (local && local.manifest && local.manifest.contributes && local.manifest.contributes.localizations && local.manifest.contributes.localizations.length > 0) { - return Promise.resolve(); + return Promise.resolve(undefined); } return this.extensionService.getExtensions() .then(runningExtensions => this.computeReloadState(runningExtensions, installed)); @@ -1086,6 +1146,150 @@ export class ReloadAction extends ExtensionAction { } } +export class SetColorThemeAction extends ExtensionAction { + + static getColorThemes(colorThemes: IColorTheme[], extension: IExtension): IColorTheme[] { + return colorThemes.filter(c => c.extensionData && ExtensionIdentifier.equals(c.extensionData.extensionId, extension.identifier.id)); + } + + private static readonly EnabledClass = 'extension-action theme'; + private static readonly DisabledClass = `${SetColorThemeAction.EnabledClass} disabled`; + + private disposables: IDisposable[] = []; + + constructor( + private readonly colorThemes: IColorTheme[], + @IExtensionService extensionService: IExtensionService, + @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super(`extensions.colorTheme`, localize('color theme', "Set Color Theme"), SetColorThemeAction.DisabledClass, false); + Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidColorThemeChange)(() => this.update(), this, this.disposables); + this.update(); + } + + update(): void { + this.enabled = false; + if (this.extension) { + const isInstalled = this.extension.state === ExtensionState.Installed; + if (isInstalled) { + const extensionThemes = SetColorThemeAction.getColorThemes(this.colorThemes, this.extension); + this.enabled = extensionThemes.length > 0; + } + } + this.class = this.enabled ? SetColorThemeAction.EnabledClass : SetColorThemeAction.DisabledClass; + } + + async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean, ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { + this.update(); + if (!this.enabled) { + return; + } + let extensionThemes = SetColorThemeAction.getColorThemes(this.colorThemes, this.extension); + const currentTheme = this.colorThemes.filter(t => t.settingsId === this.configurationService.getValue(COLOR_THEME_SETTING))[0]; + showCurrentTheme = showCurrentTheme || extensionThemes.some(t => t.id === currentTheme.id); + if (showCurrentTheme) { + extensionThemes = extensionThemes.filter(t => t.id !== currentTheme.id); + } + + const delayer = new Delayer(100); + const picks: (IQuickPickItem | IQuickPickSeparator)[] = []; + picks.push(...extensionThemes.map(theme => ({ label: theme.label, id: theme.id }))); + if (showCurrentTheme) { + picks.push({ type: 'separator', label: localize('current', "Current") }); + picks.push({ label: currentTheme.label, id: currentTheme.id }); + } + const pickedTheme = await this.quickInputService.pick( + picks, + { + placeHolder: localize('select color theme', "Select Color Theme"), + onDidFocus: item => delayer.trigger(() => this.workbenchThemeService.setColorTheme(item.id, undefined)), + ignoreFocusLost + }); + let confValue = this.configurationService.inspect(COLOR_THEME_SETTING); + const target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; + return this.workbenchThemeService.setColorTheme(pickedTheme ? pickedTheme.id : currentTheme.id, target); + } + + dispose() { + this.disposables = dispose(this.disposables); + super.dispose(); + } +} + +export class SetFileIconThemeAction extends ExtensionAction { + + private static readonly EnabledClass = 'extension-action theme'; + private static readonly DisabledClass = `${SetFileIconThemeAction.EnabledClass} disabled`; + + private disposables: IDisposable[] = []; + + static getFileIconThemes(fileIconThemes: IFileIconTheme[], extension: IExtension): IFileIconTheme[] { + return fileIconThemes.filter(c => c.extensionData && ExtensionIdentifier.equals(c.extensionData.extensionId, extension.identifier.id)); + } + + constructor( + private readonly fileIconThemes: IFileIconTheme[], + @IExtensionService extensionService: IExtensionService, + @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { + super(`extensions.fileIconTheme`, localize('file icon theme', "Set File Icon Theme"), SetFileIconThemeAction.DisabledClass, false); + Event.any(extensionService.onDidChangeExtensions, workbenchThemeService.onDidFileIconThemeChange)(() => this.update(), this, this.disposables); + this.update(); + } + + update(): void { + this.enabled = false; + if (this.extension) { + const isInstalled = this.extension.state === ExtensionState.Installed; + if (isInstalled) { + const extensionThemes = SetFileIconThemeAction.getFileIconThemes(this.fileIconThemes, this.extension); + this.enabled = extensionThemes.length > 0; + } + } + this.class = this.enabled ? SetFileIconThemeAction.EnabledClass : SetFileIconThemeAction.DisabledClass; + } + + async run({ showCurrentTheme, ignoreFocusLost }: { showCurrentTheme: boolean, ignoreFocusLost: boolean } = { showCurrentTheme: false, ignoreFocusLost: false }): Promise { + await this.update(); + if (!this.enabled) { + return; + } + let extensionThemes = SetFileIconThemeAction.getFileIconThemes(this.fileIconThemes, this.extension); + const currentTheme = this.fileIconThemes.filter(t => t.settingsId === this.configurationService.getValue(ICON_THEME_SETTING))[0] || this.workbenchThemeService.getFileIconTheme(); + showCurrentTheme = showCurrentTheme || extensionThemes.some(t => t.id === currentTheme.id); + if (showCurrentTheme) { + extensionThemes = extensionThemes.filter(t => t.id !== currentTheme.id); + } + + const delayer = new Delayer(100); + const picks: (IQuickPickItem | IQuickPickSeparator)[] = []; + picks.push(...extensionThemes.map(theme => ({ label: theme.label, id: theme.id }))); + if (showCurrentTheme && currentTheme.label) { + picks.push({ type: 'separator', label: localize('current', "Current") }); + picks.push({ label: currentTheme.label, id: currentTheme.id }); + } + const pickedTheme = await this.quickInputService.pick( + picks, + { + placeHolder: localize('select file icon theme', "Select File Icon Theme"), + onDidFocus: item => delayer.trigger(() => this.workbenchThemeService.setFileIconTheme(item.id, undefined)), + ignoreFocusLost + }); + let confValue = this.configurationService.inspect(ICON_THEME_SETTING); + const target = typeof confValue.workspace !== 'undefined' ? ConfigurationTarget.WORKSPACE : ConfigurationTarget.USER; + return this.workbenchThemeService.setFileIconTheme(pickedTheme ? pickedTheme.id : currentTheme.id, target); + } + + dispose() { + this.disposables = dispose(this.disposables); + super.dispose(); + } +} + export class OpenExtensionsViewletAction extends ShowViewletAction { static ID = VIEWLET_ID; @@ -1096,9 +1300,9 @@ export class OpenExtensionsViewletAction extends ShowViewletAction { label: string, @IViewletService viewletService: IViewletService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IPartService partService: IPartService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(id, label, VIEWLET_ID, viewletService, editorGroupService, partService); + super(id, label, VIEWLET_ID, viewletService, editorGroupService, layoutService); } } @@ -1335,7 +1539,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { viewlet.focus(); const names = this.recommendations.map(({ extensionId }) => extensionId); return this.extensionWorkbenchService.queryGallery({ names, source: 'install-all-workspace-recommendations' }).then(pager => { - let installPromises: Promise[] = []; + let installPromises: Promise[] = []; let model = new PagedModel(pager); for (let i = 0; i < pager.total; i++) { installPromises.push(model.resolve(i, CancellationToken.None).then(e => { @@ -1413,7 +1617,7 @@ export class IgnoreExtensionRecommendationAction extends Action { public run(): Promise { this.extensionsTipsService.toggleIgnoredRecommendation(this.extension.identifier.id, true); - return Promise.resolve(null); + return Promise.resolve(); } dispose(): void { @@ -1443,7 +1647,7 @@ export class UndoIgnoreExtensionRecommendationAction extends Action { public run(): Promise { this.extensionsTipsService.toggleIgnoredRecommendation(this.extension.identifier.id, false); - return Promise.resolve(null); + return Promise.resolve(); } dispose(): void { @@ -1604,6 +1808,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl command: { id: ConfigureWorkspaceRecommendedExtensionsAction.ID, title: { value: `${ExtensionsLabel}: ${ConfigureWorkspaceRecommendedExtensionsAction.LABEL}`, original: 'Extensions: Configure Recommended Extensions (Workspace)' }, + category: localize('extensions', "Extensions") }, when: this.workspaceContextKey }); @@ -1615,6 +1820,7 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl command: { id: ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, title: { value: `${ExtensionsLabel}: ${ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL}`, original: 'Extensions: Configure Recommended Extensions (Workspace Folder)' }, + category: localize('extensions', "Extensions") }, when: this.workspaceFolderContextKey }); @@ -1627,7 +1833,8 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: AddToWorkspaceRecommendationsAction.ADD_ID, - title: { value: `${ExtensionsLabel}: ${AddToWorkspaceRecommendationsAction.ADD_LABEL}`, original: 'Extensions: Add to Recommended Extensions (Workspace)' } + title: { value: `${ExtensionsLabel}: ${AddToWorkspaceRecommendationsAction.ADD_LABEL}`, original: 'Extensions: Add to Recommended Extensions (Workspace)' }, + category: localize('extensions', "Extensions") }, when: this.addToWorkspaceRecommendationsContextKey }); @@ -1640,7 +1847,8 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: AddToWorkspaceFolderRecommendationsAction.ADD_ID, - title: { value: `${ExtensionsLabel}: ${AddToWorkspaceFolderRecommendationsAction.ADD_LABEL}`, original: 'Extensions: Add to Recommended Extensions (Workspace Folder)' } + title: { value: `${ExtensionsLabel}: ${AddToWorkspaceFolderRecommendationsAction.ADD_LABEL}`, original: 'Extensions: Add to Recommended Extensions (Workspace Folder)' }, + category: localize('extensions', "Extensions") }, when: this.addToWorkspaceFolderRecommendationsContextKey }); @@ -1653,7 +1861,8 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: AddToWorkspaceRecommendationsAction.IGNORE_ID, - title: { value: `${ExtensionsLabel}: ${AddToWorkspaceRecommendationsAction.IGNORE_LABEL}`, original: 'Extensions: Ignore Recommended Extension (Workspace)' } + title: { value: `${ExtensionsLabel}: ${AddToWorkspaceRecommendationsAction.IGNORE_LABEL}`, original: 'Extensions: Ignore Recommended Extension (Workspace)' }, + category: localize('extensions', "Extensions") }, when: this.addToWorkspaceRecommendationsContextKey }); @@ -1666,7 +1875,8 @@ export class ConfigureRecommendedExtensionsCommandsContributor extends Disposabl MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: AddToWorkspaceFolderRecommendationsAction.IGNORE_ID, - title: { value: `${ExtensionsLabel}: ${AddToWorkspaceFolderRecommendationsAction.IGNORE_LABEL}`, original: 'Extensions: Ignore Recommended Extension (Workspace Folder)' } + title: { value: `${ExtensionsLabel}: ${AddToWorkspaceFolderRecommendationsAction.IGNORE_LABEL}`, original: 'Extensions: Ignore Recommended Extension (Workspace Folder)' }, + category: localize('extensions', "Extensions") }, when: this.addToWorkspaceFolderRecommendationsContextKey }); @@ -1802,7 +2012,7 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio }); } - private getSelectionPosition(content: string, resource: URI, path: json.JSONPath): Promise { + private getSelectionPosition(content: string, resource: URI, path: json.JSONPath): Promise { const tree = json.parseTree(content); const node = json.findNodeAtLocation(tree, path); if (node && node.parent && node.parent.children) { @@ -1821,7 +2031,7 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio }; }); } - return Promise.resolve(); + return Promise.resolve(undefined); } private getOrCreateExtensionsFile(extensionsFileResource: URI): Promise<{ created: boolean, extensionsFileResource: URI, content: string }> { @@ -1863,7 +2073,7 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfi public run(): Promise { switch (this.contextService.getWorkbenchState()) { case WorkbenchState.FOLDER: - return this.openExtensionsFile(this.contextService.getWorkspace().folders[0].toResource(paths.join('.vscode', 'extensions.json'))); + return this.openExtensionsFile(this.contextService.getWorkspace().folders[0].toResource(EXTENSIONS_CONFIG)); case WorkbenchState.WORKSPACE: return this.openWorkspaceConfigurationFile(this.contextService.getWorkspace().configuration!); } @@ -1908,7 +2118,7 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac return Promise.resolve(pickFolderPromise) .then(workspaceFolder => { if (workspaceFolder) { - return this.openExtensionsFile(workspaceFolder.toResource(paths.join('.vscode', 'extensions.json'))); + return this.openExtensionsFile(workspaceFolder.toResource(EXTENSIONS_CONFIG)); } return null; }); @@ -1961,7 +2171,7 @@ export class AddToWorkspaceFolderRecommendationsAction extends AbstractConfigure if (!workspaceFolder) { return Promise.resolve(); } - const configurationFile = workspaceFolder.toResource(paths.join('.vscode', 'extensions.json')); + const configurationFile = workspaceFolder.toResource(EXTENSIONS_CONFIG); return this.getWorkspaceFolderExtensionsConfigContent(configurationFile).then(content => { const extensionIdLowerCase = extensionId.id.toLowerCase(); if (shouldRecommend) { @@ -2078,18 +2288,18 @@ export class StatusLabelAction extends Action implements IExtensionContainer { private static readonly ENABLED_CLASS = 'extension-status-label'; private static readonly DISABLED_CLASS = `${StatusLabelAction.ENABLED_CLASS} hide`; + private initialStatus: ExtensionState | null = null; private status: ExtensionState | null = null; private enablementState: EnablementState | null = null; - private version: string | null = null; private _extension: IExtension; get extension(): IExtension { return this._extension; } set extension(extension: IExtension) { if (!(this._extension && extension && areSameExtensions(this._extension.identifier, extension.identifier))) { // Different extension. Reset + this.initialStatus = null; this.status = null; this.enablementState = null; - this.version = null; } this._extension = extension; this.update(); @@ -2116,10 +2326,11 @@ export class StatusLabelAction extends Action implements IExtensionContainer { const currentStatus = this.status; const currentEnablementState = this.enablementState; - const currentVersion = this.version; this.status = this.extension.state; + if (this.initialStatus === null) { + this.initialStatus = this.status; + } this.enablementState = this.extension.enablementState; - this.version = this.extension.version; const runningExtensions = await this.extensionService.getExtensions(); const canAddExtension = () => { @@ -2144,7 +2355,7 @@ export class StatusLabelAction extends Action implements IExtensionContainer { if (currentStatus !== null) { if (currentStatus === ExtensionState.Installing && this.status === ExtensionState.Installed) { - return canAddExtension() ? currentVersion !== this.version ? localize('updated', "Updated") : localize('installed', "Installed") : null; + return canAddExtension() ? this.initialStatus === ExtensionState.Installed ? localize('updated', "Updated") : localize('installed', "Installed") : null; } if (currentStatus === ExtensionState.Uninstalling && this.status === ExtensionState.Uninstalled) { return canRemoveExtension() ? localize('uninstalled', "Uninstalled") : null; @@ -2167,7 +2378,7 @@ export class StatusLabelAction extends Action implements IExtensionContainer { } run(): Promise { - return Promise.resolve(null); + return Promise.resolve(); } } @@ -2192,7 +2403,7 @@ export class MaliciousStatusLabelAction extends ExtensionAction { } run(): Promise { - return Promise.resolve(null); + return Promise.resolve(); } } @@ -2339,14 +2550,14 @@ export class OpenExtensionsFolderAction extends Action { } run(): Promise { - const extensionsHome = this.environmentService.extensionsPath; + const extensionsHome = URI.file(this.environmentService.extensionsPath); - return Promise.resolve(this.fileService.resolveFile(URI.file(extensionsHome))).then(file => { - let itemToShow: string; + return Promise.resolve(this.fileService.resolveFile(extensionsHome)).then(file => { + let itemToShow: URI; if (file.children && file.children.length > 0) { - itemToShow = file.children[0].resource.fsPath; + itemToShow = file.children[0].resource; } else { - itemToShow = paths.normalize(extensionsHome, true); + itemToShow = extensionsHome; } return this.windowsService.showItemInFolder(itemToShow); @@ -2385,7 +2596,7 @@ export class InstallVSIXAction extends Action { buttonLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install")) }).then(result => { if (!result) { - return Promise.resolve(null); + return Promise.resolve(); } return Promise.all(result.map(vsix => { if (!this.storageService.getBoolean(vsix, StorageScope.GLOBAL)) { @@ -2415,14 +2626,14 @@ export class InstallVSIXAction extends Action { }, { label: localize('thirdPartyExt.no', 'No'), - run: () => { return Promise.resolve(null); } + run: () => { return Promise.resolve(); } }, { label: localize('thirdPartyExt.dontShowAgain', 'Don\'t Show Again'), isSecondary: true, run: () => { this.storageService.store(vsix, true, StorageScope.GLOBAL); - return Promise.resolve(null); + return Promise.resolve(); } } ], @@ -2445,11 +2656,11 @@ export class InstallVSIXAction extends Action { ); }); } - })); + })).then(() => Promise.resolve()); })); } else { this.notificationService.error(localize('InstallVSIXAction.allowNone', 'Your extension policy does not allow downloading extensions. Please change your extension policy and try again.')); - return Promise.resolve(null); + return Promise.resolve(); } // {{SQL CARBON EDIT}} - End } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActivationProgress.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActivationProgress.ts similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/extensionsActivationProgress.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensionsActivationProgress.ts diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsAutoProfiler.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts similarity index 94% rename from src/vs/workbench/parts/extensions/electron-browser/extensionsAutoProfiler.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts index bb2f1a1b18..bc3def4a9d 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsAutoProfiler.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler.ts @@ -11,15 +11,15 @@ import { ILogService } from 'vs/platform/log/common/log'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { onUnexpectedError } from 'vs/base/common/errors'; import { tmpdir } from 'os'; -import { join } from 'path'; +import { join } from 'vs/base/common/path'; import { writeFile } from 'vs/base/node/pfs'; -import { IExtensionHostProfileService, ReportExtensionIssueAction } from 'vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor'; +import { IExtensionHostProfileService, ReportExtensionIssueAction } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { localize } from 'vs/nls'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { RuntimeExtensionsInput } from 'vs/workbench/services/extensions/electron-browser/runtimeExtensionsInput'; +import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput'; import { generateUuid } from 'vs/base/common/uuid'; -import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchContribution { @@ -49,7 +49,7 @@ export class ExtensionsAutoProfiler extends Disposable implements IWorkbenchCont if (event.isResponsive && this._session.has(target)) { // stop profiling when responsive again - this._session.get(target).cancel(); + this._session.get(target)!.cancel(); } else if (!event.isResponsive && !this._session.has(target)) { // start profiling if not yet profiling diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsDependencyChecker.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsDependencyChecker.ts new file mode 100644 index 0000000000..193b450895 --- /dev/null +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsDependencyChecker.ts @@ -0,0 +1,79 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { localize } from 'vs/nls'; +import { values } from 'vs/base/common/map'; +import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { Action } from 'vs/base/common/actions'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { Disposable } from 'vs/base/common/lifecycle'; + +export class ExtensionDependencyChecker extends Disposable implements IWorkbenchContribution { + + constructor( + @IExtensionService private readonly extensionService: IExtensionService, + @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @INotificationService private readonly notificationService: INotificationService, + @IWindowService private readonly windowService: IWindowService + ) { + super(); + CommandsRegistry.registerCommand('workbench.extensions.installMissingDepenencies', () => this.installMissingDependencies()); + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: 'workbench.extensions.installMissingDepenencies', + category: localize('extensions', "Extensions"), + title: localize('auto install missing deps', "Install Missing Dependencies") + } + }); + } + + private async getUninstalledMissingDependencies(): Promise { + const allMissingDependencies = await this.getAllMissingDependencies(); + const localExtensions = await this.extensionsWorkbenchService.queryLocal(); + return allMissingDependencies.filter(id => localExtensions.every(l => !areSameExtensions(l.identifier, { id }))); + } + + private async getAllMissingDependencies(): Promise { + const runningExtensions = await this.extensionService.getExtensions(); + const runningExtensionsIds: Set = runningExtensions.reduce((result, r) => { result.add(r.identifier.value.toLowerCase()); return result; }, new Set()); + const missingDependencies: Set = new Set(); + for (const extension of runningExtensions) { + if (extension.extensionDependencies) { + extension.extensionDependencies.forEach(dep => { + if (!runningExtensionsIds.has(dep.toLowerCase())) { + missingDependencies.add(dep); + } + }); + } + } + return values(missingDependencies); + } + + private async installMissingDependencies(): Promise { + const missingDependencies = await this.getUninstalledMissingDependencies(); + if (missingDependencies.length) { + const extensions = (await this.extensionsWorkbenchService.queryGallery({ names: missingDependencies, pageSize: missingDependencies.length })).firstPage; + if (extensions.length) { + await Promise.all(extensions.map(extension => this.extensionsWorkbenchService.install(extension))); + this.notificationService.notify({ + severity: Severity.Info, + message: localize('finished installing missing deps', "Finished installing missing dependencies. Please reload the window now."), + actions: { + primary: [new Action('realod', localize('reload', "Realod Window"), '', true, + () => this.windowService.reloadWindow())] + } + }); + } + } else { + this.notificationService.info(localize('no missing deps', "There are no missing dependencies to install.")); + } + } +} diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts similarity index 96% rename from src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts index a25a03a0f1..bb6b7de3c8 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsList.ts @@ -12,10 +12,10 @@ import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { Event } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; -import { IExtension, IExtensionsWorkbenchService, ExtensionContainers } from 'vs/workbench/parts/extensions/common/extensions'; -import { InstallAction, UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionItem, StatusLabelAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; +import { IExtension, IExtensionsWorkbenchService, ExtensionContainers } from 'vs/workbench/contrib/extensions/common/extensions'; +import { InstallAction, UpdateAction, ManageExtensionAction, ReloadAction, MaliciousStatusLabelAction, ExtensionActionItem, StatusLabelAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { Label, RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget } from 'vs/workbench/parts/extensions/electron-browser/extensionsWidgets'; +import { Label, RatingsWidget, InstallCountWidget, RecommendationWidget, RemoteBadgeWidget } from 'vs/workbench/contrib/extensions/electron-browser/extensionsWidgets'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -35,7 +35,7 @@ export interface ITemplateData { //ratings: HTMLElement; author: HTMLElement; description: HTMLElement; - extension: IExtension; + extension: IExtension | null; disposables: IDisposable[]; extensionDisposables: IDisposable[]; actionbar: ActionBar; diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts similarity index 91% rename from src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts index 786245100d..5ae7d42b1e 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViewlet.ts @@ -23,20 +23,20 @@ import { ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowDisabledExtensionsAction, ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction -} from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; +} from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; import { IExtensionManagementService, IExtensionManagementServerService, IExtensionManagementServer, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; +import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionsListView, EnabledExtensionsView, DisabledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView, BuiltInThemesExtensionsView, BuiltInBasicsExtensionsView, GroupByServerExtensionsView, DefaultRecommendedExtensionsView } from './extensionsViews'; -import { OpenGlobalSettingsAction } from 'vs/workbench/parts/preferences/browser/preferencesActions'; +import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import Severity from 'vs/base/common/severity'; import { IActivityService, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ViewsRegistry, IViewDescriptor } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions } from 'vs/workbench/common/views'; import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -44,15 +44,16 @@ import { getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/commo import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; -import { Query } from 'vs/workbench/parts/extensions/common/extensionQuery'; -import { SuggestEnabledInput, attachSuggestEnabledInputBoxStyler } from 'vs/workbench/parts/codeEditor/electron-browser/suggestEnabledInput'; +import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; +import { SuggestEnabledInput, attachSuggestEnabledInputBoxStyler } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { Registry } from 'vs/platform/registry/common/platform'; interface SearchInputEvent extends Event { target: HTMLInputElement; @@ -106,7 +107,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio viewDescriptors.push(...this.createExtensionsViewDescriptorsForServer(this.extensionManagementServerService.remoteExtensionManagementServer)); } - ViewsRegistry.registerViews(viewDescriptors, VIEW_CONTAINER); + Registry.as(Extensions.ViewsRegistry).registerViews(viewDescriptors, VIEW_CONTAINER); } // View used for any kind of searching @@ -115,7 +116,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctor: ExtensionsListView, + ctorDescriptor: { ctor: ExtensionsListView }, when: ContextKeyExpr.and(ContextKeyExpr.has('searchExtensions'), ContextKeyExpr.not('searchInstalledExtensions'), ContextKeyExpr.not('searchBuiltInExtensions'), ContextKeyExpr.not('recommendedExtensions'), ContextKeyExpr.not('groupByServersContext')), weight: 100 }; @@ -128,7 +129,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctor: EnabledExtensionsView, + ctorDescriptor: { ctor: EnabledExtensionsView }, when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.has('hasInstalledExtensions')), weight: 40, canToggleVisibility: true, @@ -143,7 +144,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctor: DisabledExtensionsView, + ctorDescriptor: { ctor: DisabledExtensionsView }, when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.has('hasInstalledExtensions')), weight: 10, canToggleVisibility: true, @@ -159,7 +160,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctor: ExtensionsListView, + ctorDescriptor: { ctor: ExtensionsListView }, when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.not('hasInstalledExtensions')), weight: 60, order: 1 @@ -170,7 +171,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return [{ id: `server.extensionsList.${server.authority}`, name: server.label, - ctor: GroupByServerExtensionsView, + ctorDescriptor: { ctor: GroupByServerExtensionsView }, when: ContextKeyExpr.has('groupByServersContext'), weight: 100 }]; @@ -184,7 +185,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctor: DefaultRecommendedExtensionsView, + ctorDescriptor: { ctor: DefaultRecommendedExtensionsView }, when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.has('defaultRecommendedExtensions')), weight: 40, order: 2, @@ -199,7 +200,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctor: RecommendedExtensionsView, + ctorDescriptor: { ctor: RecommendedExtensionsView }, when: ContextKeyExpr.has('recommendedExtensions'), weight: 50, canToggleVisibility: true, @@ -214,7 +215,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctor: WorkspaceRecommendedExtensionsView, + ctorDescriptor: { ctor: WorkspaceRecommendedExtensionsView }, when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), ContextKeyExpr.has('nonEmptyWorkspace')), weight: 50, canToggleVisibility: true, @@ -227,7 +228,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctor: BuiltInExtensionsView, + ctorDescriptor: { ctor: BuiltInExtensionsView }, when: ContextKeyExpr.has('searchBuiltInExtensions'), weight: 100, canToggleVisibility: true @@ -239,7 +240,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctor: BuiltInThemesExtensionsView, + ctorDescriptor: { ctor: BuiltInThemesExtensionsView }, when: ContextKeyExpr.has('searchBuiltInExtensions'), weight: 100, canToggleVisibility: true @@ -251,7 +252,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio return { id, name: viewIdNameMappings[id], - ctor: BuiltInBasicsExtensionsView, + ctorDescriptor: { ctor: BuiltInBasicsExtensionsView }, when: ContextKeyExpr.has('searchBuiltInExtensions'), weight: 100, canToggleVisibility: true @@ -276,11 +277,12 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio private searchBox: SuggestEnabledInput; private extensionsBox: HTMLElement; private primaryActions: IAction[]; - private secondaryActions: IAction[]; + private secondaryActions: IAction[] | null; private disposables: IDisposable[] = []; + private searchViewletState: object; constructor( - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, @IProgressService2 private readonly progressService: IProgressService2, @IInstantiationService instantiationService: IInstantiationService, @@ -296,7 +298,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService ) { - super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, partService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); this.searchDelayer = new ThrottledDelayer(500); this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService); @@ -308,6 +310,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.defaultRecommendedExtensionsContextKey = DefaultRecommendedExtensionsContext.bindTo(contextKeyService); this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue(ShowRecommendationsOnlyOnDemandKey)); this.disposables.push(this.viewletService.onDidViewletOpen(this.onViewletOpen, this, this.disposables)); + this.searchViewletState = this.getMemento(StorageScope.WORKSPACE); this.extensionManagementService.getInstalled(ExtensionType.User).then(result => { this.hasInstalledExtensionsContextKey.set(result.length > 0); @@ -331,6 +334,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio const header = append(this.root, $('.header')); const placeholder = localize('searchExtensions', "Search Extensions in Marketplace"); + const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : ''; this.searchBox = this.instantiationService.createInstance(SuggestEnabledInput, `${VIEWLET_ID}.searchbox`, header, { triggerCharacters: ['@'], @@ -341,7 +345,11 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio else { return 'd'; } }, provideResults: (query) => Query.suggestions(query) - }, placeholder, 'extensions:searchinput', { placeholderText: placeholder }); + }, placeholder, 'extensions:searchinput', { placeholderText: placeholder, value: searchValue }); + + if (this.searchBox.getValue()) { + this.triggerSearch(); + } this.disposables.push(attachSuggestEnabledInputBoxStyler(this.searchBox, this.themeService)); @@ -432,6 +440,16 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio return this.searchBox.getValue().replace(/@category/g, 'category').replace(/@tag:/g, 'tag:').replace(/@ext:/g, 'ext:'); } + protected saveState(): void { + const value = this.searchBox.getValue(); + if (ExtensionsListView.isInstalledExtensionsQuery(value)) { + this.searchViewletState['query.value'] = value; + } else { + this.searchViewletState['query.value'] = ''; + } + super.saveState(); + } + private doSearch(): Promise { const value = this.normalizedQuery(); this.searchExtensionsContextKey.set(!!value); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts similarity index 93% rename from src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts index 73c402ddc9..e427569d18 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsViews.ts @@ -15,34 +15,35 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { append, $, toggleClass } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Delegate, Renderer, IExtensionsViewState } from 'vs/workbench/parts/extensions/electron-browser/extensionsList'; +import { Delegate, Renderer, IExtensionsViewState } from 'vs/workbench/contrib/extensions/electron-browser/extensionsList'; import { IExtension, IExtensionsWorkbenchService } from '../common/extensions'; import { Query } from '../common/extensionQuery'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { OpenGlobalSettingsAction } from 'vs/workbench/parts/preferences/browser/preferencesActions'; +import { OpenGlobalSettingsAction } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; +import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, ManageExtensionAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; import { WorkbenchPagedList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { distinct } from 'vs/base/common/arrays'; -import { IExperimentService, IExperiment, ExperimentActionType } from 'vs/workbench/parts/experiments/node/experimentService'; +import { IExperimentService, IExperiment, ExperimentActionType } from 'vs/workbench/contrib/experiments/node/experimentService'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { getKeywordsForExtension } from 'vs/workbench/parts/extensions/electron-browser/extensionsUtils'; import { IAction } from 'vs/base/common/actions'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import product from 'vs/platform/product/node/product'; class ExtensionsViewState extends Disposable implements IExtensionsViewState { @@ -67,7 +68,7 @@ export class ExtensionsListView extends ViewletPanel { private extensionsList: HTMLElement; private badge: CountBadge; protected badgeContainer: HTMLElement; - private list: WorkbenchPagedList; + private list: WorkbenchPagedList | null; constructor( private options: IViewletViewOptions, @@ -84,7 +85,8 @@ export class ExtensionsListView extends ViewletPanel { @ITelemetryService private readonly telemetryService: ITelemetryService, @IConfigurationService configurationService: IConfigurationService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IExperimentService private readonly experimentService: IExperimentService + @IExperimentService private readonly experimentService: IExperimentService, + @IWorkbenchThemeService private readonly workbenchThemeService: IWorkbenchThemeService ) { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService); } @@ -131,7 +133,9 @@ export class ExtensionsListView extends ViewletPanel { protected layoutBody(height: number, width: number): void { this.extensionsList.style.height = height + 'px'; - this.list.layout(height, width); + if (this.list) { + this.list.layout(height, width); + } } async show(query: string): Promise> { @@ -166,7 +170,7 @@ export class ExtensionsListView extends ViewletPanel { } count(): number { - return this.list.length; + return this.list ? this.list.length : 0; } protected showEmptyModel(): Promise> { @@ -175,24 +179,24 @@ export class ExtensionsListView extends ViewletPanel { return Promise.resolve(emptyModel); } - private onContextMenu(e: IListContextMenuEvent): void { + private async onContextMenu(e: IListContextMenuEvent): Promise { if (e.element) { - this.extensionService.getExtensions() - .then(runningExtensions => { - const manageExtensionAction = this.instantiationService.createInstance(ManageExtensionAction); - manageExtensionAction.extension = e.element; - const groups = manageExtensionAction.getActionGroups(runningExtensions); - let actions: IAction[] = []; - for (const menuActions of groups) { - actions = [...actions, ...menuActions, new Separator()]; - } - if (manageExtensionAction.enabled) { - this.contextMenuService.showContextMenu({ - getAnchor: () => e.anchor, - getActions: () => actions.slice(0, actions.length - 1) - }); - } + const runningExtensions = await this.extensionService.getExtensions(); + const colorThemes = await this.workbenchThemeService.getColorThemes(); + const fileIconThemes = await this.workbenchThemeService.getFileIconThemes(); + const manageExtensionAction = this.instantiationService.createInstance(ManageExtensionAction); + manageExtensionAction.extension = e.element; + const groups = manageExtensionAction.getActionGroups(runningExtensions, colorThemes, fileIconThemes); + let actions: IAction[] = []; + for (const menuActions of groups) { + actions = [...actions, ...menuActions, new Separator()]; + } + if (manageExtensionAction.enabled) { + this.contextMenuService.showContextMenu({ + getAnchor: () => e.anchor, + getActions: () => actions.slice(0, actions.length - 1) }); + } } } @@ -220,7 +224,8 @@ export class ExtensionsListView extends ViewletPanel { if (showThemesOnly) { const themesExtensions = result.filter(e => { - return e.local.manifest + return e.local + && e.local.manifest && e.local.manifest.contributes && Array.isArray(e.local.manifest.contributes.themes) && e.local.manifest.contributes.themes.length; @@ -229,7 +234,7 @@ export class ExtensionsListView extends ViewletPanel { } if (showBasicsOnly) { const basics = result.filter(e => { - return e.local.manifest + return e.local && e.local.manifest && e.local.manifest.contributes && Array.isArray(e.local.manifest.contributes.grammars) && e.local.manifest.contributes.grammars.length @@ -239,7 +244,8 @@ export class ExtensionsListView extends ViewletPanel { } if (showFeaturesOnly) { const others = result.filter(e => { - return e.local.manifest + return e.local + && e.local.manifest && e.local.manifest.contributes && (!Array.isArray(e.local.manifest.contributes.grammars) || e.local.identifier.id === 'vscode.git') && !Array.isArray(e.local.manifest.contributes.themes); @@ -268,7 +274,7 @@ export class ExtensionsListView extends ViewletPanel { result = result .filter(e => e.type === ExtensionType.User && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1) - && (!categories.length || categories.some(category => (e.local.manifest.categories || []).some(c => c.toLowerCase() === category)))); + && (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category)))); return this.getPagedModel(this.sortExtensions(result, options)); } @@ -282,7 +288,7 @@ export class ExtensionsListView extends ViewletPanel { .sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)) .filter(extension => extension.outdated && (extension.name.toLowerCase().indexOf(value) > -1 || extension.displayName.toLowerCase().indexOf(value) > -1) - && (!categories.length || categories.some(category => extension.local.manifest.categories.some(c => c.toLowerCase() === category)))); + && (!categories.length || categories.some(category => !!extension.local && extension.local.manifest.categories!.some(c => c.toLowerCase() === category)))); return this.getPagedModel(this.sortExtensions(result, options)); } @@ -297,7 +303,7 @@ export class ExtensionsListView extends ViewletPanel { .sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)) .filter(e => runningExtensions.every(r => !areSameExtensions({ id: r.identifier.value }, e.identifier)) && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1) - && (!categories.length || categories.some(category => (e.local.manifest.categories || []).some(c => c.toLowerCase() === category)))); + && (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category)))); return this.getPagedModel(this.sortExtensions(result, options)); } @@ -312,7 +318,7 @@ export class ExtensionsListView extends ViewletPanel { .sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)) .filter(e => runningExtensions.some(r => areSameExtensions({ id: r.identifier.value }, e.identifier)) && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1) - && (!categories.length || categories.some(category => (e.local.manifest.categories || []).some(c => c.toLowerCase() === category)))); + && (!categories.length || categories.some(category => (e.local && e.local.manifest.categories || []).some(c => c.toLowerCase() === category)))); return this.getPagedModel(this.sortExtensions(result, options)); } @@ -365,7 +371,8 @@ export class ExtensionsListView extends ViewletPanel { text = query.value.replace(extensionRegex, (m, ext) => { // Get curated keywords - const keywords = getKeywordsForExtension(ext); + const lookup = product.extensionKeywords || {}; + const keywords = lookup[ext] || []; // Get mode name const modeId = this.modeService.getModeIdByFilepathOrFirstLine(`.${ext}`); @@ -388,7 +395,7 @@ export class ExtensionsListView extends ViewletPanel { if (!hasUserDefinedSortOrder) { const searchExperiments = await this.getSearchExperiments(); for (const experiment of searchExperiments) { - if (text.toLowerCase() === experiment.action.properties['searchText'] && Array.isArray(experiment.action.properties['preferredResults'])) { + if (experiment.action && text.toLowerCase() === experiment.action.properties['searchText'] && Array.isArray(experiment.action.properties['preferredResults'])) { preferredResults = experiment.action.properties['preferredResults']; options.source += `-experiment-${experiment.id}`; break; @@ -429,11 +436,11 @@ export class ExtensionsListView extends ViewletPanel { private sortExtensions(extensions: IExtension[], options: IQueryOptions): IExtension[] { switch (options.sortBy) { case SortBy.InstallCount: - extensions = extensions.sort((e1, e2) => e2.installCount - e1.installCount); + extensions = extensions.sort((e1, e2) => typeof e2.installCount === 'number' && typeof e1.installCount === 'number' ? e2.installCount - e1.installCount : NaN); break; case SortBy.AverageRating: case SortBy.WeightedRating: - extensions = extensions.sort((e1, e2) => e2.rating - e1.rating); + extensions = extensions.sort((e1, e2) => typeof e2.rating === 'number' && typeof e1.rating === 'number' ? e2.rating - e1.rating : NaN); break; default: extensions = extensions.sort((e1, e2) => e1.displayName.localeCompare(e2.displayName)); @@ -762,6 +769,10 @@ export class ExtensionsListView extends ViewletPanel { focus(): void { super.focus(); + if (!this.list) { + return; + } + if (!(this.list.getFocus().length || this.list.getSelection().length)) { this.list.focusNext(); } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsWidgets.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsWidgets.ts similarity index 89% rename from src/vs/workbench/parts/extensions/electron-browser/extensionsWidgets.ts rename to src/vs/workbench/contrib/extensions/electron-browser/extensionsWidgets.ts index eb6ee2260b..32547c6392 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsWidgets.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsWidgets.ts @@ -11,10 +11,12 @@ import * as platform from 'vs/base/common/platform'; import { localize } from 'vs/nls'; import { IExtensionManagementServerService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ILabelService } from 'vs/platform/label/common/label'; -import { extensionButtonProminentBackground, extensionButtonProminentForeground } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; +import { extensionButtonProminentBackground, extensionButtonProminentForeground } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND } from 'vs/workbench/common/theme'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { IWindowService } from 'vs/platform/windows/common/windows'; export abstract class ExtensionWidget extends Disposable implements IExtensionContainer { private _extension: IExtension; @@ -135,13 +137,14 @@ export class RatingsWidget extends ExtensionWidget { } } } - this.container.title = this.extension.ratingCount > 1 ? localize('ratedByUsers', "Rated by {0} users", this.extension.ratingCount) : localize('ratedBySingleUser', "Rated by 1 user"); + this.container.title = this.extension.ratingCount === 1 ? localize('ratedBySingleUser', "Rated by 1 user") + : typeof this.extension.ratingCount === 'number' && this.extension.ratingCount > 1 ? localize('ratedByUsers', "Rated by {0} users", this.extension.ratingCount) : localize('noRating', "No rating"); } } export class RecommendationWidget extends ExtensionWidget { - private element: HTMLElement; + private element?: HTMLElement; private disposables: IDisposable[] = []; constructor( @@ -160,7 +163,7 @@ export class RecommendationWidget extends ExtensionWidget { if (this.element) { this.parent.removeChild(this.element); } - this.element = null; + this.element = undefined; this.disposables = dispose(this.disposables); } @@ -206,6 +209,7 @@ export class RemoteBadgeWidget extends ExtensionWidget { @IThemeService private readonly themeService: IThemeService, @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @IWindowService private readonly windowService: IWindowService ) { super(); this.render(); @@ -231,6 +235,9 @@ export class RemoteBadgeWidget extends ExtensionWidget { append(this.element, $('span.octicon.octicon-file-symlink-directory')); const applyBadgeStyle = () => { + if (!this.element) { + return; + } const bgColor = this.themeService.getTheme().getColor(STATUS_BAR_HOST_NAME_BACKGROUND); const fgColor = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY ? this.themeService.getTheme().getColor(STATUS_BAR_NO_FOLDER_FOREGROUND) : this.themeService.getTheme().getColor(STATUS_BAR_FOREGROUND); this.element.style.backgroundColor = bgColor ? bgColor.toString() : ''; @@ -240,7 +247,11 @@ export class RemoteBadgeWidget extends ExtensionWidget { this.themeService.onThemeChange(applyBadgeStyle, this, this.disposables); this.workspaceContextService.onDidChangeWorkbenchState(applyBadgeStyle, this, this.disposables); - const updateTitle = () => this.element.title = localize('remote extension title', "Extension in {0}", this.labelService.getHostLabel()); + const updateTitle = () => { + if (this.element) { + this.element.title = localize('remote extension title', "Extension in {0}", this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.windowService.getConfiguration().remoteAuthority)); + } + }; this.labelService.onDidChangeFormatters(() => updateTitle(), this, this.disposables); updateTitle(); } diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/EmptyStar.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/EmptyStar.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/EmptyStar.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/EmptyStar.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/FullStarLight.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/FullStarLight.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/FullStarLight.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/FullStarLight.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/HalfStarLight.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/HalfStarLight.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/HalfStarLight.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/HalfStarLight.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/clear-inverse.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/clear-inverse.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/clear-inverse.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/clear-inverse.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/clear.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/clear.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/clear.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/clear.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/defaultIcon.png b/src/vs/workbench/contrib/extensions/electron-browser/media/defaultIcon.png similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/defaultIcon.png rename to src/vs/workbench/contrib/extensions/electron-browser/media/defaultIcon.png diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionActions.css similarity index 98% rename from src/vs/workbench/parts/extensions/electron-browser/media/extensionActions.css rename to src/vs/workbench/contrib/extensions/electron-browser/media/extensionActions.css index a3058b260d..4671a2886f 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensionActions.css +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionActions.css @@ -31,6 +31,7 @@ .monaco-action-bar .action-item.disabled .action-label.extension-action.install:not(.installing), .monaco-action-bar .action-item.disabled .action-label.extension-action.uninstall:not(.uninstalling), .monaco-action-bar .action-item.disabled .action-label.extension-action.update, +.monaco-action-bar .action-item.disabled .action-label.extension-action.theme, .monaco-action-bar .action-item.disabled .action-label.extension-action.extension-editor-dropdown-action, .monaco-action-bar .action-item.disabled .action-label.extension-action.reload, .monaco-action-bar .action-item.disabled .action-label.disable-status.hide, diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionEditor.css similarity index 98% rename from src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css rename to src/vs/workbench/contrib/extensions/electron-browser/media/extensionEditor.css index dd2b972dc0..33b0be9b62 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css +++ b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionEditor.css @@ -276,7 +276,7 @@ } .extension-editor > .body > .content table code:not(:empty) { - font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); font-size: 90%; background-color: rgba(128, 128, 128, 0.17); border-radius: 4px; diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensions-dark.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/extensions-dark.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/extensions-dark.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/extensions-dark.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensions.css b/src/vs/workbench/contrib/extensions/electron-browser/media/extensions.css similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/extensions.css rename to src/vs/workbench/contrib/extensions/electron-browser/media/extensions.css diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionsViewlet.css similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css rename to src/vs/workbench/contrib/extensions/electron-browser/media/extensionsViewlet.css diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionsWidgets.css b/src/vs/workbench/contrib/extensions/electron-browser/media/extensionsWidgets.css similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/extensionsWidgets.css rename to src/vs/workbench/contrib/extensions/electron-browser/media/extensionsWidgets.css diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/language-icon.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/language-icon.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/language-icon.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/language-icon.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/loading.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/loading.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/loading.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/loading.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/manage-inverse.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/manage-inverse.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/manage-inverse.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/manage-inverse.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/manage.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/manage.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/manage.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/manage.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/markdown.css b/src/vs/workbench/contrib/extensions/electron-browser/media/markdown.css similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/markdown.css rename to src/vs/workbench/contrib/extensions/electron-browser/media/markdown.css diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/profile-start-inverse.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/profile-start-inverse.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/profile-start-inverse.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/profile-start-inverse.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/profile-start.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/profile-start.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/profile-start.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/profile-start.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/profile-stop-inverse.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/profile-stop-inverse.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/profile-stop-inverse.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/profile-stop-inverse.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/profile-stop.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/profile-stop.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/profile-stop.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/profile-stop.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/runtimeExtensionsEditor.css b/src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/runtimeExtensionsEditor.css rename to src/vs/workbench/contrib/extensions/electron-browser/media/runtimeExtensionsEditor.css diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/save-inverse.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/save-inverse.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/save-inverse.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/save-inverse.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/save.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/save.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/save.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/save.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/start-inverse.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/start-inverse.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/start-inverse.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/start-inverse.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/start.svg b/src/vs/workbench/contrib/extensions/electron-browser/media/start.svg similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/start.svg rename to src/vs/workbench/contrib/extensions/electron-browser/media/start.svg diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/theme-icon.png b/src/vs/workbench/contrib/extensions/electron-browser/media/theme-icon.png similarity index 100% rename from src/vs/workbench/parts/extensions/electron-browser/media/theme-icon.png rename to src/vs/workbench/contrib/extensions/electron-browser/media/theme-icon.png diff --git a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts similarity index 90% rename from src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts rename to src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts index 0a89726d05..f9858e871d 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -6,16 +6,16 @@ import 'vs/css!./media/runtimeExtensionsEditor'; import * as nls from 'vs/nls'; import * as os from 'os'; -import product from 'vs/platform/node/product'; -import pkg from 'vs/platform/node/package'; +import product from 'vs/platform/product/node/product'; +import pkg from 'vs/platform/product/node/package'; import { Action, IAction } from 'vs/base/common/actions'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/parts/extensions/common/extensions'; +import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IExtensionService, IExtensionDescription, IExtensionsStatus, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService, IExtensionsStatus, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions'; import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { append, $, addClass, toggleClass, Dimension, clearNode } from 'vs/base/browser/dom'; @@ -32,17 +32,18 @@ import { memoize } from 'vs/base/common/decorators'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { Event } from 'vs/base/common/event'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { RuntimeExtensionsInput } from 'vs/workbench/services/extensions/electron-browser/runtimeExtensionsInput'; -import { IDebugService } from 'vs/workbench/parts/debug/common/debug'; +import { RuntimeExtensionsInput } from 'vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput'; +import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { randomPort } from 'vs/base/node/ports'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ILabelService } from 'vs/platform/label/common/label'; import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; -import { join } from 'path'; +import { join } from 'vs/base/common/path'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { ExtensionIdentifier, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); export const CONTEXT_PROFILE_SESSION_STATE = new RawContextKey('profileSessionState', 'none'); @@ -62,12 +63,12 @@ export interface IExtensionHostProfileService { readonly onDidChangeLastProfile: Event; readonly state: ProfileSessionState; - readonly lastProfile: IExtensionHostProfile; + readonly lastProfile: IExtensionHostProfile | null; startProfiling(): void; stopProfiling(): void; - getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile; + getUnresponsiveProfile(extensionId: ExtensionIdentifier): IExtensionHostProfile | undefined; setUnresponsiveProfile(extensionId: ExtensionIdentifier, profile: IExtensionHostProfile): void; } @@ -90,7 +91,7 @@ interface IRuntimeExtension { description: IExtensionDescription; marketplaceInfo: IExtension; status: IExtensionsStatus; - profileInfo: IExtensionProfileInformation; + profileInfo?: IExtensionProfileInformation; unresponsiveProfile?: IExtensionHostProfile; } @@ -98,10 +99,10 @@ export class RuntimeExtensionsEditor extends BaseEditor { public static readonly ID: string = 'workbench.editor.runtimeExtensions'; - private _list: WorkbenchList; - private _profileInfo: IExtensionHostProfile; + private _list: WorkbenchList | null; + private _profileInfo: IExtensionHostProfile | null; - private _elements: IRuntimeExtension[]; + private _elements: IRuntimeExtension[] | null; private _extensionsDescriptions: IExtensionDescription[]; private _updateSoon: RunOnceScheduler; private _profileSessionState: IContextKey; @@ -118,7 +119,8 @@ export class RuntimeExtensionsEditor extends BaseEditor { @IInstantiationService private readonly _instantiationService: IInstantiationService, @IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService, @IStorageService storageService: IStorageService, - @ILabelService private readonly _labelService: ILabelService + @ILabelService private readonly _labelService: ILabelService, + @IWindowService private readonly _windowService: IWindowService ) { super(RuntimeExtensionsEditor.ID, telemetryService, themeService, storageService); @@ -214,7 +216,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { description: extensionDescription, marketplaceInfo: marketplaceMap[ExtensionIdentifier.toKey(extensionDescription.identifier)], status: statusMap[extensionDescription.identifier.value], - profileInfo: profileInfo, + profileInfo: profileInfo || undefined, unresponsiveProfile: this._extensionHostProfileService.getUnresponsiveProfile(extensionDescription.identifier) }; } @@ -299,9 +301,9 @@ export class RuntimeExtensionsEditor extends BaseEditor { toggleClass(data.root, 'odd', index % 2 === 1); - data.name.textContent = element.marketplaceInfo ? element.marketplaceInfo.displayName : element.description.displayName; + data.name.textContent = element.marketplaceInfo ? element.marketplaceInfo.displayName : element.description.displayName || ''; - const activationTimes = element.status.activationTimes; + const activationTimes = element.status.activationTimes!; let syncTime = activationTimes.codeLoadingTime + activationTimes.activateCallTime; data.activationTime.textContent = activationTimes.startup ? `Startup Activation: ${syncTime}ms` : `Activation: ${syncTime}ms`; @@ -377,13 +379,13 @@ export class RuntimeExtensionsEditor extends BaseEditor { el.innerHTML = renderOcticons(`$(rss) ${element.description.extensionLocation.authority}`); data.msgContainer.appendChild(el); - const hostLabel = this._labelService.getHostLabel(); + const hostLabel = this._labelService.getHostLabel(REMOTE_HOST_SCHEME, this._windowService.getConfiguration().remoteAuthority); if (hostLabel) { el.innerHTML = renderOcticons(`$(rss) ${hostLabel}`); } } - if (this._profileInfo) { + if (this._profileInfo && element.profileInfo) { data.profileTime.textContent = `Profile: ${(element.profileInfo.totalTime / 1000).toFixed(2)}ms`; } else { data.profileTime.textContent = ''; @@ -402,7 +404,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { horizontalScrolling: false }) as WorkbenchList; - this._list.splice(0, this._list.length, this._elements); + this._list.splice(0, this._list.length, this._elements || undefined); this._list.onContextMenu((e) => { if (!e.element) { @@ -415,8 +417,8 @@ export class RuntimeExtensionsEditor extends BaseEditor { actions.push(new Separator()); if (e.element.marketplaceInfo) { - actions.push(new Action('runtimeExtensionsEditor.action.disableWorkspace', nls.localize('disable workspace', "Disable (Workspace)"), null, true, () => this._extensionsWorkbenchService.setEnablement(e.element.marketplaceInfo, EnablementState.WorkspaceDisabled))); - actions.push(new Action('runtimeExtensionsEditor.action.disable', nls.localize('disable', "Disable"), null, true, () => this._extensionsWorkbenchService.setEnablement(e.element.marketplaceInfo, EnablementState.Disabled))); + actions.push(new Action('runtimeExtensionsEditor.action.disableWorkspace', nls.localize('disable workspace', "Disable (Workspace)"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.WorkspaceDisabled))); + actions.push(new Action('runtimeExtensionsEditor.action.disable', nls.localize('disable', "Disable"), undefined, true, () => this._extensionsWorkbenchService.setEnablement(e.element!.marketplaceInfo, EnablementState.Disabled))); actions.push(new Separator()); } const state = this._extensionHostProfileService.state; @@ -440,7 +442,9 @@ export class RuntimeExtensionsEditor extends BaseEditor { } public layout(dimension: Dimension): void { - this._list.layout(dimension.height); + if (this._list) { + this._list.layout(dimension.height); + } } } @@ -467,18 +471,18 @@ export class ReportExtensionIssueAction extends Action { private static _label = nls.localize('reportExtensionIssue', "Report Issue"); private readonly _url: string; - private readonly _task: () => Promise; + private readonly _task?: () => Promise; constructor(extension: { description: IExtensionDescription; marketplaceInfo: IExtension; - status: IExtensionsStatus; + status?: IExtensionsStatus; unresponsiveProfile?: IExtensionHostProfile }) { super(ReportExtensionIssueAction._id, ReportExtensionIssueAction._label, 'extension-action report-issue'); this.enabled = extension.marketplaceInfo && extension.marketplaceInfo.type === ExtensionType.User - && Boolean(extension.description.repository) && Boolean(extension.description.repository.url); + && !!extension.description.repository && !!extension.description.repository.url; const { url, task } = ReportExtensionIssueAction._generateNewIssueUrl(extension); this._url = url; @@ -495,11 +499,11 @@ export class ReportExtensionIssueAction extends Action { private static _generateNewIssueUrl(extension: { description: IExtensionDescription; marketplaceInfo: IExtension; - status: IExtensionsStatus; + status?: IExtensionsStatus; unresponsiveProfile?: IExtensionHostProfile }): { url: string, task?: () => Promise } { - let task: () => Promise; + let task: (() => Promise) | undefined; let baseUrl = extension.marketplaceInfo && extension.marketplaceInfo.type === ExtensionType.User && extension.description.repository ? extension.description.repository.url : undefined; if (!!baseUrl) { baseUrl = `${baseUrl.indexOf('.git') !== -1 ? baseUrl.substr(0, baseUrl.length - 4) : baseUrl}/issues/new/`; @@ -517,10 +521,10 @@ export class ReportExtensionIssueAction extends Action { let path = join(os.homedir(), `${extension.description.identifier.value}-unresponsive.cpuprofile.txt`); task = async () => { const profiler = await import('v8-inspect-profiler'); - const data = profiler.rewriteAbsolutePaths({ profile: extension.unresponsiveProfile.data }, 'pii_removed'); + const data = profiler.rewriteAbsolutePaths({ profile: extension.unresponsiveProfile!.data }, 'pii_removed'); profiler.writeProfile(data, path).then(undefined, onUnexpectedError); }; - message = `:warning: Make sure to **attach** this file from your *home*-directory: \`${path}\` :warning:\n\nFind more details here: https://github.com/Microsoft/vscode/wiki/Explain:-extension-causes-high-cpu-load`; + message = `:warning: Make sure to **attach** this file from your *home*-directory:\n:warning:\`${path}\`\n\nFind more details here: https://github.com/Microsoft/vscode/wiki/Explain:-extension-causes-high-cpu-load`; } else { // generic @@ -577,7 +581,7 @@ export class DebugExtensionHostAction extends Action { } } - return this._debugService.startDebugging(null, { + return this._debugService.startDebugging(undefined, { type: 'node', name: nls.localize('debugExtensionHost.launch.name', "Attach Extension Host"), request: 'attach', @@ -599,7 +603,7 @@ export class StartExtensionHostProfileAction extends Action { run(): Promise { this._extensionHostProfileService.startProfiling(); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -616,7 +620,7 @@ export class StopExtensionHostProfileAction extends Action { run(): Promise { this._extensionHostProfileService.stopProfiling(); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -657,7 +661,7 @@ export class SaveExtensionHostProfileAction extends Action { } const profileInfo = this._extensionHostProfileService.lastProfile; - let dataToWrite: object = profileInfo.data; + let dataToWrite: object = profileInfo ? profileInfo.data : {}; if (this._environmentService.isBuilt) { const profiler = await import('v8-inspect-profiler'); @@ -672,6 +676,6 @@ export class SaveExtensionHostProfileAction extends Action { picked = picked + '.txt'; } - return writeFile(picked, JSON.stringify(profileInfo.data, null, '\t')); + return writeFile(picked, JSON.stringify(profileInfo ? profileInfo.data : {}, null, '\t')); } } diff --git a/src/vs/workbench/services/extensions/electron-browser/runtimeExtensionsInput.ts b/src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput.ts similarity index 100% rename from src/vs/workbench/services/extensions/electron-browser/runtimeExtensionsInput.ts rename to src/vs/workbench/contrib/extensions/electron-browser/runtimeExtensionsInput.ts diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts similarity index 98% rename from src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts rename to src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts index 45a6cb0717..6a5a02d758 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/node/extensionsWorkbenchService.ts @@ -24,11 +24,11 @@ import { IWindowService } from 'vs/platform/windows/common/windows'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; // {{SQL CARBON EDIT}} -import { IExtension, IExtensionDependencies, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/parts/extensions/common/extensions'; +import { IExtension, IExtensionDependencies, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey, AutoCheckUpdatesConfigurationKey, ExtensionsPolicyKey, ExtensionsPolicy } from 'vs/workbench/contrib/extensions/common/extensions'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; -import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; -import product from 'vs/platform/node/product'; +import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; +import product from 'vs/platform/product/node/product'; import { ILogService } from 'vs/platform/log/common/log'; import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -37,12 +37,12 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IFileService } from 'vs/platform/files/common/files'; import { IExtensionManifest, ExtensionType, ExtensionIdentifierWithVersion, IExtension as IPlatformExtension } from 'vs/platform/extensions/common/extensions'; -import { isUIExtension } from 'vs/platform/extensions/node/extensionsUtil'; +import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil'; // {{SQL CARBON EDIT}} -import pkg from 'vs/platform/node/package'; import { isEngineValid } from 'vs/platform/extensions/node/extensionValidator'; import { ExtensionManagementError } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import pkg from 'vs/platform/product/node/package'; interface IExtensionStateProvider { (extension: Extension): T; @@ -570,6 +570,8 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, this._onChange.fire(extension); this.eventuallyAutoUpdateExtensions(); }); + } else { + this._onChange.fire(extension); } } @@ -734,7 +736,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, installVersion(extension: IExtension, version: string): Promise { if (!(extension instanceof Extension)) { - return Promise.resolve(); + return Promise.resolve(extension); } if (!extension.gallery) { @@ -956,6 +958,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService, this.installed.push(extension); } extension.local = local; + extension.gallery = gallery; } } this._onChange.fire(error ? undefined : extension); diff --git a/src/vs/workbench/parts/extensions/test/common/extensionQuery.test.ts b/src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts similarity index 98% rename from src/vs/workbench/parts/extensions/test/common/extensionQuery.test.ts rename to src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts index ebecffbc7e..ad135a0cf3 100644 --- a/src/vs/workbench/parts/extensions/test/common/extensionQuery.test.ts +++ b/src/vs/workbench/contrib/extensions/test/common/extensionQuery.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Query } from 'vs/workbench/parts/extensions/common/extensionQuery'; +import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; suite('Extension query', () => { test('parse', () => { diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts similarity index 98% rename from src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts rename to src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 609614f028..11ed9b0604 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -6,16 +6,16 @@ import * as assert from 'assert'; import { assign } from 'vs/base/common/objects'; import { generateUuid } from 'vs/base/common/uuid'; -import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; -import * as ExtensionsActions from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; -import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import * as ExtensionsActions from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; +import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/node/extensionsWorkbenchService'; import { IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, IExtensionTipsService, ILocalExtension, IGalleryExtension, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, EnablementState, InstallOperation, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; @@ -24,19 +24,20 @@ import { Emitter } from 'vs/base/common/event'; import { IPager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { IExtensionService, IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService, TestWindowService } from 'vs/workbench/test/workbenchTestServices'; +import { TestContextService, TestWindowService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { URLService } from 'vs/platform/url/common/urlService'; import { URI } from 'vs/base/common/uri'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/node/extensionManagementServerService'; -import { IRemoteAgentService } from 'vs/workbench/services/remote/node/remoteAgentService'; +import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/electron-browser/extensionManagementServerService'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; -import { ExtensionIdentifier, IExtensionContributions, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionContributions, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; suite('ExtensionsActions Test', () => { @@ -63,7 +64,7 @@ suite('ExtensionsActions Test', () => { instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); - + instantiationService.stub(ISharedProcessService, TestSharedProcessService); instantiationService.stub(IExtensionManagementService, ExtensionManagementService); instantiationService.stub(IExtensionManagementService, 'onInstallExtension', installEvent.event); @@ -84,7 +85,7 @@ suite('ExtensionsActions Test', () => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); instantiationService.stubPromise(IExtensionManagementService, 'getExtensionsReport', []); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); - instantiationService.stub(IExtensionService, { getExtensions: () => Promise.resolve([]), onDidChangeExtensions: new Emitter().event, canAddExtension: (extension: IExtensionDescription) => false, canRemoveExtension: (extension: IExtensionDescription) => false }); + instantiationService.stub(IExtensionService, >{ getExtensions: () => Promise.resolve([]), onDidChangeExtensions: new Emitter().event, canAddExtension: (extension: IExtensionDescription) => false, canRemoveExtension: (extension: IExtensionDescription) => false }); await (instantiationService.get(IExtensionEnablementService)).reset(); instantiationService.set(IExtensionsWorkbenchService, instantiationService.createInstance(ExtensionsWorkbenchService)); diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts similarity index 90% rename from src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts rename to src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts index 9f46e01e12..fc79f50c9c 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -5,7 +5,7 @@ import * as sinon from 'sinon'; import * as assert from 'assert'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as fs from 'fs'; import * as os from 'os'; import * as uuid from 'vs/base/common/uuid'; @@ -13,40 +13,41 @@ import { IExtensionGalleryService, IGalleryExtensionAssets, IGalleryExtension, IExtensionManagementService, IExtensionEnablementService, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { Emitter } from 'vs/base/common/event'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestTextResourceConfigurationService, TestContextService, TestLifecycleService, TestEnvironmentService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { TestTextResourceConfigurationService, TestContextService, TestLifecycleService, TestEnvironmentService, TestStorageService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { IFileService } from 'vs/platform/files/common/files'; -import { FileService } from 'vs/workbench/services/files/electron-browser/fileService'; +import { FileService } from 'vs/workbench/services/files/node/fileService'; import * as extfs from 'vs/base/node/extfs'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IPager } from 'vs/base/common/paging'; import { assign } from 'vs/base/common/objects'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions'; +import { ConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { IURLService } from 'vs/platform/url/common/url'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { INotificationService, Severity, IPromptChoice, IPromptOptions } from 'vs/platform/notification/common/notification'; import { URLService } from 'vs/platform/url/common/urlService'; -import { IExperimentService } from 'vs/workbench/parts/experiments/node/experimentService'; -import { TestExperimentService } from 'vs/workbench/parts/experiments/test/electron-browser/experimentService.test'; +import { IExperimentService } from 'vs/workbench/contrib/experiments/node/experimentService'; +import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; const mockExtensionGallery: IGalleryExtension[] = [ aGalleryExtension('MockExtension1', { diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts similarity index 96% rename from src/vs/workbench/parts/extensions/test/electron-browser/extensionsViews.test.ts rename to src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index 2e9dd7e2af..b4e20ded35 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -6,17 +6,17 @@ import * as assert from 'assert'; import { assign } from 'vs/base/common/objects'; import { generateUuid } from 'vs/base/common/uuid'; -import { ExtensionsListView } from 'vs/workbench/parts/extensions/electron-browser/extensionsViews'; +import { ExtensionsListView } from 'vs/workbench/contrib/extensions/electron-browser/extensionsViews'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; -import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/node/extensionsWorkbenchService'; import { IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, IExtensionTipsService, ILocalExtension, IGalleryExtension, IQueryOptions, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier, IExtensionManagementServerService, IExtensionManagementServer, EnablementState, ExtensionRecommendationReason, SortBy } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; @@ -26,7 +26,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService, TestWindowService } from 'vs/workbench/test/workbenchTestServices'; +import { TestContextService, TestWindowService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IWindowService } from 'vs/platform/windows/common/windows'; @@ -34,11 +34,12 @@ import { URLService } from 'vs/platform/url/common/urlService'; import { URI } from 'vs/base/common/uri'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { SinonStub } from 'sinon'; -import { IExperimentService, ExperimentService, ExperimentState, ExperimentActionType } from 'vs/workbench/parts/experiments/node/experimentService'; -import { IRemoteAgentService } from 'vs/workbench/services/remote/node/remoteAgentService'; +import { IExperimentService, ExperimentService, ExperimentState, ExperimentActionType } from 'vs/workbench/contrib/experiments/node/experimentService'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; -import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/node/extensionManagementServerService'; +import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/electron-browser/extensionManagementServerService'; import { ExtensionIdentifier, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; suite('ExtensionsListView Tests', () => { @@ -79,6 +80,7 @@ suite('ExtensionsListView Tests', () => { instantiationService.stub(IConfigurationService, new TestConfigurationService()); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); + instantiationService.stub(ISharedProcessService, TestSharedProcessService); instantiationService.stub(IExperimentService, ExperimentService); instantiationService.stub(IExtensionManagementService, ExtensionManagementService); diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts similarity index 98% rename from src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts rename to src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 67c70d1589..5fc2ab1339 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -8,15 +8,15 @@ import * as assert from 'assert'; import * as fs from 'fs'; import { assign } from 'vs/base/common/objects'; import { generateUuid } from 'vs/base/common/uuid'; -import { IExtensionsWorkbenchService, ExtensionState, AutoCheckUpdatesConfigurationKey, AutoUpdateConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions'; -import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; +import { IExtensionsWorkbenchService, ExtensionState, AutoCheckUpdatesConfigurationKey, AutoUpdateConfigurationKey } from 'vs/workbench/contrib/extensions/common/extensions'; +import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/node/extensionsWorkbenchService'; import { IExtensionManagementService, IExtensionGalleryService, IExtensionEnablementService, IExtensionTipsService, ILocalExtension, IGalleryExtension, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IGalleryExtensionAssets, IExtensionIdentifier, EnablementState, InstallOperation, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; -import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService'; +import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/electron-browser/extensionTipsService'; import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/electron-browser/extensionEnablementService.test'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; import { IURLService } from 'vs/platform/url/common/url'; @@ -26,7 +26,7 @@ import { IPager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService, TestWindowService } from 'vs/workbench/test/workbenchTestServices'; +import { TestContextService, TestWindowService, TestSharedProcessService } from 'vs/workbench/test/workbenchTestServices'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IWindowService } from 'vs/platform/windows/common/windows'; @@ -37,9 +37,10 @@ import { URLService } from 'vs/platform/url/common/urlService'; import { URI } from 'vs/base/common/uri'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/node/extensionManagementServerService'; -import { IRemoteAgentService } from 'vs/workbench/services/remote/node/remoteAgentService'; +import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/electron-browser/extensionManagementServerService'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; suite('ExtensionsWorkbenchServiceTest', () => { @@ -65,6 +66,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); instantiationService.stub(IURLService, URLService); + instantiationService.stub(ISharedProcessService, TestSharedProcessService); instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IConfigurationService, { diff --git a/src/vs/workbench/parts/execution/common/execution.ts b/src/vs/workbench/contrib/externalTerminal/common/externalTerminal.ts similarity index 65% rename from src/vs/workbench/parts/execution/common/execution.ts rename to src/vs/workbench/contrib/externalTerminal/common/externalTerminal.ts index 385fc704d3..12fe121189 100644 --- a/src/vs/workbench/parts/execution/common/execution.ts +++ b/src/vs/workbench/contrib/externalTerminal/common/externalTerminal.ts @@ -6,10 +6,21 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProcessEnvironment } from 'vs/base/common/platform'; -export const ITerminalService = createDecorator('nativeTerminalService'); +export const IExternalTerminalService = createDecorator('nativeTerminalService'); -export interface ITerminalService { +export interface IExternalTerminalService { _serviceBrand: any; openTerminal(path: string): void; runInTerminal(title: string, cwd: string, args: string[], env: IProcessEnvironment): Promise; +} + +export interface IExternalTerminalConfiguration { + terminal: { + explorerKind: 'integrated' | 'external', + external: { + linuxExec: string, + osxExec: string, + windowsExec: string + } + }; } \ No newline at end of file diff --git a/src/vs/workbench/parts/execution/electron-browser/TerminalHelper.scpt b/src/vs/workbench/contrib/externalTerminal/electron-browser/TerminalHelper.scpt similarity index 100% rename from src/vs/workbench/parts/execution/electron-browser/TerminalHelper.scpt rename to src/vs/workbench/contrib/externalTerminal/electron-browser/TerminalHelper.scpt diff --git a/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts b/src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.contribution.ts similarity index 81% rename from src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts rename to src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.contribution.ts index eede5b176f..6ba526d561 100644 --- a/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts +++ b/src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.contribution.ts @@ -8,32 +8,32 @@ import * as env from 'vs/base/common/platform'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import * as paths from 'vs/base/common/paths'; +import * as paths from 'vs/base/common/path'; import { URI as uri } from 'vs/base/common/uri'; -import { ITerminalService } from 'vs/workbench/parts/execution/common/execution'; +import { IExternalTerminalConfiguration, IExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal'; import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Extensions, IConfigurationRegistry, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { ITerminalService as IIntegratedTerminalService, KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED } from 'vs/workbench/parts/terminal/common/terminal'; -import { getDefaultTerminalWindows, getDefaultTerminalLinuxReady, DEFAULT_TERMINAL_OSX, ITerminalConfiguration } from 'vs/workbench/parts/execution/electron-browser/terminal'; -import { WinTerminalService, MacTerminalService, LinuxTerminalService } from 'vs/workbench/parts/execution/electron-browser/terminalService'; +import { ITerminalService as IIntegratedTerminalService, KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED } from 'vs/workbench/contrib/terminal/common/terminal'; +import { getDefaultTerminalWindows, getDefaultTerminalLinuxReady, DEFAULT_TERMINAL_OSX } from 'vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal'; +import { WindowsExternalTerminalService, MacExternalTerminalService, LinuxExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/electron-browser/externalTerminalService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IFileService } from 'vs/platform/files/common/files'; import { IListService } from 'vs/platform/list/browser/listService'; -import { getMultiSelectedResources } from 'vs/workbench/parts/files/browser/files'; +import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { Schemas } from 'vs/base/common/network'; import { distinct } from 'vs/base/common/arrays'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; if (env.isWindows) { - registerSingleton(ITerminalService, WinTerminalService, true); + registerSingleton(IExternalTerminalService, WindowsExternalTerminalService, true); } else if (env.isMacintosh) { - registerSingleton(ITerminalService, MacTerminalService, true); + registerSingleton(IExternalTerminalService, MacExternalTerminalService, true); } else if (env.isLinux) { - registerSingleton(ITerminalService, LinuxTerminalService, true); + registerSingleton(IExternalTerminalService, LinuxExternalTerminalService, true); } getDefaultTerminalLinuxReady().then(defaultTerminalLinux => { @@ -87,13 +87,13 @@ CommandsRegistry.registerCommand({ const editorService = accessor.get(IEditorService); const fileService = accessor.get(IFileService); const integratedTerminalService = accessor.get(IIntegratedTerminalService); - const terminalService = accessor.get(ITerminalService); + const terminalService = accessor.get(IExternalTerminalService); const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService); return fileService.resolveFiles(resources.map(r => ({ resource: r }))).then(stats => { - const directoriesToOpen = distinct(stats.map(({ stat }) => stat.isDirectory ? stat.resource.fsPath : paths.dirname(stat.resource.fsPath))); + const directoriesToOpen = distinct(stats.filter(data => data.success).map(({ stat }) => stat!.isDirectory ? stat!.resource.fsPath : paths.dirname(stat!.resource.fsPath))); return directoriesToOpen.map(dir => { - if (configurationService.getValue().terminal.explorerKind === 'integrated') { + if (configurationService.getValue().terminal.explorerKind === 'integrated') { const instance = integratedTerminalService.createTerminal({ cwd: dir }, true); if (instance && (resources.length === 1 || !resource || dir === resource.fsPath || dir === paths.dirname(resource.fsPath))) { integratedTerminalService.setActiveInstance(instance); @@ -115,7 +115,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ weight: KeybindingWeight.WorkbenchContrib, handler: (accessor) => { const historyService = accessor.get(IHistoryService); - const terminalService = accessor.get(ITerminalService); + const terminalService = accessor.get(IExternalTerminalService); const root = historyService.getLastActiveWorkspaceRoot(Schemas.file); if (root) { terminalService.openTerminal(root.fsPath); diff --git a/src/vs/workbench/parts/execution/electron-browser/terminal.ts b/src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.ts similarity index 90% rename from src/vs/workbench/parts/execution/electron-browser/terminal.ts rename to src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.ts index 98f7e2d3b0..584a22ae46 100644 --- a/src/vs/workbench/parts/execution/electron-browser/terminal.ts +++ b/src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.ts @@ -45,14 +45,3 @@ export function getDefaultTerminalWindows(): string { } return _DEFAULT_TERMINAL_WINDOWS; } - -export interface ITerminalConfiguration { - terminal: { - explorerKind: 'integrated' | 'external', - external: { - linuxExec: string, - osxExec: string, - windowsExec: string - } - }; -} diff --git a/src/vs/workbench/parts/execution/electron-browser/terminalService.ts b/src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminalService.ts similarity index 82% rename from src/vs/workbench/parts/execution/electron-browser/terminalService.ts rename to src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminalService.ts index 29d8e9ee66..ac603f8ecc 100644 --- a/src/vs/workbench/parts/execution/electron-browser/terminalService.ts +++ b/src/vs/workbench/contrib/externalTerminal/electron-browser/externalTerminalService.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as cp from 'child_process'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as processes from 'vs/base/node/processes'; import * as nls from 'vs/nls'; import { assign } from 'vs/base/common/objects'; -import { ITerminalService } from 'vs/workbench/parts/execution/common/execution'; +import { IExternalTerminalService, IExternalTerminalConfiguration } from 'vs/workbench/contrib/externalTerminal/common/externalTerminal'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ITerminalConfiguration, getDefaultTerminalWindows, getDefaultTerminalLinuxReady, DEFAULT_TERMINAL_OSX } from 'vs/workbench/parts/execution/electron-browser/terminal'; +import { getDefaultTerminalWindows, getDefaultTerminalLinuxReady, DEFAULT_TERMINAL_OSX } from 'vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { getPathFromAmdModule } from 'vs/base/common/amd'; @@ -21,7 +21,7 @@ enum WinSpawnType { CMDER } -export class WinTerminalService implements ITerminalService { +export class WindowsExternalTerminalService implements IExternalTerminalService { public _serviceBrand: any; private static readonly CMD = 'cmd.exe'; @@ -32,14 +32,14 @@ export class WinTerminalService implements ITerminalService { } public openTerminal(cwd?: string): void { - const configuration = this._configurationService.getValue(); + const configuration = this._configurationService.getValue(); this.spawnTerminal(cp, configuration, processes.getWindowsShell(), cwd); } public runInTerminal(title: string, dir: string, args: string[], envVars: IProcessEnvironment): Promise { - const configuration = this._configurationService.getValue(); + const configuration = this._configurationService.getValue(); const terminalConfig = configuration.terminal.external; const exec = terminalConfig.windowsExec || getDefaultTerminalWindows(); @@ -64,14 +64,14 @@ export class WinTerminalService implements ITerminalService { windowsVerbatimArguments: true }; - const cmd = cp.spawn(WinTerminalService.CMD, cmdArgs, options); + const cmd = cp.spawn(WindowsExternalTerminalService.CMD, cmdArgs, options); cmd.on('error', e); c(undefined); }); } - private spawnTerminal(spawner, configuration: ITerminalConfiguration, command: string, cwd?: string): Promise { + private spawnTerminal(spawner, configuration: IExternalTerminalConfiguration, command: string, cwd?: string): Promise { const terminalConfig = configuration.terminal.external; const exec = terminalConfig.windowsExec || getDefaultTerminalWindows(); const spawnType = this.getSpawnType(exec); @@ -113,7 +113,7 @@ export class WinTerminalService implements ITerminalService { } } -export class MacTerminalService implements ITerminalService { +export class MacExternalTerminalService implements IExternalTerminalService { public _serviceBrand: any; private static readonly OSASCRIPT = '/usr/bin/osascript'; // osascript is the AppleScript interpreter on OS X @@ -123,14 +123,14 @@ export class MacTerminalService implements ITerminalService { ) { } public openTerminal(cwd?: string): void { - const configuration = this._configurationService.getValue(); + const configuration = this._configurationService.getValue(); this.spawnTerminal(cp, configuration, cwd); } public runInTerminal(title: string, dir: string, args: string[], envVars: IProcessEnvironment): Promise { - const configuration = this._configurationService.getValue(); + const configuration = this._configurationService.getValue(); const terminalConfig = configuration.terminal.external; const terminalApp = terminalConfig.osxExec || DEFAULT_TERMINAL_OSX; @@ -142,7 +142,7 @@ export class MacTerminalService implements ITerminalService { // and then launches the program inside that window. const script = terminalApp === DEFAULT_TERMINAL_OSX ? 'TerminalHelper' : 'iTermHelper'; - const scriptpath = getPathFromAmdModule(require, `vs/workbench/parts/execution/electron-browser/${script}.scpt`); + const scriptpath = getPathFromAmdModule(require, `vs/workbench/contrib/externalTerminal/electron-browser/${script}.scpt`); const osaArgs = [ scriptpath, @@ -169,7 +169,7 @@ export class MacTerminalService implements ITerminalService { } let stderr = ''; - const osa = cp.spawn(MacTerminalService.OSASCRIPT, osaArgs); + const osa = cp.spawn(MacExternalTerminalService.OSASCRIPT, osaArgs); osa.on('error', e); osa.stderr.on('data', (data) => { stderr += data.toString(); @@ -192,7 +192,7 @@ export class MacTerminalService implements ITerminalService { }); } - private spawnTerminal(spawner, configuration: ITerminalConfiguration, cwd?: string): Promise { + private spawnTerminal(spawner, configuration: IExternalTerminalConfiguration, cwd?: string): Promise { const terminalConfig = configuration.terminal.external; const terminalApp = terminalConfig.osxExec || DEFAULT_TERMINAL_OSX; @@ -204,7 +204,7 @@ export class MacTerminalService implements ITerminalService { } } -export class LinuxTerminalService implements ITerminalService { +export class LinuxExternalTerminalService implements IExternalTerminalService { public _serviceBrand: any; private static readonly WAIT_MESSAGE = nls.localize('press.any.key', "Press any key to continue..."); @@ -215,14 +215,14 @@ export class LinuxTerminalService implements ITerminalService { public openTerminal(cwd?: string): void { - const configuration = this._configurationService.getValue(); + const configuration = this._configurationService.getValue(); this.spawnTerminal(cp, configuration, cwd); } public runInTerminal(title: string, dir: string, args: string[], envVars: IProcessEnvironment): Promise { - const configuration = this._configurationService.getValue(); + const configuration = this._configurationService.getValue(); const terminalConfig = configuration.terminal.external; const execPromise = terminalConfig.linuxExec ? Promise.resolve(terminalConfig.linuxExec) : getDefaultTerminalLinuxReady(); @@ -240,7 +240,7 @@ export class LinuxTerminalService implements ITerminalService { termArgs.push('bash'); termArgs.push('-c'); - const bashCommand = `${quote(args)}; echo; read -p "${LinuxTerminalService.WAIT_MESSAGE}" -n1;`; + const bashCommand = `${quote(args)}; echo; read -p "${LinuxExternalTerminalService.WAIT_MESSAGE}" -n1;`; termArgs.push(`''${bashCommand}''`); // wrapping argument in two sets of ' because node is so "friendly" that it removes one set... // merge environment variables into a copy of the process.env @@ -276,7 +276,7 @@ export class LinuxTerminalService implements ITerminalService { }); } - private spawnTerminal(spawner, configuration: ITerminalConfiguration, cwd?: string): Promise { + private spawnTerminal(spawner, configuration: IExternalTerminalConfiguration, cwd?: string): Promise { const terminalConfig = configuration.terminal.external; const execPromise = terminalConfig.linuxExec ? Promise.resolve(terminalConfig.linuxExec) : getDefaultTerminalLinuxReady(); const env = cwd ? { cwd: cwd } : undefined; diff --git a/src/vs/workbench/parts/execution/electron-browser/iTermHelper.scpt b/src/vs/workbench/contrib/externalTerminal/electron-browser/iTermHelper.scpt similarity index 100% rename from src/vs/workbench/parts/execution/electron-browser/iTermHelper.scpt rename to src/vs/workbench/contrib/externalTerminal/electron-browser/iTermHelper.scpt diff --git a/src/vs/workbench/parts/execution/test/electron-browser/terminalService.test.ts b/src/vs/workbench/contrib/externalTerminal/test/electron-browser/externalTerminalService.test.ts similarity index 86% rename from src/vs/workbench/parts/execution/test/electron-browser/terminalService.test.ts rename to src/vs/workbench/contrib/externalTerminal/test/electron-browser/externalTerminalService.test.ts index 6191a50f16..957f5b60dc 100644 --- a/src/vs/workbench/parts/execution/test/electron-browser/terminalService.test.ts +++ b/src/vs/workbench/contrib/externalTerminal/test/electron-browser/externalTerminalService.test.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { deepEqual, equal } from 'assert'; -import { WinTerminalService, LinuxTerminalService, MacTerminalService } from 'vs/workbench/parts/execution/electron-browser/terminalService'; -import { getDefaultTerminalWindows, getDefaultTerminalLinuxReady, DEFAULT_TERMINAL_OSX } from 'vs/workbench/parts/execution/electron-browser/terminal'; +import { WindowsExternalTerminalService, LinuxExternalTerminalService, MacExternalTerminalService } from 'vs/workbench/contrib/externalTerminal/electron-browser/externalTerminalService'; +import { getDefaultTerminalWindows, getDefaultTerminalLinuxReady, DEFAULT_TERMINAL_OSX } from 'vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal'; -suite('Execution - TerminalService', () => { +suite('ExternalTerminalService', () => { let mockOnExit: Function; let mockOnError: Function; let mockConfig: any; @@ -42,7 +42,7 @@ suite('Execution - TerminalService', () => { }; } }; - let testService = new WinTerminalService(mockConfig); + let testService = new WindowsExternalTerminalService(mockConfig); (testService).spawnTerminal( mockSpawner, mockConfig, @@ -67,7 +67,7 @@ suite('Execution - TerminalService', () => { } }; mockConfig.terminal.external.windowsExec = undefined; - let testService = new WinTerminalService(mockConfig); + let testService = new WindowsExternalTerminalService(mockConfig); (testService).spawnTerminal( mockSpawner, mockConfig, @@ -91,7 +91,7 @@ suite('Execution - TerminalService', () => { }; } }; - let testService = new WinTerminalService(mockConfig); + let testService = new WindowsExternalTerminalService(mockConfig); (testService).spawnTerminal( mockSpawner, mockConfig, @@ -115,7 +115,7 @@ suite('Execution - TerminalService', () => { return { on: (evt: any) => evt }; } }; - let testService = new WinTerminalService(mockConfig); + let testService = new WindowsExternalTerminalService(mockConfig); (testService).spawnTerminal( mockSpawner, mockConfig, @@ -138,7 +138,7 @@ suite('Execution - TerminalService', () => { }; } }; - let testService = new MacTerminalService(mockConfig); + let testService = new MacExternalTerminalService(mockConfig); (testService).spawnTerminal( mockSpawner, mockConfig, @@ -161,7 +161,7 @@ suite('Execution - TerminalService', () => { } }; mockConfig.terminal.external.osxExec = undefined; - let testService = new MacTerminalService(mockConfig); + let testService = new MacExternalTerminalService(mockConfig); (testService).spawnTerminal( mockSpawner, mockConfig, @@ -184,7 +184,7 @@ suite('Execution - TerminalService', () => { }; } }; - let testService = new LinuxTerminalService(mockConfig); + let testService = new LinuxExternalTerminalService(mockConfig); (testService).spawnTerminal( mockSpawner, mockConfig, @@ -208,7 +208,7 @@ suite('Execution - TerminalService', () => { } }; mockConfig.terminal.external.linuxExec = undefined; - let testService = new LinuxTerminalService(mockConfig); + let testService = new LinuxExternalTerminalService(mockConfig); (testService).spawnTerminal( mockSpawner, mockConfig, diff --git a/src/vs/workbench/parts/feedback/electron-browser/feedback.contribution.ts b/src/vs/workbench/contrib/feedback/electron-browser/feedback.contribution.ts similarity index 93% rename from src/vs/workbench/parts/feedback/electron-browser/feedback.contribution.ts rename to src/vs/workbench/contrib/feedback/electron-browser/feedback.contribution.ts index 958a0ca82b..a3248ca69b 100644 --- a/src/vs/workbench/parts/feedback/electron-browser/feedback.contribution.ts +++ b/src/vs/workbench/contrib/feedback/electron-browser/feedback.contribution.ts @@ -6,7 +6,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IStatusbarRegistry, Extensions, StatusbarItemDescriptor } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; -import { FeedbackStatusbarItem } from 'vs/workbench/parts/feedback/electron-browser/feedbackStatusbarItem'; +import { FeedbackStatusbarItem } from 'vs/workbench/contrib/feedback/electron-browser/feedbackStatusbarItem'; import { localize } from 'vs/nls'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; diff --git a/src/vs/workbench/parts/feedback/electron-browser/feedback.ts b/src/vs/workbench/contrib/feedback/electron-browser/feedback.ts similarity index 98% rename from src/vs/workbench/parts/feedback/electron-browser/feedback.ts rename to src/vs/workbench/contrib/feedback/electron-browser/feedback.ts index 667b4f239d..cea80b6fa3 100644 --- a/src/vs/workbench/parts/feedback/electron-browser/feedback.ts +++ b/src/vs/workbench/contrib/feedback/electron-browser/feedback.ts @@ -8,17 +8,17 @@ import * as nls from 'vs/nls'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { Dropdown } from 'vs/base/browser/ui/dropdown/dropdown'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import * as dom from 'vs/base/browser/dom'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IIntegrityService } from 'vs/platform/integrity/common/integrity'; +import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { attachButtonStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { editorWidgetBackground, widgetShadow, inputBorder, inputForeground, inputBackground, inputActiveOptionBorder, editorBackground, buttonBackground, contrastBorder, darken } from 'vs/platform/theme/common/colorRegistry'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { Button } from 'vs/base/browser/ui/button/button'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export const FEEDBACK_VISIBLE_CONFIG = 'workbench.statusBar.feedback.visible'; @@ -66,7 +66,7 @@ export class FeedbackDropdown extends Dropdown { @ITelemetryService private readonly telemetryService: ITelemetryService, @IIntegrityService private readonly integrityService: IIntegrityService, @IThemeService private readonly themeService: IThemeService, - @IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(container, { contextViewProvider: options.contextViewProvider, diff --git a/src/vs/workbench/parts/feedback/electron-browser/feedbackStatusbarItem.ts b/src/vs/workbench/contrib/feedback/electron-browser/feedbackStatusbarItem.ts similarity index 92% rename from src/vs/workbench/parts/feedback/electron-browser/feedbackStatusbarItem.ts rename to src/vs/workbench/contrib/feedback/electron-browser/feedbackStatusbarItem.ts index c9f157790a..4deefc14f5 100644 --- a/src/vs/workbench/parts/feedback/electron-browser/feedbackStatusbarItem.ts +++ b/src/vs/workbench/contrib/feedback/electron-browser/feedbackStatusbarItem.ts @@ -5,15 +5,14 @@ import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; -import { FeedbackDropdown, IFeedback, IFeedbackDelegate, FEEDBACK_VISIBLE_CONFIG, IFeedbackDropdownOptions } from 'vs/workbench/parts/feedback/electron-browser/feedback'; +import { FeedbackDropdown, IFeedback, IFeedbackDelegate, FEEDBACK_VISIBLE_CONFIG, IFeedbackDropdownOptions } from 'vs/workbench/contrib/feedback/electron-browser/feedback'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND } from 'vs/workbench/common/theme'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; -import { IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationChangeEvent, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { clearNode, EventHelper, addClass, removeClass, addDisposableListener } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; @@ -62,7 +61,7 @@ export class FeedbackStatusbarItem extends Themable implements IStatusbarItem { @IContextViewService private readonly contextViewService: IContextViewService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IThemeService themeService: IThemeService ) { super(themeService); @@ -155,7 +154,7 @@ export class FeedbackStatusbarItem extends Themable implements IStatusbarItem { class HideAction extends Action { constructor( - @IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService ) { super('feedback.hide', localize('hide', "Hide")); } diff --git a/src/vs/workbench/parts/feedback/electron-browser/media/close-dark.svg b/src/vs/workbench/contrib/feedback/electron-browser/media/close-dark.svg similarity index 100% rename from src/vs/workbench/parts/feedback/electron-browser/media/close-dark.svg rename to src/vs/workbench/contrib/feedback/electron-browser/media/close-dark.svg diff --git a/src/vs/workbench/parts/feedback/electron-browser/media/close.svg b/src/vs/workbench/contrib/feedback/electron-browser/media/close.svg similarity index 100% rename from src/vs/workbench/parts/feedback/electron-browser/media/close.svg rename to src/vs/workbench/contrib/feedback/electron-browser/media/close.svg diff --git a/src/vs/workbench/parts/feedback/electron-browser/media/feedback.css b/src/vs/workbench/contrib/feedback/electron-browser/media/feedback.css similarity index 99% rename from src/vs/workbench/parts/feedback/electron-browser/media/feedback.css rename to src/vs/workbench/contrib/feedback/electron-browser/media/feedback.css index d589b36762..c9af14d735 100644 --- a/src/vs/workbench/parts/feedback/electron-browser/media/feedback.css +++ b/src/vs/workbench/contrib/feedback/electron-browser/media/feedback.css @@ -128,8 +128,7 @@ font-family: inherit; border: 1px solid transparent; } - - + .vs .monaco-workbench .feedback-form .cancel { background: url('close.svg') center center no-repeat; } diff --git a/src/vs/workbench/parts/feedback/electron-browser/media/happy.svg b/src/vs/workbench/contrib/feedback/electron-browser/media/happy.svg similarity index 100% rename from src/vs/workbench/parts/feedback/electron-browser/media/happy.svg rename to src/vs/workbench/contrib/feedback/electron-browser/media/happy.svg diff --git a/src/vs/workbench/parts/feedback/electron-browser/media/info.svg b/src/vs/workbench/contrib/feedback/electron-browser/media/info.svg similarity index 100% rename from src/vs/workbench/parts/feedback/electron-browser/media/info.svg rename to src/vs/workbench/contrib/feedback/electron-browser/media/info.svg diff --git a/src/vs/workbench/parts/feedback/electron-browser/media/sad.svg b/src/vs/workbench/contrib/feedback/electron-browser/media/sad.svg similarity index 100% rename from src/vs/workbench/parts/feedback/electron-browser/media/sad.svg rename to src/vs/workbench/contrib/feedback/electron-browser/media/sad.svg diff --git a/src/vs/workbench/parts/feedback/electron-browser/media/smiley.svg b/src/vs/workbench/contrib/feedback/electron-browser/media/smiley.svg similarity index 100% rename from src/vs/workbench/parts/feedback/electron-browser/media/smiley.svg rename to src/vs/workbench/contrib/feedback/electron-browser/media/smiley.svg diff --git a/src/vs/workbench/parts/feedback/electron-browser/media/twitter.svg b/src/vs/workbench/contrib/feedback/electron-browser/media/twitter.svg similarity index 100% rename from src/vs/workbench/parts/feedback/electron-browser/media/twitter.svg rename to src/vs/workbench/contrib/feedback/electron-browser/media/twitter.svg diff --git a/src/vs/workbench/parts/files/browser/editors/binaryFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts similarity index 90% rename from src/vs/workbench/parts/files/browser/editors/binaryFileEditor.ts rename to src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts index 9c2860b229..aba6b45685 100644 --- a/src/vs/workbench/parts/files/browser/editors/binaryFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/binaryFileEditor.ts @@ -9,9 +9,9 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; -import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { URI } from 'vs/base/common/uri'; -import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/parts/files/common/files'; +import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; import { IFileService } from 'vs/platform/files/common/files'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -57,14 +57,14 @@ export class BinaryFileEditor extends BaseBinaryResourceEditor { private openExternal(resource: URI): void { this.windowsService.openExternal(resource.toString()).then(didOpen => { if (!didOpen) { - return this.windowsService.showItemInFolder(resource.fsPath); + return this.windowsService.showItemInFolder(resource); } return undefined; }); } - getTitle(): string { + getTitle(): string | null { return this.input ? this.input.getName() : nls.localize('binaryFileEditor', "Binary File Viewer"); } } diff --git a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts similarity index 91% rename from src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts rename to src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts index 20e7b07f5f..ba53450975 100644 --- a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorTracker.ts @@ -10,10 +10,10 @@ import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationEvent, FileOperation, IFileService, FileChangeType, FileChangesEvent } from 'vs/platform/files/common/files'; -import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle'; -import { distinct } from 'vs/base/common/arrays'; +import { distinct, coalesce } from 'vs/base/common/arrays'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { isLinux } from 'vs/base/common/platform'; @@ -22,9 +22,9 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { SideBySideEditor } from 'vs/workbench/browser/parts/editor/sideBySideEditor'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/parts/files/common/files'; +import { BINARY_FILE_EDITOR_ID } from 'vs/workbench/contrib/files/common/files'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ResourceQueue, timeout } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -95,12 +95,12 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // are visible in any editor. since this is a fast operation in the case nothing has changed, // we tolerate the additional work. distinct( - this.editorService.visibleEditors + coalesce(this.editorService.visibleEditors .map(editorInput => { const resource = toResource(editorInput, { supportSideBySide: true }); return resource ? this.textFileService.models.get(resource) : undefined; - }) - .filter(model => model && !model.isDirty()), + })) + .filter(model => !model.isDirty()), m => m.getResource().toString() ).forEach(model => this.queueModelLoad(model)); } @@ -113,7 +113,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut private onFileOperation(e: FileOperationEvent): void { // Handle moves specially when file is opened - if (e.operation === FileOperation.MOVE) { + if (e.operation === FileOperation.MOVE && e.target) { this.handleMovedFileInOpenedEditors(e.resource, e.target.resource); } @@ -284,7 +284,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut if (editorResource && resource.toString() === editorResource.toString()) { const control = editor.getControl(); if (isCodeEditor(control)) { - return control.saveViewState(); + return control.saveViewState() || undefined; } } } @@ -308,8 +308,8 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // // Note: we also consider the added event because it could be that a file was added // and updated right after. - distinct([...e.getUpdated(), ...e.getAdded()] - .map(u => this.textFileService.models.get(u.resource)) + distinct(coalesce([...e.getUpdated(), ...e.getAdded()] + .map(u => this.textFileService.models.get(u.resource))) .filter(model => model && !model.isDirty()), m => m.getResource().toString()) .forEach(model => this.queueModelLoad(model)); } @@ -321,25 +321,26 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // to have a size of 2 (1 running load and 1 queued load). const queue = this.modelLoadQueue.queueFor(model.getResource()); if (queue.size <= 1) { - queue.queue(() => model.load().then(null, onUnexpectedError)); + queue.queue(() => model.load().then(undefined, onUnexpectedError)); } } private handleUpdatesToVisibleBinaryEditors(e: FileChangesEvent): void { const editors = this.editorService.visibleControls; editors.forEach(editor => { - const resource = toResource(editor.input, { supportSideBySide: true }); + const resource = editor.input ? toResource(editor.input, { supportSideBySide: true }) : undefined; // Support side-by-side binary editors too let isBinaryEditor = false; if (editor instanceof SideBySideEditor) { - isBinaryEditor = editor.getMasterEditor().getId() === BINARY_FILE_EDITOR_ID; + const masterEditor = editor.getMasterEditor(); + isBinaryEditor = !!masterEditor && masterEditor.getId() === BINARY_FILE_EDITOR_ID; } else { isBinaryEditor = editor.getId() === BINARY_FILE_EDITOR_ID; } // Binary editor that should reload from event - if (resource && isBinaryEditor && (e.contains(resource, FileChangeType.UPDATED) || e.contains(resource, FileChangeType.ADDED))) { + if (resource && editor.input && isBinaryEditor && (e.contains(resource, FileChangeType.UPDATED) || e.contains(resource, FileChangeType.ADDED))) { this.editorService.openEditor(editor.input, { forceReload: true, preserveFocus: true }, editor.group); } }); @@ -347,10 +348,10 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut private handleOutOfWorkspaceWatchers(): void { const visibleOutOfWorkspacePaths = new ResourceMap(); - this.editorService.visibleEditors.map(editorInput => { + coalesce(this.editorService.visibleEditors.map(editorInput => { return toResource(editorInput, { supportSideBySide: true }); - }).filter(resource => { - return !!resource && this.fileService.canHandleResource(resource) && !this.contextService.isInsideWorkspace(resource); + })).filter(resource => { + return this.fileService.canHandleResource(resource) && !this.contextService.isInsideWorkspace(resource); }).forEach(resource => { visibleOutOfWorkspacePaths.set(resource, resource); }); diff --git a/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts similarity index 92% rename from src/vs/workbench/parts/files/browser/editors/textFileEditor.ts rename to src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index bbf1f2f845..0d4d71c122 100644 --- a/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -6,14 +6,15 @@ import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as types from 'vs/base/common/types'; -import * as paths from 'vs/base/common/paths'; +import { isValidBasename } from 'vs/base/common/extpath'; +import { basename } from 'vs/base/common/resources'; import { Action } from 'vs/base/common/actions'; -import { VIEWLET_ID, TEXT_FILE_EDITOR_ID, IExplorerService } from 'vs/workbench/parts/files/common/files'; +import { VIEWLET_ID, TEXT_FILE_EDITOR_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { ITextFileEditorModel, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; import { EditorOptions, TextEditorOptions, IEditorCloseEvent } from 'vs/workbench/common/editor'; import { BinaryEditorModel } from 'vs/workbench/common/editor/binaryEditorModel'; -import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { FileOperationError, FileOperationResult, FileChangesEvent, IFileService, FALLBACK_MAX_MEMORY_SIZE_MB, MIN_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/common/files'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -26,7 +27,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ScrollType } from 'vs/editor/common/editorCommon'; import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; @@ -81,7 +82,7 @@ export class TextFileEditor extends BaseTextEditor { } private updateRestoreViewStateConfiguration(): void { - this.restoreViewState = this.configurationService.getValue(null, 'workbench.editor.restoreViewState'); + this.restoreViewState = this.configurationService.getValue(undefined, 'workbench.editor.restoreViewState'); } getTitle(): string { @@ -177,14 +178,14 @@ export class TextFileEditor extends BaseTextEditor { if ((error).fileOperationResult === FileOperationResult.FILE_IS_DIRECTORY) { this.openAsFolder(input); - return Promise.reject(new Error(nls.localize('openFolderError', "File is a directory"))); + return Promise.reject(new Error(nls.localize('openFolderError', "File is a directory"))); } // Offer to create a file from the error if we have a file not found and the name is valid - if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND && paths.isValidBasename(paths.basename(input.getResource().fsPath))) { + if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND && isValidBasename(basename(input.getResource()))) { return Promise.reject(createErrorWithActions(toErrorMessage(error), { actions: [ - new Action('workbench.files.action.createMissingFile', nls.localize('createFile', "Create File"), null, true, () => { + new Action('workbench.files.action.createMissingFile', nls.localize('createFile', "Create File"), undefined, true, () => { return this.fileService.updateContent(input.getResource(), '').then(() => this.editorService.openEditor({ resource: input.getResource(), options: { @@ -197,18 +198,18 @@ export class TextFileEditor extends BaseTextEditor { } if ((error).fileOperationResult === FileOperationResult.FILE_EXCEED_MEMORY_LIMIT) { - const memoryLimit = Math.max(MIN_MAX_MEMORY_SIZE_MB, +this.configurationService.getValue(null, 'files.maxMemoryForLargeFilesMB') || FALLBACK_MAX_MEMORY_SIZE_MB); + const memoryLimit = Math.max(MIN_MAX_MEMORY_SIZE_MB, +this.configurationService.getValue(undefined, 'files.maxMemoryForLargeFilesMB') || FALLBACK_MAX_MEMORY_SIZE_MB); return Promise.reject(createErrorWithActions(toErrorMessage(error), { actions: [ - new Action('workbench.window.action.relaunchWithIncreasedMemoryLimit', nls.localize('relaunchWithIncreasedMemoryLimit', "Restart with {0} MB", memoryLimit), null, true, () => { + new Action('workbench.window.action.relaunchWithIncreasedMemoryLimit', nls.localize('relaunchWithIncreasedMemoryLimit', "Restart with {0} MB", memoryLimit), undefined, true, () => { return this.windowsService.relaunch({ addArgs: [ `--max-memory=${memoryLimit}` ] }); }), - new Action('workbench.window.action.configureMemoryLimit', nls.localize('configureMemoryLimit', 'Configure Memory Limit'), null, true, () => { + new Action('workbench.window.action.configureMemoryLimit', nls.localize('configureMemoryLimit', 'Configure Memory Limit'), undefined, true, () => { return this.preferencesService.openGlobalSettings(undefined, { query: 'files.maxMemoryForLargeFilesMB' }); }) ] @@ -227,13 +228,16 @@ export class TextFileEditor extends BaseTextEditor { } private openAsFolder(input: FileEditorInput): void { + if (!this.group) { + return; + } // Since we cannot open a folder, we have to restore the previous input if any and close the editor this.group.closeEditor(this.input).then(() => { // Best we can do is to reveal the folder in the explorer if (this.contextService.isInsideWorkspace(input.getResource())) { - this.viewletService.openViewlet(VIEWLET_ID, true).then(() => { + this.viewletService.openViewlet(VIEWLET_ID).then(() => { this.explorerService.select(input.getResource(), true); }); } diff --git a/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts similarity index 86% rename from src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts rename to src/vs/workbench/contrib/files/browser/explorerViewlet.ts index 9d899d9331..ff1a6e2ea3 100644 --- a/src/vs/workbench/parts/files/electron-browser/explorerViewlet.ts +++ b/src/vs/workbench/contrib/files/browser/explorerViewlet.ts @@ -6,12 +6,12 @@ import 'vs/css!./media/explorerviewlet'; import { localize } from 'vs/nls'; import * as DOM from 'vs/base/browser/dom'; -import { VIEWLET_ID, ExplorerViewletVisibleContext, IFilesConfiguration, OpenEditorsVisibleContext, OpenEditorsVisibleCondition, VIEW_CONTAINER } from 'vs/workbench/parts/files/common/files'; +import { VIEWLET_ID, ExplorerViewletVisibleContext, IFilesConfiguration, OpenEditorsVisibleContext, OpenEditorsVisibleCondition, VIEW_CONTAINER } from 'vs/workbench/contrib/files/common/files'; import { ViewContainerViewlet, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { ExplorerView } from 'vs/workbench/parts/files/electron-browser/views/explorerView'; -import { EmptyView } from 'vs/workbench/parts/files/electron-browser/views/emptyView'; -import { OpenEditorsView } from 'vs/workbench/parts/files/electron-browser/views/openEditorsView'; +import { ExplorerView } from 'vs/workbench/contrib/files/browser/views/explorerView'; +import { EmptyView } from 'vs/workbench/contrib/files/browser/views/emptyView'; +import { OpenEditorsView } from 'vs/workbench/contrib/files/browser/views/openEditorsView'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -21,18 +21,19 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ViewsRegistry, IViewDescriptor } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions } from 'vs/workbench/common/views'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { DelegatingEditorService } from 'vs/workbench/services/editor/browser/editorService'; -import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; -import { IEditorInput } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor } from 'vs/workbench/common/editor'; import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { KeyChord, KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { Registry } from 'vs/platform/registry/common/platform'; export class ExplorerViewletViewsContribution extends Disposable implements IWorkbenchContribution { @@ -56,7 +57,8 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor } private registerViews(): void { - const viewDescriptors = ViewsRegistry.getViews(VIEW_CONTAINER); + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + const viewDescriptors = viewsRegistry.getViews(VIEW_CONTAINER); let viewDescriptorsToRegister: IViewDescriptor[] = []; let viewDescriptorsToDeregister: IViewDescriptor[] = []; @@ -88,10 +90,10 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor } if (viewDescriptorsToRegister.length) { - ViewsRegistry.registerViews(viewDescriptorsToRegister, VIEW_CONTAINER); + viewsRegistry.registerViews(viewDescriptorsToRegister, VIEW_CONTAINER); } if (viewDescriptorsToDeregister.length) { - ViewsRegistry.deregisterViews(viewDescriptorsToDeregister, VIEW_CONTAINER); + viewsRegistry.deregisterViews(viewDescriptorsToDeregister, VIEW_CONTAINER); } } @@ -99,7 +101,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor return { id: OpenEditorsView.ID, name: OpenEditorsView.NAME, - ctor: OpenEditorsView, + ctorDescriptor: { ctor: OpenEditorsView }, order: 0, when: OpenEditorsVisibleCondition, canToggleVisibility: true, @@ -114,7 +116,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor return { id: EmptyView.ID, name: EmptyView.NAME, - ctor: EmptyView, + ctorDescriptor: { ctor: EmptyView }, order: 1, canToggleVisibility: false }; @@ -124,7 +126,7 @@ export class ExplorerViewletViewsContribution extends Disposable implements IWor return { id: ExplorerView.ID, name: localize('folders', "Folders"), - ctor: ExplorerView, + ctorDescriptor: { ctor: ExplorerView }, order: 1, canToggleVisibility: false }; @@ -148,7 +150,7 @@ export class ExplorerViewlet extends ViewContainerViewlet { private viewletVisibleContextKey: IContextKey; constructor( - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IStorageService protected storageService: IStorageService, @@ -161,7 +163,7 @@ export class ExplorerViewlet extends ViewContainerViewlet { @IContextMenuService contextMenuService: IContextMenuService, @IExtensionService extensionService: IExtensionService ) { - super(VIEWLET_ID, ExplorerViewlet.EXPLORER_VIEWS_STATE, true, configurationService, partService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + super(VIEWLET_ID, ExplorerViewlet.EXPLORER_VIEWS_STATE, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); this.viewletVisibleContextKey = ExplorerViewletVisibleContext.bindTo(contextKeyService); @@ -181,7 +183,7 @@ export class ExplorerViewlet extends ViewContainerViewlet { // We try to be smart and only use the delay if we recognize that the user action is likely to cause // a new entry in the opened editors view. const delegatingEditorService = this.instantiationService.createInstance(DelegatingEditorService); - delegatingEditorService.setEditorOpenHandler((group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions) => { + delegatingEditorService.setEditorOpenHandler((group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise => { let openEditorsView = this.getOpenEditorsView(); if (openEditorsView) { let delay = 0; @@ -197,8 +199,8 @@ export class ExplorerViewlet extends ViewContainerViewlet { openEditorsView.setStructuralRefreshDelay(delay); } - const onSuccessOrError = (editor?: BaseEditor) => { - let openEditorsView = this.getOpenEditorsView(); + const onSuccessOrError = (editor: BaseEditor | null) => { + const openEditorsView = this.getOpenEditorsView(); if (openEditorsView) { openEditorsView.setStructuralRefreshDelay(0); } diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts similarity index 95% rename from src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts rename to src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index 8e0876c156..873a2b6ca7 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -5,25 +5,25 @@ import * as nls from 'vs/nls'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ToggleAutoSaveAction, GlobalNewUntitledFileAction, ShowOpenedFileInNewWindow, FocusFilesExplorer, GlobalCompareResourcesAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler } from 'vs/workbench/parts/files/electron-browser/fileActions'; -import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTION_CONTEXT } from 'vs/workbench/parts/files/electron-browser/saveErrorHandler'; +import { ToggleAutoSaveAction, GlobalNewUntitledFileAction, ShowOpenedFileInNewWindow, FocusFilesExplorer, GlobalCompareResourcesAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler, cutFileHandler } from 'vs/workbench/contrib/files/browser/fileActions'; +import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTION_CONTEXT } from 'vs/workbench/contrib/files/browser/saveErrorHandler'; import { SyncActionDescriptor, MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import { openWindowCommand, REVEAL_IN_OS_COMMAND_ID, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, REVEAL_IN_OS_LABEL, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL } from 'vs/workbench/parts/files/electron-browser/fileCommands'; +import { openWindowCommand, REVEAL_IN_OS_COMMAND_ID, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, REVEAL_IN_OS_LABEL, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL } from 'vs/workbench/contrib/files/browser/fileCommands'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; -import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService } from 'vs/workbench/parts/files/common/files'; +import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; -import { OPEN_FOLDER_SETTINGS_COMMAND, OPEN_FOLDER_SETTINGS_LABEL } from 'vs/workbench/parts/preferences/browser/preferencesActions'; import { AutoSaveContext } from 'vs/workbench/services/textfile/common/textfiles'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { WorkbenchListDoubleSelection } from 'vs/platform/list/browser/listService'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; +import { SupportsWorkspacesContext } from 'vs/workbench/common/contextkeys'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; // Contribute Global Actions @@ -160,12 +160,12 @@ function appendEditorTitleContextMenuItem(id: string, title: string, when: Conte // Editor Title Menu for Conflict Resolution appendSaveConflictEditorTitleAction('workbench.files.action.acceptLocalChanges', nls.localize('acceptLocalChanges', "Use your changes and overwrite disk contents"), { - light: URI.parse(require.toUrl(`vs/workbench/parts/files/electron-browser/media/check.svg`)), - dark: URI.parse(require.toUrl(`vs/workbench/parts/files/electron-browser/media/check-inverse.svg`)) + light: URI.parse(require.toUrl(`vs/workbench/contrib/files/browser/media/check.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/files/browser/media/check-inverse.svg`)) }, -10, acceptLocalChangesCommand); appendSaveConflictEditorTitleAction('workbench.files.action.revertLocalChanges', nls.localize('revertLocalChanges', "Discard your changes and revert to content on disk"), { - light: URI.parse(require.toUrl(`vs/workbench/parts/files/electron-browser/media/undo.svg`)), - dark: URI.parse(require.toUrl(`vs/workbench/parts/files/electron-browser/media/undo-inverse.svg`)) + light: URI.parse(require.toUrl(`vs/workbench/contrib/files/browser/media/undo.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/files/browser/media/undo-inverse.svg`)) }, -9, revertLocalChangesCommand); function appendSaveConflictEditorTitleAction(id: string, title: string, iconLocation: { dark: URI; light?: URI; }, order: number, command: ICommandHandler): void { @@ -479,17 +479,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { id: ADD_ROOT_FOLDER_COMMAND_ID, title: ADD_ROOT_FOLDER_LABEL }, - when: ContextKeyExpr.and(ExplorerRootContext) -}); - -MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { - group: '2_workspace', - order: 20, - command: { - id: OPEN_FOLDER_SETTINGS_COMMAND, - title: OPEN_FOLDER_SETTINGS_LABEL - }, - when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext) + when: ContextKeyExpr.and(ExplorerRootContext, SupportsWorkspacesContext) }); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts similarity index 88% rename from src/vs/workbench/parts/files/electron-browser/fileActions.ts rename to src/vs/workbench/contrib/files/browser/fileActions.ts index b0af27d844..0e1650239a 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -7,25 +7,25 @@ import 'vs/css!./media/fileactions'; import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import { isWindows, isLinux } from 'vs/base/common/platform'; -import * as paths from 'vs/base/common/paths'; +import * as extpath from 'vs/base/common/extpath'; import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as strings from 'vs/base/common/strings'; import { Action } from 'vs/base/common/actions'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { VIEWLET_ID, IExplorerService } from 'vs/workbench/parts/files/common/files'; +import { VIEWLET_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService, AutoSaveConfiguration } from 'vs/platform/files/common/files'; -import { toResource, IUntitledResourceInput } from 'vs/workbench/common/editor'; -import { ExplorerViewlet } from 'vs/workbench/parts/files/electron-browser/explorerViewlet'; +import { toResource, IUntitledResourceInput, ITextEditor } from 'vs/workbench/common/editor'; +import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IInstantiationService, ServicesAccessor, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; import { ITextModel } from 'vs/editor/common/model'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { REVEAL_IN_EXPLORER_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID } from 'vs/workbench/parts/files/electron-browser/fileCommands'; +import { REVEAL_IN_EXPLORER_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID } from 'vs/workbench/contrib/files/browser/fileCommands'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -40,10 +40,9 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Constants } from 'vs/editor/common/core/uint'; import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; -import { IViewlet } from 'vs/workbench/common/viewlet'; import { coalesce } from 'vs/base/common/arrays'; import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree'; -import { ExplorerItem } from 'vs/workbench/parts/files/common/explorerModel'; +import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { onUnexpectedError } from 'vs/base/common/errors'; import { sequence } from 'vs/base/common/async'; @@ -102,12 +101,20 @@ export class BaseErrorReportingAction extends Action { } const PLACEHOLDER_URI = URI.file(''); +function refreshIfSeparator(value: string, explorerService: IExplorerService): void { + if (value && ((value.indexOf('/') >= 0) || (value.indexOf('\\') >= 0))) { + // New input contains separator, multiple resources will get created workaround for #68204 + explorerService.refresh(); + } +} /* New File */ export class NewFileAction extends BaseErrorReportingAction { static readonly ID = 'workbench.files.action.createFileFromExplorer'; static readonly LABEL = nls.localize('createNewFile', "New File"); + private toDispose: IDisposable[] = []; + constructor( private getElement: () => ExplorerItem, @INotificationService notificationService: INotificationService, @@ -117,13 +124,17 @@ export class NewFileAction extends BaseErrorReportingAction { ) { super('explorer.newFile', NEW_FILE_LABEL, notificationService); this.class = 'explorer-action new-file'; + this.toDispose.push(this.explorerService.onDidChangeEditable(e => { + const elementIsBeingEdited = this.explorerService.isEditable(e); + this.enabled = !elementIsBeingEdited; + })); } run(): Promise { let folder: ExplorerItem; const element = this.getElement(); if (element) { - folder = element.isDirectory ? element : element.parent; + folder = element.isDirectory ? element : element.parent!; } else { folder = this.explorerService.roots[0]; } @@ -136,8 +147,9 @@ export class NewFileAction extends BaseErrorReportingAction { return folder.fetchChildren(this.fileService).then(() => { folder.addChild(stat); - const onSuccess = value => { + const onSuccess = (value: string) => { return this.fileService.createFile(resources.joinPath(folder.resource, value)).then(stat => { + refreshIfSeparator(value, this.explorerService); return this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); }, (error) => { this.onErrorWithRetry(error, () => onSuccess(value)); @@ -158,6 +170,11 @@ export class NewFileAction extends BaseErrorReportingAction { }); }); } + + dispose(): void { + super.dispose(); + dispose(this.toDispose); + } } /* New Folder */ @@ -165,6 +182,8 @@ export class NewFolderAction extends BaseErrorReportingAction { static readonly ID = 'workbench.files.action.createFolderFromExplorer'; static readonly LABEL = nls.localize('createNewFolder', "New Folder"); + private toDispose: IDisposable[] = []; + constructor( private getElement: () => ExplorerItem, @INotificationService notificationService: INotificationService, @@ -173,13 +192,17 @@ export class NewFolderAction extends BaseErrorReportingAction { ) { super('explorer.newFolder', NEW_FOLDER_LABEL, notificationService); this.class = 'explorer-action new-folder'; + this.toDispose.push(this.explorerService.onDidChangeEditable(e => { + const elementIsBeingEdited = this.explorerService.isEditable(e); + this.enabled = !elementIsBeingEdited; + })); } run(): Promise { let folder: ExplorerItem; const element = this.getElement(); if (element) { - folder = element.isDirectory ? element : element.parent; + folder = element.isDirectory ? element : element.parent!; } else { folder = this.explorerService.roots[0]; } @@ -192,8 +215,9 @@ export class NewFolderAction extends BaseErrorReportingAction { return folder.fetchChildren(this.fileService).then(() => { folder.addChild(stat); - const onSuccess = value => { + const onSuccess = (value: string) => { return this.fileService.createFolder(resources.joinPath(folder.resource, value)).then(stat => { + refreshIfSeparator(value, this.explorerService); return this.explorerService.select(stat.resource, true); }, (error) => { this.onErrorWithRetry(error, () => onSuccess(value)); @@ -214,6 +238,11 @@ export class NewFolderAction extends BaseErrorReportingAction { }); }); } + + dispose(): void { + super.dispose(); + dispose(this.toDispose); + } } /* Create new file from anywhere: Open untitled */ @@ -256,7 +285,7 @@ class BaseDeleteFileAction extends BaseErrorReportingAction { ) { super('moveFileToTrash', MOVE_FILE_TO_TRASH_LABEL, notificationService); - this.useTrash = useTrash && elements.every(e => !paths.isUNC(e.resource.fsPath)); // on UNC shares there is no trash + this.useTrash = useTrash && elements.every(e => !extpath.isUNC(e.resource.fsPath)); // on UNC shares there is no trash this.enabled = this.elements && this.elements.every(e => !e.isReadonly); } @@ -362,7 +391,7 @@ class BaseDeleteFileAction extends BaseErrorReportingAction { .then(undefined, (error: any) => { // Handle error to delete file(s) from a modal confirmation dialog let errorMessage: string; - let detailMessage: string; + let detailMessage: string | undefined; let primaryButton: string; if (this.useTrash) { errorMessage = isWindows ? nls.localize('binFailed', "Failed to delete using the Recycle Bin. Do you want to permanently delete instead?") : nls.localize('trashFailed', "Failed to delete using the Trash. Do you want to permanently delete instead?"); @@ -480,22 +509,23 @@ class PasteFileAction extends BaseErrorReportingAction { // Find target let target: ExplorerItem; if (this.element.resource.toString() === fileToPaste.toString()) { - target = this.element.parent; + target = this.element.parent!; } else { - target = this.element.isDirectory ? this.element : this.element.parent; + target = this.element.isDirectory ? this.element : this.element.parent!; } const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwirte: pasteShouldMove }); // Copy File const promise = pasteShouldMove ? this.fileService.moveFile(fileToPaste, targetFile) : this.fileService.copyFile(fileToPaste, targetFile); - return promise.then(stat => { + return promise.then(stat => { if (pasteShouldMove) { // Cut is done. Make sure to clear cut state. this.explorerService.setToCopy([], false); } if (!stat.isDirectory) { - return this.editorService.openEditor({ resource: stat.resource, options: { pinned: true, preserveFocus: true } }); + return this.editorService.openEditor({ resource: stat.resource, options: { pinned: true, preserveFocus: true } }) + .then(types.withNullAsUndefined); } return undefined; @@ -515,7 +545,7 @@ export function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste break; } - name = incrementFileName(name, fileToPaste.isDirectory); + name = incrementFileName(name, !!fileToPaste.isDirectory); candidate = resources.joinPath(targetFolder.resource, name); } @@ -719,7 +749,7 @@ export abstract class BaseSaveAllAction extends BaseErrorReportingAction { public run(context?: any): Promise { return this.doRun(context).then(() => true, error => { this.onError(error); - return null; + return false; }); } @@ -814,7 +844,7 @@ export class ShowActiveFileInExplorer extends Action { } public run(): Promise { - const resource = toResource(this.editorService.activeEditor, { supportSideBySide: true }); + const resource = toResource(this.editorService.activeEditor || null, { supportSideBySide: true }); if (resource) { this.commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, resource); } else { @@ -839,7 +869,7 @@ export class CollapseExplorerView extends Action { } public run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true).then((viewlet: ExplorerViewlet) => { + return this.viewletService.openViewlet(VIEWLET_ID).then((viewlet: ExplorerViewlet) => { const explorerView = viewlet.getExplorerView(); if (explorerView) { explorerView.collapseAll(); @@ -863,7 +893,7 @@ export class RefreshExplorerView extends Action { } public run(): Promise { - return this.viewletService.openViewlet(VIEWLET_ID, true).then(() => + return this.viewletService.openViewlet(VIEWLET_ID).then(() => this.explorerService.refresh() ); } @@ -879,24 +909,29 @@ export class ShowOpenedFileInNewWindow extends Action { label: string, @IEditorService private readonly editorService: IEditorService, @IWindowService private readonly windowService: IWindowService, - @INotificationService private readonly notificationService: INotificationService + @INotificationService private readonly notificationService: INotificationService, + @IFileService private readonly fileService: IFileService ) { super(id, label); } public run(): Promise { - const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: true, filter: Schemas.file /* todo@remote */ }); + const fileResource = toResource(this.editorService.activeEditor || null, { supportSideBySide: true }); if (fileResource) { - this.windowService.openWindow([fileResource], { forceNewWindow: true, forceOpenWorkspaceAsFile: true }); + if (this.fileService.canHandleResource(fileResource)) { + this.windowService.openWindow([{ uri: fileResource, typeHint: 'file' }], { forceNewWindow: true, forceOpenWorkspaceAsFile: true }); + } else { + this.notificationService.info(nls.localize('openFileToShowInNewWindow.unsupportedschema', "The active editor must contain an openable resource.")); + } } else { - this.notificationService.info(nls.localize('openFileToShowInNewWindow', "Open a file first to open in new window")); + this.notificationService.info(nls.localize('openFileToShowInNewWindow.nofile', "Open a file first to open in new window")); } return Promise.resolve(true); } } -export function validateFileName(item: ExplorerItem, name: string): string { +export function validateFileName(item: ExplorerItem, name: string): string | null { // Produce a well formed file name name = getWellFormedFileName(name); @@ -922,7 +957,7 @@ export function validateFileName(item: ExplorerItem, name: string): string { } // Invalid File name - if (names.some((folderName) => !paths.isValidBasename(folderName))) { + if (names.some((folderName) => !extpath.isValidBasename(folderName))) { return nls.localize('invalidFileNameError', "The name **{0}** is not valid as a file or folder name. Please choose a different name.", trimLongName(name)); } @@ -984,7 +1019,7 @@ export class CompareWithClipboardAction extends Action { } public run(): Promise { - const resource: URI = toResource(this.editorService.activeEditor, { supportSideBySide: true }); + const resource = toResource(this.editorService.activeEditor || null, { supportSideBySide: true }); if (resource && (this.fileService.canHandleResource(resource) || resource.scheme === Schemas.untitled)) { if (!this.registrationDisposal) { const provider = this.instantiationService.createInstance(ClipboardContentProvider); @@ -1024,7 +1059,7 @@ class ClipboardContentProvider implements ITextModelContentProvider { } interface IExplorerContext { - stat: ExplorerItem; + stat?: ExplorerItem; selection: ExplorerItem[]; } @@ -1036,27 +1071,27 @@ function getContext(listWidget: ListWidget): IExplorerContext { const selection = tree.getSelection(); // Only respect the selection if user clicked inside it (focus belongs to it) - return { stat, selection: selection && selection.indexOf(stat) >= 0 ? selection : [] }; + return { stat, selection: selection && typeof stat !== 'undefined' && selection.indexOf(stat) >= 0 ? selection : [] }; } // TODO@isidor these commands are calling into actions due to the complex inheritance action structure. // It should be the other way around, that actions call into commands. function openExplorerAndRunAction(accessor: ServicesAccessor, constructor: IConstructorSignature1<() => ExplorerItem, Action>): Promise { - const instantationService = accessor.get(IInstantiationService); + const instantiationService = accessor.get(IInstantiationService); const listService = accessor.get(IListService); const viewletService = accessor.get(IViewletService); const activeViewlet = viewletService.getActiveViewlet(); - let explorerPromise: Promise = Promise.resolve(activeViewlet); + let explorerPromise = Promise.resolve(activeViewlet); if (!activeViewlet || activeViewlet.getId() !== VIEWLET_ID) { explorerPromise = viewletService.openViewlet(VIEWLET_ID, true); } return explorerPromise.then((explorer: ExplorerViewlet) => { const explorerView = explorer.getExplorerView(); - if (explorerView && explorerView.isBodyVisible()) { + if (explorerView && explorerView.isBodyVisible() && listService.lastFocusedList) { explorerView.focus(); const { stat } = getContext(listService.lastFocusedList); - const action = instantationService.createInstance(constructor, () => stat); + const action = instantiationService.createInstance(constructor, () => stat); return action.run(); } @@ -1083,15 +1118,22 @@ export const renameHandler = (accessor: ServicesAccessor) => { const listService = accessor.get(IListService); const explorerService = accessor.get(IExplorerService); const textFileService = accessor.get(ITextFileService); + if (!listService.lastFocusedList) { + return; + } + const { stat } = getContext(listService.lastFocusedList); + if (!stat) { + return; + } explorerService.setEditable(stat, { validationMessage: value => validateFileName(stat, value), onFinish: (value, success) => { if (success) { - const parentResource = stat.parent.resource; + const parentResource = stat.parent!.resource; const targetResource = resources.joinPath(parentResource, value); - textFileService.move(stat.resource, targetResource).then(undefined, onUnexpectedError); + textFileService.move(stat.resource, targetResource).then(() => refreshIfSeparator(value, explorerService), onUnexpectedError); } explorerService.setEditable(stat, null); } @@ -1099,51 +1141,70 @@ export const renameHandler = (accessor: ServicesAccessor) => { }; export const moveFileToTrashHandler = (accessor: ServicesAccessor) => { - const instantationService = accessor.get(IInstantiationService); + const instantiationService = accessor.get(IInstantiationService); const listService = accessor.get(IListService); + if (!listService.lastFocusedList) { + return Promise.resolve(); + } const explorerContext = getContext(listService.lastFocusedList); const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; - const moveFileToTrashAction = instantationService.createInstance(BaseDeleteFileAction, stats, true); + const moveFileToTrashAction = instantiationService.createInstance(BaseDeleteFileAction, stats, true); return moveFileToTrashAction.run(); }; export const deleteFileHandler = (accessor: ServicesAccessor) => { - const instantationService = accessor.get(IInstantiationService); + const instantiationService = accessor.get(IInstantiationService); const listService = accessor.get(IListService); + if (!listService.lastFocusedList) { + return Promise.resolve(); + } const explorerContext = getContext(listService.lastFocusedList); const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; - const deleteFileAction = instantationService.createInstance(BaseDeleteFileAction, stats, false); + const deleteFileAction = instantiationService.createInstance(BaseDeleteFileAction, stats, false); return deleteFileAction.run(); }; export const copyFileHandler = (accessor: ServicesAccessor) => { const listService = accessor.get(IListService); + if (!listService.lastFocusedList) { + return; + } const explorerContext = getContext(listService.lastFocusedList); const explorerService = accessor.get(IExplorerService); - const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; - explorerService.setToCopy(stats, false); - pasteShouldMove = false; + if (explorerContext.stat) { + const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; + explorerService.setToCopy(stats, false); + pasteShouldMove = false; + } }; export const cutFileHandler = (accessor: ServicesAccessor) => { const listService = accessor.get(IListService); + if (!listService.lastFocusedList) { + return; + } const explorerContext = getContext(listService.lastFocusedList); const explorerService = accessor.get(IExplorerService); - const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; - explorerService.setToCopy(stats, true); - pasteShouldMove = true; + if (explorerContext.stat) { + const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat]; + explorerService.setToCopy(stats, true); + pasteShouldMove = true; + } }; export const pasteFileHandler = (accessor: ServicesAccessor) => { - const instantationService = accessor.get(IInstantiationService); + const instantiationService = accessor.get(IInstantiationService); const listService = accessor.get(IListService); const clipboardService = accessor.get(IClipboardService); + if (!listService.lastFocusedList) { + return Promise.resolve(); + } const explorerContext = getContext(listService.lastFocusedList); return sequence(resources.distinctParents(clipboardService.readResources(), r => r).map(toCopy => { - const pasteFileAction = instantationService.createInstance(PasteFileAction, explorerContext.stat); + const pasteFileAction = instantiationService.createInstance(PasteFileAction, explorerContext.stat); return () => pasteFileAction.run(toCopy); })); }; diff --git a/src/vs/workbench/parts/files/electron-browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts similarity index 88% rename from src/vs/workbench/parts/files/electron-browser/fileCommands.ts rename to src/vs/workbench/contrib/files/browser/fileCommands.ts index 78e467dfd5..961bb7cfe6 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -4,16 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as paths from 'vs/base/common/paths'; import { URI } from 'vs/base/common/uri'; // {{SQL CARBON EDIT}} - Import EditorInput import { toResource, IEditorCommandsContext, EditorInput } from 'vs/workbench/common/editor'; -import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; +import { IWindowsService, IWindowService, IURIToOpen, IOpenSettings } from 'vs/platform/windows/common/windows'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ExplorerFocusCondition, FileOnDiskContentProvider, VIEWLET_ID, IExplorerService } from 'vs/workbench/parts/files/common/files'; -import { ExplorerViewlet } from 'vs/workbench/parts/files/electron-browser/explorerViewlet'; +import { ExplorerFocusCondition, FileOnDiskContentProvider, VIEWLET_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ITextFileService, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -30,16 +29,18 @@ import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; import { isWindows, isMacintosh } from 'vs/base/common/platform'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { sequence } from 'vs/base/common/async'; -import { getResourceForCommand, getMultiSelectedResources } from 'vs/workbench/parts/files/browser/files'; +import { getResourceForCommand, getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { getMultiSelectedEditorContexts } from 'vs/workbench/browser/parts/editor/editorCommands'; import { Schemas } from 'vs/base/common/network'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +// {{SQL CARBON EDIT}} - Import EditorInput import { IEditorService, SIDE_GROUP, IResourceEditorReplacement } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ILabelService } from 'vs/platform/label/common/label'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { basename } from 'vs/base/common/resources'; // {{SQL CARBON EDIT}} import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService'; @@ -80,25 +81,18 @@ export const ResourceSelectedForCompareContext = new RawContextKey('res export const REMOVE_ROOT_FOLDER_COMMAND_ID = 'removeRootFolder'; export const REMOVE_ROOT_FOLDER_LABEL = nls.localize('removeFolderFromWorkspace', "Remove Folder from Workspace"); -export const openWindowCommand = (accessor: ServicesAccessor, input: Array | { fileURIs: URI[], folderURIs: URI[], forceNewWindow: boolean, forceReuseWindow?: boolean, diffMode?: boolean, addMode?: boolean }, forceNewWindow: boolean) => { - const windowService = accessor.get(IWindowService); - if (Array.isArray(input)) { - windowService.openWindow(input.map(p => typeof p === 'string' ? URI.file(p) : p), { forceNewWindow }); - } else if (input) { - if (Array.isArray(input.folderURIs) && input.folderURIs.length) { - windowService.openWindow(input.folderURIs, { forceNewWindow: input.forceNewWindow, diffMode: input.diffMode, addMode: input.addMode, forceReuseWindow: input.forceReuseWindow }); - } - if (Array.isArray(input.fileURIs) && input.fileURIs.length) { - windowService.openWindow(input.fileURIs, { forceNewWindow: input.forceNewWindow, forceOpenWorkspaceAsFile: true, diffMode: input.diffMode, addMode: input.addMode, forceReuseWindow: input.forceReuseWindow }); - } +export const openWindowCommand = (accessor: ServicesAccessor, urisToOpen: IURIToOpen[], options?: IOpenSettings) => { + if (Array.isArray(urisToOpen)) { + const windowService = accessor.get(IWindowService); + windowService.openWindow(urisToOpen, options); } }; // {{SQL CARBON EDIT}} function save( - resource: URI, + resource: URI | null, isSaveAs: boolean, - options: ISaveOptions, + options: ISaveOptions | undefined, editorService: IEditorService, fileService: IFileService, untitledEditorService: IUntitledEditorService, @@ -126,7 +120,7 @@ function save( // Save As (or Save untitled with associated path) if (isSaveAs || resource.scheme === Schemas.untitled) { - let encodingOfSource: string; + let encodingOfSource: string | undefined; if (resource.scheme === Schemas.untitled) { encodingOfSource = untitledEditorService.getEncoding(resource); } else if (fileService.canHandleResource(resource)) { @@ -134,24 +128,24 @@ function save( encodingOfSource = textModel && textModel.getEncoding(); // text model can be null e.g. if this is a binary file! } - let viewStateOfSource: IEditorViewState; + let viewStateOfSource: IEditorViewState | null; const activeTextEditorWidget = getCodeEditor(editorService.activeTextEditorWidget); if (activeTextEditorWidget) { - const activeResource = toResource(editorService.activeEditor, { supportSideBySide: true }); + const activeResource = toResource(editorService.activeEditor || null, { supportSideBySide: true }); if (activeResource && (fileService.canHandleResource(activeResource) || resource.scheme === Schemas.untitled) && activeResource.toString() === resource.toString()) { viewStateOfSource = activeTextEditorWidget.saveViewState(); } } // Special case: an untitled file with associated path gets saved directly unless "saveAs" is true - let savePromise: Promise; + let savePromise: Promise; if (!isSaveAs && resource.scheme === Schemas.untitled && untitledEditorService.hasAssociatedFilePath(resource)) { savePromise = textFileService.save(resource, options).then((result) => { if (result) { return resource.with({ scheme: Schemas.file }); } - return null; + return undefined; }); } @@ -167,7 +161,7 @@ function save( return savePromise.then((target) => { if (!target || target.toString() === resource.toString()) { - return undefined; // save canceled or same resource used + return false; // save canceled or same resource used } const replacement: IResourceInput = { @@ -175,7 +169,7 @@ function save( encoding: encodingOfSource, options: { pinned: true, - viewState: viewStateOfSource + viewState: viewStateOfSource || undefined } }; @@ -195,7 +189,7 @@ function save( // Pin the active editor if we are saving it const activeControl = editorService.activeControl; const activeEditorResource = activeControl && activeControl.input && activeControl.input.getResource(); - if (activeEditorResource && activeEditorResource.toString() === resource.toString()) { + if (activeControl && activeEditorResource && activeEditorResource.toString() === resource.toString()) { activeControl.group.pinEditor(activeControl.input); } @@ -223,7 +217,7 @@ function saveAll(saveAllArguments: any, editorService: IEditorService, untitledE groupIdToUntitledResourceInput.set(g.id, []); } - groupIdToUntitledResourceInput.get(g.id).push({ + groupIdToUntitledResourceInput.get(g.id)!.push({ encoding: untitledEditorService.getEncoding(resource), resource, options: { @@ -246,7 +240,7 @@ function saveAll(saveAllArguments: any, editorService: IEditorService, untitledE let replacementPairs: IResourceEditorReplacement[] = []; inputs.forEach(i => { const targetResult = result.results.filter(r => r.success && r.source.toString() === i.resource.toString()).pop(); - if (targetResult) { + if (targetResult && targetResult.target) { //i.resource = targetResult.target; let editor = i; const replacement: IResourceInput = { @@ -278,7 +272,7 @@ CommandsRegistry.registerCommand({ if (resources.length) { return textFileService.revertAll(resources, { force: true }).then(undefined, error => { - notificationService.error(nls.localize('genericRevertError', "Failed to revert '{0}': {1}", resources.map(r => paths.basename(r.fsPath)).join(', '), toErrorMessage(error, false))); + notificationService.error(nls.localize('genericRevertError', "Failed to revert '{0}': {1}", resources.map(r => basename(r)).join(', '), toErrorMessage(error, false))); }); } @@ -302,8 +296,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // Set side input if (resources.length) { return fileService.resolveFiles(resources.map(resource => ({ resource }))).then(resolved => { - const editors = resolved.filter(r => r.success && !r.stat.isDirectory).map(r => ({ - resource: r.stat.resource + const editors = resolved.filter(r => r.stat && r.success && !r.stat.isDirectory).map(r => ({ + resource: r.stat!.resource })); return editorService.openEditors(editors, SIDE_GROUP); @@ -333,7 +327,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const uri = getResourceForCommand(resource, accessor.get(IListService), editorService); if (uri && uri.scheme === Schemas.file /* only files on disk supported for now */) { - const name = paths.basename(uri.fsPath); + const name = basename(uri); const editorLabel = nls.localize('modifiedLabel', "{0} (on disk) ↔ {1}", name, name); return editorService.openEditor({ leftResource: uri.with({ scheme: COMPARE_WITH_SAVED_SCHEMA }), rightResource: uri, label: editorLabel }).then(() => undefined); @@ -343,7 +337,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -let globalResourceToCompare: URI; +let globalResourceToCompare: URI | null; let resourceSelectedForCompareContext: IContextKey; CommandsRegistry.registerCommand({ id: SELECT_FOR_COMPARE_COMMAND_ID, @@ -381,18 +375,21 @@ CommandsRegistry.registerCommand({ const editorService = accessor.get(IEditorService); const listService = accessor.get(IListService); - return editorService.openEditor({ - leftResource: globalResourceToCompare, - rightResource: getResourceForCommand(resource, listService, editorService) - }).then(() => undefined); + const rightResource = getResourceForCommand(resource, listService, editorService); + if (globalResourceToCompare && rightResource) { + editorService.openEditor({ + leftResource: globalResourceToCompare, + rightResource + }).then(undefined, onUnexpectedError); + } } }); function revealResourcesInOS(resources: URI[], windowsService: IWindowsService, notificationService: INotificationService, workspaceContextService: IWorkspaceContextService): void { if (resources.length) { - sequence(resources.map(r => () => windowsService.showItemInFolder(paths.normalize(r.fsPath, true)))); + sequence(resources.map(r => () => windowsService.showItemInFolder(r))); } else if (workspaceContextService.getWorkspace().folders.length) { - windowsService.showItemInFolder(paths.normalize(workspaceContextService.getWorkspace().folders[0].uri.fsPath, true)); + windowsService.showItemInFolder(workspaceContextService.getWorkspace().folders[0].uri); } else { notificationService.info(nls.localize('openFileToReveal', "Open a file first to reveal")); } @@ -420,7 +417,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); const activeInput = editorService.activeEditor; - const resources = activeInput && activeInput.getResource() ? [activeInput.getResource()] : []; + const resource = activeInput ? activeInput.getResource() : null; + const resources = resource ? [resource] : []; revealResourcesInOS(resources, accessor.get(IWindowsService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); } }); @@ -473,7 +471,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ handler: (accessor) => { const editorService = accessor.get(IEditorService); const activeInput = editorService.activeEditor; - const resources = activeInput && activeInput.getResource() ? [activeInput.getResource()] : []; + const resource = activeInput ? activeInput.getResource() : null; + const resources = resource ? [resource] : []; resourcesToClipboard(resources, false, accessor.get(IClipboardService), accessor.get(INotificationService), accessor.get(ILabelService)); } }); @@ -487,8 +486,7 @@ CommandsRegistry.registerCommand({ const uri = getResourceForCommand(resource, accessor.get(IListService), accessor.get(IEditorService)); viewletService.openViewlet(VIEWLET_ID, false).then((viewlet: ExplorerViewlet) => { - const isInsideWorkspace = contextService.isInsideWorkspace(uri); - if (isInsideWorkspace) { + if (uri && contextService.isInsideWorkspace(uri)) { const explorerView = viewlet.getExplorerView(); if (explorerView) { explorerView.setExpanded(true); @@ -512,7 +510,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S, handler: (accessor, resourceOrObject: URI | object | { from: string }) => { const editorService = accessor.get(IEditorService); - let resource: URI | undefined = undefined; + let resource: URI | null = null; if (resourceOrObject && 'from' in resourceOrObject && resourceOrObject.from === 'menu') { resource = toResource(editorService.activeEditor); } else { @@ -581,12 +579,14 @@ CommandsRegistry.registerCommand({ saveAllArg = []; contexts.forEach(context => { const editorGroup = editorGroupService.getGroup(context.groupId); - editorGroup.editors.forEach(editor => { - const resource = toResource(editor, { supportSideBySide: true }); - if (resource && (resource.scheme === Schemas.untitled || fileService.canHandleResource(resource))) { - saveAllArg.push(resource); - } - }); + if (editorGroup) { + editorGroup.editors.forEach(editor => { + const resource = toResource(editor, { supportSideBySide: true }); + if (resource && (resource.scheme === Schemas.untitled || fileService.canHandleResource(resource))) { + saveAllArg.push(resource); + } + }); + } }); } diff --git a/src/vs/workbench/parts/files/electron-browser/files.contribution.ts b/src/vs/workbench/contrib/files/browser/files.contribution.ts similarity index 94% rename from src/vs/workbench/parts/files/electron-browser/files.contribution.ts rename to src/vs/workbench/contrib/files/browser/files.contribution.ts index bd616f4dd7..2b26a14c80 100644 --- a/src/vs/workbench/parts/files/electron-browser/files.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/files.contribution.ts @@ -6,6 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from 'vs/workbench/browser/viewlet'; import * as nls from 'vs/nls'; +import { sep } from 'vs/base/common/path'; import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; @@ -13,30 +14,29 @@ import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/wor import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorInputFactory, EditorInput, IFileEditorInput, IEditorInputFactoryRegistry, Extensions as EditorInputExtensions } from 'vs/workbench/common/editor'; import { AutoSaveConfiguration, HotExitConfiguration, SUPPORTED_ENCODINGS } from 'vs/platform/files/common/files'; -import { VIEWLET_ID, SortOrderConfiguration, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/parts/files/common/files'; -import { FileEditorTracker } from 'vs/workbench/parts/files/browser/editors/fileEditorTracker'; -import { SaveErrorHandler } from 'vs/workbench/parts/files/electron-browser/saveErrorHandler'; -import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; -import { TextFileEditor } from 'vs/workbench/parts/files/browser/editors/textFileEditor'; -import { BinaryFileEditor } from 'vs/workbench/parts/files/browser/editors/binaryFileEditor'; +import { VIEWLET_ID, SortOrderConfiguration, FILE_EDITOR_INPUT_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files'; +import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; +import { SaveErrorHandler } from 'vs/workbench/contrib/files/browser/saveErrorHandler'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; +import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textFileEditor'; +import { BinaryFileEditor } from 'vs/workbench/contrib/files/browser/editors/binaryFileEditor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; -import { DirtyFilesTracker } from 'vs/workbench/parts/files/common/dirtyFilesTracker'; -import { ExplorerViewlet, ExplorerViewletViewsContribution } from 'vs/workbench/parts/files/electron-browser/explorerViewlet'; +import { DirtyFilesTracker } from 'vs/workbench/contrib/files/common/dirtyFilesTracker'; +import { ExplorerViewlet, ExplorerViewletViewsContribution } from 'vs/workbench/contrib/files/browser/explorerViewlet'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { DataUriEditorInput } from 'vs/workbench/common/editor/dataUriEditorInput'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ILabelService } from 'vs/platform/label/common/label'; -import { nativeSep } from 'vs/base/common/paths'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ExplorerService } from 'vs/workbench/parts/files/electron-browser/explorerService'; +import { ExplorerService } from 'vs/workbench/contrib/files/common/explorerService'; // Viewlet Action export class OpenExplorerViewletAction extends ShowViewletAction { @@ -48,9 +48,9 @@ export class OpenExplorerViewletAction extends ShowViewletAction { label: string, @IViewletService viewletService: IViewletService, @IEditorGroupsService editorGroupService: IEditorGroupsService, - @IPartService partService: IPartService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService ) { - super(id, label, VIEWLET_ID, viewletService, editorGroupService, partService); + super(id, label, VIEWLET_ID, viewletService, editorGroupService, layoutService); } } @@ -61,10 +61,10 @@ class FileUriLabelContribution implements IWorkbenchContribution { scheme: 'file', formatting: { label: '${authority}${path}', - separator: nativeSep, + separator: sep, tildify: !platform.isWindows, normalizeDriveLetter: platform.isWindows, - authorityPrefix: nativeSep + nativeSep, + authorityPrefix: sep + sep, workspaceSuffix: '' } }); diff --git a/src/vs/workbench/parts/files/browser/files.ts b/src/vs/workbench/contrib/files/browser/files.ts similarity index 92% rename from src/vs/workbench/parts/files/browser/files.ts rename to src/vs/workbench/contrib/files/browser/files.ts index 6d18e5f5e5..8c97d0514d 100644 --- a/src/vs/workbench/parts/files/browser/files.ts +++ b/src/vs/workbench/contrib/files/browser/files.ts @@ -5,11 +5,11 @@ import { URI } from 'vs/base/common/uri'; import { IListService, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService'; -import { OpenEditor } from 'vs/workbench/parts/files/common/files'; +import { OpenEditor } from 'vs/workbench/contrib/files/common/files'; import { toResource } from 'vs/workbench/common/editor'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { ExplorerItem } from 'vs/workbench/parts/files/common/explorerModel'; +import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { coalesce } from 'vs/base/common/arrays'; // Commands can get exeucted from a command pallete, from a context menu or from some list using a keybinding @@ -41,7 +41,7 @@ export function getResourceForCommand(resource: URI | object, listService: IList } } - return toResource(editorService.activeEditor, { supportSideBySide: true }); + return editorService.activeEditor ? toResource(editorService.activeEditor, { supportSideBySide: true }) : null; } export function getMultiSelectedResources(resource: URI | object, listService: IListService, editorService: IEditorService): Array { diff --git a/src/vs/workbench/parts/files/electron-browser/media/AddFile.svg b/src/vs/workbench/contrib/files/browser/media/AddFile.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/AddFile.svg rename to src/vs/workbench/contrib/files/browser/media/AddFile.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/AddFile_inverse.svg b/src/vs/workbench/contrib/files/browser/media/AddFile_inverse.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/AddFile_inverse.svg rename to src/vs/workbench/contrib/files/browser/media/AddFile_inverse.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/AddFolder.svg b/src/vs/workbench/contrib/files/browser/media/AddFolder.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/AddFolder.svg rename to src/vs/workbench/contrib/files/browser/media/AddFolder.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/AddFolder_inverse.svg b/src/vs/workbench/contrib/files/browser/media/AddFolder_inverse.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/AddFolder_inverse.svg rename to src/vs/workbench/contrib/files/browser/media/AddFolder_inverse.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/CollapseAll.svg b/src/vs/workbench/contrib/files/browser/media/CollapseAll.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/CollapseAll.svg rename to src/vs/workbench/contrib/files/browser/media/CollapseAll.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/CollapseAll_inverse.svg b/src/vs/workbench/contrib/files/browser/media/CollapseAll_inverse.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/CollapseAll_inverse.svg rename to src/vs/workbench/contrib/files/browser/media/CollapseAll_inverse.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/Preview.svg b/src/vs/workbench/contrib/files/browser/media/Preview.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/Preview.svg rename to src/vs/workbench/contrib/files/browser/media/Preview.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/Preview_inverse.svg b/src/vs/workbench/contrib/files/browser/media/Preview_inverse.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/Preview_inverse.svg rename to src/vs/workbench/contrib/files/browser/media/Preview_inverse.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/Refresh.svg b/src/vs/workbench/contrib/files/browser/media/Refresh.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/Refresh.svg rename to src/vs/workbench/contrib/files/browser/media/Refresh.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/Refresh_inverse.svg b/src/vs/workbench/contrib/files/browser/media/Refresh_inverse.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/Refresh_inverse.svg rename to src/vs/workbench/contrib/files/browser/media/Refresh_inverse.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/action-close-dark.svg b/src/vs/workbench/contrib/files/browser/media/action-close-dark.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/action-close-dark.svg rename to src/vs/workbench/contrib/files/browser/media/action-close-dark.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/action-close-dirty-dark.svg b/src/vs/workbench/contrib/files/browser/media/action-close-dirty-dark.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/action-close-dirty-dark.svg rename to src/vs/workbench/contrib/files/browser/media/action-close-dirty-dark.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/action-close-dirty-focus.svg b/src/vs/workbench/contrib/files/browser/media/action-close-dirty-focus.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/action-close-dirty-focus.svg rename to src/vs/workbench/contrib/files/browser/media/action-close-dirty-focus.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/action-close-dirty.svg b/src/vs/workbench/contrib/files/browser/media/action-close-dirty.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/action-close-dirty.svg rename to src/vs/workbench/contrib/files/browser/media/action-close-dirty.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/action-close-focus.svg b/src/vs/workbench/contrib/files/browser/media/action-close-focus.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/action-close-focus.svg rename to src/vs/workbench/contrib/files/browser/media/action-close-focus.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/action-close.svg b/src/vs/workbench/contrib/files/browser/media/action-close.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/action-close.svg rename to src/vs/workbench/contrib/files/browser/media/action-close.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/check-inverse.svg b/src/vs/workbench/contrib/files/browser/media/check-inverse.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/check-inverse.svg rename to src/vs/workbench/contrib/files/browser/media/check-inverse.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/check.svg b/src/vs/workbench/contrib/files/browser/media/check.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/check.svg rename to src/vs/workbench/contrib/files/browser/media/check.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/closeall.svg b/src/vs/workbench/contrib/files/browser/media/closeall.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/closeall.svg rename to src/vs/workbench/contrib/files/browser/media/closeall.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/closeall_inverse.svg b/src/vs/workbench/contrib/files/browser/media/closeall_inverse.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/closeall_inverse.svg rename to src/vs/workbench/contrib/files/browser/media/closeall_inverse.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css similarity index 92% rename from src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css rename to src/vs/workbench/contrib/files/browser/media/explorerviewlet.css index 3ec005ea58..1c499f236c 100644 --- a/src/vs/workbench/parts/files/electron-browser/media/explorerviewlet.css +++ b/src/vs/workbench/contrib/files/browser/media/explorerviewlet.css @@ -18,8 +18,8 @@ padding-left: 4px; /* align top level twistie with `Explorer` title label */ } -.explorer-viewlet .monaco-list.highlight .explorer-item:not(.explorer-item-edited), -.explorer-viewlet .monaco-list.highlight .monaco-tl-twistie { +.explorer-viewlet .explorer-folders-view.highlight .monaco-list .explorer-item:not(.explorer-item-edited), +.explorer-viewlet .explorer-folders-view.highlight .monaco-list .monaco-tl-twistie { opacity: 0.3; } @@ -120,8 +120,8 @@ line-height: normal; } -.linux > .monaco-workbench .explorer-viewlet .explorer-item .monaco-inputbox, -.mac > .monaco-workbench .explorer-viewlet .explorer-item .monaco-inputbox { +.monaco-workbench.linux .explorer-viewlet .explorer-item .monaco-inputbox, +.monaco-workbench.mac .explorer-viewlet .explorer-item .monaco-inputbox { height: 22px; } diff --git a/src/vs/workbench/parts/files/electron-browser/media/fileactions.css b/src/vs/workbench/contrib/files/browser/media/fileactions.css similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/fileactions.css rename to src/vs/workbench/contrib/files/browser/media/fileactions.css diff --git a/src/vs/workbench/parts/files/electron-browser/media/files-dark.svg b/src/vs/workbench/contrib/files/browser/media/files-dark.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/files-dark.svg rename to src/vs/workbench/contrib/files/browser/media/files-dark.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/saveall.svg b/src/vs/workbench/contrib/files/browser/media/saveall.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/saveall.svg rename to src/vs/workbench/contrib/files/browser/media/saveall.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/saveall_inverse.svg b/src/vs/workbench/contrib/files/browser/media/saveall_inverse.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/saveall_inverse.svg rename to src/vs/workbench/contrib/files/browser/media/saveall_inverse.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/split-editor-horizontal-inverse.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-inverse.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/split-editor-horizontal-inverse.svg rename to src/vs/workbench/contrib/files/browser/media/split-editor-horizontal-inverse.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/split-editor-horizontal.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-horizontal.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/split-editor-horizontal.svg rename to src/vs/workbench/contrib/files/browser/media/split-editor-horizontal.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/split-editor-vertical-inverse.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-vertical-inverse.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/split-editor-vertical-inverse.svg rename to src/vs/workbench/contrib/files/browser/media/split-editor-vertical-inverse.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/split-editor-vertical.svg b/src/vs/workbench/contrib/files/browser/media/split-editor-vertical.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/split-editor-vertical.svg rename to src/vs/workbench/contrib/files/browser/media/split-editor-vertical.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/undo-inverse.svg b/src/vs/workbench/contrib/files/browser/media/undo-inverse.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/undo-inverse.svg rename to src/vs/workbench/contrib/files/browser/media/undo-inverse.svg diff --git a/src/vs/workbench/parts/files/electron-browser/media/undo.svg b/src/vs/workbench/contrib/files/browser/media/undo.svg similarity index 100% rename from src/vs/workbench/parts/files/electron-browser/media/undo.svg rename to src/vs/workbench/contrib/files/browser/media/undo.svg diff --git a/src/vs/workbench/parts/files/electron-browser/saveErrorHandler.ts b/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts similarity index 83% rename from src/vs/workbench/parts/files/electron-browser/saveErrorHandler.ts rename to src/vs/workbench/contrib/files/browser/saveErrorHandler.ts index 5b780c818b..26a1f62d5e 100644 --- a/src/vs/workbench/parts/files/electron-browser/saveErrorHandler.ts +++ b/src/vs/workbench/contrib/files/browser/saveErrorHandler.ts @@ -5,11 +5,11 @@ import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import * as paths from 'vs/base/common/paths'; +import { basename } from 'vs/base/common/resources'; import { Action } from 'vs/base/common/actions'; import { URI } from 'vs/base/common/uri'; import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { ITextFileService, ISaveErrorHandler, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, ISaveErrorHandler, ITextFileEditorModel, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; @@ -19,10 +19,10 @@ import { ResourceMap } from 'vs/base/common/map'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { FileOnDiskContentProvider } from 'vs/workbench/parts/files/common/files'; -import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; +import { FileOnDiskContentProvider } from 'vs/workbench/contrib/files/common/files'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { SAVE_FILE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL } from 'vs/workbench/parts/files/electron-browser/fileCommands'; +import { SAVE_FILE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL } from 'vs/workbench/contrib/files/browser/fileCommands'; import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; import { INotificationService, INotificationHandle, INotificationActions, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; @@ -44,7 +44,7 @@ const conflictEditorHelp = nls.localize('userGuide', "Use the actions in the edi export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, IWorkbenchContribution { private messages: ResourceMap; private conflictResolutionContext: IContextKey; - private activeConflictResolutionResource: URI; + private activeConflictResolutionResource?: URI; constructor( @INotificationService private readonly notificationService: INotificationService, @@ -77,7 +77,7 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I private onActiveEditorChanged(): void { let isActiveEditorSaveConflictResolution = false; - let activeConflictResolutionResource: URI; + let activeConflictResolutionResource: URI | undefined; const activeInput = this.editorService.activeEditor; if (activeInput instanceof DiffEditorInput && activeInput.originalInput instanceof ResourceEditorInput && activeInput.modifiedInput instanceof FileEditorInput) { @@ -118,15 +118,15 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I message = conflictEditorHelp; - actions.primary.push(this.instantiationService.createInstance(ResolveConflictLearnMoreAction)); - actions.secondary.push(this.instantiationService.createInstance(DoNotShowResolveConflictLearnMoreAction)); + actions.primary!.push(this.instantiationService.createInstance(ResolveConflictLearnMoreAction)); + actions.secondary!.push(this.instantiationService.createInstance(DoNotShowResolveConflictLearnMoreAction)); } // Otherwise show the message that will lead the user into the save conflict editor. else { - message = nls.localize('staleSaveError', "Failed to save '{0}': The content on disk is newer. Please compare your version with the one on disk.", paths.basename(resource.fsPath)); + message = nls.localize('staleSaveError', "Failed to save '{0}': The content on disk is newer. Please compare your version with the one on disk.", basename(resource)); - actions.primary.push(this.instantiationService.createInstance(ResolveSaveConflictAction, model)); + actions.primary!.push(this.instantiationService.createInstance(ResolveSaveConflictAction, model)); } } @@ -138,41 +138,41 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I // Save Elevated if (isPermissionDenied || triedToMakeWriteable) { - actions.primary.push(this.instantiationService.createInstance(SaveElevatedAction, model, triedToMakeWriteable)); + actions.primary!.push(this.instantiationService.createInstance(SaveElevatedAction, model, triedToMakeWriteable)); } // Overwrite else if (isReadonly) { - actions.primary.push(this.instantiationService.createInstance(OverwriteReadonlyAction, model)); + actions.primary!.push(this.instantiationService.createInstance(OverwriteReadonlyAction, model)); } // Retry else { - actions.primary.push(this.instantiationService.createInstance(ExecuteCommandAction, SAVE_FILE_COMMAND_ID, nls.localize('retry', "Retry"))); + actions.primary!.push(this.instantiationService.createInstance(ExecuteCommandAction, SAVE_FILE_COMMAND_ID, nls.localize('retry', "Retry"))); } // Save As - actions.primary.push(this.instantiationService.createInstance(ExecuteCommandAction, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL)); + actions.primary!.push(this.instantiationService.createInstance(ExecuteCommandAction, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL)); // Discard - actions.primary.push(this.instantiationService.createInstance(ExecuteCommandAction, REVERT_FILE_COMMAND_ID, nls.localize('discard', "Discard"))); + actions.primary!.push(this.instantiationService.createInstance(ExecuteCommandAction, REVERT_FILE_COMMAND_ID, nls.localize('discard', "Discard"))); if (isReadonly) { if (triedToMakeWriteable) { - message = isWindows ? nls.localize('readonlySaveErrorAdmin', "Failed to save '{0}': File is write protected. Select 'Overwrite as Admin' to retry as administrator.", paths.basename(resource.fsPath)) : nls.localize('readonlySaveErrorSudo', "Failed to save '{0}': File is write protected. Select 'Overwrite as Sudo' to retry as superuser.", paths.basename(resource.fsPath)); + message = isWindows ? nls.localize('readonlySaveErrorAdmin', "Failed to save '{0}': File is write protected. Select 'Overwrite as Admin' to retry as administrator.", basename(resource)) : nls.localize('readonlySaveErrorSudo', "Failed to save '{0}': File is write protected. Select 'Overwrite as Sudo' to retry as superuser.", basename(resource)); } else { - message = nls.localize('readonlySaveError', "Failed to save '{0}': File is write protected. Select 'Overwrite' to attempt to remove protection.", paths.basename(resource.fsPath)); + message = nls.localize('readonlySaveError', "Failed to save '{0}': File is write protected. Select 'Overwrite' to attempt to remove protection.", basename(resource)); } } else if (isPermissionDenied) { - message = isWindows ? nls.localize('permissionDeniedSaveError', "Failed to save '{0}': Insufficient permissions. Select 'Retry as Admin' to retry as administrator.", paths.basename(resource.fsPath)) : nls.localize('permissionDeniedSaveErrorSudo', "Failed to save '{0}': Insufficient permissions. Select 'Retry as Sudo' to retry as superuser.", paths.basename(resource.fsPath)); + message = isWindows ? nls.localize('permissionDeniedSaveError', "Failed to save '{0}': Insufficient permissions. Select 'Retry as Admin' to retry as administrator.", basename(resource)) : nls.localize('permissionDeniedSaveErrorSudo', "Failed to save '{0}': Insufficient permissions. Select 'Retry as Sudo' to retry as superuser.", basename(resource)); } else { - message = nls.localize('genericSaveError', "Failed to save '{0}': {1}", paths.basename(resource.fsPath), toErrorMessage(error, false)); + message = nls.localize('genericSaveError', "Failed to save '{0}': {1}", basename(resource), toErrorMessage(error, false)); } } // Show message and keep function to hide in case the file gets saved/reverted const handle = this.notificationService.notify({ severity: Severity.Error, message, actions }); - Event.once(handle.onDidClose)(() => dispose(...actions.primary, ...actions.secondary)); + Event.once(handle.onDidClose)(() => dispose(...actions.primary!, ...actions.secondary!)); this.messages.set(model.getResource(), handle); } @@ -186,7 +186,10 @@ export class SaveErrorHandler extends Disposable implements ISaveErrorHandler, I const pendingResolveSaveConflictMessages: INotificationHandle[] = []; function clearPendingResolveSaveConflictMessages(): void { while (pendingResolveSaveConflictMessages.length > 0) { - pendingResolveSaveConflictMessages.pop().close(); + const item = pendingResolveSaveConflictMessages.pop(); + if (item) { + item.close(); + } } } @@ -237,7 +240,7 @@ class ResolveSaveConflictAction extends Action { run(): Promise { if (!this.model.isDisposed()) { const resource = this.model.getResource(); - const name = paths.basename(resource.fsPath); + const name = basename(resource); const editorLabel = nls.localize('saveConflictDiffLabel', "{0} (on disk) ↔ {1} (in {2}) - Resolve save conflict", name, name, this.environmentService.appNameLong); return this.editorService.openEditor( @@ -254,11 +257,11 @@ class ResolveSaveConflictAction extends Action { // Show additional help how to resolve the save conflict const actions: INotificationActions = { primary: [], secondary: [] }; - actions.primary.push(this.instantiationService.createInstance(ResolveConflictLearnMoreAction)); - actions.secondary.push(this.instantiationService.createInstance(DoNotShowResolveConflictLearnMoreAction)); + actions.primary!.push(this.instantiationService.createInstance(ResolveConflictLearnMoreAction)); + actions.secondary!.push(this.instantiationService.createInstance(DoNotShowResolveConflictLearnMoreAction)); const handle = this.notificationService.notify({ severity: Severity.Info, message: conflictEditorHelp, actions }); - Event.once(handle.onDidClose)(() => dispose(...actions.primary, ...actions.secondary)); + Event.once(handle.onDidClose)(() => dispose(...actions.primary!, ...actions.secondary!)); pendingResolveSaveConflictMessages.push(handle); }); } @@ -311,11 +314,14 @@ export const acceptLocalChangesCommand = (accessor: ServicesAccessor, resource: const modelService = accessor.get(IModelService); const control = editorService.activeControl; + if (!control) { + return; + } const editor = control.input; const group = control.group; resolverService.createModelReference(resource).then(reference => { - const model = reference.object as ITextFileEditorModel; + const model = reference.object as IResolvedTextFileEditorModel; const localModelSnapshot = model.createSnapshot(); clearPendingResolveSaveConflictMessages(); // hide any previously shown message about how to use these actions @@ -347,6 +353,9 @@ export const revertLocalChangesCommand = (accessor: ServicesAccessor, resource: const resolverService = accessor.get(ITextModelService); const control = editorService.activeControl; + if (!control) { + return; + } const editor = control.input; const group = control.group; diff --git a/src/vs/workbench/parts/files/electron-browser/views/emptyView.ts b/src/vs/workbench/contrib/files/browser/views/emptyView.ts similarity index 88% rename from src/vs/workbench/parts/files/electron-browser/views/emptyView.ts rename to src/vs/workbench/contrib/files/browser/views/emptyView.ts index 66d9fca046..9edc63a22f 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/emptyView.ts +++ b/src/vs/workbench/contrib/files/browser/views/emptyView.ts @@ -70,6 +70,9 @@ export class EmptyView extends ViewletPanel { attachButtonStyler(this.button, this.themeService); this.disposables.push(this.button.onDidClick(() => { + if (!this.actionRunner) { + return; + } const actionClass = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? AddRootFolderAction : env.isMacintosh ? OpenFileFolderAction : OpenFolderAction; const action = this.instantiationService.createInstance(actionClass, actionClass.ID, actionClass.LABEL); this.actionRunner.run(action).then(() => { @@ -82,21 +85,25 @@ export class EmptyView extends ViewletPanel { this.disposables.push(new DragAndDropObserver(container, { onDrop: e => { - container.style.backgroundColor = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND).toString(); + const color = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND); + container.style.backgroundColor = color ? color.toString() : ''; const dropHandler = this.instantiationService.createInstance(ResourcesDropHandler, { allowWorkspaceOpen: true }); dropHandler.handleDrop(e, () => undefined, targetGroup => undefined); }, onDragEnter: (e) => { - container.style.backgroundColor = this.themeService.getTheme().getColor(listDropBackground).toString(); + const color = this.themeService.getTheme().getColor(listDropBackground); + container.style.backgroundColor = color ? color.toString() : ''; }, onDragEnd: () => { - container.style.backgroundColor = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND).toString(); + const color = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND); + container.style.backgroundColor = color ? color.toString() : ''; }, onDragLeave: () => { - container.style.backgroundColor = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND).toString(); + const color = this.themeService.getTheme().getColor(SIDE_BAR_BACKGROUND); + container.style.backgroundColor = color ? color.toString() : ''; }, onDragOver: e => { - e.dataTransfer.dropEffect = 'copy'; + e.dataTransfer!.dropEffect = 'copy'; } })); diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerDecorationsProvider.ts b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts similarity index 92% rename from src/vs/workbench/parts/files/electron-browser/views/explorerDecorationsProvider.ts rename to src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts index c97db7aa21..fdb5c3c3c5 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerDecorationsProvider.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerDecorationsProvider.ts @@ -9,9 +9,8 @@ import { localize } from 'vs/nls'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IDecorationsProvider, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations'; import { listInvalidItemForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IDisposable } from 'vscode-xterm'; -import { dispose } from 'vs/base/common/lifecycle'; -import { IExplorerService } from 'vs/workbench/parts/files/common/files'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { IExplorerService } from 'vs/workbench/contrib/files/common/files'; export class ExplorerDecorationsProvider implements IDecorationsProvider { readonly label: string = localize('label', "Explorer"); diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts b/src/vs/workbench/contrib/files/browser/views/explorerView.ts similarity index 80% rename from src/vs/workbench/parts/files/electron-browser/views/explorerView.ts rename to src/vs/workbench/contrib/files/browser/views/explorerView.ts index caea38d38a..4ade85099a 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerView.ts @@ -9,14 +9,14 @@ import * as perf from 'vs/base/common/performance'; import { sequence } from 'vs/base/common/async'; import { Action, IAction } from 'vs/base/common/actions'; import { memoize } from 'vs/base/common/decorators'; -import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut } from 'vs/workbench/parts/files/common/files'; -import { NewFolderAction, NewFileAction, FileCopiedContext, RefreshExplorerView } from 'vs/workbench/parts/files/electron-browser/fileActions'; +import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut } from 'vs/workbench/contrib/files/common/files'; +import { NewFolderAction, NewFileAction, FileCopiedContext, RefreshExplorerView } from 'vs/workbench/contrib/files/browser/fileActions'; import { toResource } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import * as DOM from 'vs/base/browser/dom'; -import { CollapseAction2 } from 'vs/workbench/browser/viewlet'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; -import { ExplorerDecorationsProvider } from 'vs/workbench/parts/files/electron-browser/views/explorerDecorationsProvider'; +import { CollapseAction } from 'vs/workbench/browser/viewlet'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ExplorerDecorationsProvider } from 'vs/workbench/contrib/files/browser/views/explorerDecorationsProvider'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -26,19 +26,19 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ResourceContextKey } from 'vs/workbench/common/resources'; import { IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations'; -import { WorkbenchAsyncDataTree, IListService } from 'vs/platform/list/browser/listService'; +import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; import { DelayedDragHandler } from 'vs/base/browser/dnd'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { ILabelService } from 'vs/platform/label/common/label'; -import { ExplorerDelegate, ExplorerAccessibilityProvider, ExplorerDataSource, FilesRenderer, FilesFilter, FileSorter, FileDragAndDrop } from 'vs/workbench/parts/files/electron-browser/views/explorerViewer'; +import { ExplorerDelegate, ExplorerAccessibilityProvider, ExplorerDataSource, FilesRenderer, FilesFilter, FileSorter, FileDragAndDrop } from 'vs/workbench/contrib/files/browser/views/explorerViewer'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ExplorerItem } from 'vs/workbench/parts/files/common/explorerModel'; +import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ResourceLabels, IResourceLabelsContainer } from 'vs/workbench/browser/labels'; import { createFileIconThemableTreeContainerScope } from 'vs/workbench/browser/parts/views/views'; @@ -46,6 +46,10 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IAsyncDataTreeViewState } from 'vs/base/browser/ui/tree/asyncDataTree'; import { FuzzyScore } from 'vs/base/common/filters'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { isMacintosh } from 'vs/base/common/platform'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { withNullAsUndefined } from 'vs/base/common/types'; export class ExplorerView extends ViewletPanel { static readonly ID: string = 'workbench.explorer.fileView'; @@ -64,8 +68,6 @@ export class ExplorerView extends ViewletPanel { private dragHandler: DelayedDragHandler; private decorationProvider: ExplorerDecorationsProvider; private autoReveal = false; - // Ignore first active editor change, since on startup we already reveal the active editor - private ignoreActiveEditorChange = true; constructor( options: IViewletPanelOptions, @@ -74,14 +76,13 @@ export class ExplorerView extends ViewletPanel { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IProgressService private readonly progressService: IProgressService, @IEditorService private readonly editorService: IEditorService, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IKeybindingService keybindingService: IKeybindingService, @IContextKeyService private readonly contextKeyService: IContextKeyService, @IConfigurationService configurationService: IConfigurationService, @IDecorationsService decorationService: IDecorationsService, @ILabelService private readonly labelService: ILabelService, @IThemeService private readonly themeService: IWorkbenchThemeService, - @IListService private readonly listService: IListService, @IMenuService private readonly menuService: IMenuService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IExplorerService private readonly explorerService: IExplorerService, @@ -173,15 +174,15 @@ export class ExplorerView extends ViewletPanel { const isEditing = !!this.explorerService.getEditableData(e); if (isEditing) { - await this.tree.expand(e.parent); + await this.tree.expand(e.parent!); } else { - DOM.removeClass(this.tree.getHTMLElement(), 'highlight'); + DOM.removeClass(treeContainer, 'highlight'); } await this.refresh(e.parent); if (isEditing) { - DOM.addClass(this.tree.getHTMLElement(), 'highlight'); + DOM.addClass(treeContainer, 'highlight'); this.tree.reveal(e); } else { this.tree.domFocus(); @@ -196,10 +197,7 @@ export class ExplorerView extends ViewletPanel { // When the explorer viewer is loaded, listen to changes to the editor input this.disposables.push(this.editorService.onDidActiveEditorChange(() => { - if (!this.ignoreActiveEditorChange) { - this.selectActiveFile(); - } - this.ignoreActiveEditorChange = false; + this.selectActiveFile(true); })); // Also handle configuration updates @@ -213,7 +211,7 @@ export class ExplorerView extends ViewletPanel { await this.setTreeInput(); } // Find resource to focus from active editor input if set - this.selectActiveFile(true); + this.selectActiveFile(false, true); } })); } @@ -228,7 +226,7 @@ export class ExplorerView extends ViewletPanel { actions.push(this.instantiationService.createInstance(NewFileAction, getFocus)); actions.push(this.instantiationService.createInstance(NewFolderAction, getFocus)); actions.push(this.instantiationService.createInstance(RefreshExplorerView, RefreshExplorerView.ID, RefreshExplorerView.LABEL)); - actions.push(this.instantiationService.createInstance(CollapseAction2, this.tree, true, 'explorer-action collapse-explorer')); + actions.push(this.instantiationService.createInstance(CollapseAction, this.tree, true, 'explorer-action collapse-explorer')); return actions; } @@ -251,13 +249,19 @@ export class ExplorerView extends ViewletPanel { } } - private selectActiveFile(reveal?: boolean): void { + private selectActiveFile(deselect?: boolean, reveal = this.autoReveal): void { if (this.autoReveal) { const activeFile = this.getActiveFile(); if (activeFile) { - this.explorerService.select(this.getActiveFile(), reveal); - } else { + const focus = this.tree.getFocus(); + if (focus.length === 1 && focus[0].resource.toString() === activeFile.toString()) { + // No action needed, active file is already focused + return; + } + this.explorerService.select(activeFile, reveal); + } else if (deselect) { this.tree.setSelection([]); + this.tree.setFocus([]); } } } @@ -274,20 +278,21 @@ export class ExplorerView extends ViewletPanel { this.disposables.push(createFileIconThemableTreeContainerScope(container, this.themeService)); - this.tree = new WorkbenchAsyncDataTree(container, new ExplorerDelegate(), [filesRenderer], + this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, container, new ExplorerDelegate(), [filesRenderer], this.instantiationService.createInstance(ExplorerDataSource), { accessibilityProvider: new ExplorerAccessibilityProvider(), ariaLabel: nls.localize('treeAriaLabel', "Files Explorer"), identityProvider: { - getId: stat => stat.resource + getId: stat => (stat).resource }, keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: stat => { - if (this.explorerService.isEditable(stat)) { + const item = stat; + if (this.explorerService.isEditable(item)) { return undefined; } - return stat.name; + return item.name; } }, multipleSelectionSupport: true, @@ -295,7 +300,7 @@ export class ExplorerView extends ViewletPanel { sorter: this.instantiationService.createInstance(FileSorter), dnd: this.instantiationService.createInstance(FileDragAndDrop), autoExpandSingleChildren: true - }, this.contextKeyService, this.listService, this.themeService, this.configurationService, this.keybindingService); + }) as WorkbenchAsyncDataTree; this.disposables.push(this.tree); // Bind context keys @@ -303,71 +308,46 @@ export class ExplorerView extends ViewletPanel { ExplorerFocusedContext.bindTo(this.tree.contextKeyService); // Update resource context based on focused element - this.disposables.push(this.tree.onDidChangeFocus(e => { - const stat = e.elements && e.elements.length ? e.elements[0] : undefined; - const isSingleFolder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER; - const resource = stat ? stat.resource : isSingleFolder ? this.contextService.getWorkspace().folders[0].uri : undefined; - this.resourceContext.set(resource); - this.folderContext.set((isSingleFolder && !stat) || stat && stat.isDirectory); - this.readonlyContext.set(stat && stat.isReadonly); - this.rootContext.set(!stat || (stat && stat.isRoot)); - })); - - // TODO@Isidor: use TreeResourceNavigator2 just like search and listen to the `onDidOpenResource` instead - + this.disposables.push(this.tree.onDidChangeFocus(e => this.onFocusChanged(e.elements))); + this.onFocusChanged([]); + const explorerNavigator = new TreeResourceNavigator2(this.tree); + this.disposables.push(explorerNavigator); // Open when selecting via keyboard - this.disposables.push(this.tree.onDidChangeSelection(e => { - if (!e.browserEvent) { - // Only react on selection change events caused by user interaction (ignore those which are caused by us doing tree.setSelection). - return; - } - const selection = e.elements; + this.disposables.push(explorerNavigator.onDidOpenResource(e => { + const selection = this.tree.getSelection(); // Do not react if the user is expanding selection via keyboard. // Check if the item was previously also selected, if yes the user is simply expanding / collapsing current selection #66589. - const shiftDown = e.browserEvent instanceof KeyboardEvent && e.browserEvent.shiftKey; if (selection.length === 1 && !shiftDown) { - // Do not react if user is clicking on explorer items which are input placeholders - if (!selection[0].name) { - // Do not react if user is clicking on explorer items which are input placeholders + if (selection[0].isDirectory || this.explorerService.isEditable(undefined)) { + // Do not react if user is clicking on explorer items while some are being edited #70276 + // Do not react if clicking on directories return; } - if (selection[0].isDirectory) { - if (e.browserEvent instanceof KeyboardEvent) { - this.tree.toggleCollapsed(selection[0]); - } - return; - } - let isDoubleClick = false; - let sideBySide = false; - let isMiddleClick = false; - if (e.browserEvent instanceof MouseEvent) { - isDoubleClick = e.browserEvent.detail === 2; - isMiddleClick = e.browserEvent.button === 1; - const isLeftButton = e.browserEvent.button === 0; - - if (isLeftButton && !this.tree.openOnSingleClick && !isDoubleClick) { - return; - } - - sideBySide = this.tree.useAltAsMultipleSelectionModifier ? (e.browserEvent.ctrlKey || e.browserEvent.metaKey) : e.browserEvent.altKey; - } - - // Pass focus for keyboard events and for double click /* __GDPR__ "workbenchActionExecuted" : { "id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, "from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } }*/ this.telemetryService.publicLog('workbenchActionExecuted', { id: 'workbench.files.openFile', from: 'explorer' }); - this.ignoreActiveEditorChange = true; - this.editorService.openEditor({ resource: selection[0].resource, options: { preserveFocus: (e.browserEvent instanceof MouseEvent) && !isDoubleClick, pinned: isDoubleClick || isMiddleClick } }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP) + this.editorService.openEditor({ resource: selection[0].resource, options: { preserveFocus: e.editorOptions.preserveFocus, pinned: e.editorOptions.pinned } }, e.sideBySide ? SIDE_GROUP : ACTIVE_GROUP) .then(undefined, onUnexpectedError); } })); this.disposables.push(this.tree.onContextMenu(e => this.onContextMenu(e))); + this.disposables.push(this.tree.onKeyDown(e => { + const event = new StandardKeyboardEvent(e); + const toggleCollapsed = isMacintosh ? (event.keyCode === KeyCode.DownArrow && event.metaKey) : event.keyCode === KeyCode.Enter; + if (toggleCollapsed && !this.explorerService.isEditable(undefined)) { + const focus = this.tree.getFocus(); + if (focus.length === 1 && focus[0].isDirectory) { + this.tree.toggleCollapsed(focus[0]); + } + } + })); + // save view state on shutdown this.storageService.onWillSaveState(() => { @@ -419,12 +399,22 @@ export class ExplorerView extends ViewletPanel { this.tree.domFocus(); } }, - getActionsContext: () => selection && selection.indexOf(stat) >= 0 + getActionsContext: () => stat && selection && selection.indexOf(stat) >= 0 ? selection.map((fs: ExplorerItem) => fs.resource) : stat instanceof ExplorerItem ? [stat.resource] : [] }); } + private onFocusChanged(elements: ExplorerItem[]): void { + const stat = elements && elements.length ? elements[0] : undefined; + const isSingleFolder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER; + const resource = stat ? stat.resource : isSingleFolder ? this.contextService.getWorkspace().folders[0].uri : null; + this.resourceContext.set(resource); + this.folderContext.set((isSingleFolder && !stat) || !!stat && stat.isDirectory); + this.readonlyContext.set(!!stat && stat.isReadonly); + this.rootContext.set(!stat || (stat && stat.isRoot)); + } + // General methods /** @@ -474,11 +464,14 @@ export class ExplorerView extends ViewletPanel { input = roots; } - const rawViewState = this.storageService.get(ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, StorageScope.WORKSPACE); let viewState: IAsyncDataTreeViewState | undefined; - - if (rawViewState) { - viewState = JSON.parse(rawViewState) as IAsyncDataTreeViewState; + if (this.tree && this.tree.getInput()) { + viewState = this.tree.getViewState(); + } else { + const rawViewState = this.storageService.get(ExplorerView.TREE_VIEW_STATE_STORAGE_KEY, StorageScope.WORKSPACE); + if (rawViewState) { + viewState = JSON.parse(rawViewState) as IAsyncDataTreeViewState; + } } const previousInput = this.tree.getInput(); @@ -498,11 +491,11 @@ export class ExplorerView extends ViewletPanel { } }); - this.progressService.showWhile(promise, this.partService.isRestored() ? 800 : 1200 /* less ugly initial startup */); + this.progressService.showWhile(promise, this.layoutService.isRestored() ? 800 : 1200 /* less ugly initial startup */); return promise; } - private getActiveFile(): URI { + private getActiveFile(): URI | undefined { const input = this.editorService.activeEditor; // ignore diff editor inputs (helps to get out of diffing when returning to explorer) @@ -511,10 +504,10 @@ export class ExplorerView extends ViewletPanel { } // check for files - return toResource(input, { supportSideBySide: true }); + return withNullAsUndefined(toResource(input, { supportSideBySide: true })); } - private onSelectItem(fileStat: ExplorerItem, reveal = this.autoReveal): Promise { + private onSelectItem(fileStat: ExplorerItem | undefined, reveal = this.autoReveal): Promise { if (!fileStat || !this.isBodyVisible() || this.tree.getInput() === fileStat) { return Promise.resolve(undefined); } @@ -533,17 +526,18 @@ export class ExplorerView extends ViewletPanel { } this.tree.setFocus([fileStat]); + this.tree.setSelection([fileStat]); }); } - private onCopyItems(stats: ExplorerItem[], cut: boolean, previousCut: ExplorerItem[]): void { + private onCopyItems(stats: ExplorerItem[], cut: boolean, previousCut: ExplorerItem[] | undefined): void { this.fileCopiedContextKey.set(stats.length > 0); this.resourceCutContextKey.set(cut && stats.length > 0); if (previousCut) { - previousCut.forEach(item => this.tree.refresh(item)); + previousCut.forEach(item => this.tree.rerender(item)); } if (cut) { - stats.forEach(s => this.tree.refresh(s)); + stats.forEach(s => this.tree.rerender(s)); } } diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts similarity index 90% rename from src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts rename to src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index a9f51455d0..08c58b59d5 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -10,7 +10,7 @@ import { IListVirtualDelegate, ListDragOverEffect } from 'vs/base/browser/ui/lis import { IProgressService } from 'vs/platform/progress/common/progress'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IFileService, FileKind, IFileStat, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IDisposable, Disposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -19,17 +19,16 @@ import { ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { IFilesConfiguration, IExplorerService, IEditableData } from 'vs/workbench/parts/files/common/files'; +import { IFilesConfiguration, IExplorerService, IEditableData } from 'vs/workbench/contrib/files/common/files'; import { dirname, joinPath, isEqualOrParent, basename, hasToIgnoreCase, distinctParents } from 'vs/base/common/resources'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { localize } from 'vs/nls'; import { attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { once } from 'vs/base/common/functional'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { normalize } from 'vs/base/common/paths'; import { equals, deepClone } from 'vs/base/common/objects'; -import * as path from 'path'; -import { ExplorerItem } from 'vs/workbench/parts/files/common/explorerModel'; +import * as path from 'vs/base/common/path'; +import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; import { compareFileExtensions, compareFileNames } from 'vs/base/common/comparers'; import { fillResourceDataTransfers, CodeDataTransfers, extractResources } from 'vs/workbench/browser/dnd'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -45,7 +44,7 @@ import { URI } from 'vs/base/common/uri'; import { ITask, sequence } from 'vs/base/common/async'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; -import { findValidPasteFileTarget } from 'vs/workbench/parts/files/electron-browser/fileActions'; +import { findValidPasteFileTarget } from 'vs/workbench/contrib/files/browser/fileActions'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -66,7 +65,7 @@ export class ExplorerDataSource implements IAsyncDataSource 0 && !stat.isDirectory ? lastDot : value.length }); - const done = once(async (success: boolean, blur: boolean) => { + const done = once(async (success: boolean) => { label.element.style.display = 'none'; const value = inputBox.value; dispose(toDispose); @@ -215,35 +214,36 @@ export class FilesRenderer implements ITreeRenderer ignoreDisposeAndBlur = false, 100); + const blurDisposable = DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => { + if (!ignoreDisposeAndBlur) { + done(inputBox.isInputValid()); + } + }); const toDispose = [ inputBox, DOM.addStandardDisposableListener(inputBox.inputElement, DOM.EventType.KEY_DOWN, (e: IKeyboardEvent) => { if (e.equals(KeyCode.Enter)) { if (inputBox.validate()) { - done(true, false); + done(true); } } else if (e.equals(KeyCode.Escape)) { - done(false, false); + done(false); } }), - DOM.addDisposableListener(inputBox.inputElement, DOM.EventType.BLUR, () => { - setTimeout(() => { - if (!ignoreBlur) { - done(inputBox.isInputValid(), true); - } - }, 0); - }), + blurDisposable, label, styler ]; - return toDisposable(() => ignoreBlur = true); + return toDisposable(() => { + if (!ignoreDisposeAndBlur) { + blurDisposable.dispose(); + done(inputBox.isInputValid()); + } + }); } disposeElement?(element: ITreeNode, index: number, templateData: IFileTemplateData): void { @@ -313,7 +313,8 @@ export class FilesFilter implements ITreeFilter { // Hide those that match Hidden Patterns const cached = this.hiddenExpressionPerRoot.get(stat.root.resource.toString()); - if (cached && cached.parsed(normalize(path.relative(stat.root.resource.path, stat.resource.path), true), stat.name, name => !!stat.parent.getChild(name))) { + if (cached && cached.parsed(path.normalize(path.relative(stat.root.resource.path, stat.resource.path)), stat.name, name => !!(stat.parent && stat.parent.getChild(name)))) { + // review (isidor): is path.normalize necessary? path.relative already returns an os path return false; // hidden through pattern } @@ -337,7 +338,9 @@ export class FileSorter implements ITreeSorter { // Do not sort roots if (statA.isRoot) { if (statB.isRoot) { - return this.contextService.getWorkspaceFolder(statA.resource).index - this.contextService.getWorkspaceFolder(statB.resource).index; + const workspaceA = this.contextService.getWorkspaceFolder(statA.resource); + const workspaceB = this.contextService.getWorkspaceFolder(statB.resource); + return workspaceA && workspaceB ? (workspaceA.index - workspaceB.index) : -1; } return -1; @@ -399,7 +402,7 @@ export class FileSorter implements ITreeSorter { case 'modified': if (statA.mtime !== statB.mtime) { - return statA.mtime < statB.mtime ? 1 : -1; + return (statA.mtime && statB.mtime && statA.mtime < statB.mtime) ? 1 : -1; } return compareFileNames(statA.name, statB.name); @@ -438,7 +441,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { this.toDispose.push(this.configurationService.onDidChangeConfiguration((e) => updateDropEnablement())); } - onDragOver(data: IDragAndDropData, target: ExplorerItem, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction { + onDragOver(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction { if (!this.dropEnabled) { return false; } @@ -448,7 +451,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { const effect = (fromDesktop || isCopy) ? ListDragOverEffect.Copy : ListDragOverEffect.Move; // Desktop DND - if (fromDesktop) { + if (fromDesktop && originalEvent.dataTransfer) { const types = originalEvent.dataTransfer.types; const typesArray: string[] = []; for (let i = 0; i < types.length; i++) { @@ -472,7 +475,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { if (!target) { // Droping onto the empty area. Do not accept if items dragged are already // children of the root unless we are copying the file - if (!isCopy && items.every(i => i.parent && i.parent.isRoot)) { + if (!isCopy && items.every(i => !!i.parent && i.parent.isRoot)) { return false; } @@ -542,7 +545,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { return element.resource.toString(); } - getDragLabel(elements: ExplorerItem[]): string { + getDragLabel(elements: ExplorerItem[]): string | undefined { if (elements.length > 1) { return String(elements.length); } @@ -552,7 +555,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { const items = (data as ElementsDragAndDropData).elements; - if (items && items.length) { + if (items && items.length && originalEvent.dataTransfer) { // Apply some datatransfer types to allow for dragging the element outside of the application this.instantiationService.invokeFunction(fillResourceDataTransfers, items, originalEvent); @@ -565,12 +568,12 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } } - drop(data: IDragAndDropData, target: ExplorerItem, targetIndex: number, originalEvent: DragEvent): void { + drop(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void { // Find parent to add to if (!target) { target = this.explorerService.roots[this.explorerService.roots.length - 1]; } - if (!target.isDirectory) { + if (!target.isDirectory && target.parent) { target = target.parent; } if (target.isReadonly) { @@ -597,7 +600,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { this.windowService.focusWindow(); // Handle folders by adding to workspace if we are in workspace context - const folders = result.filter(r => r.success && r.stat.isDirectory).map(result => ({ uri: result.stat.resource })); + const folders = result.filter(r => r.success && r.stat && r.stat.isDirectory).map(result => ({ uri: result.stat!.resource })); if (folders.length > 0) { // If we are in no-workspace context, ask for confirmation to create a workspace @@ -636,9 +639,11 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // Check for name collisions const targetNames = new Set(); - targetStat.children.forEach((child) => { - targetNames.add(isLinux ? child.name : child.name.toLowerCase()); - }); + if (targetStat.children) { + targetStat.children.forEach((child) => { + targetNames.add(isLinux ? child.name : child.name.toLowerCase()); + }); + } let overwritePromise: Promise = Promise.resolve({ confirmed: true }); if (resources.some(resource => { @@ -656,7 +661,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { return overwritePromise.then(res => { if (!res.confirmed) { - return undefined; + return []; } // Run add in sequence @@ -669,7 +674,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // if the target exists and is dirty, make sure to revert it. otherwise the dirty contents // of the target file would replace the contents of the added file. since we already // confirmed the overwrite before, this is OK. - let revertPromise: Promise = Promise.resolve(null); + let revertPromise: Promise = Promise.resolve(null); if (this.textFileService.isDirty(targetFile)) { revertPromise = this.textFileService.revertAll([targetFile], { soft: true }); } @@ -745,16 +750,17 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } const folders = this.contextService.getWorkspace().folders; - let targetIndex: number; + let targetIndex: number | undefined; const workspaceCreationData: IWorkspaceFolderCreationData[] = []; const rootsToMove: IWorkspaceFolderCreationData[] = []; for (let index = 0; index < folders.length; index++) { const data = { - uri: folders[index].uri + uri: folders[index].uri, + name: folders[index].name }; if (target instanceof ExplorerItem && folders[index].uri.toString() === target.resource.toString()) { - targetIndex = workspaceCreationData.length; + targetIndex = index; } if (roots.every(r => r.resource.toString() !== folders[index].uri.toString())) { @@ -763,6 +769,9 @@ export class FileDragAndDrop implements ITreeDragAndDrop { rootsToMove.push(data); } } + if (!targetIndex) { + targetIndex = workspaceCreationData.length; + } workspaceCreationData.splice(targetIndex, 0, ...rootsToMove); return this.workspaceEditingService.updateFolders(0, workspaceCreationData.length, workspaceCreationData); diff --git a/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts similarity index 93% rename from src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts rename to src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index c8e6e8579c..3dfaf616e3 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -9,12 +9,12 @@ import { IAction, ActionRunner } from 'vs/base/common/actions'; import * as dom from 'vs/base/browser/dom'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorGroupsService, IEditorGroup, GroupChangeKind, GroupsOrder } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup, GroupChangeKind, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IEditorInput } from 'vs/workbench/common/editor'; -import { SaveAllAction, SaveAllInGroupAction, CloseGroupAction } from 'vs/workbench/parts/files/electron-browser/fileActions'; -import { OpenEditorsFocusedContext, ExplorerFocusedContext, IFilesConfiguration, OpenEditor } from 'vs/workbench/parts/files/common/files'; +import { SaveAllAction, SaveAllInGroupAction, CloseGroupAction } from 'vs/workbench/contrib/files/browser/fileActions'; +import { OpenEditorsFocusedContext, ExplorerFocusedContext, IFilesConfiguration, OpenEditor } from 'vs/workbench/contrib/files/common/files'; import { ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { CloseAllEditorsAction, CloseEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; @@ -32,15 +32,16 @@ import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; -import { DirtyEditorContext, OpenEditorsGroupContext } from 'vs/workbench/parts/files/electron-browser/fileCommands'; +import { DirtyEditorContext, OpenEditorsGroupContext } from 'vs/workbench/contrib/files/browser/fileCommands'; import { ResourceContextKey } from 'vs/workbench/common/resources'; -import { ResourcesDropHandler, fillResourceDataTransfers } from 'vs/workbench/browser/dnd'; +import { ResourcesDropHandler, fillResourceDataTransfers, CodeDataTransfers } from 'vs/workbench/browser/dnd'; import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; -import { IDragAndDropData } from 'vs/base/browser/dnd'; +import { IDragAndDropData, DataTransfers } from 'vs/base/browser/dnd'; import { memoize } from 'vs/base/common/decorators'; -import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; +import { ElementsDragAndDropData, DesktopDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { URI } from 'vs/base/common/uri'; +import { withNullAsUndefined } from 'vs/base/common/types'; const $ = dom.$; @@ -142,16 +143,16 @@ export class OpenEditorsView extends ViewletPanel { case GroupChangeKind.EDITOR_DIRTY: case GroupChangeKind.EDITOR_LABEL: case GroupChangeKind.EDITOR_PIN: { - this.list.splice(index, 1, [new OpenEditor(e.editor, group)]); + this.list.splice(index, 1, [new OpenEditor(e.editor!, group)]); break; } case GroupChangeKind.EDITOR_OPEN: { - this.list.splice(index, 0, [new OpenEditor(e.editor, group)]); + this.list.splice(index, 0, [new OpenEditor(e.editor!, group)]); setTimeout(() => this.updateSize(), this.structuralRefreshDelay); break; } case GroupChangeKind.EDITOR_CLOSE: { - const previousIndex = this.getIndex(group, undefined) + e.editorIndex + (this.showGroups ? 1 : 0); + const previousIndex = this.getIndex(group, undefined) + (e.editorIndex || 0) + (this.showGroups ? 1 : 0); this.list.splice(previousIndex, 1); this.updateSize(); break; @@ -162,7 +163,7 @@ export class OpenEditorsView extends ViewletPanel { } } })); - this.disposables.push(groupDisposables.get(group.id)); + this.disposables.push(groupDisposables.get(group.id)!); }; this.editorGroupService.groups.forEach(g => addGroupListener(g)); @@ -243,7 +244,7 @@ export class OpenEditorsView extends ViewletPanel { this.dirtyEditorFocusedContext.reset(); const element = e.elements.length ? e.elements[0] : undefined; if (element instanceof OpenEditor) { - this.dirtyEditorFocusedContext.set(this.textFileService.isDirty(element.getResource())); + this.dirtyEditorFocusedContext.set(this.textFileService.isDirty(withNullAsUndefined(element.getResource()))); this.resourceContext.set(element.getResource()); } else if (!!element) { this.groupFocusedContext.set(true); @@ -253,7 +254,7 @@ export class OpenEditorsView extends ViewletPanel { // Open when selecting via keyboard this.disposables.push(this.list.onMouseMiddleClick(e => { if (e && e.element instanceof OpenEditor) { - e.element.group.closeEditor(e.element.editor); + e.element.group.closeEditor(e.element.editor, { preserveFocus: true }); } })); this.disposables.push(this.list.onDidOpen(e => { @@ -280,7 +281,6 @@ export class OpenEditorsView extends ViewletPanel { this.listRefreshScheduler.schedule(0); this.disposables.push(this.onDidChangeBodyVisibility(visible => { - this.updateListVisibility(visible); if (visible && this.needsRefresh) { this.listRefreshScheduler.schedule(0); } @@ -310,16 +310,6 @@ export class OpenEditorsView extends ViewletPanel { } } - private updateListVisibility(isVisible: boolean): void { - if (this.list) { - if (isVisible) { - dom.show(this.list.getHTMLElement()); - } else { - dom.hide(this.list.getHTMLElement()); // make sure the list goes out of the tabindex world by hiding it - } - } - } - private get showGroups(): boolean { return this.editorGroupService.groups.length > 1; } @@ -336,7 +326,7 @@ export class OpenEditorsView extends ViewletPanel { return result; } - private getIndex(group: IEditorGroup, editor: IEditorInput): number { + private getIndex(group: IEditorGroup, editor: IEditorInput | undefined | null): number { let index = editor ? group.getIndexOfEditor(editor) : 0; if (!this.showGroups) { return index; @@ -368,7 +358,7 @@ export class OpenEditorsView extends ViewletPanel { this.editorGroupService.activateGroup(element.groupId); // needed for https://github.com/Microsoft/vscode/issues/6672 } this.editorService.openEditor(element.editor, options, options.sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => { - if (editor && !preserveActivateGroup) { + if (editor && !preserveActivateGroup && editor.group) { this.editorGroupService.activateGroup(editor.group); } }); @@ -618,13 +608,13 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop 1) { return String(elements.length); } const element = elements[0]; - return element instanceof OpenEditor ? element.editor.getName() : element.label; + return element instanceof OpenEditor ? withNullAsUndefined(element.editor.getName()) : element.label; } onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { @@ -633,7 +623,10 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop { if (i instanceof OpenEditor) { - resources.push(i.getResource()); + const resource = i.getResource(); + if (resource) { + resources.push(resource); + } } }); } @@ -645,6 +638,18 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop>; + private textModelReference: Promise> | null; private name: string; /** @@ -36,16 +34,17 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { */ constructor( private resource: URI, - preferredEncoding: string, + preferredEncoding: string | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITextFileService private readonly textFileService: ITextFileService, @ITextModelService private readonly textModelResolverService: ITextModelService, - @IHashService private readonly hashService: IHashService, @ILabelService private readonly labelService: ILabelService ) { super(); - this.setPreferredEncoding(preferredEncoding); + if (preferredEncoding) { + this.setPreferredEncoding(preferredEncoding); + } this.registerListeners(); } @@ -100,10 +99,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { setPreferredEncoding(encoding: string): void { this.preferredEncoding = encoding; - - if (encoding) { - this.forceOpenAsText = true; // encoding is a good hint to open the file as text - } + this.forceOpenAsText = true; // encoding is a good hint to open the file as text } setForceOpenAsText(): void { @@ -122,7 +118,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { getName(): string { if (!this.name) { - this.name = resources.basenameOrAuthority(this.resource); + this.name = basenameOrAuthority(this.resource); } return this.decorateLabel(this.name); @@ -130,17 +126,17 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { @memoize private get shortDescription(): string { - return paths.basename(this.labelService.getUriLabel(resources.dirname(this.resource))); + return basename(this.labelService.getUriLabel(dirname(this.resource))); } @memoize private get mediumDescription(): string { - return this.labelService.getUriLabel(resources.dirname(this.resource), { relative: true }); + return this.labelService.getUriLabel(dirname(this.resource), { relative: true }); } @memoize private get longDescription(): string { - return this.labelService.getUriLabel(resources.dirname(this.resource)); + return this.labelService.getUriLabel(dirname(this.resource)); } getDescription(verbosity: Verbosity = Verbosity.MEDIUM): string { @@ -182,6 +178,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { case Verbosity.SHORT: title = this.shortTitle; break; + default: case Verbosity.MEDIUM: title = this.mediumTitle; break; @@ -288,18 +285,6 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { return !!this.textFileService.models.get(this.resource); } - getTelemetryDescriptor(): object { - const descriptor = super.getTelemetryDescriptor(); - descriptor['resource'] = telemetryURIDescriptor(this.getResource(), path => this.hashService.createSHA1(path)); - - /* __GDPR__FRAGMENT__ - "EditorTelemetryDescriptor" : { - "resource": { "${inline}": [ "${URIDescriptor}" ] } - } - */ - return descriptor; - } - dispose(): void { // Model reference diff --git a/src/vs/workbench/parts/files/common/explorerModel.ts b/src/vs/workbench/contrib/files/common/explorerModel.ts similarity index 96% rename from src/vs/workbench/parts/files/common/explorerModel.ts rename to src/vs/workbench/contrib/files/common/explorerModel.ts index 9f21065524..cf4b814556 100644 --- a/src/vs/workbench/parts/files/common/explorerModel.ts +++ b/src/vs/workbench/contrib/files/common/explorerModel.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; -import * as paths from 'vs/base/common/paths'; +import { isEqual } from 'vs/base/common/extpath'; +import { posix } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { ResourceMap } from 'vs/base/common/map'; import { isLinux } from 'vs/base/common/platform'; @@ -331,24 +332,24 @@ export class ExplorerItem { // For performance reasons try to do the comparison as fast as possible if (resource && this.resource.scheme === resource.scheme && equalsIgnoreCase(this.resource.authority, resource.authority) && (resources.hasToIgnoreCase(resource) ? startsWithIgnoreCase(resource.path, this.resource.path) : startsWith(resource.path, this.resource.path))) { - return this.findByPath(rtrim(resource.path, paths.sep), this.resource.path.length); + return this.findByPath(rtrim(resource.path, posix.sep), this.resource.path.length); } return null; //Unable to find } private findByPath(path: string, index: number): ExplorerItem | null { - if (paths.isEqual(rtrim(this.resource.path, paths.sep), path, !isLinux)) { + if (isEqual(rtrim(this.resource.path, posix.sep), path, !isLinux)) { return this; } if (this.isDirectory) { // Ignore separtor to more easily deduct the next name to search - while (index < path.length && path[index] === paths.sep) { + while (index < path.length && path[index] === posix.sep) { index++; } - let indexOfNextSep = path.indexOf(paths.sep, index); + let indexOfNextSep = path.indexOf(posix.sep, index); if (indexOfNextSep === -1) { // If there is no separator take the remainder of the path indexOfNextSep = path.length; diff --git a/src/vs/workbench/parts/files/electron-browser/explorerService.ts b/src/vs/workbench/contrib/files/common/explorerService.ts similarity index 79% rename from src/vs/workbench/parts/files/electron-browser/explorerService.ts rename to src/vs/workbench/contrib/files/common/explorerService.ts index 8e3590b071..32d5923c45 100644 --- a/src/vs/workbench/parts/files/electron-browser/explorerService.ts +++ b/src/vs/workbench/contrib/files/common/explorerService.ts @@ -6,13 +6,13 @@ import { Event, Emitter } from 'vs/base/common/event'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IExplorerService, IEditableData, IFilesConfiguration, SortOrder, SortOrderConfiguration } from 'vs/workbench/parts/files/common/files'; -import { ExplorerItem, ExplorerModel } from 'vs/workbench/parts/files/common/explorerModel'; +import { IExplorerService, IEditableData, IFilesConfiguration, SortOrder, SortOrderConfiguration } from 'vs/workbench/contrib/files/common/files'; +import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel'; import { URI } from 'vs/base/common/uri'; import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files'; import { dirname } from 'vs/base/common/resources'; import { memoize } from 'vs/base/common/decorators'; -import { ResourceGlobMatcher } from 'vs/workbench/electron-browser/resources'; +import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IExpression } from 'vs/base/common/glob'; @@ -37,7 +37,7 @@ export class ExplorerService implements IExplorerService { private _onDidSelectItem = new Emitter<{ item?: ExplorerItem, reveal?: boolean }>(); private _onDidCopyItems = new Emitter<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>(); private disposables: IDisposable[] = []; - private editableStats = new Map(); + private editable: { stat: ExplorerItem, data: IEditableData } | undefined; private _sortOrder: SortOrder; private cutItems: ExplorerItem[] | undefined; @@ -48,7 +48,9 @@ export class ExplorerService implements IExplorerService { @IWorkspaceContextService private contextService: IWorkspaceContextService, @IClipboardService private clipboardService: IClipboardService, @IEditorService private editorService: IEditorService - ) { } + ) { + this._sortOrder = this.configurationService.getValue('explorer.sortOrder'); + } get roots(): ExplorerItem[] { return this.model.roots; @@ -110,11 +112,10 @@ export class ExplorerService implements IExplorerService { setEditable(stat: ExplorerItem, data: IEditableData | null): void { if (!data) { - this.editableStats.delete(stat); + this.editable = undefined; } else { - this.editableStats.set(stat, data); + this.editable = { stat, data }; } - this._onDidChangeEditable.fire(stat); } @@ -131,11 +132,11 @@ export class ExplorerService implements IExplorerService { } getEditableData(stat: ExplorerItem): IEditableData | undefined { - return this.editableStats.get(stat); + return this.editable && this.editable.stat === stat ? this.editable.data : undefined; } - isEditable(stat: ExplorerItem): boolean { - return this.editableStats.has(stat); + isEditable(stat: ExplorerItem | undefined): boolean { + return !!this.editable && (this.editable.stat === stat || !stat); } select(resource: URI, reveal?: boolean): Promise { @@ -217,7 +218,7 @@ export class ExplorerService implements IExplorerService { const newParentResource = dirname(newElement.resource); // Handle Rename - if (oldParentResource && newParentResource && oldParentResource.toString() === newParentResource.toString()) { + if (oldParentResource.toString() === newParentResource.toString()) { const modelElements = this.model.findAll(oldResource); modelElements.forEach(modelElement => { // Rename File (Model) @@ -227,7 +228,7 @@ export class ExplorerService implements IExplorerService { } // Handle Move - else if (oldParentResource && newParentResource) { + else { const newParents = this.model.findAll(newParentResource); const modelElements = this.model.findAll(oldResource); @@ -264,74 +265,74 @@ export class ExplorerService implements IExplorerService { // be fired first over the other or not at all. setTimeout(() => { // Filter to the ones we care - e = this.filterToViewRelevantEvents(e); - const explorerItemChanged = (item: ExplorerItem) => { - item.forgetChildren(); - this._onDidChangeItem.fire(item); + const shouldRefresh = () => { + e = this.filterToViewRelevantEvents(e); + // Handle added files/folders + const added = e.getAdded(); + if (added.length) { + + // Check added: Refresh if added file/folder is not part of resolved root and parent is part of it + const ignoredPaths: { [resource: string]: boolean } = <{ [resource: string]: boolean }>{}; + for (let i = 0; i < added.length; i++) { + const change = added[i]; + + // Find parent + const parent = dirname(change.resource); + + // Continue if parent was already determined as to be ignored + if (ignoredPaths[parent.toString()]) { + continue; + } + + // Compute if parent is visible and added file not yet part of it + const parentStat = this.model.findClosest(parent); + if (parentStat && parentStat.isDirectoryResolved && !this.model.findClosest(change.resource)) { + return true; + } + + // Keep track of path that can be ignored for faster lookup + if (!parentStat || !parentStat.isDirectoryResolved) { + ignoredPaths[parent.toString()] = true; + } + } + } + + // Handle deleted files/folders + const deleted = e.getDeleted(); + if (deleted.length) { + + // Check deleted: Refresh if deleted file/folder part of resolved root + for (let j = 0; j < deleted.length; j++) { + const del = deleted[j]; + const item = this.model.findClosest(del.resource); + if (item && item.parent) { + return true; + } + } + } + + // Handle updated files/folders if we sort by modified + if (this._sortOrder === SortOrderConfiguration.MODIFIED) { + const updated = e.getUpdated(); + + // Check updated: Refresh if updated file/folder part of resolved root + for (let j = 0; j < updated.length; j++) { + const upd = updated[j]; + const item = this.model.findClosest(upd.resource); + + if (item && item.parent) { + return true; + } + } + } + + return false; }; - // Handle added files/folders - const added = e.getAdded(); - if (added.length) { - - // Check added: Refresh if added file/folder is not part of resolved root and parent is part of it - const ignoredPaths: { [resource: string]: boolean } = <{ [resource: string]: boolean }>{}; - for (let i = 0; i < added.length; i++) { - const change = added[i]; - - // Find parent - const parent = dirname(change.resource); - if (!parent) { - continue; - } - - // Continue if parent was already determined as to be ignored - if (ignoredPaths[parent.toString()]) { - continue; - } - - // Compute if parent is visible and added file not yet part of it - const parentStat = this.model.findClosest(parent); - if (parentStat && parentStat.isDirectoryResolved && !this.model.findClosest(change.resource)) { - explorerItemChanged(parentStat); - } - - // Keep track of path that can be ignored for faster lookup - if (!parentStat || !parentStat.isDirectoryResolved) { - ignoredPaths[parent.toString()] = true; - } - } + if (shouldRefresh()) { + this.roots.forEach(r => r.forgetChildren()); + this._onDidChangeItem.fire(undefined); } - - // Handle deleted files/folders - const deleted = e.getDeleted(); - if (deleted.length) { - - // Check deleted: Refresh if deleted file/folder part of resolved root - for (let j = 0; j < deleted.length; j++) { - const del = deleted[j]; - const item = this.model.findClosest(del.resource); - if (item && item.parent) { - explorerItemChanged(item.parent); - } - } - } - - // Handle updated files/folders if we sort by modified - if (this._sortOrder === SortOrderConfiguration.MODIFIED) { - const updated = e.getUpdated(); - - // Check updated: Refresh if updated file/folder part of resolved root - for (let j = 0; j < updated.length; j++) { - const upd = updated[j]; - const item = this.model.findClosest(upd.resource); - - if (item && item.parent) { - explorerItemChanged(item.parent); - } - } - } - }, ExplorerService.EXPLORER_FILE_CHANGES_REACT_DELAY); } diff --git a/src/vs/workbench/parts/files/common/files.ts b/src/vs/workbench/contrib/files/common/files.ts similarity index 94% rename from src/vs/workbench/parts/files/common/files.ts rename to src/vs/workbench/contrib/files/common/files.ts index 685ad2fd76..1375206aeb 100644 --- a/src/vs/workbench/parts/files/common/files.ts +++ b/src/vs/workbench/contrib/files/common/files.ts @@ -15,13 +15,13 @@ import { Event } from 'vs/base/common/event'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService, ILanguageSelection } from 'vs/editor/common/services/modeService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { InputFocusedContextKey } from 'vs/platform/workbench/common/contextkeys'; +import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { Registry } from 'vs/platform/registry/common/platform'; import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer } from 'vs/workbench/common/views'; import { Schemas } from 'vs/base/common/network'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; -import { ExplorerItem } from 'vs/workbench/parts/files/common/explorerModel'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; /** * Explorer viewlet id. @@ -33,7 +33,7 @@ export const VIEWLET_ID = 'workbench.view.explorer'; export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); export interface IEditableData { - validationMessage: (value: string) => string; + validationMessage: (value: string) => string | null; onFinish: (value: string, success: boolean) => void; } @@ -47,9 +47,10 @@ export interface IExplorerService { readonly onDidSelectItem: Event<{ item?: ExplorerItem, reveal?: boolean }>; readonly onDidCopyItems: Event<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>; - setEditable(stat: ExplorerItem, data: IEditableData): void; + setEditable(stat: ExplorerItem, data: IEditableData | null): void; getEditableData(stat: ExplorerItem): IEditableData | undefined; - isEditable(stat: ExplorerItem): boolean; + // If undefined is passed checks if any element is currently being edited. + isEditable(stat: ExplorerItem | undefined): boolean; findClosest(resource: URI): ExplorerItem | null; refresh(): void; setToCopy(stats: ExplorerItem[], cut: boolean): void; @@ -91,11 +92,6 @@ export const OpenEditorsVisibleCondition = ContextKeyExpr.has(openEditorsVisible export const FilesExplorerFocusCondition = ContextKeyExpr.and(ContextKeyExpr.has(explorerViewletVisibleId), ContextKeyExpr.has(filesExplorerFocusId), ContextKeyExpr.not(InputFocusedContextKey)); export const ExplorerFocusCondition = ContextKeyExpr.and(ContextKeyExpr.has(explorerViewletVisibleId), ContextKeyExpr.has(explorerViewletFocusId), ContextKeyExpr.not(InputFocusedContextKey)); -/** - * Preferences editor id. - */ -export const PREFERENCES_EDITOR_ID = 'workbench.editor.preferencesEditor'; - /** * Text file editor id. */ diff --git a/src/vs/workbench/parts/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts similarity index 81% rename from src/vs/workbench/parts/files/test/browser/fileEditorInput.test.ts rename to src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts index 7b9c64b232..26482aef4e 100644 --- a/src/vs/workbench/parts/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorInput.test.ts @@ -2,10 +2,11 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ + import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { join } from 'vs/base/common/paths'; -import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; +import { toResource } from 'vs/base/test/common/utils'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -16,10 +17,6 @@ import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textF import { IModelService } from 'vs/editor/common/services/modelService'; import { timeout } from 'vs/base/common/async'; -function toResource(self, path) { - return URI.file(join('C:\\', Buffer.from(self.test.fullTitle()).toString('base64'), path)); -} - class ServiceAccessor { constructor( @IEditorService public editorService: IEditorService, @@ -40,9 +37,9 @@ suite('Files - FileEditorInput', () => { }); test('Basics', function () { - let input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/file.js'), undefined); - const otherInput = instantiationService.createInstance(FileEditorInput, toResource(this, 'foo/bar/otherfile.js'), undefined); - const otherInputSame = instantiationService.createInstance(FileEditorInput, toResource(this, 'foo/bar/file.js'), undefined); + let input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined); + const otherInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/otherfile.js'), undefined); + const otherInputSame = instantiationService.createInstance(FileEditorInput, toResource.call(this, 'foo/bar/file.js'), undefined); assert(input.matches(input)); assert(input.matches(otherInputSame)); @@ -54,13 +51,13 @@ suite('Files - FileEditorInput', () => { assert.strictEqual('file.js', input.getName()); - assert.strictEqual(toResource(this, '/foo/bar/file.js').fsPath, input.getResource().fsPath); + assert.strictEqual(toResource.call(this, '/foo/bar/file.js').fsPath, input.getResource().fsPath); assert(input.getResource() instanceof URI); - input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar.html'), undefined); + input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar.html'), undefined); - const inputToResolve: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/file.js'), undefined); - const sameOtherInput: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/file.js'), undefined); + const inputToResolve: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined); + const sameOtherInput: FileEditorInput = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/file.js'), undefined); return inputToResolve.resolve().then(resolved => { assert.ok(inputToResolve.isResolved()); @@ -99,10 +96,10 @@ suite('Files - FileEditorInput', () => { }); test('matches', function () { - const input1 = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), undefined); - const input2 = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), undefined); - const input3 = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/other.js'), undefined); - const input2Upper = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/UPDATEFILE.js'), undefined); + const input1 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); + const input2 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); + const input3 = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/other.js'), undefined); + const input2Upper = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/UPDATEFILE.js'), undefined); assert.strictEqual(input1.matches(null), false); assert.strictEqual(input1.matches(input1), true); @@ -113,7 +110,7 @@ suite('Files - FileEditorInput', () => { }); test('getEncoding/setEncoding', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), undefined); + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); input.setEncoding('utf16', EncodingMode.Encode); assert.equal(input.getEncoding(), 'utf16'); @@ -126,10 +123,10 @@ suite('Files - FileEditorInput', () => { }); test('save', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), undefined); + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); return input.resolve().then((resolved: TextFileEditorModel) => { - resolved.textEditorModel.setValue('changed'); + resolved.textEditorModel!.setValue('changed'); assert.ok(input.isDirty()); return input.save().then(() => { @@ -141,10 +138,10 @@ suite('Files - FileEditorInput', () => { }); test('revert', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), undefined); + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); return input.resolve().then((resolved: TextFileEditorModel) => { - resolved.textEditorModel.setValue('changed'); + resolved.textEditorModel!.setValue('changed'); assert.ok(input.isDirty()); return input.revert().then(() => { @@ -156,7 +153,7 @@ suite('Files - FileEditorInput', () => { }); test('resolve handles binary files', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), undefined); + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_IS_BINARY)); @@ -168,7 +165,7 @@ suite('Files - FileEditorInput', () => { }); test('resolve handles too large files', function () { - const input = instantiationService.createInstance(FileEditorInput, toResource(this, '/foo/bar/updatefile.js'), undefined); + const input = instantiationService.createInstance(FileEditorInput, toResource.call(this, '/foo/bar/updatefile.js'), undefined); accessor.textFileService.setResolveTextContentErrorOnce(new FileOperationError('error', FileOperationResult.FILE_TOO_LARGE)); diff --git a/src/vs/workbench/parts/files/test/browser/fileEditorTracker.test.ts b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts similarity index 69% rename from src/vs/workbench/parts/files/test/browser/fileEditorTracker.test.ts rename to src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts index cdb7ac28f4..dd3a7ee41a 100644 --- a/src/vs/workbench/parts/files/test/browser/fileEditorTracker.test.ts +++ b/src/vs/workbench/contrib/files/test/browser/fileEditorTracker.test.ts @@ -4,22 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { FileEditorTracker } from 'vs/workbench/parts/files/browser/editors/fileEditorTracker'; -import { URI } from 'vs/base/common/uri'; -import { join } from 'vs/base/common/paths'; +import { FileEditorTracker } from 'vs/workbench/contrib/files/browser/editors/fileEditorTracker'; +import { toResource } from 'vs/base/test/common/utils'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { workbenchInstantiationService, TestTextFileService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { FileChangesEvent, FileChangeType, IFileService, snapshotToString } from 'vs/platform/files/common/files'; -import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { timeout } from 'vs/base/common/async'; -function toResource(self: any, path: string) { - return URI.file(join('C:\\', Buffer.from(self.test.fullTitle()).toString('base64'), path)); -} - class ServiceAccessor { constructor( @IEditorService public editorService: IEditorService, @@ -43,11 +37,11 @@ suite('Files - FileEditorTracker', () => { test('file change event updates model', function () { const tracker = instantiationService.createInstance(FileEditorTracker); - const resource = toResource(this, '/path/index.txt'); + const resource = toResource.call(this, '/path/index.txt'); - return accessor.textFileService.models.loadOrCreate(resource).then((model: TextFileEditorModel) => { + return accessor.textFileService.models.loadOrCreate(resource).then((model: IResolvedTextFileEditorModel) => { model.textEditorModel.setValue('Super Good'); - assert.equal(snapshotToString(model.createSnapshot()), 'Super Good'); + assert.equal(snapshotToString(model.createSnapshot()!), 'Super Good'); return model.save().then(() => { @@ -55,7 +49,7 @@ suite('Files - FileEditorTracker', () => { accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource, type: FileChangeType.UPDATED }])); return timeout(0).then(() => { // due to event updating model async - assert.equal(snapshotToString(model.createSnapshot()), 'Hello Html'); + assert.equal(snapshotToString(model.createSnapshot()!), 'Hello Html'); tracker.dispose(); }); diff --git a/src/vs/workbench/parts/files/test/electron-browser/explorerModel.test.ts b/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts similarity index 55% rename from src/vs/workbench/parts/files/test/electron-browser/explorerModel.test.ts rename to src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts index 70e0b53564..6f3657563e 100644 --- a/src/vs/workbench/parts/files/test/electron-browser/explorerModel.test.ts +++ b/src/vs/workbench/contrib/files/test/electron-browser/explorerModel.test.ts @@ -6,43 +6,36 @@ import * as assert from 'assert'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -import { join } from 'vs/base/common/paths'; -import { validateFileName } from 'vs/workbench/parts/files/electron-browser/fileActions'; -import { ExplorerItem } from 'vs/workbench/parts/files/common/explorerModel'; +import { join } from 'vs/base/common/path'; +import { validateFileName } from 'vs/workbench/contrib/files/browser/fileActions'; +import { ExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel'; +import { toResource } from 'vs/base/test/common/utils'; -function createStat(path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number): ExplorerItem { - return new ExplorerItem(toResource(path), null, isFolder, false, false, name, mtime); +function createStat(this: any, path: string, name: string, isFolder: boolean, hasChildren: boolean, size: number, mtime: number): ExplorerItem { + return new ExplorerItem(toResource.call(this, path), undefined, isFolder, false, false, name, mtime); } -function toResource(path) { - if (isWindows) { - return URI.file(join('C:\\', path)); - } else { - return URI.file(join('/home/john', path)); - } -} +suite('Files - View Model', function () { -suite('Files - View Model', () => { - - test('Properties', () => { + test('Properties', function () { const d = new Date().getTime(); - let s = createStat('/path/to/stat', 'sName', true, true, 8096, d); + let s = createStat.call(this, '/path/to/stat', 'sName', true, true, 8096, d); assert.strictEqual(s.isDirectoryResolved, false); - assert.strictEqual(s.resource.fsPath, toResource('/path/to/stat').fsPath); + assert.strictEqual(s.resource.fsPath, toResource.call(this, '/path/to/stat').fsPath); assert.strictEqual(s.name, 'sName'); assert.strictEqual(s.isDirectory, true); assert.strictEqual(s.mtime, new Date(d).getTime()); - s = createStat('/path/to/stat', 'sName', false, false, 8096, d); + s = createStat.call(this, '/path/to/stat', 'sName', false, false, 8096, d); }); test('Add and Remove Child, check for hasChild', function () { const d = new Date().getTime(); - const s = createStat('/path/to/stat', 'sName', true, false, 8096, d); + const s = createStat.call(this, '/path/to/stat', 'sName', true, false, 8096, d); - const child1 = createStat('/path/to/stat/foo', 'foo', true, false, 8096, d); - const child4 = createStat('/otherpath/to/other/otherbar.html', 'otherbar.html', false, false, 8096, d); + const child1 = createStat.call(this, '/path/to/stat/foo', 'foo', true, false, 8096, d); + const child4 = createStat.call(this, '/otherpath/to/other/otherbar.html', 'otherbar.html', false, false, 8096, d); s.addChild(child1); @@ -57,16 +50,16 @@ suite('Files - View Model', () => { // Assert that adding a child updates its path properly s.addChild(child4); - assert.strictEqual(child4.resource.fsPath, toResource('/path/to/stat/' + child4.name).fsPath); + assert.strictEqual(child4.resource.fsPath, toResource.call(this, '/path/to/stat/' + child4.name).fsPath); }); - test('Move', () => { + test('Move', function () { const d = new Date().getTime(); - const s1 = createStat('/', '/', true, false, 8096, d); - const s2 = createStat('/path', 'path', true, false, 8096, d); - const s3 = createStat('/path/to', 'to', true, false, 8096, d); - const s4 = createStat('/path/to/stat', 'stat', false, false, 8096, d); + const s1 = createStat.call(this, '/', '/', true, false, 8096, d); + const s2 = createStat.call(this, '/path', 'path', true, false, 8096, d); + const s3 = createStat.call(this, '/path/to', 'to', true, false, 8096, d); + const s4 = createStat.call(this, '/path/to/stat', 'stat', false, false, 8096, d); s1.addChild(s2); s2.addChild(s3); @@ -75,12 +68,12 @@ suite('Files - View Model', () => { s4.move(s1); // Assert the new path of the moved element - assert.strictEqual(s4.resource.fsPath, toResource('/' + s4.name).fsPath); + assert.strictEqual(s4.resource.fsPath, toResource.call(this, '/' + s4.name).fsPath); // Move a subtree with children - const leaf = createStat('/leaf', 'leaf', true, false, 8096, d); - const leafC1 = createStat('/leaf/folder', 'folder', true, false, 8096, d); - const leafCC2 = createStat('/leaf/folder/index.html', 'index.html', true, false, 8096, d); + const leaf = createStat.call(this, '/leaf', 'leaf', true, false, 8096, d); + const leafC1 = createStat.call(this, '/leaf/folder', 'folder', true, false, 8096, d); + const leafCC2 = createStat.call(this, '/leaf/folder/index.html', 'index.html', true, false, 8096, d); leaf.addChild(leafC1); leafC1.addChild(leafCC2); @@ -91,47 +84,47 @@ suite('Files - View Model', () => { assert.strictEqual(leafCC2.resource.fsPath, URI.file(leafC1.resource.fsPath + '/' + leafCC2.name).fsPath); }); - test('Rename', () => { + test('Rename', function () { const d = new Date().getTime(); - const s1 = createStat('/', '/', true, false, 8096, d); - const s2 = createStat('/path', 'path', true, false, 8096, d); - const s3 = createStat('/path/to', 'to', true, false, 8096, d); - const s4 = createStat('/path/to/stat', 'stat', true, false, 8096, d); + const s1 = createStat.call(this, '/', '/', true, false, 8096, d); + const s2 = createStat.call(this, '/path', 'path', true, false, 8096, d); + const s3 = createStat.call(this, '/path/to', 'to', true, false, 8096, d); + const s4 = createStat.call(this, '/path/to/stat', 'stat', true, false, 8096, d); s1.addChild(s2); s2.addChild(s3); s3.addChild(s4); assert.strictEqual(s1.getChild(s2.name), s2); - const s2renamed = createStat('/otherpath', 'otherpath', true, true, 8096, d); + const s2renamed = createStat.call(this, '/otherpath', 'otherpath', true, true, 8096, d); s2.rename(s2renamed); assert.strictEqual(s1.getChild(s2.name), s2); // Verify the paths have changed including children assert.strictEqual(s2.name, s2renamed.name); assert.strictEqual(s2.resource.fsPath, s2renamed.resource.fsPath); - assert.strictEqual(s3.resource.fsPath, toResource('/otherpath/to').fsPath); - assert.strictEqual(s4.resource.fsPath, toResource('/otherpath/to/stat').fsPath); + assert.strictEqual(s3.resource.fsPath, toResource.call(this, '/otherpath/to').fsPath); + assert.strictEqual(s4.resource.fsPath, toResource.call(this, '/otherpath/to/stat').fsPath); - const s4renamed = createStat('/otherpath/to/statother.js', 'statother.js', true, false, 8096, d); + const s4renamed = createStat.call(this, '/otherpath/to/statother.js', 'statother.js', true, false, 8096, d); s4.rename(s4renamed); assert.strictEqual(s3.getChild(s4.name), s4); assert.strictEqual(s4.name, s4renamed.name); assert.strictEqual(s4.resource.fsPath, s4renamed.resource.fsPath); }); - test('Find', () => { + test('Find', function () { const d = new Date().getTime(); - const s1 = createStat('/', '/', true, false, 8096, d); - const s2 = createStat('/path', 'path', true, false, 8096, d); - const s3 = createStat('/path/to', 'to', true, false, 8096, d); - const s4 = createStat('/path/to/stat', 'stat', true, false, 8096, d); - const s4Upper = createStat('/path/to/STAT', 'stat', true, false, 8096, d); + const s1 = createStat.call(this, '/', '/', true, false, 8096, d); + const s2 = createStat.call(this, '/path', 'path', true, false, 8096, d); + const s3 = createStat.call(this, '/path/to', 'to', true, false, 8096, d); + const s4 = createStat.call(this, '/path/to/stat', 'stat', true, false, 8096, d); + const s4Upper = createStat.call(this, '/path/to/STAT', 'stat', true, false, 8096, d); - const child1 = createStat('/path/to/stat/foo', 'foo', true, false, 8096, d); - const child2 = createStat('/path/to/stat/foo/bar.html', 'bar.html', false, false, 8096, d); + const child1 = createStat.call(this, '/path/to/stat/foo', 'foo', true, false, 8096, d); + const child2 = createStat.call(this, '/path/to/stat/foo/bar.html', 'bar.html', false, false, 8096, d); s1.addChild(s2); s2.addChild(s3); @@ -151,22 +144,22 @@ suite('Files - View Model', () => { assert.strictEqual(s1.find(s4Upper.resource), s4); } - assert.strictEqual(s1.find(toResource('foobar')), null); + assert.strictEqual(s1.find(toResource.call(this, 'foobar')), null); - assert.strictEqual(s1.find(toResource('/')), s1); - assert.strictEqual(s1.find(toResource('')), s1); + assert.strictEqual(s1.find(toResource.call(this, '/')), s1); + // assert.strictEqual(s1.find(toResource.call(this, '')), s1); //TODO@isidor this fails with proper paths usage }); test('Find with mixed case', function () { const d = new Date().getTime(); - const s1 = createStat('/', '/', true, false, 8096, d); - const s2 = createStat('/path', 'path', true, false, 8096, d); - const s3 = createStat('/path/to', 'to', true, false, 8096, d); - const s4 = createStat('/path/to/stat', 'stat', true, false, 8096, d); + const s1 = createStat.call(this, '/', '/', true, false, 8096, d); + const s2 = createStat.call(this, '/path', 'path', true, false, 8096, d); + const s3 = createStat.call(this, '/path/to', 'to', true, false, 8096, d); + const s4 = createStat.call(this, '/path/to/stat', 'stat', true, false, 8096, d); - const child1 = createStat('/path/to/stat/foo', 'foo', true, false, 8096, d); - const child2 = createStat('/path/to/stat/foo/bar.html', 'bar.html', false, false, 8096, d); + const child1 = createStat.call(this, '/path/to/stat/foo', 'foo', true, false, 8096, d); + const child2 = createStat.call(this, '/path/to/stat/foo/bar.html', 'bar.html', false, false, 8096, d); s1.addChild(s2); s2.addChild(s3); @@ -175,18 +168,18 @@ suite('Files - View Model', () => { child1.addChild(child2); if (isLinux) { // linux is case sensitive - assert.ok(!s1.find(toResource('/path/to/stat/Foo'))); - assert.ok(!s1.find(toResource('/Path/to/stat/foo/bar.html'))); + assert.ok(!s1.find(toResource.call(this, '/path/to/stat/Foo'))); + assert.ok(!s1.find(toResource.call(this, '/Path/to/stat/foo/bar.html'))); } else { - assert.ok(s1.find(toResource('/path/to/stat/Foo'))); - assert.ok(s1.find(toResource('/Path/to/stat/foo/bar.html'))); + assert.ok(s1.find(toResource.call(this, '/path/to/stat/Foo'))); + assert.ok(s1.find(toResource.call(this, '/Path/to/stat/foo/bar.html'))); } }); test('Validate File Name (For Create)', function () { const d = new Date().getTime(); - const s = createStat('/path/to/stat', 'sName', true, true, 8096, d); - const sChild = createStat('/path/to/stat/alles.klar', 'alles.klar', true, true, 8096, d); + const s = createStat.call(this, '/path/to/stat', 'sName', true, true, 8096, d); + const sChild = createStat.call(this, '/path/to/stat/alles.klar', 'alles.klar', true, true, 8096, d); s.addChild(sChild); assert(validateFileName(s, null!) !== null); @@ -210,8 +203,8 @@ suite('Files - View Model', () => { test('Validate File Name (For Rename)', function () { const d = new Date().getTime(); - const s = createStat('/path/to/stat', 'sName', true, true, 8096, d); - const sChild = createStat('/path/to/stat/alles.klar', 'alles.klar', true, true, 8096, d); + const s = createStat.call(this, '/path/to/stat', 'sName', true, true, 8096, d); + const sChild = createStat.call(this, '/path/to/stat/alles.klar', 'alles.klar', true, true, 8096, d); s.addChild(sChild); assert(validateFileName(s, 'alles.klar') === null); @@ -226,7 +219,7 @@ suite('Files - View Model', () => { test('Validate Multi-Path File Names', function () { const d = new Date().getTime(); - const wsFolder = createStat('/', 'workspaceFolder', true, false, 8096, d); + const wsFolder = createStat.call(this, '/', 'workspaceFolder', true, false, 8096, d); assert(validateFileName(wsFolder, 'foo/bar') === null); assert(validateFileName(wsFolder, 'foo\\bar') === null); @@ -235,13 +228,13 @@ suite('Files - View Model', () => { assert(validateFileName(wsFolder, '/slashAtBeginning') !== null); // attempting to add a child to a deeply nested file - const s1 = createStat('/path', 'path', true, false, 8096, d); - const s2 = createStat('/path/to', 'to', true, false, 8096, d); - const s3 = createStat('/path/to/stat', 'stat', true, false, 8096, d); + const s1 = createStat.call(this, '/path', 'path', true, false, 8096, d); + const s2 = createStat.call(this, '/path/to', 'to', true, false, 8096, d); + const s3 = createStat.call(this, '/path/to/stat', 'stat', true, false, 8096, d); wsFolder.addChild(s1); s1.addChild(s2); s2.addChild(s3); - const fileDeeplyNested = createStat('/path/to/stat/fileNested', 'fileNested', false, false, 8096, d); + const fileDeeplyNested = createStat.call(this, '/path/to/stat/fileNested', 'fileNested', false, false, 8096, d); s3.addChild(fileDeeplyNested); assert(validateFileName(wsFolder, '/path/to/stat/fileNested/aChild') !== null); @@ -270,8 +263,8 @@ suite('Files - View Model', () => { merge2.addChild(child); (merge2)._isDirectoryResolved = true; ExplorerItem.mergeLocalWithDisk(merge2, merge1); - assert.strictEqual(merge1.getChild('foo.html').name, 'foo.html'); - assert.deepEqual(merge1.getChild('foo.html').parent, merge1, 'Check parent'); + assert.strictEqual(merge1.getChild('foo.html')!.name, 'foo.html'); + assert.deepEqual(merge1.getChild('foo.html')!.parent, merge1, 'Check parent'); // Verify that merge does not replace existing children, but updates properties in that case const existingChild = merge1.getChild('foo.html'); diff --git a/src/vs/workbench/parts/files/test/electron-browser/fileActions.test.ts b/src/vs/workbench/contrib/files/test/electron-browser/fileActions.test.ts similarity index 98% rename from src/vs/workbench/parts/files/test/electron-browser/fileActions.test.ts rename to src/vs/workbench/contrib/files/test/electron-browser/fileActions.test.ts index 27f2dab796..52fe9bf58a 100644 --- a/src/vs/workbench/parts/files/test/electron-browser/fileActions.test.ts +++ b/src/vs/workbench/contrib/files/test/electron-browser/fileActions.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { incrementFileName } from 'vs/workbench/parts/files/electron-browser/fileActions'; +import { incrementFileName } from 'vs/workbench/contrib/files/browser/fileActions'; suite('Files - Increment file name', () => { diff --git a/src/vs/workbench/contrib/format/browser/format.contribution.ts b/src/vs/workbench/contrib/format/browser/format.contribution.ts new file mode 100644 index 0000000000..3e9036281c --- /dev/null +++ b/src/vs/workbench/contrib/format/browser/format.contribution.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { setFormatterConflictCallback, FormatMode, FormatKind } from 'vs/editor/contrib/format/format'; +import { IDisposable } from 'vs/base/common/lifecycle'; +import { localize } from 'vs/nls'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/contrib/extensions/common/extensions'; + + +class FormattingConflictHandler { + + private _registration: IDisposable; + + constructor( + @INotificationService notificationService: INotificationService, + @IViewletService private readonly _viewletService: IViewletService, + ) { + + this._registration = setFormatterConflictCallback((ids, model, mode) => { + if (mode & FormatMode.Auto) { + return; + } + if (ids.length === 0) { + const langName = model.getLanguageIdentifier().language; + const message = mode & FormatKind.Document + ? localize('no.documentprovider', "There is no document formatter for '{0}'-files installed.", langName) + : localize('no.selectionprovider', "There is no selection formatter for '{0}'-files installed.", langName); + + const choice = { + label: localize('install.formatter', "Install Formatter..."), + run: () => { + return this._viewletService.openViewlet(VIEWLET_ID, true).then(viewlet => { + if (viewlet) { + (viewlet as IExtensionsViewlet).search(`category:formatters ${langName}`); + } + }); + } + }; + notificationService.prompt(Severity.Info, message, [choice]); + } + }); + } + + dispose(): void { + this._registration.dispose(); + } +} + +Registry.as(Extensions.Workbench).registerWorkbenchContribution( + FormattingConflictHandler, + LifecyclePhase.Restored +); diff --git a/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts new file mode 100644 index 0000000000..543b840a7d --- /dev/null +++ b/src/vs/workbench/contrib/issue/electron-browser/issue.contribution.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Registry } from 'vs/platform/registry/common/platform'; +import * as nls from 'vs/nls'; +import product from 'vs/platform/product/node/product'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; +import { OpenIssueReporterAction, ReportPerformanceIssueUsingReporterAction, OpenProcessExplorer } from 'vs/workbench/contrib/issue/electron-browser/issueActions'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issue'; +import { WorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issueService'; + +const helpCategory = nls.localize('help', "Help"); +const workbenchActionsRegistry = Registry.as(Extensions.WorkbenchActions); + +if (!!product.reportIssueUrl) { + workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenIssueReporterAction, OpenIssueReporterAction.ID, OpenIssueReporterAction.LABEL), 'Help: Open Issue Reporter', helpCategory); + workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ReportPerformanceIssueUsingReporterAction, ReportPerformanceIssueUsingReporterAction.ID, ReportPerformanceIssueUsingReporterAction.LABEL), 'Help: Report Performance Issue', helpCategory); +} + +const developerCategory = nls.localize('developer', "Developer"); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenProcessExplorer, OpenProcessExplorer.ID, OpenProcessExplorer.LABEL), 'Developer: Open Process Explorer', developerCategory); + +registerSingleton(IWorkbenchIssueService, WorkbenchIssueService, true); diff --git a/src/vs/workbench/services/issue/common/issue.ts b/src/vs/workbench/contrib/issue/electron-browser/issue.ts similarity index 100% rename from src/vs/workbench/services/issue/common/issue.ts rename to src/vs/workbench/contrib/issue/electron-browser/issue.ts diff --git a/src/vs/workbench/contrib/issue/electron-browser/issueActions.ts b/src/vs/workbench/contrib/issue/electron-browser/issueActions.ts new file mode 100644 index 0000000000..1755c79eaf --- /dev/null +++ b/src/vs/workbench/contrib/issue/electron-browser/issueActions.ts @@ -0,0 +1,60 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Action } from 'vs/base/common/actions'; +import * as nls from 'vs/nls'; +import { IssueType } from 'vs/platform/issue/common/issue'; +import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issue'; + +export class OpenIssueReporterAction extends Action { + static readonly ID = 'workbench.action.openIssueReporter'; + static readonly LABEL = nls.localize({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue"); + + constructor( + id: string, + label: string, + @IWorkbenchIssueService private readonly issueService: IWorkbenchIssueService + ) { + super(id, label); + } + + run(): Promise { + return this.issueService.openReporter().then(() => true); + } +} + +export class OpenProcessExplorer extends Action { + static readonly ID = 'workbench.action.openProcessExplorer'; + static readonly LABEL = nls.localize('openProcessExplorer', "Open Process Explorer"); + + constructor( + id: string, + label: string, + @IWorkbenchIssueService private readonly issueService: IWorkbenchIssueService + ) { + super(id, label); + } + + run(): Promise { + return this.issueService.openProcessExplorer().then(() => true); + } +} + +export class ReportPerformanceIssueUsingReporterAction extends Action { + static readonly ID = 'workbench.action.reportPerformanceIssueUsingReporter'; + static readonly LABEL = nls.localize('reportPerformanceIssue', "Report Performance Issue"); + + constructor( + id: string, + label: string, + @IWorkbenchIssueService private readonly issueService: IWorkbenchIssueService + ) { + super(id, label); + } + + run(): Promise { + return this.issueService.openReporter({ issueType: IssueType.PerformanceIssue }).then(() => true); + } +} diff --git a/src/vs/workbench/services/issue/electron-browser/workbenchIssueService.ts b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts similarity index 98% rename from src/vs/workbench/services/issue/electron-browser/workbenchIssueService.ts rename to src/vs/workbench/contrib/issue/electron-browser/issueService.ts index 62a7f87097..d56c9289d7 100644 --- a/src/vs/workbench/services/issue/electron-browser/workbenchIssueService.ts +++ b/src/vs/workbench/contrib/issue/electron-browser/issueService.ts @@ -10,7 +10,7 @@ import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IExtensionManagementService, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { webFrame } from 'electron'; import { assign } from 'vs/base/common/objects'; -import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; +import { IWorkbenchIssueService } from 'vs/workbench/contrib/issue/electron-browser/issue'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; @@ -98,4 +98,3 @@ function getColor(theme: ITheme, key: string): string | undefined { const color = theme.getColor(key); return color ? color.toString() : undefined; } - diff --git a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts similarity index 97% rename from src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts rename to src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts index 861bbae6d6..8beb2ad93b 100644 --- a/src/vs/workbench/parts/localizations/electron-browser/localizations.contribution.ts +++ b/src/vs/workbench/contrib/localizations/browser/localizations.contribution.ts @@ -10,7 +10,7 @@ import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/plat import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { Disposable } from 'vs/base/common/lifecycle'; -import { ConfigureLocaleAction } from 'vs/workbench/parts/localizations/electron-browser/localizationsActions'; +import { ConfigureLocaleAction } from 'vs/workbench/contrib/localizations/browser/localizationsActions'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -21,12 +21,12 @@ import Severity from 'vs/base/common/severity'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { URI } from 'vs/base/common/uri'; -import { join } from 'vs/base/common/paths'; +import { join } from 'vs/base/common/path'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { VIEWLET_ID as EXTENSIONS_VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/parts/extensions/common/extensions'; -import { minimumTranslatedStrings } from 'vs/platform/node/minimalTranslations'; +import { VIEWLET_ID as EXTENSIONS_VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/contrib/extensions/common/extensions'; +import { minimumTranslatedStrings } from 'vs/workbench/contrib/localizations/browser/minimalTranslations'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; @@ -135,7 +135,7 @@ export class LocalizationWorkbenchContribution extends Disposable implements IWo const loc = manifest && manifest.contributes && manifest.contributes.localizations && manifest.contributes.localizations.filter(x => x.languageId.toLowerCase() === locale)[0]; const languageName = loc ? (loc.languageName || locale) : locale; const languageDisplayName = loc ? (loc.localizedLanguageName || loc.languageName || locale) : locale; - const translationsFromPack = translation && translation.contents ? translation.contents['vs/platform/node/minimalTranslations'] : {}; + const translationsFromPack = translation && translation.contents ? translation.contents['vs/workbench/contrib/localizations/browser/minimalTranslations'] : {}; const promptMessageKey = extensionToInstall ? 'installAndRestartMessage' : 'showLanguagePackExtensions'; const useEnglish = !translationsFromPack[promptMessageKey]; diff --git a/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts new file mode 100644 index 0000000000..50f176ad93 --- /dev/null +++ b/src/vs/workbench/contrib/localizations/browser/localizationsActions.ts @@ -0,0 +1,87 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { join } from 'vs/base/common/path'; +import { URI } from 'vs/base/common/uri'; +import { ILocalizationsService, LanguageType } from 'vs/platform/localizations/common/localizations'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { language } from 'vs/base/common/platform'; +import { firstIndex } from 'vs/base/common/arrays'; +import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; + +export class ConfigureLocaleAction extends Action { + public static readonly ID = 'workbench.action.configureLocale'; + public static readonly LABEL = localize('configureLocale', "Configure Display Language"); + + constructor(id: string, label: string, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @ILocalizationsService private readonly localizationService: ILocalizationsService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, + @IWindowsService private readonly windowsService: IWindowsService, + @INotificationService private readonly notificationService: INotificationService, + @IViewletService private readonly viewletService: IViewletService, + @IDialogService private readonly dialogService: IDialogService + ) { + super(id, label); + } + + private async getLanguageOptions(): Promise { + // Contributed languages are those installed via extension packs, so does not include English + const availableLanguages = ['en', ...await this.localizationService.getLanguageIds(LanguageType.Contributed)]; + availableLanguages.sort(); + + return availableLanguages + .map(language => { return { label: language }; }) + .concat({ label: localize('installAdditionalLanguages', "Install additional languages...") }); + } + + public async run(event?: any): Promise { + const languageOptions = await this.getLanguageOptions(); + const currentLanguageIndex = firstIndex(languageOptions, l => l.label === language); + + try { + const selectedLanguage = await this.quickInputService.pick(languageOptions, + { + canPickMany: false, + placeHolder: localize('chooseDisplayLanguage', "Select Display Language"), + activeItem: languageOptions[currentLanguageIndex] + }); + + if (selectedLanguage === languageOptions[languageOptions.length - 1]) { + return this.viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true) + .then((viewlet: IExtensionsViewlet) => { + viewlet.search('@category:"language packs"'); + viewlet.focus(); + }); + } + + if (selectedLanguage) { + const file = URI.file(join(this.environmentService.appSettingsHome, 'locale.json')); + await this.jsonEditingService.write(file, { key: 'locale', value: selectedLanguage.label }, true); + const restart = await this.dialogService.confirm({ + type: 'info', + message: localize('relaunchDisplayLanguageMessage', "A restart is required for the change in display language to take effect."), + detail: localize('relaunchDisplayLanguageDetail', "Press the restart button to restart {0} and change the display language.", this.environmentService.appNameLong), + primaryButton: localize('restart', "&&Restart") + }); + + if (restart.confirmed) { + this.windowsService.relaunch({}); + } + } + } catch (e) { + this.notificationService.error(e); + } + } +} diff --git a/src/vs/platform/node/minimalTranslations.ts b/src/vs/workbench/contrib/localizations/browser/minimalTranslations.ts similarity index 100% rename from src/vs/platform/node/minimalTranslations.ts rename to src/vs/workbench/contrib/localizations/browser/minimalTranslations.ts diff --git a/src/vs/workbench/parts/logs/common/logConstants.ts b/src/vs/workbench/contrib/logs/common/logConstants.ts similarity index 100% rename from src/vs/workbench/parts/logs/common/logConstants.ts rename to src/vs/workbench/contrib/logs/common/logConstants.ts diff --git a/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts similarity index 89% rename from src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts rename to src/vs/workbench/contrib/logs/common/logs.contribution.ts index c2cc1e127a..99d8727189 100644 --- a/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -4,23 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import { join } from 'vs/base/common/paths'; +import { join } from 'vs/base/common/path'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/parts/output/common/output'; +import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/contrib/output/common/output'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import * as Constants from 'vs/workbench/parts/logs/common/logConstants'; +import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -// {{SQL CARBON EDIT}} -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionService } from 'vs/workbench/services/extensions/electron-browser/extensionService'; -// {{SQL CARBON EDIT}} - End -import { OpenLogsFolderAction, SetLogLevelAction } from 'vs/workbench/parts/logs/electron-browser/logsActions'; +import { OpenLogsFolderAction, SetLogLevelAction } from 'vs/workbench/contrib/logs/common/logsActions'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; class LogOutputChannels extends Disposable implements IWorkbenchContribution { @@ -57,4 +53,4 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { } } -Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Eventually); \ No newline at end of file +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Eventually); diff --git a/src/vs/workbench/parts/logs/electron-browser/logsActions.ts b/src/vs/workbench/contrib/logs/common/logsActions.ts similarity index 94% rename from src/vs/workbench/parts/logs/electron-browser/logsActions.ts rename to src/vs/workbench/contrib/logs/common/logsActions.ts index ccb2ff0487..7e5b080fee 100644 --- a/src/vs/workbench/parts/logs/electron-browser/logsActions.ts +++ b/src/vs/workbench/contrib/logs/common/logsActions.ts @@ -5,11 +5,12 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import * as paths from 'vs/base/common/paths'; +import { join } from 'vs/base/common/path'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { ILogService, LogLevel, DEFAULT_LOG_LEVEL } from 'vs/platform/log/common/log'; import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; +import { URI } from 'vs/base/common/uri'; export class OpenLogsFolderAction extends Action { @@ -24,7 +25,7 @@ export class OpenLogsFolderAction extends Action { } run(): Promise { - return this.windowsService.showItemInFolder(paths.join(this.environmentService.logsPath, 'main.log')); + return this.windowsService.showItemInFolder(URI.file(join(this.environmentService.logsPath, 'main.log'))); } } diff --git a/src/vs/workbench/parts/markers/electron-browser/constants.ts b/src/vs/workbench/contrib/markers/browser/constants.ts similarity index 100% rename from src/vs/workbench/parts/markers/electron-browser/constants.ts rename to src/vs/workbench/contrib/markers/browser/constants.ts diff --git a/src/vs/workbench/parts/markers/electron-browser/markers.contribution.ts b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts similarity index 73% rename from src/vs/workbench/parts/markers/electron-browser/markers.contribution.ts rename to src/vs/workbench/contrib/markers/browser/markers.contribution.ts index 613b23e510..3efc323eed 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markers.contribution.ts +++ b/src/vs/workbench/contrib/markers/browser/markers.contribution.ts @@ -3,29 +3,28 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { clipboard } from 'electron'; -import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import 'vs/workbench/contrib/markers/browser/markersFileDecorations'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { KeybindingsRegistry, KeybindingWeight, IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { localize } from 'vs/nls'; -import { Marker, RelatedInformation } from 'vs/workbench/parts/markers/electron-browser/markersModel'; -import { MarkersPanel } from 'vs/workbench/parts/markers/electron-browser/markersPanel'; -import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { Marker, RelatedInformation } from 'vs/workbench/contrib/markers/browser/markersModel'; +import { MarkersPanel } from 'vs/workbench/contrib/markers/browser/markersPanel'; +import { MenuId, MenuRegistry, SyncActionDescriptor, registerAction } from 'vs/platform/actions/common/actions'; import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ToggleMarkersPanelAction, ShowProblemsPanelAction } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; -import Constants from 'vs/workbench/parts/markers/electron-browser/constants'; -import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; +import { ToggleMarkersPanelAction, ShowProblemsPanelAction } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; +import Constants from 'vs/workbench/contrib/markers/browser/constants'; +import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { IMarkersWorkbenchService, MarkersWorkbenchService, ActivityUpdater } from 'vs/workbench/parts/markers/electron-browser/markers'; +import { IMarkersWorkbenchService, MarkersWorkbenchService, ActivityUpdater } from 'vs/workbench/contrib/markers/browser/markers'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; - -import './markersFileDecorations'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { ActivePanelContext } from 'vs/workbench/common/panel'; registerSingleton(IMarkersWorkbenchService, MarkersWorkbenchService, false); @@ -105,9 +104,9 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMarkersPanelActi registry.registerWorkbenchAction(new SyncActionDescriptor(ShowProblemsPanelAction, ShowProblemsPanelAction.ID, ShowProblemsPanelAction.LABEL), 'View: Focus Problems (Errors, Warnings, Infos)', Messages.MARKERS_PANEL_VIEW_CATEGORY); registerAction({ id: Constants.MARKER_COPY_ACTION_ID, - title: localize('copyMarker', "Copy"), + title: { value: localize('copyMarker', "Copy"), original: 'Copy' }, handler(accessor) { - copyMarker(accessor.get(IPanelService)); + copyMarker(accessor.get(IPanelService), accessor.get(IClipboardService)); }, menu: { menuId: MenuId.ProblemsPanelContext, @@ -123,9 +122,9 @@ registerAction({ }); registerAction({ id: Constants.MARKER_COPY_MESSAGE_ACTION_ID, - title: localize('copyMessage', "Copy Message"), + title: { value: localize('copyMessage', "Copy Message"), original: 'Copy Message' }, handler(accessor) { - copyMessage(accessor.get(IPanelService)); + copyMessage(accessor.get(IPanelService), accessor.get(IClipboardService)); }, menu: { menuId: MenuId.ProblemsPanelContext, @@ -135,9 +134,9 @@ registerAction({ }); registerAction({ id: Constants.RELATED_INFORMATION_COPY_MESSAGE_ACTION_ID, - title: localize('copyMessage', "Copy Message"), + title: { value: localize('copyMessage', "Copy Message"), original: 'Copy Message' }, handler(accessor) { - copyRelatedInformationMessage(accessor.get(IPanelService)); + copyRelatedInformationMessage(accessor.get(IPanelService), accessor.get(IClipboardService)); }, menu: { menuId: MenuId.ProblemsPanelContext, @@ -178,11 +177,11 @@ registerAction({ panel.markersViewModel.multiline = true; } }, - title: localize('show multiline', "Show message in multiple lines"), + title: { value: localize('show multiline', "Show message in multiple lines"), original: 'Problems: Show message in multiple lines' }, category: localize('problems', "Problems"), menu: { menuId: MenuId.CommandPalette, - when: new RawContextKey('activePanel', Constants.MARKERS_PANEL_ID) + when: ActivePanelContext.isEqualTo(Constants.MARKERS_PANEL_ID) } }); registerAction({ @@ -194,40 +193,40 @@ registerAction({ panel.markersViewModel.multiline = false; } }, - title: localize('show singleline', "Show message in single line"), + title: { value: localize('show singleline', "Show message in single line"), original: 'Problems: Show message in single line' }, category: localize('problems', "Problems"), menu: { menuId: MenuId.CommandPalette, - when: new RawContextKey('activePanel', Constants.MARKERS_PANEL_ID) + when: ActivePanelContext.isEqualTo(Constants.MARKERS_PANEL_ID) } }); -function copyMarker(panelService: IPanelService) { +function copyMarker(panelService: IPanelService, clipboardService: IClipboardService) { const activePanel = panelService.getActivePanel(); if (activePanel instanceof MarkersPanel) { const element = (activePanel).getFocusElement(); if (element instanceof Marker) { - clipboard.writeText(`${element}`); + clipboardService.writeText(`${element}`); } } } -function copyMessage(panelService: IPanelService) { +function copyMessage(panelService: IPanelService, clipboardService: IClipboardService) { const activePanel = panelService.getActivePanel(); if (activePanel instanceof MarkersPanel) { const element = (activePanel).getFocusElement(); if (element instanceof Marker) { - clipboard.writeText(element.marker.message); + clipboardService.writeText(element.marker.message); } } } -function copyRelatedInformationMessage(panelService: IPanelService) { +function copyRelatedInformationMessage(panelService: IPanelService, clipboardService: IClipboardService) { const activePanel = panelService.getActivePanel(); if (activePanel instanceof MarkersPanel) { const element = (activePanel).getFocusElement(); if (element instanceof RelatedInformation) { - clipboard.writeText(element.raw.message); + clipboardService.writeText(element.raw.message); } } } @@ -246,64 +245,6 @@ function focusProblemsFilter(panelService: IPanelService) { } } -interface IActionDescriptor { - id: string; - handler: ICommandHandler; - - // ICommandUI - title?: string; - category?: string; - f1?: boolean; - - // - menu?: { - menuId: MenuId, - when?: ContextKeyExpr; - group?: string; - }; - - // - keybinding?: { - when?: ContextKeyExpr; - weight?: number; - keys: IKeybindings; - }; -} - -function registerAction(desc: IActionDescriptor) { - - const { id, handler, title, category, menu, keybinding } = desc; - - // 1) register as command - CommandsRegistry.registerCommand(id, handler); - - // 2) menus - let command = { id, title, category }; - if (menu) { - let { menuId, when, group } = menu; - MenuRegistry.appendMenuItem(menuId, { - command, - when, - group - }); - } - - // 3) keybindings - if (keybinding) { - let { when, weight, keys } = keybinding; - KeybindingsRegistry.registerKeybindingRule({ - id, - when, - weight, - primary: keys.primary, - secondary: keys.secondary, - linux: keys.linux, - mac: keys.mac, - win: keys.win - }); - } -} - MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { group: '4_panels', command: { diff --git a/src/vs/workbench/contrib/markers/browser/markers.ts b/src/vs/workbench/contrib/markers/browser/markers.ts new file mode 100644 index 0000000000..e738fd6a50 --- /dev/null +++ b/src/vs/workbench/contrib/markers/browser/markers.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { MarkersModel, compareMarkersByUri } from './markersModel'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IMarkerService, MarkerSeverity, IMarker } from 'vs/platform/markers/common/markers'; +import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; +import { localize } from 'vs/nls'; +import Constants from './constants'; +import { URI } from 'vs/base/common/uri'; +import { groupBy } from 'vs/base/common/arrays'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; + +export const IMarkersWorkbenchService = createDecorator('markersWorkbenchService'); + +export interface IFilter { + filterText: string; + useFilesExclude: boolean; +} + +export interface IMarkersWorkbenchService { + _serviceBrand: any; + readonly markersModel: MarkersModel; +} + +export class MarkersWorkbenchService extends Disposable implements IMarkersWorkbenchService { + _serviceBrand: any; + + readonly markersModel: MarkersModel; + + constructor( + @IMarkerService private readonly markerService: IMarkerService, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super(); + this.markersModel = this._register(instantiationService.createInstance(MarkersModel, this.readMarkers())); + + for (const group of groupBy(this.readMarkers(), compareMarkersByUri)) { + this.markersModel.setResourceMarkers(group[0].resource, group); + } + + this._register(markerService.onMarkerChanged(resources => this.onMarkerChanged(resources))); + } + + private onMarkerChanged(resources: URI[]): void { + for (const resource of resources) { + this.markersModel.setResourceMarkers(resource, this.readMarkers(resource)); + } + } + + private readMarkers(resource?: URI): IMarker[] { + return this.markerService.read({ resource, severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }); + } + +} + +export class ActivityUpdater extends Disposable implements IWorkbenchContribution { + + constructor( + @IActivityService private readonly activityService: IActivityService, + @IMarkersWorkbenchService private readonly markersWorkbenchService: IMarkersWorkbenchService + ) { + super(); + this._register(this.markersWorkbenchService.markersModel.onDidChange(() => this.updateBadge())); + this.updateBadge(); + } + + private updateBadge(): void { + const total = this.markersWorkbenchService.markersModel.resourceMarkers.reduce((r, rm) => r + rm.markers.length, 0); + const message = localize('totalProblems', 'Total {0} Problems', total); + this.activityService.showActivity(Constants.MARKERS_PANEL_ID, new NumberBadge(total, () => message)); + } +} diff --git a/src/vs/workbench/parts/markers/electron-browser/markersFileDecorations.ts b/src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts similarity index 100% rename from src/vs/workbench/parts/markers/electron-browser/markersFileDecorations.ts rename to src/vs/workbench/contrib/markers/browser/markersFileDecorations.ts diff --git a/src/vs/workbench/parts/markers/electron-browser/markersFilterOptions.ts b/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts similarity index 64% rename from src/vs/workbench/parts/markers/electron-browser/markersFilterOptions.ts rename to src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts index c5ca6a296d..9418029d64 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersFilterOptions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersFilterOptions.ts @@ -3,10 +3,12 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; +import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { IFilter, matchesPrefix, matchesFuzzy, matchesFuzzy2 } from 'vs/base/common/filters'; -import { ParsedExpression, IExpression, splitGlobAware, getEmptyExpression, parse } from 'vs/base/common/glob'; +import { IExpression, splitGlobAware, getEmptyExpression } from 'vs/base/common/glob'; import * as strings from 'vs/base/common/strings'; +import { URI } from 'vs/base/common/uri'; +import { ResourceGlobMatcher } from 'vs/base/common/resources'; export class FilterOptions { @@ -16,19 +18,17 @@ export class FilterOptions { readonly filterErrors: boolean = false; readonly filterWarnings: boolean = false; readonly filterInfos: boolean = false; - readonly excludePattern: ParsedExpression | null = null; - readonly includePattern: ParsedExpression | null = null; readonly textFilter: string = ''; + readonly excludesMatcher: ResourceGlobMatcher; + readonly includesMatcher: ResourceGlobMatcher; - constructor(readonly filter: string = '', excludePatterns: IExpression = {}) { + constructor(readonly filter: string = '', filesExclude: { root: URI, expression: IExpression }[] | IExpression = []) { filter = filter.trim(); - for (const key of Object.keys(excludePatterns)) { - if (excludePatterns[key]) { - this.setPattern(excludePatterns, key); - } - delete excludePatterns[key]; - } - const includePatterns: IExpression = getEmptyExpression(); + + const filesExcludeByRoot = Array.isArray(filesExclude) ? filesExclude : []; + const excludesExpression: IExpression = Array.isArray(filesExclude) ? getEmptyExpression() : filesExclude; + + const includeExpression: IExpression = getEmptyExpression(); if (filter) { const filters = splitGlobAware(filter, ',').map(s => s.trim()).filter(s => !!s.length); for (const f of filters) { @@ -36,19 +36,16 @@ export class FilterOptions { this.filterWarnings = this.filterWarnings || this.matches(f, Messages.MARKERS_PANEL_FILTER_WARNINGS); this.filterInfos = this.filterInfos || this.matches(f, Messages.MARKERS_PANEL_FILTER_INFOS); if (strings.startsWith(f, '!')) { - this.setPattern(excludePatterns, strings.ltrim(f, '!')); + this.setPattern(excludesExpression, strings.ltrim(f, '!')); } else { - this.setPattern(includePatterns, f); + this.setPattern(includeExpression, f); this.textFilter += ` ${f}`; } } } - if (Object.keys(excludePatterns).length) { - this.excludePattern = parse(excludePatterns); - } - if (Object.keys(includePatterns).length) { - this.includePattern = parse(includePatterns); - } + + this.excludesMatcher = new ResourceGlobMatcher(excludesExpression, filesExcludeByRoot); + this.includesMatcher = new ResourceGlobMatcher(includeExpression, []); this.textFilter = this.textFilter.trim(); } diff --git a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts b/src/vs/workbench/contrib/markers/browser/markersModel.ts similarity index 97% rename from src/vs/workbench/parts/markers/electron-browser/markersModel.ts rename to src/vs/workbench/contrib/markers/browser/markersModel.ts index fa962433d5..67c57c1c84 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersModel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersModel.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as paths from 'vs/base/common/paths'; +import { basename } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Range, IRange } from 'vs/editor/common/core/range'; import { IMarker, MarkerSeverity, IRelatedInformation, IMarkerData } from 'vs/platform/markers/common/markers'; @@ -47,7 +47,7 @@ export class ResourceMarkers { get path(): string { return this.resource.fsPath; } @memoize - get name(): string { return paths.basename(this.resource.fsPath); } + get name(): string { return basename(this.resource); } @memoize get hash(): string { diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts similarity index 84% rename from src/vs/workbench/parts/markers/electron-browser/markersPanel.ts rename to src/vs/workbench/contrib/markers/browser/markersPanel.ts index 8eb66d7b24..50df818981 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -11,16 +11,16 @@ import { IAction, IActionItem, Action } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Panel } from 'vs/workbench/browser/panel'; import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import Constants from 'vs/workbench/parts/markers/electron-browser/constants'; -import { Marker, ResourceMarkers, RelatedInformation, MarkersModel } from 'vs/workbench/parts/markers/electron-browser/markersModel'; +import Constants from 'vs/workbench/contrib/markers/browser/constants'; +import { Marker, ResourceMarkers, RelatedInformation, MarkersModel } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkersFilterActionItem, MarkersFilterAction, IMarkersFilterActionChangeEvent, IMarkerFilterController } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; +import { MarkersFilterActionItem, MarkersFilterAction, IMarkersFilterActionChangeEvent, IMarkerFilterController } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; +import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { IMarkersWorkbenchService } from 'vs/workbench/parts/markers/electron-browser/markers'; +import { IMarkersWorkbenchService } from 'vs/workbench/contrib/markers/browser/markers'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { localize } from 'vs/nls'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -28,17 +28,16 @@ import { Iterator } from 'vs/base/common/iterator'; import { ITreeElement, ITreeNode, ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree'; import { Relay, Event, Emitter } from 'vs/base/common/event'; import { WorkbenchObjectTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService'; -import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/markersFilterOptions'; -import { IExpression, getEmptyExpression } from 'vs/base/common/glob'; -import { mixin, deepClone } from 'vs/base/common/objects'; -import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { isAbsolute, join } from 'vs/base/common/paths'; -import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, TreeElement, MarkersTreeAccessibilityProvider, MarkersViewModel } from 'vs/workbench/parts/markers/electron-browser/markersTreeViewer'; +import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; +import { IExpression } from 'vs/base/common/glob'; +import { deepClone } from 'vs/base/common/objects'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { FilterData, Filter, VirtualDelegate, ResourceMarkersRenderer, MarkerRenderer, RelatedInformationRenderer, TreeElement, MarkersTreeAccessibilityProvider, MarkersViewModel, ResourceDragAndDrop } from 'vs/workbench/contrib/markers/browser/markersTreeViewer'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Separator, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { domEvent } from 'vs/base/browser/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -188,7 +187,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { public openFileAtElement(element: any, preserveFocus: boolean, sideByside: boolean, pinned: boolean): boolean { const { resource, selection, event, data } = element instanceof Marker ? { resource: element.resource, selection: element.range, event: 'problems.selectDiagnostic', data: this.getTelemetryData(element.marker) } : element instanceof RelatedInformation ? { resource: element.raw.resource, selection: element.raw, event: 'problems.selectRelatedInformation', data: this.getTelemetryData(element.marker) } : { resource: null, selection: null, event: null, data: null }; - if (resource && selection) { + if (resource && selection && event) { /* __GDPR__ "problems.selectDiagnostic" : { "source": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }, @@ -229,13 +228,13 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.cachedFilterStats = undefined; if (marker) { - this.tree.refresh(marker); + this.tree.rerender(marker); } else { this.tree.setChildren(null, createModelIterator(this.markersWorkbenchService.markersModel)); } const { total, filtered } = this.getFilterStats(); - dom.toggleClass(this.treeContainer, 'hidden', total > 0 && filtered === 0); + dom.toggleClass(this.treeContainer, 'hidden', total === 0 || filtered === 0); this.renderMessage(); this._onDidFilter.fire(); } @@ -247,54 +246,30 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private updateFilter() { this.cachedFilterStats = undefined; - const excludeExpression = this.getExcludeExpression(this.filterAction.useFilesExclude); - this.filter.options = new FilterOptions(this.filterAction.filterText, excludeExpression); + this.filter.options = new FilterOptions(this.filterAction.filterText, this.getFilesExcludeExpressions()); this.tree.refilter(); this._onDidFilter.fire(); const { total, filtered } = this.getFilterStats(); - dom.toggleClass(this.treeContainer, 'hidden', total > 0 && filtered === 0); + dom.toggleClass(this.treeContainer, 'hidden', total === 0 || filtered === 0); this.renderMessage(); } - private getExcludeExpression(useFilesExclude: boolean): IExpression { - if (!useFilesExclude) { - return {}; + private getFilesExcludeExpressions(): { root: URI, expression: IExpression }[] | IExpression { + if (!this.filterAction.useFilesExclude) { + return []; } const workspaceFolders = this.workspaceContextService.getWorkspace().folders; - if (workspaceFolders.length) { - const result = getEmptyExpression(); - for (const workspaceFolder of workspaceFolders) { - mixin(result, this.getExcludesForFolder(workspaceFolder)); - } - return result; - } else { - return this.getFilesExclude(); - } - } - - private getExcludesForFolder(workspaceFolder: IWorkspaceFolder): IExpression { - const expression = this.getFilesExclude(workspaceFolder.uri); - return this.getAbsoluteExpression(expression, workspaceFolder.uri.fsPath); + return workspaceFolders.length + ? workspaceFolders.map(workspaceFolder => ({ root: workspaceFolder.uri, expression: this.getFilesExclude(workspaceFolder.uri) })) + : this.getFilesExclude(); } private getFilesExclude(resource?: URI): IExpression { return deepClone(this.configurationService.getValue('files.exclude', { resource })) || {}; } - private getAbsoluteExpression(expr: IExpression, root: string): IExpression { - return Object.keys(expr) - .reduce((absExpr: IExpression, key: string) => { - if (expr[key] && !isAbsolute(key)) { - const absPattern = join(root, key); - absExpr[absPattern] = expr[key]; - } - - return absExpr; - }, Object.create(null)); - } - private createMessageBox(parent: HTMLElement): void { this.messageBoxContainer = dom.append(parent, dom.$('.message-box-container')); this.messageBoxContainer.setAttribute('aria-labelledby', 'markers-panel-arialabel'); @@ -319,7 +294,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.instantiationService.createInstance(MarkerRenderer, this.markersViewModel), this.instantiationService.createInstance(RelatedInformationRenderer) ]; - this.filter = new Filter(); + this.filter = new Filter(new FilterOptions()); const accessibilityProvider = this.instantiationService.createInstance(MarkersTreeAccessibilityProvider); const identityProvider = { @@ -335,7 +310,9 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { { filter: this.filter, accessibilityProvider, - identityProvider + identityProvider, + dnd: new ResourceDragAndDrop(this.instantiationService), + expandOnlyOnTwistieClick: (e: TreeElement) => e instanceof Marker && e.relatedInformation.length > 0 } ) as any as WorkbenchObjectTree; @@ -355,7 +332,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { const markersNavigator = this._register(new TreeResourceNavigator2(this.tree, { openOnFocus: true })); this._register(Event.debounce(markersNavigator.onDidOpenResource, (last, event) => event, 75, true)(options => { - this.openFileAtElement(options.element, options.editorOptions.preserveFocus, options.sideBySide, options.editorOptions.pinned); + this.openFileAtElement(options.element, !!options.editorOptions.preserveFocus, options.sideBySide, !!options.editorOptions.pinned); })); this._register(this.tree.onDidChangeCollapseState(({ node }) => { const { element } = node; @@ -380,14 +357,26 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { // move focus to input, whenever a key is pressed in the panel container this._register(domEvent(parent, 'keydown')(e => { - if (this.filterInputActionItem && this.keybindingService.mightProducePrintableCharacter(e)) { + if (this.filterInputActionItem && this.keybindingService.mightProducePrintableCharacter(new StandardKeyboardEvent(e))) { this.filterInputActionItem.focus(); } })); + + this._register(Event.any(this.tree.onDidChangeSelection, this.tree.onDidChangeFocus)(() => { + const elements = [...this.tree.getSelection(), ...this.tree.getFocus()]; + for (const element of elements) { + if (element instanceof Marker) { + const viewModel = this.markersViewModel.getViewModel(element); + if (viewModel) { + viewModel.showLightBulb(); + } + } + } + })); } private createActions(): void { - this.collapseAllAction = new Action('vs.tree.collapse', localize('collapse', "Collapse"), 'monaco-tree-action collapse-all', true, async () => { + this.collapseAllAction = new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action collapse-all', true, async () => { this.tree.collapseAll(); this.tree.setSelection([]); this.tree.setFocus([]); @@ -400,10 +389,29 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } private createListeners(): void { - const onModelChange = Event.debounce(this.markersWorkbenchService.markersModel.onDidChange, (uris, uri) => { if (!uris) { uris = []; } uris.push(uri); return uris; }, 0); + const onModelOrActiveEditorChanged = Event.debounce(Event.any(this.markersWorkbenchService.markersModel.onDidChange, Event.map(this.editorService.onDidActiveEditorChange, () => true)), (result, e) => { + if (!result) { + result = { + resources: [], + activeEditorChanged: false + }; + } + if (e === true) { + result.activeEditorChanged = true; + } else { + result.resources.push(e); + } + return result; + }, 0); - this._register(onModelChange(this.onDidChangeModel, this)); - this._register(this.editorService.onDidActiveEditorChange(this.onActiveEditorChanged, this)); + this._register(onModelOrActiveEditorChanged(({ resources, activeEditorChanged }) => { + if (resources) { + this.onDidChangeModel(resources); + } + if (activeEditorChanged) { + this.onActiveEditorChanged(); + } + }, this)); this._register(this.tree.onDidChangeSelection(() => this.onSelected())); this._register(this.filterAction.onDidChange((event: IMarkersFilterActionChangeEvent) => { if (event.filterText || event.useFilesExclude) { @@ -433,14 +441,15 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } private isCurrentResourceGotAddedToMarkersData(changedResources: URI[]) { - if (!this.currentActiveResource) { + const currentlyActiveResource = this.currentActiveResource; + if (!currentlyActiveResource) { return false; } const resourceForCurrentActiveResource = this.getResourceForCurrentActiveResource(); if (resourceForCurrentActiveResource) { return false; } - return changedResources.some(r => r.toString() === this.currentActiveResource.toString()); + return changedResources.some(r => r.toString() === currentlyActiveResource.toString()); } private onActiveEditorChanged(): void { @@ -450,13 +459,13 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { private setCurrentActiveEditor(): void { const activeEditor = this.editorService.activeEditor; - this.currentActiveResource = activeEditor ? activeEditor.getResource() : undefined; + this.currentActiveResource = activeEditor ? activeEditor.getResource() : null; } private onSelected(): void { let selection = this.tree.getSelection(); if (selection && selection.length > 0) { - this.lastSelectedRelativeTop = this.tree.getRelativeTop(selection[0]); + this.lastSelectedRelativeTop = this.tree.getRelativeTop(selection[0]) || 0; } } @@ -464,7 +473,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.cachedFilterStats = undefined; this.tree.setChildren(null, createModelIterator(this.markersWorkbenchService.markersModel)); const { total, filtered } = this.getFilterStats(); - dom.toggleClass(this.treeContainer, 'hidden', total > 0 && filtered === 0); + dom.toggleClass(this.treeContainer, 'hidden', total === 0 || filtered === 0); this.renderMessage(); } @@ -603,41 +612,43 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { } private onContextMenu(e: ITreeContextMenuEvent): void { - if (!e.element) { + const element = e.element; + if (!element) { return; } e.browserEvent.preventDefault(); e.browserEvent.stopPropagation(); - this._getMenuActions(e.element).then(actions => { - this.contextMenuService.showContextMenu({ - getAnchor: () => e.anchor, - getActions: () => actions, - getActionItem: (action) => { - const keybinding = this.keybindingService.lookupKeybinding(action.id); - if (keybinding) { - return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel() }); - } - return null; - }, - onHide: (wasCancelled?: boolean) => { - if (wasCancelled) { - this.tree.domFocus(); - } + this.contextMenuService.showContextMenu({ + getAnchor: () => e.anchor!, + getActions: () => this.getMenuActions(element), + getActionItem: (action) => { + const keybinding = this.keybindingService.lookupKeybinding(action.id); + if (keybinding) { + return new ActionItem(action, action, { label: true, keybinding: keybinding.getLabel() }); } - }); + return null; + }, + onHide: (wasCancelled?: boolean) => { + if (wasCancelled) { + this.tree.domFocus(); + } + } }); } - private async _getMenuActions(element: TreeElement): Promise { + private getMenuActions(element: TreeElement): IAction[] { const result: IAction[] = []; if (element instanceof Marker) { - const quickFixActions = await this.markersWorkbenchService.getQuickFixActions(element); - if (quickFixActions.length) { - result.push(...quickFixActions); - result.push(new Separator()); + const viewModel = this.markersViewModel.getViewModel(element); + if (viewModel) { + const quickFixActions = viewModel.quickFixAction.quickFixes; + if (quickFixActions.length) { + result.push(...quickFixActions); + result.push(new Separator()); + } } } @@ -655,11 +666,11 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { return result; } - public getFocusElement(): TreeElement { + public getFocusElement() { return this.tree.getFocus()[0]; } - public getActionItem(action: IAction): IActionItem { + public getActionItem(action: IAction): IActionItem | null { if (action.id === MarkersFilterAction.ID) { this.filterInputActionItem = this.instantiationService.createInstance(MarkersFilterActionItem, this.filterAction, this); return this.filterInputActionItem; @@ -716,4 +727,4 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.markersViewModel.dispose(); this.disposables = dispose(this.disposables); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts similarity index 85% rename from src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts rename to src/vs/workbench/contrib/markers/browser/markersPanelActions.ts index 778d28e636..56883723e6 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersPanelActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts @@ -5,32 +5,30 @@ import { Delayer } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; -import { Action, IActionChangeEvent } from 'vs/base/common/actions'; +import { Action, IActionChangeEvent, IAction } from 'vs/base/common/actions'; import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; -import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; -import Constants from 'vs/workbench/parts/markers/electron-browser/constants'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import Messages from 'vs/workbench/contrib/markers/browser/messages'; +import Constants from 'vs/workbench/contrib/markers/browser/constants'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachStylerCallback, attachCheckboxStyler } from 'vs/platform/theme/common/styler'; -import { IMarkersWorkbenchService } from 'vs/workbench/parts/markers/electron-browser/markers'; +import { IMarkersWorkbenchService } from 'vs/workbench/contrib/markers/browser/markers'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { BaseActionItem, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { badgeBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; import { Checkbox } from 'vs/base/browser/ui/checkbox/checkbox'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ContextScopedHistoryInputBox } from 'vs/platform/widget/browser/contextScopedHistoryWidget'; -import { Marker } from 'vs/workbench/parts/markers/electron-browser/markersModel'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { isEqual } from 'vs/base/common/resources'; +import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; +import { Marker } from 'vs/workbench/contrib/markers/browser/markersModel'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; -import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/markersFilterOptions'; +import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; export class ToggleMarkersPanelAction extends TogglePanelAction { @@ -39,11 +37,11 @@ export class ToggleMarkersPanelAction extends TogglePanelAction { public static readonly LABEL = Messages.MARKERS_PANEL_TOGGLE_LABEL; constructor(id: string, label: string, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IPanelService panelService: IPanelService, @IMarkersWorkbenchService markersWorkbenchService: IMarkersWorkbenchService ) { - super(id, label, Constants.MARKERS_PANEL_ID, panelService, partService); + super(id, label, Constants.MARKERS_PANEL_ID, panelService, layoutService); } } @@ -60,7 +58,7 @@ export class ShowProblemsPanelAction extends Action { public run(): Promise { this.panelService.openPanel(Constants.MARKERS_PANEL_ID, true); - return Promise.resolve(null); + return Promise.resolve(); } } @@ -197,8 +195,9 @@ export class MarkersFilterActionItem extends BaseActionItem { private createBadge(container: HTMLElement): void { this.filterBadge = DOM.append(container, DOM.$('.markers-panel-filter-badge')); - this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder }, colors => { + this._register(attachStylerCallback(this.themeService, { badgeBackground, badgeForeground, contrastBorder }, colors => { const background = colors.badgeBackground ? colors.badgeBackground.toString() : null; + const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : null; const border = colors.contrastBorder ? colors.contrastBorder.toString() : null; this.filterBadge.style.backgroundColor = background; @@ -206,6 +205,7 @@ export class MarkersFilterActionItem extends BaseActionItem { this.filterBadge.style.borderWidth = border ? '1px' : null; this.filterBadge.style.borderStyle = border ? 'solid' : null; this.filterBadge.style.borderColor = border; + this.filterBadge.style.color = foreground; })); this.updateBadge(); this._register(this.filterController.onDidFilter(() => this.updateBadge())); @@ -293,29 +293,31 @@ export class MarkersFilterActionItem extends BaseActionItem { export class QuickFixAction extends Action { public static readonly ID: string = 'workbench.actions.problems.quickfix'; + private static readonly CLASS: string = 'markers-panel-action-quickfix'; + private static readonly AUTO_FIX_CLASS: string = QuickFixAction.CLASS + ' autofixable'; - private updated: boolean = false; private disposables: IDisposable[] = []; - private _onShowQuickFixes: Emitter = new Emitter(); + private readonly _onShowQuickFixes: Emitter = new Emitter(); readonly onShowQuickFixes: Event = this._onShowQuickFixes.event; + private _quickFixes: IAction[] = []; + get quickFixes(): IAction[] { + return this._quickFixes; + } + set quickFixes(quickFixes: IAction[]) { + this._quickFixes = quickFixes; + this.enabled = this._quickFixes.length > 0; + } + + autoFixable(autofixable: boolean) { + this.class = autofixable ? QuickFixAction.AUTO_FIX_CLASS : QuickFixAction.CLASS; + } + constructor( readonly marker: Marker, - @IModelService modelService: IModelService, - @IMarkersWorkbenchService private readonly markerWorkbenchService: IMarkersWorkbenchService, ) { - super(QuickFixAction.ID, Messages.MARKERS_PANEL_ACTION_TOOLTIP_QUICKFIX, 'markers-panel-action-quickfix', false); - this.disposables.push(this._onShowQuickFixes); - if (modelService.getModel(this.marker.resource)) { - this.update(); - } else { - modelService.onModelAdded(model => { - if (isEqual(model.uri, marker.resource)) { - this.update(); - } - }, this, this.disposables); - } + super(QuickFixAction.ID, Messages.MARKERS_PANEL_ACTION_TOOLTIP_QUICKFIX, QuickFixAction.CLASS, false); } run(): Promise { @@ -323,13 +325,6 @@ export class QuickFixAction extends Action { return Promise.resolve(); } - private update(): void { - if (!this.updated) { - this.markerWorkbenchService.hasQuickFixes(this.marker).then(hasFixes => this.enabled = hasFixes); - this.updated = true; - } - } - dispose(): void { dispose(this.disposables); super.dispose(); @@ -340,7 +335,6 @@ export class QuickFixActionItem extends ActionItem { constructor(action: QuickFixAction, @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IMarkersWorkbenchService private readonly markerWorkbenchService: IMarkersWorkbenchService ) { super(null, action, { icon: true, label: false }); } @@ -358,11 +352,12 @@ export class QuickFixActionItem extends ActionItem { return; } const elementPosition = DOM.getDomNodePagePosition(this.element); - this.markerWorkbenchService.getQuickFixActions((this.getAction()).marker).then(actions => { + const quickFixes = (this.getAction()).quickFixes; + if (quickFixes.length) { this.contextMenuService.showContextMenu({ getAnchor: () => ({ x: elementPosition.left + 10, y: elementPosition.top + elementPosition.height + 4 }), - getActions: () => actions + getActions: () => quickFixes }); - }); + } } } diff --git a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts similarity index 72% rename from src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts rename to src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts index 07d1613f03..333c58610d 100644 --- a/src/vs/workbench/parts/markers/electron-browser/markersTreeViewer.ts +++ b/src/vs/workbench/contrib/markers/browser/markersTreeViewer.ts @@ -5,31 +5,44 @@ import * as dom from 'vs/base/browser/dom'; import * as network from 'vs/base/common/network'; -import * as paths from 'vs/base/common/paths'; +import * as paths from 'vs/base/common/path'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IMarker, MarkerSeverity } from 'vs/platform/markers/common/markers'; -import { ResourceMarkers, Marker, RelatedInformation } from 'vs/workbench/parts/markers/electron-browser/markersModel'; -import Messages from 'vs/workbench/parts/markers/electron-browser/messages'; +import { ResourceMarkers, Marker, RelatedInformation } from 'vs/workbench/contrib/markers/browser/markersModel'; +import Messages from 'vs/workbench/contrib/markers/browser/messages'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { QuickFixAction, QuickFixActionItem } from 'vs/workbench/parts/markers/electron-browser/markersPanelActions'; +import { QuickFixAction, QuickFixActionItem } from 'vs/workbench/contrib/markers/browser/markersPanelActions'; import { ILabelService } from 'vs/platform/label/common/label'; -import { dirname } from 'vs/base/common/resources'; +import { dirname, basename, isEqual } from 'vs/base/common/resources'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { ITreeFilter, TreeVisibility, TreeFilterResult, ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree'; -import { FilterOptions } from 'vs/workbench/parts/markers/electron-browser/markersFilterOptions'; +import { ITreeFilter, TreeVisibility, TreeFilterResult, ITreeRenderer, ITreeNode, ITreeDragAndDrop, ITreeDragOverReaction } from 'vs/base/browser/ui/tree/tree'; +import { FilterOptions } from 'vs/workbench/contrib/markers/browser/markersFilterOptions'; import { IMatch } from 'vs/base/common/filters'; import { Event, Emitter } from 'vs/base/common/event'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { Action } from 'vs/base/common/actions'; +import { Action, IAction } from 'vs/base/common/actions'; import { localize } from 'vs/nls'; +import { IDragAndDropData } from 'vs/base/browser/dnd'; +import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; +import { fillResourceDataTransfers } from 'vs/workbench/browser/dnd'; +import { CancelablePromise, createCancelablePromise, Delayer } from 'vs/base/common/async'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { Range } from 'vs/editor/common/core/range'; +import { getCodeActions, CodeActionSet } from 'vs/editor/contrib/codeAction/codeAction'; +import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; +import { ITextModel } from 'vs/editor/common/model'; +import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; export type TreeElement = ResourceMarkers | Marker | RelatedInformation; @@ -53,7 +66,7 @@ export class MarkersTreeAccessibilityProvider implements IAccessibilityProvider< constructor(@ILabelService private readonly labelService: ILabelService) { } - public getAriaLabel(element: TreeElement): string { + public getAriaLabel(element: TreeElement): string | null { if (element instanceof ResourceMarkers) { const path = this.labelService.getUriLabel(element.resource, { relative: true }) || element.resource.fsPath; return Messages.MARKERS_TREE_ARIA_LABEL_RESOURCE(element.markers.length, element.name, paths.dirname(path)); @@ -76,15 +89,17 @@ const enum TemplateId { export class VirtualDelegate implements IListVirtualDelegate { + static LINE_HEIGHT: number = 22; + constructor(private readonly markersViewState: MarkersViewModel) { } getHeight(element: TreeElement): number { if (element instanceof Marker) { const viewModel = this.markersViewState.getViewModel(element); const noOfLines = !viewModel || viewModel.multiline ? element.lines.length : 1; - return noOfLines * 22; + return noOfLines * VirtualDelegate.LINE_HEIGHT; } - return 22; + return VirtualDelegate.LINE_HEIGHT; } getTemplateId(element: TreeElement): string { @@ -232,7 +247,7 @@ class MarkerWidget extends Disposable { private disposables: IDisposable[] = []; constructor( - parent: HTMLElement, + private parent: HTMLElement, private readonly markersViewModel: MarkersViewModel, instantiationService: IInstantiationService ) { @@ -246,7 +261,7 @@ class MarkerWidget extends Disposable { this._register(toDisposable(() => this.disposables = dispose(this.disposables))); } - render(element: Marker, filterData: MarkerFilterData): void { + render(element: Marker, filterData: MarkerFilterData | undefined): void { this.actionBar.clear(); this.multilineActionbar.clear(); if (this.disposables.length) { @@ -259,6 +274,8 @@ class MarkerWidget extends Disposable { this.renderMultilineActionbar(element); this.renderMessageAndDetails(element, filterData); + this.disposables.push(dom.addDisposableListener(this.parent, dom.EventType.MOUSE_OVER, () => this.markersViewModel.onMarkerMouseHover(element))); + this.disposables.push(dom.addDisposableListener(this.parent, dom.EventType.MOUSE_LEAVE, () => this.markersViewModel.onMarkerMouseLeave(element))); } private renderQuickfixActionbar(marker: Marker): void { @@ -285,14 +302,14 @@ class MarkerWidget extends Disposable { const viewModel = this.markersViewModel.getViewModel(marker); const multiline = viewModel && viewModel.multiline; const action = new Action('problems.action.toggleMultiline'); - action.enabled = viewModel && marker.lines.length > 1; + action.enabled = !!viewModel && marker.lines.length > 1; action.tooltip = multiline ? localize('single line', "Show message in single line") : localize('multi line', "Show message in multiple lines"); action.class = multiline ? 'octicon octicon-chevron-up' : 'octicon octicon-chevron-down'; action.run = () => { if (viewModel) { viewModel.multiline = !viewModel.multiline; } return Promise.resolve(); }; this.multilineActionbar.push([action], { icon: true, label: false }); } - private renderMessageAndDetails(element: Marker, filterData: MarkerFilterData) { + private renderMessageAndDetails(element: Marker, filterData: MarkerFilterData | undefined) { const { marker, lines } = element; const viewState = this.markersViewModel.getViewModel(element); const multiline = !viewState || viewState.multiline; @@ -305,11 +322,14 @@ class MarkerWidget extends Disposable { lastLineElement = dom.append(messageContainer, dom.$('.marker-message-line')); const highlightedLabel = new HighlightedLabel(lastLineElement, false); highlightedLabel.set(lines[index], lineMatches[index]); + if (lines[index] === '') { + lastLineElement.style.height = `${VirtualDelegate.LINE_HEIGHT}px`; + } } this.renderDetails(marker, filterData, multiline ? lastLineElement : this.messageAndDetailsContainer); } - private renderDetails(marker: IMarker, filterData: MarkerFilterData, parent: HTMLElement): void { + private renderDetails(marker: IMarker, filterData: MarkerFilterData | undefined, parent: HTMLElement): void { dom.addClass(parent, 'details-container'); const sourceMatches = filterData && filterData.sourceMatches || []; const codeMatches = filterData && filterData.codeMatches || []; @@ -371,7 +391,7 @@ export class RelatedInformationRenderer implements ITreeRenderer { - options = new FilterOptions(); + constructor(public options: FilterOptions) { } filter(element: TreeElement, parentVisibility: TreeVisibility): TreeFilterResult { if (element instanceof ResourceMarkers) { @@ -402,17 +422,17 @@ export class Filter implements ITreeFilter { return false; } - if (this.options.excludePattern && !!this.options.excludePattern(resourceMarkers.resource.fsPath)) { + if (this.options.excludesMatcher.matches(resourceMarkers.resource)) { return false; } - const uriMatches = FilterOptions._filter(this.options.textFilter, paths.basename(resourceMarkers.resource.fsPath)); + const uriMatches = FilterOptions._filter(this.options.textFilter, basename(resourceMarkers.resource)); if (this.options.textFilter && uriMatches) { return { visibility: true, data: { type: FilterDataType.ResourceMarkers, uriMatches } }; } - if (this.options.includePattern && this.options.includePattern(resourceMarkers.resource.fsPath)) { + if (this.options.includesMatcher.matches(resourceMarkers.resource)) { return true; } @@ -455,7 +475,7 @@ export class Filter implements ITreeFilter { return true; } - const uriMatches = FilterOptions._filter(this.options.textFilter, paths.basename(relatedInformation.raw.resource.fsPath)); + const uriMatches = FilterOptions._filter(this.options.textFilter, basename(relatedInformation.raw.resource)); const messageMatches = FilterOptions._messageFilter(this.options.textFilter, paths.basename(relatedInformation.raw.message)); if (uriMatches || messageMatches) { @@ -471,11 +491,26 @@ export class MarkerViewModel extends Disposable { private readonly _onDidChange: Emitter = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; + private modelPromise: CancelablePromise | null = null; + private codeActionsPromise: CancelablePromise | null = null; + constructor( private readonly marker: Marker, - @IInstantiationService private instantiationService: IInstantiationService + @IModelService private modelService: IModelService, + @IInstantiationService private instantiationService: IInstantiationService, + @IBulkEditService private readonly bulkEditService: IBulkEditService, + @ICommandService private readonly commandService: ICommandService, + @IEditorService private readonly editorService: IEditorService ) { super(); + this._register(toDisposable(() => { + if (this.modelPromise) { + this.modelPromise.cancel(); + } + if (this.codeActionsPromise) { + this.codeActionsPromise.cancel(); + } + })); } private _multiline: boolean = true; @@ -497,6 +532,91 @@ export class MarkerViewModel extends Disposable { } return this._quickFixAction; } + + showLightBulb(): void { + this.setQuickFixes(true); + } + + showQuickfixes(): void { + this.setQuickFixes(false).then(() => this.quickFixAction.run()); + } + + async getQuickFixes(waitForModel: boolean): Promise { + const codeActions = await this.getCodeActions(waitForModel); + return codeActions ? this.toActions(codeActions) : []; + } + + private async setQuickFixes(waitForModel: boolean): Promise { + const codeActions = await this.getCodeActions(waitForModel); + this.quickFixAction.quickFixes = codeActions ? this.toActions(codeActions) : []; + this.quickFixAction.autoFixable(!!codeActions && codeActions.hasAutoFix); + } + + private getCodeActions(waitForModel: boolean): Promise { + if (this.codeActionsPromise !== null) { + return this.codeActionsPromise; + } + return this.getModel(waitForModel) + .then(model => { + if (model) { + if (!this.codeActionsPromise) { + this.codeActionsPromise = createCancelablePromise(cancellationToken => { + return getCodeActions(model, new Range(this.marker.range.startLineNumber, this.marker.range.startColumn, this.marker.range.endLineNumber, this.marker.range.endColumn), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken); + }); + } + return this.codeActionsPromise; + } + return null; + }); + } + + private toActions(codeActions: CodeActionSet): IAction[] { + return codeActions.actions.map(codeAction => new Action( + codeAction.command ? codeAction.command.id : codeAction.title, + codeAction.title, + undefined, + true, + () => { + return this.openFileAtMarker(this.marker) + .then(() => applyCodeAction(codeAction, this.bulkEditService, this.commandService)); + })); + } + + private openFileAtMarker(element: Marker): Promise { + const { resource, selection } = { resource: element.resource, selection: element.range }; + return this.editorService.openEditor({ + resource, + options: { + selection, + preserveFocus: true, + pinned: false, + revealIfVisible: true + }, + }, ACTIVE_GROUP).then(() => undefined); + } + + private getModel(waitForModel: boolean): Promise { + const model = this.modelService.getModel(this.marker.resource); + if (model) { + return Promise.resolve(model); + } + if (waitForModel) { + if (this.modelPromise === null) { + this.modelPromise = createCancelablePromise(cancellationToken => { + return new Promise((c) => { + this._register(this.modelService.onModelAdded(model => { + if (isEqual(model.uri, this.marker.resource)) { + c(model); + } + })); + }); + }); + } + return this.modelPromise; + } + return Promise.resolve(null); + } + } export class MarkersViewModel extends Disposable { @@ -509,6 +629,9 @@ export class MarkersViewModel extends Disposable { private bulkUpdate: boolean = false; + private hoveredMarker: Marker | null; + private hoverDelayer: Delayer = new Delayer(300); + constructor( multiline: boolean = true, @IInstantiationService private instantiationService: IInstantiationService @@ -543,6 +666,9 @@ export class MarkersViewModel extends Disposable { dispose(value.disposables); } this.markersViewStates.delete(marker.hash); + if (this.hoveredMarker === marker) { + this.hoveredMarker = null; + } } this.markersPerResource.delete(resource.toString()); } @@ -552,6 +678,24 @@ export class MarkersViewModel extends Disposable { return value ? value.viewModel : null; } + onMarkerMouseHover(marker: Marker): void { + this.hoveredMarker = marker; + this.hoverDelayer.trigger(() => { + if (this.hoveredMarker) { + const model = this.getViewModel(this.hoveredMarker); + if (model) { + model.showLightBulb(); + } + } + }); + } + + onMarkerMouseLeave(marker: Marker): void { + if (this.hoveredMarker === marker) { + this.hoveredMarker = null; + } + } + private _multiline: boolean = true; get multiline(): boolean { return this._multiline; @@ -584,3 +728,43 @@ export class MarkersViewModel extends Disposable { } } + +export class ResourceDragAndDrop implements ITreeDragAndDrop { + constructor( + private instantiationService: IInstantiationService + ) { } + + onDragOver(data: IDragAndDropData, targetElement: TreeElement, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction { + return false; + } + + getDragURI(element: TreeElement): string | null { + if (element instanceof ResourceMarkers) { + return element.resource.toString(); + } + return null; + } + + getDragLabel?(elements: TreeElement[]): string | undefined { + if (elements.length > 1) { + return String(elements.length); + } + const element = elements[0]; + return element instanceof ResourceMarkers ? basename(element.resource) : undefined; + } + + onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { + const elements = (data as ElementsDragAndDropData).elements; + const resources: URI[] = elements + .filter(e => e instanceof ResourceMarkers) + .map((resourceMarker: ResourceMarkers) => resourceMarker.resource); + + if (resources.length) { + // Apply some datatransfer types to allow for dragging the element outside of the application + this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, originalEvent); + } + } + + drop(data: IDragAndDropData, targetElement: TreeElement, targetIndex: number, originalEvent: DragEvent): void { + } +} diff --git a/src/vs/workbench/parts/markers/electron-browser/media/excludeSettings-dark.svg b/src/vs/workbench/contrib/markers/browser/media/excludeSettings-dark.svg similarity index 100% rename from src/vs/workbench/parts/markers/electron-browser/media/excludeSettings-dark.svg rename to src/vs/workbench/contrib/markers/browser/media/excludeSettings-dark.svg diff --git a/src/vs/workbench/parts/markers/electron-browser/media/excludeSettings.svg b/src/vs/workbench/contrib/markers/browser/media/excludeSettings.svg similarity index 100% rename from src/vs/workbench/parts/markers/electron-browser/media/excludeSettings.svg rename to src/vs/workbench/contrib/markers/browser/media/excludeSettings.svg diff --git a/src/vs/workbench/contrib/markers/browser/media/lightbulb-autofix-dark.svg b/src/vs/workbench/contrib/markers/browser/media/lightbulb-autofix-dark.svg new file mode 100644 index 0000000000..40678e79d7 --- /dev/null +++ b/src/vs/workbench/contrib/markers/browser/media/lightbulb-autofix-dark.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/workbench/contrib/markers/browser/media/lightbulb-autofix.svg b/src/vs/workbench/contrib/markers/browser/media/lightbulb-autofix.svg new file mode 100644 index 0000000000..a4b4858e4d --- /dev/null +++ b/src/vs/workbench/contrib/markers/browser/media/lightbulb-autofix.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/vs/workbench/parts/markers/electron-browser/media/lightbulb-dark.svg b/src/vs/workbench/contrib/markers/browser/media/lightbulb-dark.svg similarity index 100% rename from src/vs/workbench/parts/markers/electron-browser/media/lightbulb-dark.svg rename to src/vs/workbench/contrib/markers/browser/media/lightbulb-dark.svg diff --git a/src/vs/workbench/parts/markers/electron-browser/media/lightbulb.svg b/src/vs/workbench/contrib/markers/browser/media/lightbulb.svg similarity index 100% rename from src/vs/workbench/parts/markers/electron-browser/media/lightbulb.svg rename to src/vs/workbench/contrib/markers/browser/media/lightbulb.svg diff --git a/src/vs/workbench/parts/markers/electron-browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css similarity index 93% rename from src/vs/workbench/parts/markers/electron-browser/media/markers.css rename to src/vs/workbench/contrib/markers/browser/media/markers.css index aa0998dfee..c2cfcb1a63 100644 --- a/src/vs/workbench/parts/markers/electron-browser/media/markers.css +++ b/src/vs/workbench/contrib/markers/browser/media/markers.css @@ -93,7 +93,6 @@ .markers-panel .markers-panel-container .tree-container .monaco-tl-contents { display: flex; line-height: 22px; - margin-right: 20px; } .hc-black .markers-panel .markers-panel-container .tree-container .monaco-tl-contents { @@ -193,10 +192,19 @@ margin-right: 0px; } +.markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix.autofixable { + background: url('lightbulb-autofix.svg') center center no-repeat; +} + .vs-dark .markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix { background: url('lightbulb-dark.svg') center/80% no-repeat; } +.vs-dark .markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix.autofixable, +.hc-black .markers-panel .monaco-tl-contents .actions .action-label.icon.markers-panel-action-quickfix.autofixable { + background: url('lightbulb-autofix-dark.svg') center center no-repeat; +} + .markers-panel .monaco-tl-contents .actions .monaco-action-bar { display: none; } diff --git a/src/vs/workbench/contrib/markers/browser/media/status-error-inverse.svg b/src/vs/workbench/contrib/markers/browser/media/status-error-inverse.svg new file mode 100644 index 0000000000..3c852a7ffd --- /dev/null +++ b/src/vs/workbench/contrib/markers/browser/media/status-error-inverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/parts/preferences/browser/media/status-error.svg b/src/vs/workbench/contrib/markers/browser/media/status-error.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/status-error.svg rename to src/vs/workbench/contrib/markers/browser/media/status-error.svg diff --git a/src/vs/workbench/contrib/markers/browser/media/status-info-inverse.svg b/src/vs/workbench/contrib/markers/browser/media/status-info-inverse.svg new file mode 100644 index 0000000000..d38c363e0e --- /dev/null +++ b/src/vs/workbench/contrib/markers/browser/media/status-info-inverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/contrib/markers/browser/media/status-info.svg b/src/vs/workbench/contrib/markers/browser/media/status-info.svg new file mode 100644 index 0000000000..6e2e22f67b --- /dev/null +++ b/src/vs/workbench/contrib/markers/browser/media/status-info.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/contrib/markers/browser/media/status-warning-inverse.svg b/src/vs/workbench/contrib/markers/browser/media/status-warning-inverse.svg new file mode 100644 index 0000000000..df44e61b32 --- /dev/null +++ b/src/vs/workbench/contrib/markers/browser/media/status-warning-inverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/contrib/markers/browser/media/status-warning.svg b/src/vs/workbench/contrib/markers/browser/media/status-warning.svg new file mode 100644 index 0000000000..f4e2a84b0a --- /dev/null +++ b/src/vs/workbench/contrib/markers/browser/media/status-warning.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/parts/markers/electron-browser/messages.ts b/src/vs/workbench/contrib/markers/browser/messages.ts similarity index 98% rename from src/vs/workbench/parts/markers/electron-browser/messages.ts rename to src/vs/workbench/contrib/markers/browser/messages.ts index 85d621f0b3..1051269458 100644 --- a/src/vs/workbench/parts/markers/electron-browser/messages.ts +++ b/src/vs/workbench/contrib/markers/browser/messages.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as paths from 'vs/base/common/paths'; +import { basename } from 'vs/base/common/resources'; import { MarkerSeverity, IRelatedInformation } from 'vs/platform/markers/common/markers'; import { Marker } from './markersModel'; @@ -63,6 +63,6 @@ export default class Messages { : nls.localize('problems.tree.aria.label.marker.nosource', "Problem: {0} at line {1} and character {2}.{3}", marker.marker.message, marker.marker.startLineNumber, marker.marker.startColumn, relatedInformationMessage); } } - public static readonly MARKERS_TREE_ARIA_LABEL_RELATED_INFORMATION = (relatedInformation: IRelatedInformation): string => nls.localize('problems.tree.aria.label.relatedinfo.message', "{0} at line {1} and character {2} in {3}", relatedInformation.message, relatedInformation.startLineNumber, relatedInformation.startColumn, paths.basename(relatedInformation.resource.fsPath)); + public static readonly MARKERS_TREE_ARIA_LABEL_RELATED_INFORMATION = (relatedInformation: IRelatedInformation): string => nls.localize('problems.tree.aria.label.relatedinfo.message', "{0} at line {1} and character {2} in {3}", relatedInformation.message, relatedInformation.startLineNumber, relatedInformation.startColumn, basename(relatedInformation.resource)); public static SHOW_ERRORS_WARNINGS_ACTION_LABEL: string = nls.localize('errors.warnings.show.label', "Show Errors and Warnings"); } diff --git a/src/vs/workbench/parts/markers/test/electron-browser/markersModel.test.ts b/src/vs/workbench/contrib/markers/test/electron-browser/markersModel.test.ts similarity index 99% rename from src/vs/workbench/parts/markers/test/electron-browser/markersModel.test.ts rename to src/vs/workbench/contrib/markers/test/electron-browser/markersModel.test.ts index 228108abde..ea82ea10d9 100644 --- a/src/vs/workbench/parts/markers/test/electron-browser/markersModel.test.ts +++ b/src/vs/workbench/contrib/markers/test/electron-browser/markersModel.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { IMarker, MarkerSeverity, IRelatedInformation } from 'vs/platform/markers/common/markers'; -import { MarkersModel, Marker, ResourceMarkers, RelatedInformation } from 'vs/workbench/parts/markers/electron-browser/markersModel'; +import { MarkersModel, Marker, ResourceMarkers, RelatedInformation } from 'vs/workbench/contrib/markers/browser/markersModel'; import { groupBy } from 'vs/base/common/collections'; class TestMarkersModel extends MarkersModel { diff --git a/src/vs/workbench/parts/outline/electron-browser/outline.contribution.ts b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts similarity index 77% rename from src/vs/workbench/parts/outline/electron-browser/outline.contribution.ts rename to src/vs/workbench/contrib/outline/browser/outline.contribution.ts index 94a6ec5262..a102ac7dd0 100644 --- a/src/vs/workbench/parts/outline/electron-browser/outline.contribution.ts +++ b/src/vs/workbench/contrib/outline/browser/outline.contribution.ts @@ -4,17 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { ViewsRegistry, IViewDescriptor } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { OutlinePanel } from './outlinePanel'; -import { VIEW_CONTAINER } from 'vs/workbench/parts/files/common/files'; +import { VIEW_CONTAINER } from 'vs/workbench/contrib/files/common/files'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { OutlineConfigKeys, OutlineViewId } from 'vs/workbench/parts/outline/electron-browser/outline'; +import { OutlineConfigKeys, OutlineViewId } from 'vs/editor/contrib/documentSymbols/outline'; const _outlineDesc = { id: OutlineViewId, name: localize('name', "Outline"), - ctor: OutlinePanel, + ctorDescriptor: { ctor: OutlinePanel }, canToggleVisibility: true, hideByDefault: false, collapsed: true, @@ -23,7 +23,7 @@ const _outlineDesc = { focusCommand: { id: 'outline.focus' } }; -ViewsRegistry.registerViews([_outlineDesc], VIEW_CONTAINER); +Registry.as(ViewExtensions.ViewsRegistry).registerViews([_outlineDesc], VIEW_CONTAINER); Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ 'id': 'outline', @@ -41,11 +41,6 @@ Registry.as(ConfigurationExtensions.Configuration).regis 'type': 'boolean', 'default': true }, - [OutlineConfigKeys.problemsEnabled]: { - 'description': localize('outline.showProblem', "Show Errors & Warnings on Outline Elements."), - 'type': 'boolean', - 'default': true - }, [OutlineConfigKeys.problemsColors]: { 'description': localize('outline.problem.colors', "Use colors for Errors & Warnings."), 'type': 'boolean', diff --git a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.css b/src/vs/workbench/contrib/outline/browser/outlinePanel.css similarity index 77% rename from src/vs/workbench/parts/outline/electron-browser/outlinePanel.css rename to src/vs/workbench/contrib/outline/browser/outlinePanel.css index 791b75154f..1bb11faee3 100644 --- a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.css +++ b/src/vs/workbench/contrib/outline/browser/outlinePanel.css @@ -12,6 +12,7 @@ width: 100%; height: 2px; padding-bottom: 3px; + position: absolute; } .monaco-workbench .outline-panel .outline-progress .monaco-progress-container { @@ -22,20 +23,6 @@ height: 2px; } -.monaco-workbench .outline-panel .outline-input { - box-sizing: border-box; - padding: 2px 9px 5px 9px; - position: relative; -} - -.monaco-workbench .outline-panel .outline-input .monaco-inputbox { - width: 100%; -} - -.monaco-workbench .outline-panel .outline-input .monaco-inputbox .input { - padding-right: 22px; -} - .monaco-workbench .outline-panel .outline-tree { height: 100%; } @@ -50,10 +37,6 @@ display: inherit; } -.monaco-workbench .outline-panel.message .outline-input { - display: none; -} - .monaco-workbench .outline-panel.message .outline-progress { display: none; } @@ -72,7 +55,3 @@ /* allows text color to use the default when selected */ color: inherit !important; } - -.monaco-workbench .outline-panel.no-icons .outline-element .outline-element-icon { - display: none; -} diff --git a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts similarity index 53% rename from src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts rename to src/vs/workbench/contrib/outline/browser/outlinePanel.ts index 8ad6fa9e5b..f5703198eb 100644 --- a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePanel.ts @@ -3,55 +3,50 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { posix } from 'path'; import * as dom from 'vs/base/browser/dom'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { Action, IAction, RadioGroup } from 'vs/base/common/actions'; -import { firstIndex } from 'vs/base/common/arrays'; import { createCancelablePromise, TimeoutTimer } from 'vs/base/common/async'; -import { isPromiseCanceledError, onUnexpectedError } from 'vs/base/common/errors'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; import { defaultGenerator } from 'vs/base/common/idGenerator'; -import { KeyCode } from 'vs/base/common/keyCodes'; import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { LRUCache } from 'vs/base/common/map'; import { escape } from 'vs/base/common/strings'; -import { URI } from 'vs/base/common/uri'; -import { ITree } from 'vs/base/parts/tree/browser/tree'; import 'vs/css!./outlinePanel'; import { ICodeEditor, isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { DocumentSymbolProviderRegistry } from 'vs/editor/common/modes'; import { LanguageFeatureRegistry } from 'vs/editor/common/modes/languageFeatureRegistry'; -import { OutlineElement, OutlineModel, TreeElement } from 'vs/editor/contrib/documentSymbols/outlineModel'; +import { OutlineElement, OutlineModel, TreeElement, IOutlineMarker } from 'vs/editor/contrib/documentSymbols/outlineModel'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { WorkbenchTree } from 'vs/platform/list/browser/listService'; -import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; +import { WorkbenchDataTree } from 'vs/platform/list/browser/listService'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { attachInputBoxStyler, attachProgressBarStyler } from 'vs/platform/theme/common/styler'; +import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { CollapseAction } from 'vs/workbench/browser/viewlet'; -import { IViewsService } from 'vs/workbench/common/views'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { OutlineController, OutlineDataSource, OutlineItemComparator, OutlineItemCompareType, OutlineItemFilter, OutlineRenderer, OutlineTreeState } from 'vs/editor/contrib/documentSymbols/outlineTree'; -import { OutlineConfigKeys, OutlineViewFiltered, OutlineViewFocused, OutlineViewId } from './outline'; +import { OutlineConfigKeys, OutlineViewFocused, OutlineViewFiltered } from 'vs/editor/contrib/documentSymbols/outline'; +import { FuzzyScore } from 'vs/base/common/filters'; +import { OutlineDataSource, OutlineItemComparator, OutlineSortOrder, OutlineVirtualDelegate, OutlineGroupRenderer, OutlineElementRenderer, OutlineItem, OutlineIdentityProvider, OutlineNavigationLabelProvider } from 'vs/editor/contrib/documentSymbols/outlineTree'; +import { IDataTreeViewState } from 'vs/base/browser/ui/tree/dataTree'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { basename } from 'vs/base/common/resources'; +import { IDataSource } from 'vs/base/browser/ui/tree/tree'; +import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; +import { MarkerSeverity } from 'vs/platform/markers/common/markers'; class RequestState { @@ -77,10 +72,10 @@ class RequestOracle { private _disposables = new Array(); private _sessionDisposable: IDisposable; - private _lastState: RequestState; + private _lastState?: RequestState; constructor( - private readonly _callback: (editor: ICodeEditor, change: IModelContentChangedEvent) => any, + private readonly _callback: (editor: ICodeEditor | undefined, change: IModelContentChangedEvent | undefined) => any, private readonly _featureRegistry: LanguageFeatureRegistry, @IEditorService private readonly _editorService: IEditorService, ) { @@ -104,7 +99,7 @@ class RequestOracle { codeEditor = widget.getModifiedEditor(); } - if (!codeEditor || !codeEditor.getModel()) { + if (!codeEditor || !codeEditor.hasModel()) { this._lastState = undefined; this._callback(undefined, undefined); return; @@ -117,7 +112,7 @@ class RequestOracle { this._featureRegistry.all(codeEditor.getModel()).length ); - if (thisState.equals(this._lastState)) { + if (this._lastState && thisState.equals(this._lastState)) { // prevent unneccesary changes... return; } @@ -128,10 +123,10 @@ class RequestOracle { let handle: any; let contentListener = codeEditor.onDidChangeModelContent(event => { clearTimeout(handle); - handle = setTimeout(() => this._callback(codeEditor, event), 350); + handle = setTimeout(() => this._callback(codeEditor!, event), 350); }); let modeListener = codeEditor.onDidChangeModelLanguage(_ => { - this._callback(codeEditor, undefined); + this._callback(codeEditor!, undefined); }); let disposeListener = codeEditor.onDidDispose(() => { this._callback(undefined, undefined); @@ -149,13 +144,21 @@ class RequestOracle { class SimpleToggleAction extends Action { - constructor(label: string, checked: boolean, callback: (action: SimpleToggleAction) => any, className?: string) { - super(`simple` + defaultGenerator.nextId(), label, className, true, _ => { + private readonly _listener: IDisposable; + + constructor(state: OutlineViewState, label: string, isChecked: () => boolean, callback: (action: SimpleToggleAction) => any, className?: string) { + super(`simple` + defaultGenerator.nextId(), label, className, true, () => { this.checked = !this.checked; callback(this); - return undefined; + return Promise.resolve(); }); - this.checked = checked; + this.checked = isChecked(); + this._listener = state.onDidChange(() => this.checked = isChecked()); + } + + dispose(): void { + this._listener.dispose(); + super.dispose(); } } @@ -164,7 +167,7 @@ class OutlineViewState { private _followCursor = false; private _filterOnType = true; - private _sortBy = OutlineItemCompareType.ByKind; + private _sortBy = OutlineSortOrder.ByKind; private _onDidChange = new Emitter<{ followCursor?: boolean, sortBy?: boolean, filterOnType?: boolean }>(); readonly onDidChange = this._onDidChange.event; @@ -191,19 +194,23 @@ class OutlineViewState { } } - set sortBy(value: OutlineItemCompareType) { + set sortBy(value: OutlineSortOrder) { if (value !== this._sortBy) { this._sortBy = value; this._onDidChange.fire({ sortBy: true }); } } - get sortBy(): OutlineItemCompareType { + get sortBy(): OutlineSortOrder { return this._sortBy; } persist(storageService: IStorageService): void { - storageService.store('outline/state', JSON.stringify({ followCursor: this.followCursor, sortBy: this.sortBy }), StorageScope.WORKSPACE); + storageService.store('outline/state', JSON.stringify({ + followCursor: this.followCursor, + sortBy: this.sortBy, + filterOnType: this.filterOnType, + }), StorageScope.WORKSPACE); } restore(storageService: IStorageService): void { @@ -219,6 +226,9 @@ class OutlineViewState { } this.followCursor = data.followCursor; this.sortBy = data.sortBy; + if (typeof data.filterOnType === 'boolean') { + this.filterOnType = data.filterOnType; + } } } @@ -228,19 +238,18 @@ export class OutlinePanel extends ViewletPanel { private _editorDisposables = new Array(); private _outlineViewState = new OutlineViewState(); - private _requestOracle: RequestOracle; - private _cachedHeight: number; + private _requestOracle?: RequestOracle; private _domNode: HTMLElement; private _message: HTMLDivElement; private _inputContainer: HTMLDivElement; - private _input: InputBox; private _progressBar: ProgressBar; - private _tree: WorkbenchTree; + private _tree: WorkbenchDataTree; private _treeDataSource: OutlineDataSource; - private _treeRenderer: OutlineRenderer; - private _treeFilter: OutlineItemFilter; + private _treeRenderer: OutlineElementRenderer; private _treeComparator: OutlineItemComparator; - private _treeStates = new LRUCache(10); + private _treeStates = new LRUCache(10); + + private _treeFakeUIEvent = new UIEvent('me'); private readonly _contextKeyFocused: IContextKey; private readonly _contextKeyFiltered: IContextKey; @@ -251,14 +260,14 @@ export class OutlinePanel extends ViewletPanel { @IThemeService private readonly _themeService: IThemeService, @IStorageService private readonly _storageService: IStorageService, @IEditorService private readonly _editorService: IEditorService, - @IMarkerService private readonly _markerService: IMarkerService, + @IMarkerDecorationsService private readonly _markerDecorationService: IMarkerDecorationsService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IKeybindingService keybindingService: IKeybindingService, @IConfigurationService configurationService: IConfigurationService, @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService contextMenuService: IContextMenuService, ) { - super(options, _keybindingService, contextMenuService, configurationService); + super(options, keybindingService, contextMenuService, configurationService); this._outlineViewState.restore(this._storageService); this._contextKeyFocused = OutlineViewFocused.bindTo(contextKeyService); this._contextKeyFiltered = OutlineViewFiltered.bindTo(contextKeyService); @@ -303,73 +312,57 @@ export class OutlinePanel extends ViewletPanel { progressContainer, this._message, this._inputContainer, treeContainer ); - this._input = new InputBox(this._inputContainer, null, { - placeholder: this._outlineViewState.filterOnType ? localize('filter.placeholder', "Filter") : localize('find.placeholder', "Find") - }); - this._input.disable(); + this._treeRenderer = this._instantiationService.createInstance(OutlineElementRenderer); + this._treeDataSource = new OutlineDataSource(); + this._treeComparator = new OutlineItemComparator(this._outlineViewState.sortBy); + this._tree = this._instantiationService.createInstance( + WorkbenchDataTree, + treeContainer, + new OutlineVirtualDelegate(), + [new OutlineGroupRenderer(), this._treeRenderer], + this._treeDataSource as IDataSource, + { + expandOnlyOnTwistieClick: true, + multipleSelectionSupport: false, + filterOnType: this._outlineViewState.filterOnType, + sorter: this._treeComparator, + identityProvider: new OutlineIdentityProvider(), + keyboardNavigationLabelProvider: this._instantiationService.createInstance(OutlineNavigationLabelProvider) + } + ) as WorkbenchDataTree; - this.disposables.push(attachInputBoxStyler(this._input, this._themeService)); - this.disposables.push(dom.addStandardDisposableListener(this._input.inputElement, 'keyup', event => { - if (event.keyCode === KeyCode.DownArrow) { - this._tree.focusNext(); - this._tree.domFocus(); - } else if (event.keyCode === KeyCode.UpArrow) { - this._tree.focusPrevious(); - this._tree.domFocus(); - } else if (event.keyCode === KeyCode.Enter) { - let element = this._tree.getFocus(); - if (element instanceof OutlineElement) { - this._revealTreeSelection(OutlineModel.get(element), element, true, false); - } - } else if (event.keyCode === KeyCode.Escape) { - this._input.value = ''; - this._tree.domFocus(); + this._disposables.push(this._tree); + this._disposables.push(this._outlineViewState.onDidChange(this._onDidChangeUserState, this)); + + // override the globally defined behaviour + this._tree.updateOptions({ + filterOnType: this._outlineViewState.filterOnType + }); + + // feature: filter on type - keep tree and menu in sync + this.disposables.push(this._tree.onDidUpdateOptions(e => { + this._outlineViewState.filterOnType = Boolean(e.filterOnType); + })); + + // feature: expand all nodes when filtering (not when finding) + let viewState: IDataTreeViewState | undefined; + this.disposables.push(this._tree.onDidChangeTypeFilterPattern(pattern => { + if (!this._tree.options.filterOnType) { + return; + } + if (!viewState && pattern) { + viewState = this._tree.getViewState(); + this._tree.expandAll(); + } else if (!pattern && viewState) { + this._tree.setInput(this._tree.getInput()!, viewState); + viewState = undefined; } })); - const $this = this; - const controller = new class extends OutlineController { - - constructor() { - super({}, $this.configurationService); - } - - onKeyDown(tree: ITree, event: IKeyboardEvent) { - let handled = super.onKeyDown(tree, event); - if (handled) { - return true; - } - if (this.upKeyBindingDispatcher.has(event.keyCode)) { - return false; - } - // crazy -> during keydown focus moves to the input box - // and because of that the keyup event is handled by the - // input field - if ($this._keybindingService.mightProducePrintableCharacter(event)) { - $this._input.focus(); - return true; - } - return false; - } - }; - - this._treeRenderer = this._instantiationService.createInstance(OutlineRenderer); - this._treeDataSource = new OutlineDataSource(); - this._treeComparator = new OutlineItemComparator(this._outlineViewState.sortBy); - this._treeFilter = new OutlineItemFilter(); - this._tree = this._instantiationService.createInstance(WorkbenchTree, treeContainer, { controller, renderer: this._treeRenderer, dataSource: this._treeDataSource, sorter: this._treeComparator, filter: this._treeFilter }, {}); - - this._treeRenderer.renderProblemColors = this._configurationService.getValue(OutlineConfigKeys.problemsColors); - this._treeRenderer.renderProblemBadges = this._configurationService.getValue(OutlineConfigKeys.problemsBadges); - - this._disposables.push(this._tree, this._input); - this._disposables.push(this._outlineViewState.onDidChange(this._onDidChangeUserState, this)); - // feature: toggle icons - dom.toggleClass(this._domNode, 'no-icons', !this._configurationService.getValue(OutlineConfigKeys.icons)); this.disposables.push(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(OutlineConfigKeys.icons)) { - dom.toggleClass(this._domNode, 'no-icons', !this._configurationService.getValue(OutlineConfigKeys.icons)); + this._tree.updateChildren(); } })); @@ -384,12 +377,8 @@ export class OutlinePanel extends ViewletPanel { })); } - protected layoutBody(height: number): void { - if (height !== this._cachedHeight) { - this._cachedHeight = height; - const treeHeight = height - (5 /*progressbar height*/ + 33 /*input height*/); - this._tree.layout(treeHeight); - } + protected layoutBody(height: number, width: number): void { + this._tree.layout(height, width); } getActions(): IAction[] { @@ -402,13 +391,13 @@ export class OutlinePanel extends ViewletPanel { getSecondaryActions(): IAction[] { let group = new RadioGroup([ - new SimpleToggleAction(localize('sortByPosition', "Sort By: Position"), this._outlineViewState.sortBy === OutlineItemCompareType.ByPosition, _ => this._outlineViewState.sortBy = OutlineItemCompareType.ByPosition), - new SimpleToggleAction(localize('sortByName', "Sort By: Name"), this._outlineViewState.sortBy === OutlineItemCompareType.ByName, _ => this._outlineViewState.sortBy = OutlineItemCompareType.ByName), - new SimpleToggleAction(localize('sortByKind', "Sort By: Type"), this._outlineViewState.sortBy === OutlineItemCompareType.ByKind, _ => this._outlineViewState.sortBy = OutlineItemCompareType.ByKind), + new SimpleToggleAction(this._outlineViewState, localize('sortByPosition', "Sort By: Position"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByPosition, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByPosition), + new SimpleToggleAction(this._outlineViewState, localize('sortByName', "Sort By: Name"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByName, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByName), + new SimpleToggleAction(this._outlineViewState, localize('sortByKind', "Sort By: Type"), () => this._outlineViewState.sortBy === OutlineSortOrder.ByKind, _ => this._outlineViewState.sortBy = OutlineSortOrder.ByKind), ]); let result = [ - new SimpleToggleAction(localize('followCur', "Follow Cursor"), this._outlineViewState.followCursor, action => this._outlineViewState.followCursor = action.checked), - new SimpleToggleAction(localize('filterOnType', "Filter on Type"), this._outlineViewState.filterOnType, action => this._outlineViewState.filterOnType = action.checked), + new SimpleToggleAction(this._outlineViewState, localize('followCur', "Follow Cursor"), () => this._outlineViewState.followCursor, action => this._outlineViewState.followCursor = action.checked), + new SimpleToggleAction(this._outlineViewState, localize('filterOnType', "Filter on Type"), () => this._outlineViewState.filterOnType, action => this._outlineViewState.filterOnType = action.checked), new Separator(), ...group.actions, ]; @@ -425,21 +414,23 @@ export class OutlinePanel extends ViewletPanel { } if (e.sortBy) { this._treeComparator.type = this._outlineViewState.sortBy; - this._tree.refresh(undefined, true); + this._tree.resort(); } if (e.filterOnType) { - this._applyTypeToFilter(); + this._tree.updateOptions({ + filterOnType: this._outlineViewState.filterOnType + }); } } private _showMessage(message: string) { dom.addClass(this._domNode, 'message'); - this._tree.setInput(undefined); + this._tree.setInput(undefined!); this._progressBar.stop().hide(); this._message.innerText = escape(message); } - private static _createOutlineModel(model: ITextModel, disposables: IDisposable[]): Promise { + private static _createOutlineModel(model: ITextModel, disposables: IDisposable[]): Promise { let promise = createCancelablePromise(token => OutlineModel.create(model, token)); disposables.push({ dispose() { promise.cancel(); } }); return promise.catch(err => { @@ -450,44 +441,42 @@ export class OutlinePanel extends ViewletPanel { }); } - private async _doUpdate(editor: ICodeEditor, event: IModelContentChangedEvent): Promise { + private async _doUpdate(editor: ICodeEditor | undefined, event: IModelContentChangedEvent | undefined): Promise { dispose(this._editorDisposables); this._editorDisposables = new Array(); this._progressBar.infinite().show(150); - this._input.disable(); - if (!event) { - this._input.value = ''; + + let oldModel = this._tree.getInput(); + + // persist state + if (oldModel) { + let state = this._tree.getViewState(); + this._treeStates.set(oldModel.textModel.uri.toString(), state); } - if (!editor || !DocumentSymbolProviderRegistry.has(editor.getModel())) { + if (!editor || !editor.hasModel() || !DocumentSymbolProviderRegistry.has(editor.getModel())) { return this._showMessage(localize('no-editor', "There are no editors open that can provide outline information.")); } let textModel = editor.getModel(); - let loadingMessage: IDisposable; - let oldModel = this._tree.getInput(); + let loadingMessage: IDisposable | undefined; if (!oldModel) { loadingMessage = new TimeoutTimer( - () => this._showMessage(localize('loading', "Loading document symbols for '{0}'...", posix.basename(textModel.uri.path))), + () => this._showMessage(localize('loading', "Loading document symbols for '{0}'...", basename(textModel.uri))), 100 ); } - let model = await OutlinePanel._createOutlineModel(textModel, this._editorDisposables); + let createdModel = await OutlinePanel._createOutlineModel(textModel, this._editorDisposables); dispose(loadingMessage); - if (!model) { + if (!createdModel) { return; } - if (TreeElement.empty(model)) { - return this._showMessage(localize('no-symbols', "No symbols found in document '{0}'", posix.basename(textModel.uri.path))); - } - - let newSize = TreeElement.size(model); - if (newSize > 7500) { - // this is a workaround for performance issues with the tree: https://github.com/Microsoft/vscode/issues/18180 - return this._showMessage(localize('too-many-symbols', "We are sorry, but this file is too large for showing an outline.")); + let newModel = createdModel; + if (TreeElement.empty(newModel)) { + return this._showMessage(localize('no-symbols', "No symbols found in document '{0}'", basename(textModel.uri))); } dom.removeClass(this._domNode, 'message'); @@ -495,6 +484,7 @@ export class OutlinePanel extends ViewletPanel { if (event && oldModel && textModel.getLineCount() >= 25) { // heuristic: when the symbols-to-lines ratio changes by 50% between edits // wait a little (and hope that the next change isn't as drastic). + let newSize = TreeElement.size(newModel); let newLength = textModel.getValueLength(); let newRatio = newSize / newLength; let oldSize = TreeElement.size(oldModel); @@ -503,7 +493,7 @@ export class OutlinePanel extends ViewletPanel { if (newRatio <= oldRatio * 0.5 || newRatio >= oldRatio * 1.5) { let waitPromise = new Promise(resolve => { - let handle = setTimeout(() => { + let handle: any = setTimeout(() => { handle = undefined; resolve(true); }, 2000); @@ -523,152 +513,99 @@ export class OutlinePanel extends ViewletPanel { this._progressBar.stop().hide(); - if (oldModel && oldModel.merge(model)) { - this._tree.refresh(undefined, true); - model = oldModel; - + if (oldModel && oldModel.merge(newModel)) { + this._tree.updateChildren(); + newModel = oldModel; } else { - // persist state - if (oldModel) { - let state = OutlineTreeState.capture(this._tree); - this._treeStates.set(oldModel.textModel.uri.toString(), state); - } - await this._tree.setInput(model); - let state = this._treeStates.get(model.textModel.uri.toString()); - await OutlineTreeState.restore(this._tree, state, this); + let state = this._treeStates.get(newModel.textModel.uri.toString()); + await this._tree.setInput(newModel, state); } - this._input.enable(); - this.layoutBody(this._cachedHeight); - // transfer focus from domNode to the tree if (this._domNode === document.activeElement) { this._tree.domFocus(); } - // feature: filter on type - // on type -> update filters - // on first type -> capture tree state - // on erase -> restore captured tree state - let beforePatternState: OutlineTreeState; - let onInputValueChanged = async pattern => { - - this._contextKeyFiltered.set(pattern.length > 0); - - if (pattern && !beforePatternState) { - beforePatternState = OutlineTreeState.capture(this._tree); - } - let item = model.updateMatches(pattern); - await this._tree.refresh(undefined, true); - if (item) { - await this._tree.expandAll(undefined /*all*/); - await this._tree.reveal(item); - this._tree.setFocus(item, this); - this._tree.setSelection([item], this); - } - - if (!pattern && beforePatternState) { - await OutlineTreeState.restore(this._tree, beforePatternState, this); - beforePatternState = undefined; - } - }; - if (this._input.value) { - onInputValueChanged(this._input.value); - } - this._editorDisposables.push(this._input.onDidChange(onInputValueChanged)); - this._editorDisposables.push(toDisposable(() => this._contextKeyFiltered.reset())); // feature: reveal outline selection in editor // on change -> reveal/select defining range this._editorDisposables.push(this._tree.onDidChangeSelection(e => { - if (e.payload === this || e.payload && e.payload.didClickOnTwistie) { + if (e.browserEvent === this._treeFakeUIEvent /* || e.payload && e.payload.didClickOnTwistie */) { return; } - let [first] = e.selection; + let [first] = e.elements; if (!(first instanceof OutlineElement)) { return; } let focus = false; let aside = false; - if (e.payload) { - if (e.payload.origin === 'keyboard') { + // todo@Joh + if (e.browserEvent) { + if (e.browserEvent.type === 'keydown') { focus = true; - - } else if (e.payload.origin === 'mouse' && e.payload.originalEvent instanceof StandardMouseEvent) { - let event = e.payload.originalEvent; - focus = event.detail === 2; - aside = !this._tree.useAltAsMultipleSelectionModifier && event.altKey || this._tree.useAltAsMultipleSelectionModifier && (event.ctrlKey || event.metaKey); + } else if (e.browserEvent.type === 'click') { + const event = new StandardMouseEvent(e.browserEvent as MouseEvent); + focus = e.browserEvent.detail === 2; + aside = (!this._tree.useAltAsMultipleSelectionModifier && event.altKey) + || (this._tree.useAltAsMultipleSelectionModifier && (event.ctrlKey || event.metaKey)); } } - this._revealTreeSelection(model, first, focus, aside); + this._revealTreeSelection(newModel, first, focus, aside); })); // feature: reveal editor selection in outline - this._revealEditorSelection(model, editor.getSelection()); - const versionIdThen = model.textModel.getVersionId(); + this._revealEditorSelection(newModel, editor.getSelection()); + const versionIdThen = newModel.textModel.getVersionId(); this._editorDisposables.push(editor.onDidChangeCursorSelection(e => { // first check if the document has changed and stop revealing the // cursor position iff it has -> we will update/recompute the // outline view then anyways - if (!model.textModel.isDisposed() && model.textModel.getVersionId() === versionIdThen) { - this._revealEditorSelection(model, e.selection); + if (!newModel.textModel.isDisposed() && newModel.textModel.getVersionId() === versionIdThen) { + this._revealEditorSelection(newModel, e.selection); } })); // feature: show markers in outline - const updateMarker = (e: URI[], ignoreEmpty?: boolean) => { + const updateMarker = (model: ITextModel, ignoreEmpty?: boolean) => { if (!this._configurationService.getValue(OutlineConfigKeys.problemsEnabled)) { return; } - if (firstIndex(e, a => a.toString() === textModel.uri.toString()) < 0) { + if (model !== textModel) { return; } - const marker = this._markerService.read({ resource: textModel.uri, severities: MarkerSeverity.Error | MarkerSeverity.Warning }); - if (marker.length > 0 || !ignoreEmpty) { - model.updateMarker(marker); - this._tree.refresh(undefined, true); + const markers: IOutlineMarker[] = []; + for (const [range, marker] of this._markerDecorationService.getLiveMarkers(textModel)) { + if (marker.severity === MarkerSeverity.Error || marker.severity === MarkerSeverity.Warning) { + markers.push({ ...range, severity: marker.severity }); + } + } + if (markers.length > 0 || !ignoreEmpty) { + newModel.updateMarker(markers); + this._tree.updateChildren(); } }; - updateMarker([textModel.uri], true); - this._editorDisposables.push(this._markerService.onMarkerChanged(updateMarker)); + updateMarker(textModel, true); + this._editorDisposables.push(this._markerDecorationService.onDidChangeMarker(updateMarker)); this._editorDisposables.push(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(OutlineConfigKeys.problemsBadges) || e.affectsConfiguration(OutlineConfigKeys.problemsColors)) { - this._treeRenderer.renderProblemColors = this._configurationService.getValue(OutlineConfigKeys.problemsColors); - this._treeRenderer.renderProblemBadges = this._configurationService.getValue(OutlineConfigKeys.problemsBadges); - this._tree.refresh(undefined, true); + this._tree.updateChildren(); return; } if (!e.affectsConfiguration(OutlineConfigKeys.problemsEnabled)) { return; } if (!this._configurationService.getValue(OutlineConfigKeys.problemsEnabled)) { - model.updateMarker([]); - this._tree.refresh(undefined, true); + newModel.updateMarker([]); + this._tree.updateChildren(); } else { - updateMarker([textModel.uri], true); + updateMarker(textModel, true); } })); } - private _applyTypeToFilter(): void { - // depending on the user setting we filter or find elements - if (this._outlineViewState.filterOnType) { - this._treeFilter.enabled = true; - this._treeDataSource.filterOnScore = true; - this._input.setPlaceHolder(localize('filter', "Filter")); - } else { - this._treeFilter.enabled = false; - this._treeDataSource.filterOnScore = false; - this._input.setPlaceHolder(localize('find', "Find")); - } - if (this._tree.getInput()) { - this._tree.refresh(undefined, true); - } - } - private async _revealTreeSelection(model: OutlineModel, element: OutlineElement, focus: boolean, aside: boolean): Promise { await this._editorService.openEditor({ @@ -681,7 +618,7 @@ export class OutlinePanel extends ViewletPanel { } as IResourceInput, aside ? SIDE_GROUP : ACTIVE_GROUP); } - private async _revealEditorSelection(model: OutlineModel, selection: Selection): Promise { + private _revealEditorSelection(model: OutlineModel, selection: Selection): void { if (!this._outlineViewState.followCursor || !this._tree.getInput() || !selection) { return; } @@ -695,54 +632,10 @@ export class OutlinePanel extends ViewletPanel { return; } let top = this._tree.getRelativeTop(item); - if (top < 0 || top > 1) { - // only when outside view port - await this._tree.reveal(item, 0.5); - } - this._tree.setFocus(item, this); - this._tree.setSelection([item], this); - } - - focusHighlightedElement(up: boolean): void { - if (!this._tree.getInput()) { - return; - } - if (!this._tree.isDOMFocused()) { - this._tree.domFocus(); - return; - } - let navi = this._tree.getNavigator(this._tree.getFocus(), false); - let candidate: any; - while (candidate = up ? navi.previous() : navi.next()) { - if (candidate instanceof OutlineElement && candidate.score && candidate.score[1] > 0) { - this._tree.setFocus(candidate, this); - this._tree.reveal(candidate).then(undefined, onUnexpectedError); - break; - } + if (top === null) { + this._tree.reveal(item, 0.5); } + this._tree.setFocus([item], this._treeFakeUIEvent); + this._tree.setSelection([item], this._treeFakeUIEvent); } } - -async function goUpOrDownToHighligthedElement(accessor: ServicesAccessor, prev: boolean) { - const viewsService = accessor.get(IViewsService); - const view = await viewsService.openView(OutlineViewId); - if (view instanceof OutlinePanel) { - view.focusHighlightedElement(prev); - } -} - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'outline.focusDownHighlighted', - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyCode.DownArrow, - when: ContextKeyExpr.and(OutlineViewFiltered, OutlineViewFocused), - handler: accessor => goUpOrDownToHighligthedElement(accessor, false) -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'outline.focusUpHighlighted', - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyCode.UpArrow, - when: ContextKeyExpr.and(OutlineViewFiltered, OutlineViewFocused), - handler: accessor => goUpOrDownToHighligthedElement(accessor, true) -}); diff --git a/src/vs/workbench/parts/output/browser/logViewer.ts b/src/vs/workbench/contrib/output/browser/logViewer.ts similarity index 82% rename from src/vs/workbench/parts/output/browser/logViewer.ts rename to src/vs/workbench/contrib/output/browser/logViewer.ts index 428fa6a31f..c988a17702 100644 --- a/src/vs/workbench/parts/output/browser/logViewer.ts +++ b/src/vs/workbench/contrib/output/browser/logViewer.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as paths from 'vs/base/common/paths'; +import { dirname, basename } from 'vs/base/common/path'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -16,9 +16,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { URI } from 'vs/base/common/uri'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IHashService } from 'vs/workbench/services/hash/common/hashService'; -import { LOG_SCHEME, IOutputChannelDescriptor } from 'vs/workbench/parts/output/common/output'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { LOG_SCHEME, IFileOutputChannelDescriptor } from 'vs/workbench/contrib/output/common/output'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWindowService } from 'vs/platform/windows/common/windows'; @@ -26,11 +25,10 @@ export class LogViewerInput extends ResourceEditorInput { public static readonly ID = 'workbench.editorinputs.output'; - constructor(private outputChannelDescriptor: IOutputChannelDescriptor, - @ITextModelService textModelResolverService: ITextModelService, - @IHashService hashService: IHashService + constructor(private outputChannelDescriptor: IFileOutputChannelDescriptor, + @ITextModelService textModelResolverService: ITextModelService ) { - super(paths.basename(outputChannelDescriptor.file.path), paths.dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), textModelResolverService, hashService); + super(basename(outputChannelDescriptor.file.path), dirname(outputChannelDescriptor.file.path), URI.from({ scheme: LOG_SCHEME, path: outputChannelDescriptor.id }), textModelResolverService); } public getTypeId(): string { diff --git a/src/vs/workbench/parts/output/browser/media/clear_output.svg b/src/vs/workbench/contrib/output/browser/media/clear_output.svg similarity index 100% rename from src/vs/workbench/parts/output/browser/media/clear_output.svg rename to src/vs/workbench/contrib/output/browser/media/clear_output.svg diff --git a/src/vs/workbench/parts/output/browser/media/clear_output_inverse.svg b/src/vs/workbench/contrib/output/browser/media/clear_output_inverse.svg similarity index 100% rename from src/vs/workbench/parts/output/browser/media/clear_output_inverse.svg rename to src/vs/workbench/contrib/output/browser/media/clear_output_inverse.svg diff --git a/src/vs/workbench/parts/output/browser/media/open_log_file.svg b/src/vs/workbench/contrib/output/browser/media/open_log_file.svg similarity index 100% rename from src/vs/workbench/parts/output/browser/media/open_log_file.svg rename to src/vs/workbench/contrib/output/browser/media/open_log_file.svg diff --git a/src/vs/workbench/parts/output/browser/media/open_log_file_inverse.svg b/src/vs/workbench/contrib/output/browser/media/open_log_file_inverse.svg similarity index 100% rename from src/vs/workbench/parts/output/browser/media/open_log_file_inverse.svg rename to src/vs/workbench/contrib/output/browser/media/open_log_file_inverse.svg diff --git a/src/vs/workbench/parts/output/browser/media/output.css b/src/vs/workbench/contrib/output/browser/media/output.css similarity index 100% rename from src/vs/workbench/parts/output/browser/media/output.css rename to src/vs/workbench/contrib/output/browser/media/output.css diff --git a/src/vs/workbench/parts/output/browser/media/output_lock.svg b/src/vs/workbench/contrib/output/browser/media/output_lock.svg similarity index 100% rename from src/vs/workbench/parts/output/browser/media/output_lock.svg rename to src/vs/workbench/contrib/output/browser/media/output_lock.svg diff --git a/src/vs/workbench/parts/output/browser/media/output_lock_inverse.svg b/src/vs/workbench/contrib/output/browser/media/output_lock_inverse.svg similarity index 100% rename from src/vs/workbench/parts/output/browser/media/output_lock_inverse.svg rename to src/vs/workbench/contrib/output/browser/media/output_lock_inverse.svg diff --git a/src/vs/workbench/parts/output/browser/media/output_unlock.svg b/src/vs/workbench/contrib/output/browser/media/output_unlock.svg similarity index 100% rename from src/vs/workbench/parts/output/browser/media/output_unlock.svg rename to src/vs/workbench/contrib/output/browser/media/output_unlock.svg diff --git a/src/vs/workbench/parts/output/browser/media/output_unlock_inverse.svg b/src/vs/workbench/contrib/output/browser/media/output_unlock_inverse.svg similarity index 100% rename from src/vs/workbench/parts/output/browser/media/output_unlock_inverse.svg rename to src/vs/workbench/contrib/output/browser/media/output_unlock_inverse.svg diff --git a/src/vs/workbench/parts/output/electron-browser/output.contribution.ts b/src/vs/workbench/contrib/output/browser/output.contribution.ts similarity index 71% rename from src/vs/workbench/parts/output/electron-browser/output.contribution.ts rename to src/vs/workbench/contrib/output/browser/output.contribution.ts index a05aaae670..e799362b6e 100644 --- a/src/vs/workbench/parts/output/electron-browser/output.contribution.ts +++ b/src/vs/workbench/contrib/output/browser/output.contribution.ts @@ -7,19 +7,16 @@ import * as nls from 'vs/nls'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { KeybindingsRegistry, IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { MenuId, MenuRegistry, SyncActionDescriptor, registerAction } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { OutputService, LogContentProvider } from 'vs/workbench/parts/output/electron-browser/outputServices'; -import { ToggleOutputAction, ClearOutputAction, OpenLogOutputFile, ShowLogsOutputChannelAction, OpenOutputLogFileAction } from 'vs/workbench/parts/output/browser/outputActions'; -import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_SCHEME, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_LOG_OUTPUT } from 'vs/workbench/parts/output/common/output'; +import { OutputService, LogContentProvider } from 'vs/workbench/contrib/output/browser/outputServices'; +import { ToggleOutputAction, ClearOutputAction, OpenLogOutputFile, ShowLogsOutputChannelAction, OpenOutputLogFileAction } from 'vs/workbench/contrib/output/browser/outputActions'; +import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_SCHEME, LOG_MODE_ID, LOG_MIME, CONTEXT_ACTIVE_LOG_OUTPUT } from 'vs/workbench/contrib/output/common/output'; import { PanelRegistry, Extensions, PanelDescriptor } from 'vs/workbench/browser/panel'; -import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { OutputPanel } from 'vs/workbench/parts/output/browser/outputPanel'; +import { OutputPanel } from 'vs/workbench/contrib/output/browser/outputPanel'; import { IEditorRegistry, Extensions as EditorExtensions, EditorDescriptor } from 'vs/workbench/browser/editor'; -import { LogViewer, LogViewerInput } from 'vs/workbench/parts/output/browser/logViewer'; +import { LogViewer, LogViewerInput } from 'vs/workbench/contrib/output/browser/logViewer'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -33,7 +30,6 @@ registerSingleton(IOutputService, OutputService); ModesRegistry.registerLanguage({ id: OUTPUT_MODE_ID, extensions: [], - aliases: [null], mimetypes: [OUTPUT_MIME] }); @@ -41,7 +37,6 @@ ModesRegistry.registerLanguage({ ModesRegistry.registerLanguage({ id: LOG_MODE_ID, extensions: [], - aliases: [null], mimetypes: [LOG_MIME] }); @@ -93,85 +88,25 @@ const devCategory = nls.localize('developer', "Developer"); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowLogsOutputChannelAction, ShowLogsOutputChannelAction.ID, ShowLogsOutputChannelAction.LABEL), 'Developer: Show Logs...', devCategory); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenOutputLogFileAction, OpenOutputLogFileAction.ID, OpenOutputLogFileAction.LABEL), 'Developer: Open Log File...', devCategory); -interface IActionDescriptor { - id: string; - handler: ICommandHandler; - - // ICommandUI - title: string; - category?: string; - f1?: boolean; - - // menus - menu?: { - menuId: MenuId, - when?: ContextKeyExpr; - group?: string; - }; - - // keybindings - keybinding?: { - when?: ContextKeyExpr; - weight: number; - keys: IKeybindings; - }; -} - -function registerAction(desc: IActionDescriptor) { - - const { id, handler, title, category, f1, menu, keybinding } = desc; - - // 1) register as command - CommandsRegistry.registerCommand(id, handler); - - // 2) command palette - let command = { id, title, category }; - if (f1) { - MenuRegistry.addCommand(command); - } - - // 3) menus - if (menu) { - let { menuId, when, group } = menu; - MenuRegistry.appendMenuItem(menuId, { - command, - when, - group - }); - } - - // 4) keybindings - if (keybinding) { - let { when, weight, keys } = keybinding; - KeybindingsRegistry.registerKeybindingRule({ - id, - when, - weight, - primary: keys.primary, - secondary: keys.secondary, - linux: keys.linux, - mac: keys.mac, - win: keys.win - }); - } -} - // Define clear command, contribute to editor context menu registerAction({ id: 'editor.action.clearoutput', - title: nls.localize('clearOutput.label', "Clear Output"), + title: { value: nls.localize('clearOutput.label', "Clear Output"), original: 'Clear Output' }, menu: { menuId: MenuId.EditorContext, when: CONTEXT_IN_OUTPUT }, handler(accessor) { - accessor.get(IOutputService).getActiveChannel().clear(); + const activeChannel = accessor.get(IOutputService).getActiveChannel(); + if (activeChannel) { + activeChannel.clear(); + } } }); registerAction({ id: 'workbench.action.openActiveLogOutputFile', - title: nls.localize('openActiveLogOutputFile', "View: Open Active Log Output File"), + title: { value: nls.localize('openActiveLogOutputFile', "Open Active Log Output File"), original: 'Open Active Log Output File' }, menu: { menuId: MenuId.CommandPalette, when: CONTEXT_ACTIVE_LOG_OUTPUT diff --git a/src/vs/workbench/parts/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts similarity index 80% rename from src/vs/workbench/parts/output/browser/outputActions.ts rename to src/vs/workbench/contrib/output/browser/outputActions.ts index 53c58438a6..c356957fed 100644 --- a/src/vs/workbench/parts/output/browser/outputActions.ts +++ b/src/vs/workbench/contrib/output/browser/outputActions.ts @@ -6,9 +6,9 @@ import * as nls from 'vs/nls'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { IAction, Action } from 'vs/base/common/actions'; -import { IOutputService, OUTPUT_PANEL_ID, IOutputChannelRegistry, Extensions as OutputExt, IOutputChannelDescriptor } from 'vs/workbench/parts/output/common/output'; +import { IOutputService, OUTPUT_PANEL_ID, IOutputChannelRegistry, Extensions as OutputExt, IOutputChannelDescriptor, IFileOutputChannelDescriptor } from 'vs/workbench/contrib/output/common/output'; import { SelectActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -20,7 +20,7 @@ import { groupBy } from 'vs/base/common/arrays'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { LogViewerInput } from 'vs/workbench/parts/output/browser/logViewer'; +import { LogViewerInput } from 'vs/workbench/contrib/output/browser/logViewer'; import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; export class ToggleOutputAction extends TogglePanelAction { @@ -30,10 +30,10 @@ export class ToggleOutputAction extends TogglePanelAction { constructor( id: string, label: string, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @IPanelService panelService: IPanelService, ) { - super(id, label, OUTPUT_PANEL_ID, panelService, partService); + super(id, label, OUTPUT_PANEL_ID, panelService, layoutService); } } @@ -50,9 +50,11 @@ export class ClearOutputAction extends Action { } public run(): Promise { - this.outputService.getActiveChannel().clear(); - aria.status(nls.localize('outputCleared', "Output was cleared")); - + const activeChannel = this.outputService.getActiveChannel(); + if (activeChannel) { + activeChannel.clear(); + aria.status(nls.localize('outputCleared', "Output was cleared")); + } return Promise.resolve(true); } } @@ -70,7 +72,12 @@ export class ToggleOrSetOutputScrollLockAction extends Action { constructor(id: string, label: string, @IOutputService private readonly outputService: IOutputService) { super(id, label, 'output-action output-scroll-unlock'); - this.toDispose.push(this.outputService.onActiveOutputChannel(channel => this.setClass(this.outputService.getActiveChannel().scrollLock))); + this.toDispose.push(this.outputService.onActiveOutputChannel(channel => { + const activeChannel = this.outputService.getActiveChannel(); + if (activeChannel) { + this.setClass(activeChannel.scrollLock); + } + })); } public run(newLockState?: boolean): Promise { @@ -113,7 +120,7 @@ export class SwitchOutputAction extends Action { this.class = 'output-action switch-to-output'; } - public run(channelId?: string): Promise { + public run(channelId: string): Promise { return this.outputService.showChannel(channelId); } } @@ -134,12 +141,12 @@ export class SwitchOutputActionItem extends SelectActionItem { super(null, action, [], 0, contextViewService, { ariaLabel: nls.localize('outputChannels', 'Output Channels.') }); let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); - this.toDispose.push(outputChannelRegistry.onDidRegisterChannel(() => this.updateOtions(this.outputService.getActiveChannel().id))); - this.toDispose.push(outputChannelRegistry.onDidRemoveChannel(() => this.updateOtions(this.outputService.getActiveChannel().id))); - this.toDispose.push(this.outputService.onActiveOutputChannel(activeChannelId => this.updateOtions(activeChannelId))); + this.toDispose.push(outputChannelRegistry.onDidRegisterChannel(() => this.updateOtions())); + this.toDispose.push(outputChannelRegistry.onDidRemoveChannel(() => this.updateOtions())); + this.toDispose.push(this.outputService.onActiveOutputChannel(() => this.updateOtions())); this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService)); - this.updateOtions(this.outputService.getActiveChannel().id); + this.updateOtions(); } protected getActionContext(option: string, index: number): string { @@ -147,7 +154,7 @@ export class SwitchOutputActionItem extends SelectActionItem { return channel ? channel.id : option; } - private updateOtions(selectedChannel: string): void { + private updateOtions(): void { const groups = groupBy(this.outputService.getChannelDescriptors(), (c1: IOutputChannelDescriptor, c2: IOutputChannelDescriptor) => { if (!c1.log && c2.log) { return -1; @@ -163,10 +170,11 @@ export class SwitchOutputActionItem extends SelectActionItem { const separatorIndex = showSeparator ? this.outputChannels.length : -1; const options: string[] = [...this.outputChannels.map(c => c.label), ...(showSeparator ? [SwitchOutputActionItem.SEPARATOR] : []), ...this.logChannels.map(c => nls.localize('logChannel', "Log ({0})", c.label))]; let selected = 0; - if (selectedChannel) { - selected = this.outputChannels.map(c => c.id).indexOf(selectedChannel); + const activeChannel = this.outputService.getActiveChannel(); + if (activeChannel) { + selected = this.outputChannels.map(c => c.id).indexOf(activeChannel.id); if (selected === -1) { - const logChannelIndex = this.logChannels.map(c => c.id).indexOf(selectedChannel); + const logChannelIndex = this.logChannels.map(c => c.id).indexOf(activeChannel.id); selected = logChannelIndex !== -1 ? separatorIndex + 1 + logChannelIndex : 0; } } @@ -192,17 +200,23 @@ export class OpenLogOutputFile extends Action { } private update(): void { - const outputChannelDescriptor = this.getOutputChannelDescriptor(); - this.enabled = outputChannelDescriptor && outputChannelDescriptor.file && outputChannelDescriptor.log; + this.enabled = !!this.getLogFileOutputChannelDescriptor(); } public run(): Promise { - return this.enabled ? this.editorService.openEditor(this.instantiationService.createInstance(LogViewerInput, this.getOutputChannelDescriptor())).then(() => null) : Promise.resolve(null); + const logFileOutputChannelDescriptor = this.getLogFileOutputChannelDescriptor(); + return logFileOutputChannelDescriptor ? this.editorService.openEditor(this.instantiationService.createInstance(LogViewerInput, logFileOutputChannelDescriptor)).then(() => null) : Promise.resolve(null); } - private getOutputChannelDescriptor(): IOutputChannelDescriptor { + private getLogFileOutputChannelDescriptor(): IFileOutputChannelDescriptor | null { const channel = this.outputService.getActiveChannel(); - return channel ? this.outputService.getChannelDescriptors().filter(c => c.id === channel.id)[0] : null; + if (channel) { + const descriptor = this.outputService.getChannelDescriptors().filter(c => c.id === channel.id)[0]; + if (descriptor && descriptor.file && descriptor.log) { + return descriptor; + } + } + return null; } } @@ -219,15 +233,15 @@ export class ShowLogsOutputChannelAction extends Action { } run(): Promise { - const entries: IQuickPickItem[] = this.outputService.getChannelDescriptors().filter(c => c.file && c.log) - .map(({ id, label }) => ({ id, label })); + const entries: { id: string, label: string }[] = this.outputService.getChannelDescriptors().filter(c => c.file && c.log) + .map(({ id, label }) => ({ id, label })); return this.quickInputService.pick(entries, { placeHolder: nls.localize('selectlog', "Select Log") }) .then(entry => { if (entry) { return this.outputService.showChannel(entry.id); } - return null; + return undefined; }); } } @@ -257,9 +271,9 @@ export class OpenOutputLogFileAction extends Action { return this.quickInputService.pick(entries, { placeHolder: nls.localize('selectlogFile', "Select Log file") }) .then(entry => { if (entry) { - return this.editorService.openEditor(this.instantiationService.createInstance(LogViewerInput, entry.channel)).then(() => null); + return this.editorService.openEditor(this.instantiationService.createInstance(LogViewerInput, entry.channel)).then(() => undefined); } - return null; + return undefined; }); } } \ No newline at end of file diff --git a/src/vs/workbench/parts/output/browser/outputPanel.ts b/src/vs/workbench/contrib/output/browser/outputPanel.ts similarity index 90% rename from src/vs/workbench/parts/output/browser/outputPanel.ts rename to src/vs/workbench/contrib/output/browser/outputPanel.ts index 521713539f..3ec1479464 100644 --- a/src/vs/workbench/parts/output/browser/outputPanel.ts +++ b/src/vs/workbench/contrib/output/browser/outputPanel.ts @@ -17,12 +17,12 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { AbstractTextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; -import { OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT } from 'vs/workbench/parts/output/common/output'; -import { SwitchOutputAction, SwitchOutputActionItem, ClearOutputAction, ToggleOrSetOutputScrollLockAction, OpenLogOutputFile } from 'vs/workbench/parts/output/browser/outputActions'; +import { OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT } from 'vs/workbench/contrib/output/common/output'; +import { SwitchOutputAction, SwitchOutputActionItem, ClearOutputAction, ToggleOrSetOutputScrollLockAction, OpenLogOutputFile } from 'vs/workbench/contrib/output/browser/outputActions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IWindowService } from 'vs/platform/windows/common/windows'; @@ -75,7 +75,7 @@ export class OutputPanel extends AbstractTextResourceEditor { return this.actions; } - public getActionItem(action: Action): IActionItem { + public getActionItem(action: Action): IActionItem | null { if (action.id === SwitchOutputAction.ID) { return this.instantiationService.createInstance(SwitchOutputActionItem, action); } @@ -141,7 +141,7 @@ export class OutputPanel extends AbstractTextResourceEditor { } protected createEditor(parent: HTMLElement): void { - // First create the scoped instantation service and only then construct the editor using the scoped service + // First create the scoped instantiation service and only then construct the editor using the scoped service const scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent)); this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])); super.createEditor(parent); @@ -154,11 +154,14 @@ export class OutputPanel extends AbstractTextResourceEditor { return; } - const newPositionLine = e.position.lineNumber; - const lastLine = codeEditor.getModel().getLineCount(); - const newLockState = lastLine !== newPositionLine; - const lockAction = this.actions.filter((action) => action.id === ToggleOrSetOutputScrollLockAction.ID)[0]; - lockAction.run(newLockState); + const model = codeEditor.getModel(); + if (model) { + const newPositionLine = e.position.lineNumber; + const lastLine = model.getLineCount(); + const newLockState = lastLine !== newPositionLine; + const lockAction = this.actions.filter((action) => action.id === ToggleOrSetOutputScrollLockAction.ID)[0]; + lockAction.run(newLockState); + } }); } diff --git a/src/vs/workbench/contrib/output/browser/outputServices.ts b/src/vs/workbench/contrib/output/browser/outputServices.ts new file mode 100644 index 0000000000..c0a4c57226 --- /dev/null +++ b/src/vs/workbench/contrib/output/browser/outputServices.ts @@ -0,0 +1,291 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Event, Emitter } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; +import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { EditorOptions } from 'vs/workbench/common/editor'; +import { IOutputChannelDescriptor, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, OUTPUT_SCHEME, LOG_SCHEME, CONTEXT_ACTIVE_LOG_OUTPUT, LOG_MIME, OUTPUT_MIME } from 'vs/workbench/contrib/output/common/output'; +import { OutputPanel } from 'vs/workbench/contrib/output/browser/outputPanel'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { OutputLinkProvider } from 'vs/workbench/contrib/output/common/outputLinkProvider'; +import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; +import { ITextModel } from 'vs/editor/common/model'; +import { IPanel } from 'vs/workbench/common/panel'; +import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { ILogService } from 'vs/platform/log/common/log'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IOutputChannelModel, IOutputChannelModelService } from 'vs/workbench/services/output/common/outputChannelModel'; + +const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel'; + +class OutputChannel extends Disposable implements IOutputChannel { + + scrollLock: boolean = false; + readonly model: IOutputChannelModel; + readonly id: string; + readonly label: string; + + constructor( + readonly outputChannelDescriptor: IOutputChannelDescriptor, + @IOutputChannelModelService outputChannelModelService: IOutputChannelModelService + ) { + super(); + this.id = outputChannelDescriptor.id; + this.label = outputChannelDescriptor.label; + this.model = this._register(outputChannelModelService.createOutputChannelModel(this.id, URI.from({ scheme: OUTPUT_SCHEME, path: this.id }), outputChannelDescriptor.log ? LOG_MIME : OUTPUT_MIME, outputChannelDescriptor.file)); + } + + append(output: string): void { + this.model.append(output); + } + + update(): void { + this.model.update(); + } + + clear(till?: number): void { + this.model.clear(till); + } +} + +export class OutputService extends Disposable implements IOutputService, ITextModelContentProvider { + + public _serviceBrand: any; + + private channels: Map = new Map(); + private activeChannelIdInStorage: string; + private activeChannel: OutputChannel | null; + + private readonly _onActiveOutputChannel = new Emitter(); + readonly onActiveOutputChannel: Event = this._onActiveOutputChannel.event; + + private _outputPanel: OutputPanel; + + constructor( + @IStorageService private readonly storageService: IStorageService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IPanelService private readonly panelService: IPanelService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @ITextModelService textModelResolverService: ITextModelService, + @IEnvironmentService environmentService: IEnvironmentService, + @IWindowService windowService: IWindowService, + @ILogService private readonly logService: ILogService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + ) { + super(); + this.activeChannelIdInStorage = this.storageService.get(OUTPUT_ACTIVE_CHANNEL_KEY, StorageScope.WORKSPACE, ''); + + // Register as text model content provider for output + textModelResolverService.registerTextModelContentProvider(OUTPUT_SCHEME, this); + instantiationService.createInstance(OutputLinkProvider); + + // Create output channels for already registered channels + const registry = Registry.as(Extensions.OutputChannels); + for (const channelIdentifier of registry.getChannels()) { + this.onDidRegisterChannel(channelIdentifier.id); + } + this._register(registry.onDidRegisterChannel(this.onDidRegisterChannel, this)); + + this._register(panelService.onDidPanelOpen(({ panel, focus }) => this.onDidPanelOpen(panel, !focus), this)); + this._register(panelService.onDidPanelClose(this.onDidPanelClose, this)); + + // Set active channel to first channel if not set + if (!this.activeChannel) { + const channels = this.getChannelDescriptors(); + this.activeChannel = channels && channels.length > 0 ? this.getChannel(channels[0].id) : null; + } + + this._register(this.lifecycleService.onShutdown(() => this.dispose())); + this._register(this.storageService.onWillSaveState(() => this.saveState())); + } + + provideTextContent(resource: URI): Promise | null { + const channel = this.getChannel(resource.path); + if (channel) { + return channel.model.loadModel(); + } + return null; + } + + showChannel(id: string, preserveFocus?: boolean): Promise { + const channel = this.getChannel(id); + if (!channel || this.isChannelShown(channel)) { + if (this._outputPanel && !preserveFocus) { + this._outputPanel.focus(); + } + return Promise.resolve(undefined); + } + + this.activeChannel = channel; + let promise: Promise; + if (this.isPanelShown()) { + promise = this.doShowChannel(channel, !!preserveFocus); + } else { + this.panelService.openPanel(OUTPUT_PANEL_ID); + promise = this.doShowChannel(this.activeChannel, !!preserveFocus); + } + return promise.then(() => this._onActiveOutputChannel.fire(id)); + } + + getChannel(id: string): OutputChannel | null { + return this.channels.get(id) || null; + } + + getChannelDescriptors(): IOutputChannelDescriptor[] { + return Registry.as(Extensions.OutputChannels).getChannels(); + } + + getActiveChannel(): IOutputChannel | null { + return this.activeChannel; + } + + private onDidRegisterChannel(channelId: string): void { + const channel = this.createChannel(channelId); + this.channels.set(channelId, channel); + if (!this.activeChannel || this.activeChannelIdInStorage === channelId) { + this.activeChannel = channel; + this.onDidPanelOpen(this.panelService.getActivePanel(), true) + .then(() => this._onActiveOutputChannel.fire(channelId)); + } + } + + private onDidPanelOpen(panel: IPanel | null, preserveFocus: boolean): Promise { + if (panel && panel.getId() === OUTPUT_PANEL_ID) { + this._outputPanel = this.panelService.getActivePanel(); + if (this.activeChannel) { + return this.doShowChannel(this.activeChannel, preserveFocus); + } + } + return Promise.resolve(undefined); + } + + private onDidPanelClose(panel: IPanel): void { + if (this._outputPanel && panel.getId() === OUTPUT_PANEL_ID) { + CONTEXT_ACTIVE_LOG_OUTPUT.bindTo(this.contextKeyService).set(false); + this._outputPanel.clearInput(); + } + } + + private createChannel(id: string): OutputChannel { + const channelDisposables: IDisposable[] = []; + const channel = this.instantiateChannel(id); + channel.model.onDidAppendedContent(() => { + if (!channel.scrollLock) { + const panel = this.panelService.getActivePanel(); + if (panel && panel.getId() === OUTPUT_PANEL_ID && this.isChannelShown(channel)) { + let outputPanel = panel; + outputPanel.revealLastLine(); + } + } + }, channelDisposables); + channel.model.onDispose(() => { + if (this.activeChannel === channel) { + const channels = this.getChannelDescriptors(); + const channel = channels.length ? this.getChannel(channels[0].id) : null; + if (channel && this.isPanelShown()) { + this.showChannel(channel.id, true); + } else { + this.activeChannel = channel; + if (this.activeChannel) { + this._onActiveOutputChannel.fire(this.activeChannel.id); + } + } + } + Registry.as(Extensions.OutputChannels).removeChannel(id); + dispose(channelDisposables); + }, channelDisposables); + + return channel; + } + + private instantiateChannel(id: string): OutputChannel { + const channelData = Registry.as(Extensions.OutputChannels).getChannel(id); + if (!channelData) { + this.logService.error(`Channel '${id}' is not registered yet`); + throw new Error(`Channel '${id}' is not registered yet`); + } + return this.instantiationService.createInstance(OutputChannel, channelData); + } + + private doShowChannel(channel: OutputChannel, preserveFocus: boolean): Promise { + if (this._outputPanel) { + CONTEXT_ACTIVE_LOG_OUTPUT.bindTo(this.contextKeyService).set(!!channel.outputChannelDescriptor.file && channel.outputChannelDescriptor.log); + return this._outputPanel.setInput(this.createInput(channel), EditorOptions.create({ preserveFocus }), CancellationToken.None) + .then(() => { + if (!preserveFocus) { + this._outputPanel.focus(); + } + }); + } + return Promise.resolve(undefined); + } + + private isChannelShown(channel: IOutputChannel): boolean { + return this.isPanelShown() && this.activeChannel === channel; + } + + private isPanelShown(): boolean { + const panel = this.panelService.getActivePanel(); + return !!panel && panel.getId() === OUTPUT_PANEL_ID; + } + + private createInput(channel: IOutputChannel): ResourceEditorInput { + const resource = URI.from({ scheme: OUTPUT_SCHEME, path: channel.id }); + return this.instantiationService.createInstance(ResourceEditorInput, nls.localize('output', "{0} - Output", channel.label), nls.localize('channel', "Output channel for '{0}'", channel.label), resource); + } + + private saveState(): void { + if (this.activeChannel) { + this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannel.id, StorageScope.WORKSPACE); + } + } +} + +export class LogContentProvider { + + private channelModels: Map = new Map(); + + constructor( + @IOutputService private readonly outputService: IOutputService, + @IOutputChannelModelService private readonly outputChannelModelService: IOutputChannelModelService + ) { + } + + provideTextContent(resource: URI): Promise | null { + if (resource.scheme === LOG_SCHEME) { + let channelModel = this.getChannelModel(resource); + if (channelModel) { + return channelModel.loadModel(); + } + } + return null; + } + + private getChannelModel(resource: URI): IOutputChannelModel | undefined { + const channelId = resource.path; + let channelModel = this.channelModels.get(channelId); + if (!channelModel) { + const channelDisposables: IDisposable[] = []; + const outputChannelDescriptor = this.outputService.getChannelDescriptors().filter(({ id }) => id === channelId)[0]; + if (outputChannelDescriptor && outputChannelDescriptor.file) { + channelModel = this.outputChannelModelService.createOutputChannelModel(channelId, resource, outputChannelDescriptor.log ? LOG_MIME : OUTPUT_MIME, outputChannelDescriptor.file); + channelModel.onDispose(() => dispose(channelDisposables), channelDisposables); + this.channelModels.set(channelId, channelModel); + } + } + return channelModel; + } +} diff --git a/src/vs/workbench/parts/output/common/output.ts b/src/vs/workbench/contrib/output/common/output.ts similarity index 96% rename from src/vs/workbench/parts/output/common/output.ts rename to src/vs/workbench/contrib/output/common/output.ts index b16045ee15..9c67aaab10 100644 --- a/src/vs/workbench/parts/output/common/output.ts +++ b/src/vs/workbench/contrib/output/common/output.ts @@ -68,7 +68,7 @@ export interface IOutputService { * Given the channel id returns the output channel instance. * Channel should be first registered via OutputChannelRegistry. */ - getChannel(id: string): IOutputChannel; + getChannel(id: string): IOutputChannel | null; /** * Returns an array of all known output channels descriptors. @@ -79,7 +79,7 @@ export interface IOutputService { * Returns the currently active channel. * Only one channel can be active at a given moment. */ - getActiveChannel(): IOutputChannel; + getActiveChannel(): IOutputChannel | null; /** * Show the channel with the passed id. @@ -137,6 +137,10 @@ export interface IOutputChannelDescriptor { file?: URI; } +export interface IFileOutputChannelDescriptor extends IOutputChannelDescriptor { + file: URI; +} + export interface IOutputChannelRegistry { readonly onDidRegisterChannel: Event; diff --git a/src/vs/workbench/parts/output/common/outputLinkComputer.ts b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts similarity index 93% rename from src/vs/workbench/parts/output/common/outputLinkComputer.ts rename to src/vs/workbench/contrib/output/common/outputLinkComputer.ts index f485ca4e0e..17b9e8933c 100644 --- a/src/vs/workbench/parts/output/common/outputLinkComputer.ts +++ b/src/vs/workbench/contrib/output/common/outputLinkComputer.ts @@ -6,11 +6,12 @@ import { IMirrorModel, IWorkerContext } from 'vs/editor/common/services/editorSimpleWorker'; import { ILink } from 'vs/editor/common/modes'; import { URI } from 'vs/base/common/uri'; -import * as paths from 'vs/base/common/paths'; +import * as extpath from 'vs/base/common/extpath'; import * as resources from 'vs/base/common/resources'; import * as strings from 'vs/base/common/strings'; -import * as arrays from 'vs/base/common/arrays'; import { Range } from 'vs/editor/common/core/range'; +import { isWindows } from 'vs/base/common/platform'; +import { Schemas } from 'vs/base/common/network'; export interface ICreateData { workspaceFolders: string[]; @@ -86,11 +87,11 @@ export class OutputLinkComputer { public static createPatterns(workspaceFolder: URI): RegExp[] { const patterns: RegExp[] = []; - const workspaceFolderPath = workspaceFolder.scheme === 'file' ? workspaceFolder.fsPath : workspaceFolder.path; - const workspaceFolderVariants = arrays.distinct([ - paths.normalize(workspaceFolderPath, true), - paths.normalize(workspaceFolderPath, false) - ]); + const workspaceFolderPath = workspaceFolder.scheme === Schemas.file ? workspaceFolder.fsPath : workspaceFolder.path; + const workspaceFolderVariants = [workspaceFolderPath]; + if (isWindows && workspaceFolder.scheme === Schemas.file) { + workspaceFolderVariants.push(extpath.toSlashes(workspaceFolderPath)); + } workspaceFolderVariants.forEach(workspaceFolderVariant => { const validPathCharacterPattern = '[^\\s\\(\\):<>"]'; diff --git a/src/vs/workbench/parts/output/common/outputLinkProvider.ts b/src/vs/workbench/contrib/output/common/outputLinkProvider.ts similarity index 92% rename from src/vs/workbench/parts/output/common/outputLinkProvider.ts rename to src/vs/workbench/contrib/output/common/outputLinkProvider.ts index a583388782..2fdece1d66 100644 --- a/src/vs/workbench/parts/output/common/outputLinkProvider.ts +++ b/src/vs/workbench/contrib/output/common/outputLinkProvider.ts @@ -8,9 +8,9 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { IModelService } from 'vs/editor/common/services/modelService'; import { LinkProviderRegistry, ILink } from 'vs/editor/common/modes'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { OUTPUT_MODE_ID, LOG_MODE_ID } from 'vs/workbench/parts/output/common/output'; +import { OUTPUT_MODE_ID, LOG_MODE_ID } from 'vs/workbench/contrib/output/common/output'; import { MonacoWebWorker, createWebWorker } from 'vs/editor/common/services/webWorker'; -import { ICreateData, OutputLinkComputer } from 'vs/workbench/parts/output/common/outputLinkComputer'; +import { ICreateData, OutputLinkComputer } from 'vs/workbench/contrib/output/common/outputLinkComputer'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; export class OutputLinkProvider { @@ -65,7 +65,7 @@ export class OutputLinkProvider { }; this.worker = createWebWorker(this.modelService, { - moduleId: 'vs/workbench/parts/output/common/outputLinkComputer', + moduleId: 'vs/workbench/contrib/output/common/outputLinkComputer', createData, label: 'outputLinkComputer' }); diff --git a/src/vs/workbench/parts/output/test/outputLinkProvider.test.ts b/src/vs/workbench/contrib/output/test/outputLinkProvider.test.ts similarity index 57% rename from src/vs/workbench/parts/output/test/outputLinkProvider.test.ts rename to src/vs/workbench/contrib/output/test/outputLinkProvider.test.ts index 3f2b943395..b0f8a79192 100644 --- a/src/vs/workbench/parts/output/test/outputLinkProvider.test.ts +++ b/src/vs/workbench/contrib/output/test/outputLinkProvider.test.ts @@ -5,8 +5,8 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { isMacintosh, isLinux } from 'vs/base/common/platform'; -import { OutputLinkComputer } from 'vs/workbench/parts/output/common/outputLinkComputer'; +import { isMacintosh, isLinux, isWindows } from 'vs/base/common/platform'; +import { OutputLinkComputer } from 'vs/workbench/contrib/output/common/outputLinkComputer'; import { TestContextService } from 'vs/workbench/test/workbenchTestServices'; function toOSPath(p: string): string { @@ -20,32 +20,20 @@ function toOSPath(p: string): string { suite('Workbench - OutputWorker', () => { test('OutputWorker - Link detection', function () { - let patternsSlash = OutputLinkComputer.createPatterns( - URI.file('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala') - ); + const rootFolder = isWindows ? URI.file('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala') : + URI.file('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala'); - let patternsBackSlash = OutputLinkComputer.createPatterns( - URI.file('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala') - ); + let patterns = OutputLinkComputer.createPatterns(rootFolder); let contextService = new TestContextService(); let line = toOSPath('Foo bar'); - let result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 0); - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + let result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 0); // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); - assert.equal(result[0].range.startColumn, 5); - assert.equal(result[0].range.endColumn, 84); - - line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 5); @@ -53,14 +41,7 @@ suite('Workbench - OutputWorker', () => { // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336 line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336 in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336'); - assert.equal(result[0].range.startColumn, 5); - assert.equal(result[0].range.endColumn, 88); - - line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336 in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336'); assert.equal(result[0].range.startColumn, 5); @@ -68,26 +49,14 @@ suite('Workbench - OutputWorker', () => { // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336:9 line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336:9 in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336,9'); - assert.equal(result[0].range.startColumn, 5); - assert.equal(result[0].range.endColumn, 90); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336,9'); assert.equal(result[0].range.startColumn, 5); assert.equal(result[0].range.endColumn, 90); line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336:9 in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336,9'); - assert.equal(result[0].range.startColumn, 5); - assert.equal(result[0].range.endColumn, 90); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336,9'); assert.equal(result[0].range.startColumn, 5); @@ -95,7 +64,7 @@ suite('Workbench - OutputWorker', () => { // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts>dir line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts>dir in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 5); @@ -103,7 +72,7 @@ suite('Workbench - OutputWorker', () => { // Example: at [C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336:9] line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336:9] in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336,9'); assert.equal(result[0].range.startColumn, 5); @@ -111,19 +80,13 @@ suite('Workbench - OutputWorker', () => { // Example: at [C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts] line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts] in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts]').toString()); // Example: C:\Users\someone\AppData\Local\Temp\_monacodata_9888\workspaces\express\server.js on line 8 line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts on line 8'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 90); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8'); assert.equal(result[0].range.startColumn, 1); @@ -131,34 +94,23 @@ suite('Workbench - OutputWorker', () => { // Example: C:\Users\someone\AppData\Local\Temp\_monacodata_9888\workspaces\express\server.js on line 8, column 13 line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts on line 8, column 13'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8,13'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 101); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8,13'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 101); line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts on LINE 8, COLUMN 13'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8,13'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 101); - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8,13'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 101); // Example: C:\Users\someone\AppData\Local\Temp\_monacodata_9888\workspaces\express\server.js:line 8 line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:line 8'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8'); assert.equal(result[0].range.startColumn, 1); @@ -166,13 +118,7 @@ suite('Workbench - OutputWorker', () => { // Example: at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts) line = toOSPath(' at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts)'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); - assert.equal(result[0].range.startColumn, 15); - assert.equal(result[0].range.endColumn, 94); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 15); @@ -180,13 +126,7 @@ suite('Workbench - OutputWorker', () => { // Example: at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts:278) line = toOSPath(' at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts:278)'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278'); - assert.equal(result[0].range.startColumn, 15); - assert.equal(result[0].range.endColumn, 98); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278'); assert.equal(result[0].range.startColumn, 15); @@ -194,26 +134,14 @@ suite('Workbench - OutputWorker', () => { // Example: at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts:278:34) line = toOSPath(' at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts:278:34)'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278,34'); - assert.equal(result[0].range.startColumn, 15); - assert.equal(result[0].range.endColumn, 101); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278,34'); assert.equal(result[0].range.startColumn, 15); assert.equal(result[0].range.endColumn, 101); line = toOSPath(' at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts:278:34)'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278,34'); - assert.equal(result[0].range.startColumn, 15); - assert.equal(result[0].range.endColumn, 101); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278,34'); assert.equal(result[0].range.startColumn, 15); @@ -221,13 +149,7 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts(45): error line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts(45): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 102); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); @@ -235,13 +157,7 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts (45,18): error line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts (45): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 103); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); @@ -249,26 +165,14 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts(45,18): error line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts(45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 105); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 105); line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts(45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 105); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); @@ -276,26 +180,14 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts (45,18): error line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts (45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 106); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 106); line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts (45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 106); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); @@ -303,13 +195,7 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts(45): error line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts(45): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 102); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); @@ -317,13 +203,7 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts (45,18): error line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts (45): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 103); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); @@ -331,26 +211,14 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts(45,18): error line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts(45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 105); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 105); line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts(45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 105); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); @@ -358,26 +226,14 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts (45,18): error line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts (45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 106); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 106); line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts (45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 106); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); @@ -385,13 +241,7 @@ suite('Workbench - OutputWorker', () => { // Example: C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features Special.ts (45,18): error. line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features Special.ts (45,18): error'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); - assert.equal(result.length, 1); - assert.equal(result[0].url, contextService.toResource('/lib/something/Features Special.ts').toString() + '#45,18'); - assert.equal(result[0].range.startColumn, 1); - assert.equal(result[0].range.endColumn, 114); - - result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features Special.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); @@ -399,7 +249,7 @@ suite('Workbench - OutputWorker', () => { // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts. line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts. in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 5); @@ -407,17 +257,17 @@ suite('Workbench - OutputWorker', () => { // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game\\ line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game\\ in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); // Example: at "C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts" line = toOSPath(' at "C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts" in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 6); @@ -425,7 +275,7 @@ suite('Workbench - OutputWorker', () => { // Example: at 'C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts' line = toOSPath(' at \'C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts\' in'); - result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patterns, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts\'').toString()); assert.equal(result[0].range.startColumn, 6); diff --git a/src/vs/workbench/parts/performance/electron-browser/performance.contribution.ts b/src/vs/workbench/contrib/performance/electron-browser/performance.contribution.ts similarity index 95% rename from src/vs/workbench/parts/performance/electron-browser/performance.contribution.ts rename to src/vs/workbench/contrib/performance/electron-browser/performance.contribution.ts index 74be3a4141..f312caf258 100644 --- a/src/vs/workbench/parts/performance/electron-browser/performance.contribution.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/performance.contribution.ts @@ -11,7 +11,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { Extensions as Input, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; -import { PerfviewContrib, PerfviewInput } from 'vs/workbench/parts/performance/electron-browser/perfviewEditor'; +import { PerfviewContrib, PerfviewInput } from 'vs/workbench/contrib/performance/electron-browser/perfviewEditor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { StartupProfiler } from './startupProfiler'; import { StartupTimings } from './startupTimings'; diff --git a/src/vs/workbench/parts/performance/electron-browser/perfviewEditor.ts b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts similarity index 88% rename from src/vs/workbench/parts/performance/electron-browser/perfviewEditor.ts rename to src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts index dc10e103fe..0317fed422 100644 --- a/src/vs/workbench/parts/performance/electron-browser/perfviewEditor.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/perfviewEditor.ts @@ -7,7 +7,6 @@ import { localize } from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; -import { IHashService } from 'vs/workbench/services/hash/common/hashService'; import { ITextModel } from 'vs/editor/common/model'; import { ILifecycleService, LifecyclePhase, StartupKindToString } from 'vs/platform/lifecycle/common/lifecycle'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -19,11 +18,11 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import * as perf from 'vs/base/common/performance'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { writeTransientState } from 'vs/workbench/parts/codeEditor/electron-browser/toggleWordWrap'; +import { writeTransientState } from 'vs/workbench/contrib/codeEditor/browser/toggleWordWrap'; import { mergeSort } from 'vs/base/common/arrays'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import product from 'vs/platform/node/product'; -import pkg from 'vs/platform/node/package'; +import product from 'vs/platform/product/node/product'; +import pkg from 'vs/platform/product/node/package'; export class PerfviewContrib { @@ -47,14 +46,13 @@ export class PerfviewInput extends ResourceEditorInput { static readonly Uri = URI.from({ scheme: 'perf', path: 'Startup Performance' }); constructor( - @ITextModelService textModelResolverService: ITextModelService, - @IHashService hashService: IHashService + @ITextModelService textModelResolverService: ITextModelService ) { super( localize('name', "Startup Performance"), - undefined, + null, PerfviewInput.Uri, - textModelResolverService, hashService + textModelResolverService ); } @@ -85,7 +83,11 @@ class PerfModelContentProvider implements ITextModelContentProvider { const langId = this._modeService.create('markdown'); this._model = this._modelService.getModel(resource) || this._modelService.createModel('Loading...', langId, resource); - this._modelDisposables.push(langId.onDidChange(e => this._model.setMode(e))); + this._modelDisposables.push(langId.onDidChange(e => { + if (this._model) { + this._model.setMode(e); + } + })); this._modelDisposables.push(langId); this._modelDisposables.push(this._extensionService.onDidChangeExtensionsStatus(this._updateModel, this)); @@ -102,7 +104,7 @@ class PerfModelContentProvider implements ITextModelContentProvider { this._lifecycleService.when(LifecyclePhase.Eventually), this._extensionService.whenInstalledExtensionsRegistered() ]).then(([metrics]) => { - if (!this._model.isDisposed()) { + if (this._model && !this._model.isDisposed()) { let stats = this._envService.args['prof-modules'] ? LoaderStats.get() : undefined; let md = new MarkdownBuilder(); @@ -126,9 +128,15 @@ class PerfModelContentProvider implements ITextModelContentProvider { md.heading(2, 'System Info'); md.li(`${product.nameShort}: ${pkg.version} (${product.commit || '0000000'})`); md.li(`OS: ${metrics.platform}(${metrics.release})`); - md.li(`CPUs: ${metrics.cpus.model}(${metrics.cpus.count} x ${metrics.cpus.speed})`); - md.li(`Memory(System): ${(metrics.totalmem / (1024 * 1024 * 1024)).toFixed(2)} GB(${(metrics.freemem / (1024 * 1024 * 1024)).toFixed(2)}GB free)`); - md.li(`Memory(Process): ${(metrics.meminfo.workingSetSize / 1024).toFixed(2)} MB working set(${(metrics.meminfo.peakWorkingSetSize / 1024).toFixed(2)}MB peak, ${(metrics.meminfo.privateBytes / 1024).toFixed(2)}MB private, ${(metrics.meminfo.sharedBytes / 1024).toFixed(2)}MB shared)`); + if (metrics.cpus) { + md.li(`CPUs: ${metrics.cpus.model}(${metrics.cpus.count} x ${metrics.cpus.speed})`); + } + if (typeof metrics.totalmem === 'number' && typeof metrics.freemem === 'number') { + md.li(`Memory(System): ${(metrics.totalmem / (1024 * 1024 * 1024)).toFixed(2)} GB(${(metrics.freemem / (1024 * 1024 * 1024)).toFixed(2)}GB free)`); + } + if (metrics.meminfo) { + md.li(`Memory(Process): ${(metrics.meminfo.workingSetSize / 1024).toFixed(2)} MB working set(${(metrics.meminfo.peakWorkingSetSize / 1024).toFixed(2)}MB peak, ${(metrics.meminfo.privateBytes / 1024).toFixed(2)}MB private, ${(metrics.meminfo.sharedBytes / 1024).toFixed(2)}MB shared)`); + } md.li(`VM(likelyhood): ${metrics.isVMLikelyhood}%`); md.li(`Initial Startup: ${metrics.initialStartup}`); md.li(`Has ${metrics.windowCount - 1} other windows`); @@ -138,16 +146,13 @@ class PerfModelContentProvider implements ITextModelContentProvider { private _addSummaryTable(md: MarkdownBuilder, metrics: IStartupMetrics, stats?: LoaderStats): void { - const table: (string | number)[][] = []; + const table: Array> = []; table.push(['start => app.isReady', metrics.timers.ellapsedAppReady, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['nls:start => nls:end', metrics.timers.ellapsedNlsGeneration, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['require(main.bundle.js)', metrics.initialStartup ? perf.getDuration('willLoadMainBundle', 'didLoadMainBundle') : undefined, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['app.isReady => window.loadUrl()', metrics.timers.ellapsedWindowLoad, '[main]', `initial startup: ${metrics.initialStartup}`]); - table.push(['require & init global storage', metrics.timers.ellapsedGlobalStorageInitMain, '[main]', `initial startup: ${metrics.initialStartup}`]); table.push(['window.loadUrl() => begin to require(workbench.main.js)', metrics.timers.ellapsedWindowLoadToRequire, '[main->renderer]', StartupKindToString(metrics.windowKind)]); table.push(['require(workbench.main.js)', metrics.timers.ellapsedRequire, '[renderer]', `cached data: ${(metrics.didUseCachedData ? 'YES' : 'NO')}${stats ? `, node_modules took ${stats.nodeRequireTotal}ms` : ''}`]); - table.push(['init global storage', metrics.timers.ellapsedGlobalStorageInitRenderer, '[renderer]', undefined]); - table.push(['require workspace storage', metrics.timers.ellapsedWorkspaceStorageRequire, '[renderer]', undefined]); table.push(['require & init workspace storage', metrics.timers.ellapsedWorkspaceStorageInit, '[renderer]', undefined]); table.push(['init workspace service', metrics.timers.ellapsedWorkspaceServiceInit, '[renderer]', undefined]); table.push(['register extensions & spawn extension host', metrics.timers.ellapsedExtensions, '[renderer]', undefined]); @@ -335,7 +340,7 @@ class MarkdownBuilder { return this; } - table(header: string[], rows: { toString() }[][]) { + table(header: string[], rows: Array>) { let lengths: number[] = []; header.forEach((cell, ci) => { lengths[ci] = cell.length; @@ -359,7 +364,9 @@ class MarkdownBuilder { // cells rows.forEach(row => { row.forEach((cell, ci) => { - this.value += `| ${cell + repeat(' ', lengths[ci] - cell.toString().length)} `; + if (typeof cell !== 'undefined') { + this.value += `| ${cell + repeat(' ', lengths[ci] - cell.toString().length)} `; + } }); this.value += '|\n'; }); diff --git a/src/vs/workbench/parts/performance/electron-browser/startupProfiler.ts b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts similarity index 93% rename from src/vs/workbench/parts/performance/electron-browser/startupProfiler.ts rename to src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts index fe80234c2a..a177ee14f8 100644 --- a/src/vs/workbench/parts/performance/electron-browser/startupProfiler.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts @@ -3,20 +3,20 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { dirname, join } from 'path'; -import { basename } from 'vs/base/common/paths'; +import { dirname, join, basename } from 'vs/base/common/path'; import { del, exists, readdir, readFile } from 'vs/base/node/pfs'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { PerfviewInput } from 'vs/workbench/parts/performance/electron-browser/perfviewEditor'; +import { PerfviewInput } from 'vs/workbench/contrib/performance/electron-browser/perfviewEditor'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { URI } from 'vs/base/common/uri'; export class StartupProfiler implements IWorkbenchContribution { @@ -79,7 +79,7 @@ export class StartupProfiler implements IWorkbenchContribution { }).then(res => { if (res.confirmed) { Promise.all([ - this._windowsService.showItemInFolder(join(dir, files[0])), + this._windowsService.showItemInFolder(URI.file(join(dir, files[0]))), this._createPerfIssue(files) ]).then(() => { // keep window stable until restart is selected @@ -88,7 +88,7 @@ export class StartupProfiler implements IWorkbenchContribution { message: localize('prof.thanks', "Thanks for helping us."), detail: localize('prof.detail.restart', "A final restart is required to continue to use '{0}'. Again, thank you for your contribution.", this._environmentService.appNameLong), primaryButton: localize('prof.restart', "Restart"), - secondaryButton: null + secondaryButton: undefined }).then(() => { // now we are ready to restart this._windowsService.relaunch({ removeArgs }); diff --git a/src/vs/workbench/parts/performance/electron-browser/startupTimings.ts b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts similarity index 79% rename from src/vs/workbench/parts/performance/electron-browser/startupTimings.ts rename to src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts index fcd0bd1a3d..cd2e9e34dc 100644 --- a/src/vs/workbench/parts/performance/electron-browser/startupTimings.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts @@ -9,22 +9,21 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ILifecycleService, StartupKind } from 'vs/platform/lifecycle/common/lifecycle'; -import { ILogService } from 'vs/platform/log/common/log'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IUpdateService } from 'vs/platform/update/common/update'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import * as files from 'vs/workbench/parts/files/common/files'; +import * as files from 'vs/workbench/contrib/files/common/files'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { didUseCachedData, ITimerService } from 'vs/workbench/services/timer/electron-browser/timerService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { getEntries } from 'vs/base/common/performance'; export class StartupTimings implements IWorkbenchContribution { constructor( - @ILogService private readonly _logService: ILogService, @ITimerService private readonly _timerService: ITimerService, @IWindowsService private readonly _windowsService: IWindowsService, @IEditorService private readonly _editorService: IEditorService, @@ -43,6 +42,7 @@ export class StartupTimings implements IWorkbenchContribution { const isStandardStartup = await this._isStandardStartup(); this._reportStartupTimes().catch(onUnexpectedError); this._appendStartupTimes(isStandardStartup).catch(onUnexpectedError); + this._reportPerfTicks(); } private async _reportStartupTimes(): Promise { @@ -65,18 +65,11 @@ export class StartupTimings implements IWorkbenchContribution { return; } - const waitWhenNoCachedData: () => Promise = () => { - // wait 15s for cached data to be produced - return !didUseCachedData() - ? timeout(15000) - : Promise.resolve(); - }; - const { sessionId } = await this._telemetryService.getTelemetryInfo(); Promise.all([ this._timerService.startupMetrics, - waitWhenNoCachedData(), + timeout(15000), // wait: cached data creation, telemetry sending ]).then(([startupMetrics]) => { return nfcall(appendFile, appendTo, `${startupMetrics.ellapsed}\t${product.nameShort}\t${(product.commit || '').slice(0, 10) || '0000000000'}\t${sessionId}\t${isStandardStartup ? 'standard_start' : 'NO_standard_start'}\n`); }).then(() => { @@ -95,36 +88,42 @@ export class StartupTimings implements IWorkbenchContribution { // * one text editor (not multiple, not webview, welcome etc...) // * cached data present (not rejected, not created) if (this._lifecycleService.startupKind !== StartupKind.NewWindow) { - this._logService.info('no standard startup: not a new window'); return false; } if (await this._windowsService.getWindowCount() !== 1) { - this._logService.info('no standard startup: not just one window'); return false; } - if (!this._viewletService.getActiveViewlet() || this._viewletService.getActiveViewlet().getId() !== files.VIEWLET_ID) { - this._logService.info('no standard startup: not the explorer viewlet'); + const activeViewlet = this._viewletService.getActiveViewlet(); + if (!activeViewlet || activeViewlet.getId() !== files.VIEWLET_ID) { return false; } const visibleControls = this._editorService.visibleControls; if (visibleControls.length !== 1 || !isCodeEditor(visibleControls[0].getControl())) { - this._logService.info('no standard startup: not just one text editor'); return false; } if (this._panelService.getActivePanel()) { - this._logService.info('no standard startup: panel is active'); return false; } if (!didUseCachedData()) { - this._logService.info('no standard startup: not using cached data'); return false; } if (!await this._updateService.isLatestVersion()) { - this._logService.info('no standard startup: not running latest version'); return false; } - this._logService.info('standard startup'); return true; } + + private _reportPerfTicks(): void { + const entries: Record = Object.create(null); + for (const entry of getEntries()) { + entries[entry.name] = entry.timestamp; + } + /* __GDPR__ + "startupRawTimers" : { + "entries": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } + } + */ + this._telemetryService.publicLog('startupRawTimers', { entries }); + } } diff --git a/src/vs/workbench/parts/preferences/browser/keybindingWidgets.ts b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts similarity index 90% rename from src/vs/workbench/parts/preferences/browser/keybindingWidgets.ts rename to src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts index fbf3c9339e..1e4010191a 100644 --- a/src/vs/workbench/parts/preferences/browser/keybindingWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingWidgets.ts @@ -22,7 +22,7 @@ import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/co import { IThemeService } from 'vs/platform/theme/common/themeService'; import { editorWidgetBackground, widgetShadow } from 'vs/platform/theme/common/colorRegistry'; import { ScrollType } from 'vs/editor/common/editorCommon'; -import { SearchWidget, SearchOptions } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; +import { SearchWidget, SearchOptions } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; export interface KeybindingsSearchOptions extends SearchOptions { recordEnter?: boolean; @@ -31,14 +31,14 @@ export interface KeybindingsSearchOptions extends SearchOptions { export class KeybindingsSearchWidget extends SearchWidget { - private _firstPart: ResolvedKeybinding; - private _chordPart: ResolvedKeybinding; + private _firstPart: ResolvedKeybinding | null; + private _chordPart: ResolvedKeybinding | null; private _inputValue: string; private recordDisposables: IDisposable[] = []; - private _onKeybinding = this._register(new Emitter<[ResolvedKeybinding, ResolvedKeybinding]>()); - readonly onKeybinding: Event<[ResolvedKeybinding, ResolvedKeybinding]> = this._onKeybinding.event; + private _onKeybinding = this._register(new Emitter<[ResolvedKeybinding | null, ResolvedKeybinding | null]>()); + readonly onKeybinding: Event<[ResolvedKeybinding | null, ResolvedKeybinding | null]> = this._onKeybinding.event; private _onEnter = this._register(new Emitter()); readonly onEnter: Event = this._onEnter.event; @@ -125,7 +125,7 @@ export class KeybindingsSearchWidget extends SearchWidget { let value = ''; if (this._firstPart) { - value = this._firstPart.getUserSettingsLabel(); + value = (this._firstPart.getUserSettingsLabel() || ''); } if (this._chordPart) { value = value + ' ' + this._chordPart.getUserSettingsLabel(); @@ -153,11 +153,11 @@ export class DefineKeybindingWidget extends Widget { private _onHide = this._register(new Emitter()); - private _onDidChange = this._register(new Emitter()); - onDidChange: Event = this._onDidChange.event; + private _onDidChange = this._register(new Emitter()); + onDidChange: Event = this._onDidChange.event; - private _onShowExistingKeybindings = this._register(new Emitter()); - readonly onShowExistingKeybidings: Event = this._onShowExistingKeybindings.event; + private _onShowExistingKeybindings = this._register(new Emitter()); + readonly onShowExistingKeybidings: Event = this._onShowExistingKeybindings.event; constructor( parent: HTMLElement, @@ -175,9 +175,9 @@ export class DefineKeybindingWidget extends Widget { return this._domNode.domNode; } - define(): Promise { + define(): Promise { this._keybindingInputWidget.clear(); - return new Promise((c) => { + return new Promise((c) => { if (!this._isVisible) { this._isVisible = true; this._domNode.setDisplay('block'); @@ -224,7 +224,7 @@ export class DefineKeybindingWidget extends Widget { this._domNode.setHeight(DefineKeybindingWidget.HEIGHT); const message = nls.localize('defineKeybinding.initial', "Press desired key combination and then press ENTER."); - dom.append(this._domNode.domNode, dom.$('.message', null, message)); + dom.append(this._domNode.domNode, dom.$('.message', undefined, message)); this._register(attachStylerCallback(this.themeService, { editorWidgetBackground, widgetShadow }, colors => { if (colors.editorWidgetBackground) { @@ -251,7 +251,7 @@ export class DefineKeybindingWidget extends Widget { this._showExistingKeybindingsNode = dom.append(this._domNode.domNode, dom.$('.existing')); } - private onKeybinding(keybinding: [ResolvedKeybinding, ResolvedKeybinding]): void { + private onKeybinding(keybinding: [ResolvedKeybinding | null, ResolvedKeybinding | null]): void { const [firstPart, chordPart] = keybinding; this._firstPart = firstPart; this._chordPart = chordPart; @@ -268,8 +268,8 @@ export class DefineKeybindingWidget extends Widget { } } - private getUserSettingsLabel(): string { - let label = null; + private getUserSettingsLabel(): string | null { + let label: string | null = null; if (this._firstPart) { label = this._firstPart.getUserSettingsLabel(); if (this._chordPart) { @@ -326,8 +326,10 @@ export class DefineKeybindingOverlayWidget extends Disposable implements IOverla super.dispose(); } - start(): Promise { - this._editor.revealPositionInCenterIfOutsideViewport(this._editor.getPosition(), ScrollType.Smooth); + start(): Promise { + if (this._editor.hasModel()) { + this._editor.revealPositionInCenterIfOutsideViewport(this._editor.getPosition(), ScrollType.Smooth); + } const layoutInfo = this._editor.getLayoutInfo(); this._widget.layout(new dom.Dimension(layoutInfo.width, layoutInfo.height)); return this._widget.define(); diff --git a/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts similarity index 72% rename from src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts rename to src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts index 06b27ff176..d049e46469 100644 --- a/src/vs/workbench/parts/preferences/browser/keybindingsEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditor.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { Delayer } from 'vs/base/common/async'; import * as DOM from 'vs/base/browser/dom'; import { OS } from 'vs/base/common/platform'; -import { dispose } from 'vs/base/common/lifecycle'; +import { dispose, Disposable, toDisposable, IDisposable } from 'vs/base/common/lifecycle'; import { CheckboxActionItem } from 'vs/base/browser/ui/checkbox/checkbox'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; @@ -21,37 +21,50 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService import { KeybindingsEditorModel, IKeybindingItemEntry, IListEntry, KEYBINDING_ENTRY_TEMPLATE_ID } from 'vs/workbench/services/preferences/common/keybindingsEditorModel'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService, IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; -import { DefineKeybindingWidget, KeybindingsSearchWidget, KeybindingsSearchOptions } from 'vs/workbench/parts/preferences/browser/keybindingWidgets'; +import { DefineKeybindingWidget, KeybindingsSearchWidget, KeybindingsSearchOptions } from 'vs/workbench/contrib/preferences/browser/keybindingWidgets'; import { IKeybindingsEditor, CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, - KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS -} from 'vs/workbench/parts/preferences/common/preferences'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; + KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN +} from 'vs/workbench/contrib/preferences/common/preferences'; +import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { IContextKeyService, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, ResolvedKeybinding } from 'vs/base/common/keyCodes'; -import { listHighlightForeground, badgeBackground, contrastBorder, badgeForeground } from 'vs/platform/theme/common/colorRegistry'; +import { listHighlightForeground, badgeBackground, contrastBorder, badgeForeground, listActiveSelectionForeground, listInactiveSelectionForeground, listHoverForeground, listFocusForeground } from 'vs/platform/theme/common/colorRegistry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { EditorExtensionsRegistry } from 'vs/editor/browser/editorExtensions'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { KeybindingsEditorInput } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { attachStylerCallback } from 'vs/platform/theme/common/styler'; +import { attachStylerCallback, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; +import { Emitter, Event } from 'vs/base/common/event'; const $ = DOM.$; +interface ColumnItem { + column: HTMLElement; + proportion?: number; + width: number; +} + export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor { static readonly ID: string = 'workbench.editor.keybindings'; + private _onDefineWhenExpression: Emitter = this._register(new Emitter()); + readonly onDefineWhenExpression: Event = this._onDefineWhenExpression.event; + + private _onLayout: Emitter = this._register(new Emitter()); + readonly onLayout: Event = this._onLayout.event; + private keybindingsEditorModel: KeybindingsEditorModel; private headerContainer: HTMLElement; @@ -61,6 +74,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor private overlayContainer: HTMLElement; private defineKeybindingWidget: DefineKeybindingWidget; + private columnItems: ColumnItem[] = []; private keybindingsListContainer: HTMLElement; private unAssignedKeybindingItemToRevealAndFocus: IKeybindingItemEntry; private listEntries: IListEntry[]; @@ -91,8 +105,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor @IClipboardService private readonly clipboardService: IClipboardService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IEditorService private readonly editorService: IEditorService, - @IStorageService storageService: IStorageService, - @IPreferencesService private readonly preferencesService: IPreferencesService + @IStorageService storageService: IStorageService ) { super(KeybindingsEditor.ID, telemetryService, themeService, storageService); this.delayedFiltering = new Delayer(300); @@ -116,7 +129,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor setInput(input: KeybindingsEditorInput, options: EditorOptions, token: CancellationToken): Promise { this.keybindingsEditorContextKey.set(true); return super.setInput(input, options, token) - .then(() => this.render(options && options.preserveFocus, token)); + .then(() => this.render(!!(options && options.preserveFocus), token)); } clearInput(): void { @@ -133,7 +146,22 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.overlayContainer.style.height = dimension.height + 'px'; this.defineKeybindingWidget.layout(this.dimension); + this.columnItems.forEach(columnItem => { + if (columnItem.proportion) { + columnItem.width = 0; + } + }); this.layoutKeybindingsList(); + this._onLayout.fire(); + } + + layoutColumns(columns: HTMLElement[]): void { + if (this.columnItems) { + columns.forEach((column, index) => { + column.style.paddingRight = `6px`; + column.style.width = `${this.columnItems[index].width}px`; + }); + } } focus(): void { @@ -155,16 +183,8 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.showOverlayContainer(); return this.defineKeybindingWidget.define().then(key => { if (key) { - const currentKey = keybindingEntry.keybindingItem.keybinding ? keybindingEntry.keybindingItem.keybinding.getUserSettingsLabel() : ''; - if (currentKey !== key) { - this.reportKeybindingAction(KEYBINDINGS_EDITOR_COMMAND_DEFINE, keybindingEntry.keybindingItem.command, key); - return this.keybindingEditingService.editKeybinding(key, keybindingEntry.keybindingItem.keybindingItem) - .then(() => { - if (!keybindingEntry.keybindingItem.keybinding) { // reveal only if keybinding was added to unassinged. Because the entry will be placed in different position after rendering - this.unAssignedKeybindingItemToRevealAndFocus = keybindingEntry; - } - }); - } + this.reportKeybindingAction(KEYBINDINGS_EDITOR_COMMAND_DEFINE, keybindingEntry.keybindingItem.command, key); + return this.updateKeybinding(keybindingEntry, key, keybindingEntry.keybindingItem.when); } return null; }).then(() => { @@ -178,6 +198,26 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor }); } + defineWhenExpression(keybindingEntry: IKeybindingItemEntry): void { + if (keybindingEntry.keybindingItem.keybinding) { + this.selectEntry(keybindingEntry); + this._onDefineWhenExpression.fire(keybindingEntry); + } + } + + updateKeybinding(keybindingEntry: IKeybindingItemEntry, key: string, when: string | undefined): Promise { + const currentKey = keybindingEntry.keybindingItem.keybinding ? keybindingEntry.keybindingItem.keybinding.getUserSettingsLabel() : ''; + if (currentKey !== key || keybindingEntry.keybindingItem.when !== when) { + return this.keybindingEditingService.editKeybinding(keybindingEntry.keybindingItem.keybindingItem, key, when || undefined) + .then(() => { + if (!keybindingEntry.keybindingItem.keybinding) { // reveal only if keybinding was added to unassinged. Because the entry will be placed in different position after rendering + this.unAssignedKeybindingItemToRevealAndFocus = keybindingEntry; + } + }); + } + return Promise.resolve(); + } + removeKeybinding(keybindingEntry: IKeybindingItemEntry): Promise { this.selectEntry(keybindingEntry); if (keybindingEntry.keybindingItem.keybinding) { // This should be a pre-condition @@ -341,8 +381,6 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor })); this.actionBar.push([this.recordKeysAction, this.sortByPrecedenceAction, clearInputAction], { label: false, icon: true }); - - this.createOpenKeybindingsElement(this.headerContainer); } private createRecordingBadge(container: HTMLElement): HTMLElement { @@ -362,24 +400,6 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor return recordingBadge; } - private createOpenKeybindingsElement(parent: HTMLElement): void { - const openKeybindingsContainer = DOM.append(parent, $('.open-keybindings-container')); - DOM.append(openKeybindingsContainer, $('', null, localize('header-message', "For advanced customizations open and edit"))); - const fileElement = DOM.append(openKeybindingsContainer, $('.file-name', null, localize('keybindings-file-name', "keybindings.json"))); - fileElement.tabIndex = 0; - this._register(DOM.addDisposableListener(fileElement, DOM.EventType.CLICK, () => this.preferencesService.openGlobalKeybindingSettings(true))); - this._register(DOM.addDisposableListener(fileElement, DOM.EventType.KEY_UP, e => { - const keyboardEvent = new StandardKeyboardEvent(e); - switch (keyboardEvent.keyCode) { - case KeyCode.Enter: - this.preferencesService.openGlobalKeybindingSettings(true); - keyboardEvent.preventDefault(); - keyboardEvent.stopPropagation(); - return; - } - })); - } - private layoutSearchWidget(dimension: DOM.Dimension): void { this.searchWidget.layout(dimension); DOM.toggleClass(this.headerContainer, 'small', dimension.width < 400); @@ -396,19 +416,31 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor const keybindingsListHeader = DOM.append(parent, $('.keybindings-list-header')); keybindingsListHeader.style.height = '30px'; keybindingsListHeader.style.lineHeight = '30px'; - DOM.append(keybindingsListHeader, - $('.header.actions'), - $('.header.command', null, localize('command', "Command")), - $('.header.keybinding', null, localize('keybinding', "Keybinding")), - $('.header.source', null, localize('source', "Source")), - $('.header.when', null, localize('when', "When"))); + + this.columnItems = []; + let column = $('.header.actions'); + this.columnItems.push({ column, width: 30 }); + + column = $('.header.command', undefined, localize('command', "Command")); + this.columnItems.push({ column, proportion: 0.3, width: 0 }); + + column = $('.header.keybinding', undefined, localize('keybinding', "Keybinding")); + this.columnItems.push({ column, proportion: 0.2, width: 0 }); + + column = $('.header.when', undefined, localize('when', "When")); + this.columnItems.push({ column, proportion: 0.4, width: 0 }); + + column = $('.header.source', undefined, localize('source', "Source")); + this.columnItems.push({ column, proportion: 0.1, width: 0 }); + + DOM.append(keybindingsListHeader, ...this.columnItems.map(({ column }) => column)); } private createList(parent: HTMLElement): void { this.keybindingsListContainer = DOM.append(parent, $('.keybindings-list-container')); - this.keybindingsList = this._register(this.instantiationService.createInstance(WorkbenchList, this.keybindingsListContainer, new Delegate(), [new KeybindingItemRenderer(this, this.keybindingsService)], + this.keybindingsList = this._register(this.instantiationService.createInstance(WorkbenchList, this.keybindingsListContainer, new Delegate(), [new KeybindingItemRenderer(this, this.instantiationService)], { - identityProvider: { getId: e => e.id }, + identityProvider: { getId: (e: IListEntry) => e.id }, ariaLabel: localize('keybindingsLabel', "Keybindings"), setRowLineHeight: false, horizontalScrolling: false @@ -512,6 +544,19 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor } private layoutKeybindingsList(): void { + let width = this.dimension.width - 27; + for (const columnItem of this.columnItems) { + if (columnItem.width && !columnItem.proportion) { + width = width - columnItem.width; + } + } + for (const columnItem of this.columnItems) { + if (columnItem.proportion && !columnItem.width) { + columnItem.width = width * columnItem.proportion; + } + } + + this.layoutColumns(this.columnItems.map(({ column }) => column)); const listHeight = this.dimension.height - (DOM.getDomNodePagePosition(this.headerContainer).height + 12 /*padding*/ + 30 /*list header*/); this.keybindingsListContainer.style.height = `${listHeight}px`; this.keybindingsList.layout(listHeight); @@ -557,6 +602,10 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.keybindingsList.setFocus([currentFocusIndices.length ? currentFocusIndices[0] : 0]); } + selectKeybinding(keybindingItemEntry: IKeybindingItemEntry): void { + this.selectEntry(keybindingItemEntry); + } + recordSearchKeys(): void { this.recordKeysAction.checked = true; } @@ -581,6 +630,7 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor this.createDefineAction(e.element), this.createRemoveAction(e.element), this.createResetAction(e.element), + this.createDefineWhenExpressionAction(e.element), new Separator(), this.createShowConflictsAction(e.element)] }); @@ -607,6 +657,15 @@ export class KeybindingsEditor extends BaseEditor implements IKeybindingsEditor }; } + private createDefineWhenExpressionAction(keybindingItemEntry: IKeybindingItemEntry): IAction { + return { + label: localize('editWhen', "Change When Expression"), + enabled: !!keybindingItemEntry.keybindingItem.keybinding, + id: KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, + run: () => this.defineWhenExpression(keybindingItemEntry) + }; + } + private createRemoveAction(keybindingItem: IKeybindingItemEntry): IAction { return { label: localize('removeLabel', "Remove Keybinding"), @@ -711,72 +770,80 @@ class Delegate implements IListVirtualDelegate { interface KeybindingItemTemplate { parent: HTMLElement; - actions: ActionsColumn; - command: CommandColumn; - keybinding: KeybindingColumn; - source: SourceColumn; - when: WhenColumn; + columns: Column[]; + disposable: IDisposable; } class KeybindingItemRenderer implements IListRenderer { get templateId(): string { return KEYBINDING_ENTRY_TEMPLATE_ID; } - constructor(private keybindingsEditor: IKeybindingsEditor, private keybindingsService: IKeybindingService) { } + constructor( + private keybindingsEditor: IKeybindingsEditor, + private instantiationService: IInstantiationService + ) { } + + renderTemplate(parent: HTMLElement): KeybindingItemTemplate { + DOM.addClass(parent, 'keybinding-item'); + + const actions = this.instantiationService.createInstance(ActionsColumn, parent, this.keybindingsEditor); + const command = this.instantiationService.createInstance(CommandColumn, parent, this.keybindingsEditor); + const keybinding = this.instantiationService.createInstance(KeybindingColumn, parent, this.keybindingsEditor); + const when = this.instantiationService.createInstance(WhenColumn, parent, this.keybindingsEditor); + const source = this.instantiationService.createInstance(SourceColumn, parent, this.keybindingsEditor); + + const columns: Column[] = [actions, command, keybinding, when, source]; + const disposables: IDisposable[] = [...columns]; + const elements = columns.map(({ element }) => element); + + this.keybindingsEditor.layoutColumns(elements); + this.keybindingsEditor.onLayout(() => this.keybindingsEditor.layoutColumns(elements)); + parent.setAttribute('aria-labelledby', elements.map(e => e.getAttribute('id')).join(' ')); - renderTemplate(container: HTMLElement): KeybindingItemTemplate { - DOM.addClass(container, 'keybinding-item'); - const actions = new ActionsColumn(container, this.keybindingsEditor, this.keybindingsService); - const command = new CommandColumn(container, this.keybindingsEditor); - const keybinding = new KeybindingColumn(container, this.keybindingsEditor); - const source = new SourceColumn(container, this.keybindingsEditor); - const when = new WhenColumn(container, this.keybindingsEditor); - container.setAttribute('aria-labelledby', [command.id, keybinding.id, source.id, when.id].join(' ')); return { - parent: container, - actions, - command, - keybinding, - source, - when + parent, + columns, + disposable: toDisposable(() => dispose(disposables)) }; } renderElement(keybindingEntry: IKeybindingItemEntry, index: number, template: KeybindingItemTemplate): void { DOM.toggleClass(template.parent, 'odd', index % 2 === 1); - template.actions.render(keybindingEntry); - template.command.render(keybindingEntry); - template.keybinding.render(keybindingEntry); - template.source.render(keybindingEntry); - template.when.render(keybindingEntry); + for (const column of template.columns) { + column.render(keybindingEntry); + } } disposeTemplate(template: KeybindingItemTemplate): void { - template.actions.dispose(); + template.disposable.dispose(); } } -abstract class Column { +abstract class Column extends Disposable { static COUNTER = 0; - protected element: HTMLElement; - readonly id: string; + abstract readonly element: HTMLElement; + abstract render(keybindingItemEntry: IKeybindingItemEntry): void; - constructor(protected parent: HTMLElement, protected keybindingsEditor: IKeybindingsEditor) { - this.element = this.create(parent); - this.id = this.element.getAttribute('id'); + constructor(protected keybindingsEditor: IKeybindingsEditor) { + super(); } - abstract create(parent: HTMLElement): HTMLElement; } class ActionsColumn extends Column { private actionBar: ActionBar; + readonly element: HTMLElement; - constructor(parent: HTMLElement, keybindingsEditor: IKeybindingsEditor, private keybindingsService: IKeybindingService) { - super(parent, keybindingsEditor); + constructor( + parent: HTMLElement, + keybindingsEditor: IKeybindingsEditor, + @IKeybindingService private keybindingsService: IKeybindingService + ) { + super(keybindingsEditor); + this.element = this.create(parent); } create(parent: HTMLElement): HTMLElement { @@ -826,8 +893,17 @@ class ActionsColumn extends Column { class CommandColumn extends Column { private commandColumn: HTMLElement; + readonly element: HTMLElement; - create(parent: HTMLElement): HTMLElement { + constructor( + parent: HTMLElement, + keybindingsEditor: IKeybindingsEditor, + ) { + super(keybindingsEditor); + this.element = this.create(parent); + } + + private create(parent: HTMLElement): HTMLElement { this.commandColumn = DOM.append(parent, $('.column.command', { id: 'command_' + ++Column.COUNTER })); return this.commandColumn; } @@ -839,7 +915,7 @@ class CommandColumn extends Column { const commandDefaultLabelMatched = !!keybindingItemEntry.commandDefaultLabelMatches; DOM.toggleClass(this.commandColumn, 'vertical-align-column', commandIdMatched || commandDefaultLabelMatched); this.commandColumn.setAttribute('aria-label', this.getAriaLabel(keybindingItemEntry)); - let commandLabel: HighlightedLabel; + let commandLabel: HighlightedLabel | undefined; if (keybindingItem.commandLabel) { commandLabel = new HighlightedLabel(this.commandColumn, false); commandLabel.set(keybindingItem.commandLabel, keybindingItemEntry.commandLabelMatches); @@ -864,18 +940,28 @@ class CommandColumn extends Column { class KeybindingColumn extends Column { - private keybindingColumn: HTMLElement; + private keybindingLabel: HTMLElement; + readonly element: HTMLElement; - create(parent: HTMLElement): HTMLElement { - this.keybindingColumn = DOM.append(parent, $('.column.keybinding', { id: 'keybinding_' + ++Column.COUNTER })); - return this.keybindingColumn; + constructor( + parent: HTMLElement, + keybindingsEditor: IKeybindingsEditor, + ) { + super(keybindingsEditor); + this.element = this.create(parent); + } + + private create(parent: HTMLElement): HTMLElement { + const column = DOM.append(parent, $('.column.keybinding', { id: 'keybinding_' + ++Column.COUNTER })); + this.keybindingLabel = DOM.append(column, $('div.keybinding-label')); + return column; } render(keybindingItemEntry: IKeybindingItemEntry): void { - DOM.clearNode(this.keybindingColumn); - this.keybindingColumn.setAttribute('aria-label', this.getAriaLabel(keybindingItemEntry)); + DOM.clearNode(this.keybindingLabel); + this.keybindingLabel.setAttribute('aria-label', this.getAriaLabel(keybindingItemEntry)); if (keybindingItemEntry.keybindingItem.keybinding) { - new KeybindingLabel(this.keybindingColumn, OS).set(keybindingItemEntry.keybindingItem.keybinding, keybindingItemEntry.keybindingMatches); + new KeybindingLabel(this.keybindingLabel, OS).set(keybindingItemEntry.keybindingItem.keybinding, keybindingItemEntry.keybindingMatches); } } @@ -887,6 +973,15 @@ class KeybindingColumn extends Column { class SourceColumn extends Column { private sourceColumn: HTMLElement; + readonly element: HTMLElement; + + constructor( + parent: HTMLElement, + keybindingsEditor: IKeybindingsEditor, + ) { + super(keybindingsEditor); + this.element = this.create(parent); + } create(parent: HTMLElement): HTMLElement { this.sourceColumn = DOM.append(parent, $('.column.source', { id: 'source_' + ++Column.COUNTER })); @@ -906,27 +1001,117 @@ class SourceColumn extends Column { class WhenColumn extends Column { - private whenColumn: HTMLElement; + readonly element: HTMLElement; + private whenLabel: HTMLElement; + private whenInput: InputBox; + private disposables: IDisposable[] = []; - create(parent: HTMLElement): HTMLElement { - const column = DOM.append(parent, $('.column.when')); - this.whenColumn = DOM.append(column, $('div', { id: 'when_' + ++Column.COUNTER })); - return this.whenColumn; + private _onDidAccept: Emitter = this._register(new Emitter()); + private readonly onDidAccept: Event = this._onDidAccept.event; + + private _onDidReject: Emitter = this._register(new Emitter()); + private readonly onDidReject: Event = this._onDidReject.event; + + constructor( + parent: HTMLElement, + keybindingsEditor: IKeybindingsEditor, + @IContextViewService private readonly contextViewService: IContextViewService, + @IThemeService private readonly themeService: IThemeService + ) { + super(keybindingsEditor); + this.element = this.create(parent); + this._register(toDisposable(() => this.disposables = dispose(this.disposables))); + } + + private create(parent: HTMLElement): HTMLElement { + const column = DOM.append(parent, $('.column.when', { id: 'when_' + ++Column.COUNTER })); + + this.whenLabel = DOM.append(column, $('div.when-label')); + this.whenInput = new InputBox(column, this.contextViewService, { + validationOptions: { + validation: (value) => { + try { + ContextKeyExpr.deserialize(value, true); + } catch (error) { + return { + content: error.message, + formatContent: true, + type: MessageType.ERROR + }; + } + return null; + } + }, + ariaLabel: localize('whenContextInputAriaLabel', "Type when context. Press Enter to confirm or Escape to cancel.") + }); + this._register(attachInputBoxStyler(this.whenInput, this.themeService)); + this._register(DOM.addStandardDisposableListener(this.whenInput.inputElement, DOM.EventType.KEY_DOWN, e => this.onInputKeyDown(e))); + this._register(DOM.addDisposableListener(this.whenInput.inputElement, DOM.EventType.BLUR, () => this.cancelEditing())); + + return column; + } + + private onInputKeyDown(e: IKeyboardEvent): void { + let handled = false; + if (e.equals(KeyCode.Enter)) { + this.finishEditing(); + handled = true; + } else if (e.equals(KeyCode.Escape)) { + this.cancelEditing(); + handled = true; + } + if (handled) { + e.preventDefault(); + e.stopPropagation(); + } + } + + private startEditing(): void { + DOM.addClass(this.element, 'input-mode'); + this.whenInput.focus(); + this.whenInput.select(); + } + + private finishEditing(): void { + DOM.removeClass(this.element, 'input-mode'); + this._onDidAccept.fire(); + } + + private cancelEditing(): void { + DOM.removeClass(this.element, 'input-mode'); + this._onDidReject.fire(); } render(keybindingItemEntry: IKeybindingItemEntry): void { - DOM.clearNode(this.whenColumn); - this.whenColumn.setAttribute('aria-label', this.getAriaLabel(keybindingItemEntry)); - DOM.toggleClass(this.whenColumn, 'code', !!keybindingItemEntry.keybindingItem.when); - DOM.toggleClass(this.whenColumn, 'empty', !keybindingItemEntry.keybindingItem.when); + this.disposables = dispose(this.disposables); + DOM.clearNode(this.whenLabel); + + this.keybindingsEditor.onDefineWhenExpression(e => { + if (keybindingItemEntry === e) { + this.startEditing(); + } + }, this, this.disposables); + this.whenInput.value = keybindingItemEntry.keybindingItem.when || ''; + this.whenLabel.setAttribute('aria-label', this.getAriaLabel(keybindingItemEntry)); + DOM.toggleClass(this.whenLabel, 'code', !!keybindingItemEntry.keybindingItem.when); + DOM.toggleClass(this.whenLabel, 'empty', !keybindingItemEntry.keybindingItem.when); if (keybindingItemEntry.keybindingItem.when) { - const whenLabel = new HighlightedLabel(this.whenColumn, false); + const whenLabel = new HighlightedLabel(this.whenLabel, false); whenLabel.set(keybindingItemEntry.keybindingItem.when, keybindingItemEntry.whenMatches); - this.whenColumn.title = keybindingItemEntry.keybindingItem.when; + this.element.title = keybindingItemEntry.keybindingItem.when; whenLabel.element.title = keybindingItemEntry.keybindingItem.when; } else { - this.whenColumn.textContent = '—'; + this.whenLabel.textContent = '—'; + this.element.title = ''; } + this.onDidAccept(() => { + this.keybindingsEditor.updateKeybinding(keybindingItemEntry, keybindingItemEntry.keybindingItem.keybinding ? keybindingItemEntry.keybindingItem.keybinding.getUserSettingsLabel() : '', this.whenInput.value); + this.keybindingsEditor.selectKeybinding(keybindingItemEntry); + }, this, this.disposables); + this.onDidReject(() => { + this.whenInput.value = keybindingItemEntry.keybindingItem.when || ''; + this.keybindingsEditor.selectKeybinding(keybindingItemEntry); + }, this, this.disposables); } private getAriaLabel(keybindingItemEntry: IKeybindingItemEntry): string { @@ -939,4 +1124,21 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { if (listHighlightForegroundColor) { collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .highlight { color: ${listHighlightForegroundColor}; }`); } + const listActiveSelectionForegroundColor = theme.getColor(listActiveSelectionForeground); + if (listActiveSelectionForegroundColor) { + collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list:focus .monaco-list-row.selected.focused > .column .monaco-keybinding-key { color: ${listActiveSelectionForegroundColor}; }`); + collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list:focus .monaco-list-row.selected > .column .monaco-keybinding-key { color: ${listActiveSelectionForegroundColor}; }`); + } + const listInactiveFocusAndSelectionForegroundColor = theme.getColor(listInactiveSelectionForeground); + if (listActiveSelectionForegroundColor) { + collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list .monaco-list-row.selected > .column .monaco-keybinding-key { color: ${listInactiveFocusAndSelectionForegroundColor}; }`); + } + const listHoverForegroundColor = theme.getColor(listHoverForeground); + if (listHoverForegroundColor) { + collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list .monaco-list-row:hover:not(.selected):not(.focused) > .column .monaco-keybinding-key { color: ${listHoverForegroundColor}; }`); + } + const listFocusForegroundColor = theme.getColor(listFocusForeground); + if (listFocusForegroundColor) { + collector.addRule(`.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list .monaco-list-row.focused > .column .monaco-keybinding-key { color: ${listFocusForegroundColor}; }`); + } }); diff --git a/src/vs/workbench/parts/preferences/browser/keybindingsEditorContribution.ts b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts similarity index 89% rename from src/vs/workbench/parts/preferences/browser/keybindingsEditorContribution.ts rename to src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts index 47aefbdbbb..1847e7378a 100644 --- a/src/vs/workbench/parts/preferences/browser/keybindingsEditorContribution.ts +++ b/src/vs/workbench/contrib/preferences/browser/keybindingsEditorContribution.ts @@ -14,10 +14,10 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { registerEditorContribution, ServicesAccessor, registerEditorCommand, EditorCommand } from 'vs/editor/browser/editorExtensions'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; -import { SmartSnippetInserter } from 'vs/workbench/parts/preferences/common/smartSnippetInserter'; -import { DefineKeybindingOverlayWidget } from 'vs/workbench/parts/preferences/browser/keybindingWidgets'; +import { SmartSnippetInserter } from 'vs/workbench/contrib/preferences/common/smartSnippetInserter'; +import { DefineKeybindingOverlayWidget } from 'vs/workbench/contrib/preferences/browser/keybindingWidgets'; import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets'; import { parseTree, Node } from 'vs/base/common/json'; import { ScanCodeBinding } from 'vs/base/common/scanCode'; @@ -42,8 +42,8 @@ export class DefineKeybindingController extends Disposable implements editorComm return editor.getContribution(DefineKeybindingController.ID); } - private _keybindingWidgetRenderer: KeybindingWidgetRenderer; - private _keybindingDecorationRenderer: KeybindingEditorDecorationsRenderer; + private _keybindingWidgetRenderer?: KeybindingWidgetRenderer; + private _keybindingDecorationRenderer?: KeybindingEditorDecorationsRenderer; constructor( private _editor: ICodeEditor, @@ -51,9 +51,6 @@ export class DefineKeybindingController extends Disposable implements editorComm ) { super(); - this._keybindingWidgetRenderer = null; - this._keybindingDecorationRenderer = null; - this._register(this._editor.onDidChangeModel(e => this._update())); this._update(); } @@ -62,7 +59,7 @@ export class DefineKeybindingController extends Disposable implements editorComm return DefineKeybindingController.ID; } - get keybindingWidgetRenderer(): KeybindingWidgetRenderer { + get keybindingWidgetRenderer(): KeybindingWidgetRenderer | undefined { return this._keybindingWidgetRenderer; } @@ -99,7 +96,7 @@ export class DefineKeybindingController extends Disposable implements editorComm private _disposeKeybindingWidgetRenderer(): void { if (this._keybindingWidgetRenderer) { this._keybindingWidgetRenderer.dispose(); - this._keybindingWidgetRenderer = null; + this._keybindingWidgetRenderer = undefined; } } @@ -112,7 +109,7 @@ export class DefineKeybindingController extends Disposable implements editorComm private _disposeKeybindingDecorationRenderer(): void { if (this._keybindingDecorationRenderer) { this._keybindingDecorationRenderer.dispose(); - this._keybindingDecorationRenderer = null; + this._keybindingDecorationRenderer = undefined; } } } @@ -138,9 +135,9 @@ export class KeybindingWidgetRenderer extends Disposable { this._defineWidget.start().then(keybinding => this._onAccepted(keybinding)); } - private _onAccepted(keybinding: string): void { + private _onAccepted(keybinding: string | null): void { this._editor.focus(); - if (keybinding) { + if (keybinding && this._editor.hasModel()) { const regexp = new RegExp(/\\/g); const backslash = regexp.test(keybinding); if (backslash) { @@ -169,7 +166,7 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { private _dec: string[] = []; constructor( - private _editor: ICodeEditor, + private _editor: IActiveCodeEditor, @IKeybindingService private readonly _keybindingService: IKeybindingService, ) { super(); @@ -207,7 +204,7 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { this._dec = this._editor.deltaDecorations(this._dec, newDecorations); } - private _getDecorationForEntry(model: ITextModel, entry: Node): IModelDeltaDecoration { + private _getDecorationForEntry(model: ITextModel, entry: Node): IModelDeltaDecoration | null { if (!Array.isArray(entry.children)) { return null; } @@ -239,7 +236,7 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { } if (!resolvedKeybinding.isWYSIWYG()) { const uiLabel = resolvedKeybinding.getLabel(); - if (value.value.toLowerCase() === uiLabel.toLowerCase()) { + if (typeof uiLabel === 'string' && value.value.toLowerCase() === uiLabel.toLowerCase()) { // coincidentally, this is actually WYSIWYG return null; } @@ -249,7 +246,7 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { return this._createDecoration(false, resolvedKeybinding.getLabel(), usLabel, model, value); } const expectedUserSettingsLabel = resolvedKeybinding.getUserSettingsLabel(); - if (!KeybindingEditorDecorationsRenderer._userSettingsFuzzyEquals(value.value, expectedUserSettingsLabel)) { + if (typeof expectedUserSettingsLabel === 'string' && !KeybindingEditorDecorationsRenderer._userSettingsFuzzyEquals(value.value, expectedUserSettingsLabel)) { return this._createDecoration(false, resolvedKeybinding.getLabel(), usLabel, model, value); } return null; @@ -265,13 +262,20 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { return true; } - const [parsedA1, parsedA2] = KeybindingParser.parseUserBinding(a); - const [parsedB1, parsedB2] = KeybindingParser.parseUserBinding(b); + const aParts = KeybindingParser.parseUserBinding(a); + const bParts = KeybindingParser.parseUserBinding(b); - return ( - this._userBindingEquals(parsedA1, parsedB1) - && this._userBindingEquals(parsedA2, parsedB2) - ); + if (aParts.length !== bParts.length) { + return false; + } + + for (let i = 0, len = aParts.length; i < len; i++) { + if (!this._userBindingEquals(aParts[i], bParts[i])) { + return false; + } + } + + return true; } private static _userBindingEquals(a: SimpleKeybinding | ScanCodeBinding, b: SimpleKeybinding | ScanCodeBinding): boolean { @@ -293,7 +297,7 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { return false; } - private _createDecoration(isError: boolean, uiLabel: string, usLabel: string, model: ITextModel, keyNode: Node): IModelDeltaDecoration { + private _createDecoration(isError: boolean, uiLabel: string | null, usLabel: string | null, model: ITextModel, keyNode: Node): IModelDeltaDecoration { let msg: MarkdownString; let className: string; let beforeContentClassName: string; diff --git a/src/vs/workbench/parts/preferences/browser/media/action-remove-dark.svg b/src/vs/workbench/contrib/preferences/browser/media/action-remove-dark.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/action-remove-dark.svg rename to src/vs/workbench/contrib/preferences/browser/media/action-remove-dark.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/action-remove.svg b/src/vs/workbench/contrib/preferences/browser/media/action-remove.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/action-remove.svg rename to src/vs/workbench/contrib/preferences/browser/media/action-remove.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/add.svg b/src/vs/workbench/contrib/preferences/browser/media/add.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/add.svg rename to src/vs/workbench/contrib/preferences/browser/media/add.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/add_inverse.svg b/src/vs/workbench/contrib/preferences/browser/media/add_inverse.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/add_inverse.svg rename to src/vs/workbench/contrib/preferences/browser/media/add_inverse.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/clean-dark.svg b/src/vs/workbench/contrib/preferences/browser/media/clean-dark.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/clean-dark.svg rename to src/vs/workbench/contrib/preferences/browser/media/clean-dark.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/clean.svg b/src/vs/workbench/contrib/preferences/browser/media/clean.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/clean.svg rename to src/vs/workbench/contrib/preferences/browser/media/clean.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/clear-inverse.svg b/src/vs/workbench/contrib/preferences/browser/media/clear-inverse.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/clear-inverse.svg rename to src/vs/workbench/contrib/preferences/browser/media/clear-inverse.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/clear.svg b/src/vs/workbench/contrib/preferences/browser/media/clear.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/clear.svg rename to src/vs/workbench/contrib/preferences/browser/media/clear.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/collapseAll.svg b/src/vs/workbench/contrib/preferences/browser/media/collapseAll.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/collapseAll.svg rename to src/vs/workbench/contrib/preferences/browser/media/collapseAll.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/collapseAll_inverse.svg b/src/vs/workbench/contrib/preferences/browser/media/collapseAll_inverse.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/collapseAll_inverse.svg rename to src/vs/workbench/contrib/preferences/browser/media/collapseAll_inverse.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/collapsed-dark.svg b/src/vs/workbench/contrib/preferences/browser/media/collapsed-dark.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/collapsed-dark.svg rename to src/vs/workbench/contrib/preferences/browser/media/collapsed-dark.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/collapsed.svg b/src/vs/workbench/contrib/preferences/browser/media/collapsed.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/collapsed.svg rename to src/vs/workbench/contrib/preferences/browser/media/collapsed.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/edit.svg b/src/vs/workbench/contrib/preferences/browser/media/edit.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/edit.svg rename to src/vs/workbench/contrib/preferences/browser/media/edit.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/edit_inverse.svg b/src/vs/workbench/contrib/preferences/browser/media/edit_inverse.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/edit_inverse.svg rename to src/vs/workbench/contrib/preferences/browser/media/edit_inverse.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/ellipsis-inverse.svg b/src/vs/workbench/contrib/preferences/browser/media/ellipsis-inverse.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/ellipsis-inverse.svg rename to src/vs/workbench/contrib/preferences/browser/media/ellipsis-inverse.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/ellipsis.svg b/src/vs/workbench/contrib/preferences/browser/media/ellipsis.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/ellipsis.svg rename to src/vs/workbench/contrib/preferences/browser/media/ellipsis.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/expanded-dark.svg b/src/vs/workbench/contrib/preferences/browser/media/expanded-dark.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/expanded-dark.svg rename to src/vs/workbench/contrib/preferences/browser/media/expanded-dark.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/expanded.svg b/src/vs/workbench/contrib/preferences/browser/media/expanded.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/expanded.svg rename to src/vs/workbench/contrib/preferences/browser/media/expanded.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/info.svg b/src/vs/workbench/contrib/preferences/browser/media/info.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/info.svg rename to src/vs/workbench/contrib/preferences/browser/media/info.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/keybindings.css b/src/vs/workbench/contrib/preferences/browser/media/keybindings.css similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/keybindings.css rename to src/vs/workbench/contrib/preferences/browser/media/keybindings.css diff --git a/src/vs/workbench/parts/preferences/browser/media/keybindingsEditor.css b/src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css similarity index 76% rename from src/vs/workbench/parts/preferences/browser/media/keybindingsEditor.css rename to src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css index c93cd3fb13..d11044f589 100644 --- a/src/vs/workbench/parts/preferences/browser/media/keybindingsEditor.css +++ b/src/vs/workbench/contrib/preferences/browser/media/keybindingsEditor.css @@ -45,30 +45,30 @@ margin-right: 4px; } -.keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .monaco-action-bar .action-item > .sort-by-precedence { +.keybindings-editor .monaco-action-bar .action-item > .sort-by-precedence { background: url('sort_precedence.svg') center center no-repeat; } -.hc-black .keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .monaco-action-bar .action-item > .sort-by-precedence, -.vs-dark .keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .monaco-action-bar .action-item > .sort-by-precedence { +.hc-black .keybindings-editor .monaco-action-bar .action-item > .sort-by-precedence, +.vs-dark .keybindings-editor .monaco-action-bar .action-item > .sort-by-precedence { background: url('sort_precedence_inverse.svg') center center no-repeat; } -.keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .monaco-action-bar .action-item > .record-keys { +.keybindings-editor .monaco-action-bar .action-item > .record-keys { background: url('record-keys.svg') center center no-repeat; } -.hc-black .keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .monaco-action-bar .action-item > .record-keys, -.vs-dark .keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .monaco-action-bar .action-item > .record-keys { +.hc-black .keybindings-editor .monaco-action-bar .action-item > .record-keys, +.vs-dark .keybindings-editor .monaco-action-bar .action-item > .record-keys { background: url('record-keys-inverse.svg') center center no-repeat; } -.keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .monaco-action-bar .action-item > .clear-input { +.keybindings-editor .monaco-action-bar .action-item > .clear-input { background: url('clear.svg') center center no-repeat; } -.hc-black .keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .monaco-action-bar .action-item > .clear-input, -.vs-dark .keybindings-editor > .keybindings-header > .search-container > .keybindings-search-actions-container > .monaco-action-bar .action-item > .clear-input { +.hc-black .keybindings-editor .monaco-action-bar .action-item > .clear-input, +.vs-dark .keybindings-editor .monaco-action-bar .action-item > .clear-input { background: url('clear-inverse.svg') center center no-repeat; } @@ -123,18 +123,6 @@ align-items: center; display: flex; overflow: hidden; - margin-right: 6px; -} - -.keybindings-editor > .keybindings-body > .keybindings-list-header > .actions, -.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .actions { - width: 24px; - padding-right: 2px; -} - -.keybindings-editor > .keybindings-body > .keybindings-list-header > .command, -.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .command { - flex: 0.75; } .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .command.vertical-align-column { @@ -148,29 +136,29 @@ margin-top: 2px; } -.keybindings-editor > .keybindings-body > .keybindings-list-header > .keybinding, -.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .keybinding { - flex: 0.5; -} - .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .keybinding .monaco-highlighted-label { padding-left: 10px; } -.keybindings-editor > .keybindings-body > .keybindings-list-header > .source, -.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .source { - flex: 0 0 100px; -} - -.keybindings-editor > .keybindings-body > .keybindings-list-header > .when, -.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .when { - flex: 1; -} - .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .when .empty { padding-left: 4px; } +.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .when:not(.input-mode) .monaco-inputbox, +.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .when.input-mode .when-label { + display: none; +} + +.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .when .monaco-inputbox { + width: 100%; + line-height: normal; +} + +.monaco-workbench.mac .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .when .monaco-inputbox, +.monaco-workbench.mac .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .when .monaco-inputbox { + height: 24px; +} + .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .command .monaco-highlighted-label, .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .source .monaco-highlighted-label, .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row .when .monaco-highlighted-label { @@ -179,7 +167,7 @@ } .keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column > .code { - font-family: Monaco, Menlo, Consolas, "Droid Sans Mono", "Inconsolata", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); font-size: 90%; opacity: 0.8; display: flex; @@ -201,7 +189,7 @@ color: inherit; } -.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column .monaco-action-bar { +.keybindings-editor > .keybindings-body > .keybindings-list-container .monaco-list-row > .column.actions .monaco-action-bar { display: none; flex: 1; } diff --git a/src/vs/workbench/parts/preferences/browser/media/open-file-inverse.svg b/src/vs/workbench/contrib/preferences/browser/media/open-file-inverse.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/open-file-inverse.svg rename to src/vs/workbench/contrib/preferences/browser/media/open-file-inverse.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/open-file.svg b/src/vs/workbench/contrib/preferences/browser/media/open-file.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/open-file.svg rename to src/vs/workbench/contrib/preferences/browser/media/open-file.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/preferences.css b/src/vs/workbench/contrib/preferences/browser/media/preferences.css similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/preferences.css rename to src/vs/workbench/contrib/preferences/browser/media/preferences.css diff --git a/src/vs/workbench/parts/preferences/browser/media/record-keys-inverse.svg b/src/vs/workbench/contrib/preferences/browser/media/record-keys-inverse.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/record-keys-inverse.svg rename to src/vs/workbench/contrib/preferences/browser/media/record-keys-inverse.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/record-keys.svg b/src/vs/workbench/contrib/preferences/browser/media/record-keys.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/record-keys.svg rename to src/vs/workbench/contrib/preferences/browser/media/record-keys.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/regex-dark.svg b/src/vs/workbench/contrib/preferences/browser/media/regex-dark.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/regex-dark.svg rename to src/vs/workbench/contrib/preferences/browser/media/regex-dark.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/regex.svg b/src/vs/workbench/contrib/preferences/browser/media/regex.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/regex.svg rename to src/vs/workbench/contrib/preferences/browser/media/regex.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/settingsWidgets.css b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css similarity index 97% rename from src/vs/workbench/parts/preferences/browser/media/settingsWidgets.css rename to src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css index d69c14b801..364eaf4e68 100644 --- a/src/vs/workbench/parts/preferences/browser/media/settingsWidgets.css +++ b/src/vs/workbench/contrib/preferences/browser/media/settingsWidgets.css @@ -16,7 +16,7 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-sibling { display: inline-block; line-height: 22px; - font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); } .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-sibling { diff --git a/src/vs/workbench/parts/preferences/browser/media/sort_precedence.svg b/src/vs/workbench/contrib/preferences/browser/media/sort_precedence.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/sort_precedence.svg rename to src/vs/workbench/contrib/preferences/browser/media/sort_precedence.svg diff --git a/src/vs/workbench/parts/preferences/browser/media/sort_precedence_inverse.svg b/src/vs/workbench/contrib/preferences/browser/media/sort_precedence_inverse.svg similarity index 100% rename from src/vs/workbench/parts/preferences/browser/media/sort_precedence_inverse.svg rename to src/vs/workbench/contrib/preferences/browser/media/sort_precedence_inverse.svg diff --git a/src/vs/workbench/contrib/preferences/browser/media/status-error.svg b/src/vs/workbench/contrib/preferences/browser/media/status-error.svg new file mode 100644 index 0000000000..a1ddb39fed --- /dev/null +++ b/src/vs/workbench/contrib/preferences/browser/media/status-error.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/parts/preferences/browser/preferencesActions.ts b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts similarity index 97% rename from src/vs/workbench/parts/preferences/browser/preferencesActions.ts rename to src/vs/workbench/contrib/preferences/browser/preferencesActions.ts index e2482ed264..e7d5e57157 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesActions.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesActions.ts @@ -19,7 +19,7 @@ import { IPreferencesService } from 'vs/workbench/services/preferences/common/pr export class OpenRawDefaultSettingsAction extends Action { static readonly ID = 'workbench.action.openRawDefaultSettings'; - static readonly LABEL = nls.localize('openRawDefaultSettings', "Open Raw Default Settings"); + static readonly LABEL = nls.localize('openRawDefaultSettings', "Open Default Settings (JSON)"); constructor( id: string, @@ -239,7 +239,7 @@ export class ConfigureLanguageBasedSettingsAction extends Action { const picks: IQuickPickItem[] = languages.sort().map((lang, index) => { const description: string = nls.localize('languageDescriptionConfigured', "({0})", this.modeService.getModeIdForLanguageName(lang.toLowerCase())); // construct a fake resource to be able to show nice icons if any - let fakeResource: URI; + let fakeResource: URI | undefined; const extensions = this.modeService.getExtensions(lang); if (extensions && extensions.length) { fakeResource = URI.file(extensions[0]); @@ -260,7 +260,9 @@ export class ConfigureLanguageBasedSettingsAction extends Action { .then(pick => { if (pick) { const modeId = this.modeService.getModeIdForLanguageName(pick.label.toLowerCase()); - return this.preferencesService.configureSettingsForLanguage(modeId); + if (typeof modeId === 'string') { + return this.preferencesService.configureSettingsForLanguage(modeId); + } } return undefined; }); diff --git a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts similarity index 91% rename from src/vs/workbench/parts/preferences/browser/preferencesEditor.ts rename to src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts index 546790cf37..f203182fe2 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesEditor.ts @@ -44,21 +44,21 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; import { EditorInput, EditorOptions, IEditorControl } from 'vs/workbench/common/editor'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; -import { PREFERENCES_EDITOR_ID } from 'vs/workbench/parts/files/common/files'; -import { DefaultSettingsRenderer, FolderSettingsRenderer, IPreferencesRenderer, UserSettingsRenderer, WorkspaceSettingsRenderer } from 'vs/workbench/parts/preferences/browser/preferencesRenderers'; -import { SearchWidget, SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; -import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, IPreferencesSearchService, ISearchProvider, CONTEXT_SETTINGS_JSON_EDITOR } from 'vs/workbench/parts/preferences/common/preferences'; +import { DefaultSettingsRenderer, FolderSettingsRenderer, IPreferencesRenderer, UserSettingsRenderer, WorkspaceSettingsRenderer } from 'vs/workbench/contrib/preferences/browser/preferencesRenderers'; +import { SearchWidget, SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; +import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, IPreferencesSearchService, ISearchProvider, CONTEXT_SETTINGS_JSON_EDITOR } from 'vs/workbench/contrib/preferences/common/preferences'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; -import { IFilterResult, IPreferencesService, ISearchResult, ISetting, ISettingsEditorModel, ISettingsGroup, SettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IFilterResult, IPreferencesService, ISetting, ISettingsEditorModel, ISettingsGroup, SettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; import { DefaultPreferencesEditorInput, PreferencesEditorInput } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { DefaultSettingsEditorModel, SettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IWindowService } from 'vs/platform/windows/common/windows'; +import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; export class PreferencesEditor extends BaseEditor { - static readonly ID: string = PREFERENCES_EDITOR_ID; + static readonly ID: string = 'workbench.editor.preferencesEditor'; private defaultSettingsEditorContextKey: IContextKey; private defaultSettingsJSONEditorContextKey: IContextKey; @@ -73,7 +73,7 @@ export class PreferencesEditor extends BaseEditor { private remoteSearchThrottle: ThrottledDelayer; private _lastReportedFilter: string; - private lastFocusedWidget: SearchWidget | SideBySidePreferencesWidget = null; + private lastFocusedWidget: SearchWidget | SideBySidePreferencesWidget | undefined = undefined; get minimumWidth(): number { return this.sideBySidePreferencesWidget ? this.sideBySidePreferencesWidget.minimumWidth : 0; } get maximumWidth(): number { return this.sideBySidePreferencesWidget ? this.sideBySidePreferencesWidget.maximumWidth : Number.POSITIVE_INFINITY; } @@ -84,8 +84,8 @@ export class PreferencesEditor extends BaseEditor { readonly minimumHeight = 260; - private _onDidCreateWidget = new Emitter<{ width: number; height: number; }>(); - readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; }> = this._onDidCreateWidget.event; + private _onDidCreateWidget = new Emitter<{ width: number; height: number; } | undefined>(); + readonly onDidSizeConstraintsChange: Event<{ width: number; height: number; } | undefined> = this._onDidCreateWidget.event; constructor( @IPreferencesService private readonly preferencesService: IPreferencesService, @@ -170,7 +170,7 @@ export class PreferencesEditor extends BaseEditor { this.sideBySidePreferencesWidget.layout(new DOM.Dimension(dimension.width, dimension.height - headerHeight)); } - getControl(): IEditorControl { + getControl(): IEditorControl | null { return this.sideBySidePreferencesWidget.getControl(); } @@ -210,11 +210,11 @@ export class PreferencesEditor extends BaseEditor { private updateInput(newInput: PreferencesEditorInput, options: EditorOptions, token: CancellationToken): Promise { return this.sideBySidePreferencesWidget.setInput(newInput.details, newInput.master, options, token).then(({ defaultPreferencesRenderer, editablePreferencesRenderer }) => { if (token.isCancellationRequested) { - return undefined; + return; } - this.preferencesRenderers.defaultPreferencesRenderer = defaultPreferencesRenderer; - this.preferencesRenderers.editablePreferencesRenderer = editablePreferencesRenderer; + this.preferencesRenderers.defaultPreferencesRenderer = defaultPreferencesRenderer!; + this.preferencesRenderers.editablePreferencesRenderer = editablePreferencesRenderer!; this.onInputChanged(); }); } @@ -259,7 +259,7 @@ export class PreferencesEditor extends BaseEditor { if (target === ConfigurationTarget.USER) { this.preferencesService.switchSettings(ConfigurationTarget.USER, this.preferencesService.userSettingsResource, true); } else if (target === ConfigurationTarget.WORKSPACE) { - this.preferencesService.switchSettings(ConfigurationTarget.WORKSPACE, this.preferencesService.workspaceSettingsResource, true); + this.preferencesService.switchSettings(ConfigurationTarget.WORKSPACE, this.preferencesService.workspaceSettingsResource!, true); } else if (target instanceof URI) { this.preferencesService.switchSettings(ConfigurationTarget.WORKSPACE_FOLDER, target, true); } @@ -298,7 +298,7 @@ export class PreferencesEditor extends BaseEditor { return result; } - private reportFilteringUsed(filter: string, filterResult: IFilterResult): void { + private reportFilteringUsed(filter: string, filterResult: IFilterResult | undefined): void { if (filter && filter !== this._lastReportedFilter) { const metadata = filterResult && filterResult.metadata; const counts = filterResult && this._countById(filterResult.filteredGroups); @@ -339,11 +339,11 @@ export class PreferencesEditor extends BaseEditor { class SettingsNavigator extends ArrayNavigator { - next(): ISetting { + next(): ISetting | null { return super.next() || super.first(); } - previous(): ISetting { + previous(): ISetting | null { return super.previous() || super.last(); } @@ -366,13 +366,13 @@ class PreferencesRenderersController extends Disposable { private _editablePreferencesRendererDisposables: IDisposable[] = []; private _settingsNavigator: SettingsNavigator; - private _remoteFilterCancelToken: CancellationTokenSource; + private _remoteFilterCancelToken: CancellationTokenSource | undefined; private _prefsModelsForSearch = new Map(); private _currentLocalSearchProvider: ISearchProvider; - private _currentRemoteSearchProvider: ISearchProvider; + private _currentRemoteSearchProvider: ISearchProvider | undefined; private _lastQuery: string; - private _lastFilterResult: IFilterResult; + private _lastFilterResult: IFilterResult | undefined; private readonly _onDidFilterResultsCountChange: Emitter = this._register(new Emitter()); readonly onDidFilterResultsCountChange: Event = this._onDidFilterResultsCountChange.event; @@ -387,7 +387,7 @@ class PreferencesRenderersController extends Disposable { super(); } - get lastFilterResult(): IFilterResult { + get lastFilterResult(): IFilterResult | undefined { return this._lastFilterResult; } @@ -450,20 +450,20 @@ class PreferencesRenderersController extends Disposable { if (this._remoteFilterCancelToken) { this._remoteFilterCancelToken.cancel(); this._remoteFilterCancelToken.dispose(); - this._remoteFilterCancelToken = null; + this._remoteFilterCancelToken = undefined; } this._currentRemoteSearchProvider = (updateCurrentResults && this._currentRemoteSearchProvider) || this.preferencesSearchService.getRemoteSearchProvider(query); this._remoteFilterCancelToken = new CancellationTokenSource(); - return this.filterOrSearchPreferences(query, this._currentRemoteSearchProvider, 'nlpResult', nls.localize('nlpResult', "Natural Language Results"), 1, this._remoteFilterCancelToken.token, updateCurrentResults).then(() => { + return this.filterOrSearchPreferences(query, this._currentRemoteSearchProvider!, 'nlpResult', nls.localize('nlpResult', "Natural Language Results"), 1, this._remoteFilterCancelToken.token, updateCurrentResults).then(() => { if (this._remoteFilterCancelToken) { this._remoteFilterCancelToken.dispose(); - this._remoteFilterCancelToken = null; + this._remoteFilterCancelToken = undefined; } }, err => { if (isPromiseCanceledError(err)) { - return null; + return; } else { onUnexpectedError(err); } @@ -482,12 +482,12 @@ class PreferencesRenderersController extends Disposable { private filterOrSearchPreferences(query: string, searchProvider: ISearchProvider, groupId: string, groupLabel: string, groupOrder: number, token?: CancellationToken, editableContentOnly?: boolean): Promise { this._lastQuery = query; - const filterPs: Promise[] = [this._filterOrSearchPreferences(query, this.editablePreferencesRenderer, searchProvider, groupId, groupLabel, groupOrder, token)]; + const filterPs: Promise[] = [this._filterOrSearchPreferences(query, this.editablePreferencesRenderer, searchProvider, groupId, groupLabel, groupOrder, token)]; if (!editableContentOnly) { filterPs.push( this._filterOrSearchPreferences(query, this.defaultPreferencesRenderer, searchProvider, groupId, groupLabel, groupOrder, token)); filterPs.push( - this.searchAllSettingsTargets(query, searchProvider, groupId, groupLabel, groupOrder, token).then(() => null)); + this.searchAllSettingsTargets(query, searchProvider, groupId, groupLabel, groupOrder, token).then(() => undefined)); } return Promise.all(filterPs).then(results => { @@ -500,7 +500,7 @@ class PreferencesRenderersController extends Disposable { this.consolidateAndUpdate(defaultFilterResult, editableFilterResult); this._lastFilterResult = defaultFilterResult; - return defaultFilterResult && defaultFilterResult.exactMatch; + return !!(defaultFilterResult && defaultFilterResult.exactMatch); }); } @@ -512,21 +512,21 @@ class PreferencesRenderersController extends Disposable { for (const folder of this.workspaceContextService.getWorkspace().folders) { const folderSettingsResource = this.preferencesService.getFolderSettingsResource(folder.uri); - searchPs.push(this.searchSettingsTarget(query, searchProvider, folderSettingsResource, groupId, groupLabel, groupOrder, token)); + searchPs.push(this.searchSettingsTarget(query, searchProvider, withNullAsUndefined(folderSettingsResource), groupId, groupLabel, groupOrder, token)); } return Promise.all(searchPs).then(() => { }); } - private searchSettingsTarget(query: string, provider: ISearchProvider, target: SettingsTarget, groupId: string, groupLabel: string, groupOrder: number, token?: CancellationToken): Promise { + private searchSettingsTarget(query: string, provider: ISearchProvider, target: SettingsTarget | undefined, groupId: string, groupLabel: string, groupOrder: number, token?: CancellationToken): Promise { if (!query) { // Don't open the other settings targets when query is empty this._onDidFilterResultsCountChange.fire({ target, count: 0 }); - return Promise.resolve(undefined); + return Promise.resolve(); } - return this.getPreferencesEditorModel(target).then(model => { + return this.getPreferencesEditorModel(target).then(model => { return model && this._filterOrSearchPreferencesModel('', model, provider, groupId, groupLabel, groupOrder, token); }).then(result => { const count = result ? this._flatten(result.filteredGroups).length : 0; @@ -536,17 +536,17 @@ class PreferencesRenderersController extends Disposable { return Promise.reject(err); } - return null; + return undefined; }); } - private async getPreferencesEditorModel(target: SettingsTarget): Promise { + private async getPreferencesEditorModel(target: SettingsTarget | undefined): Promise { const resource = target === ConfigurationTarget.USER ? this.preferencesService.userSettingsResource : target === ConfigurationTarget.WORKSPACE ? this.preferencesService.workspaceSettingsResource : target; if (!resource) { - return null; + return undefined; } const targetKey = resource.toString(); @@ -556,7 +556,7 @@ class PreferencesRenderersController extends Disposable { this._prefsModelsForSearch.set(targetKey, model); } catch (e) { // Will throw when the settings file doesn't exist. - return null; + return undefined; } } @@ -579,15 +579,15 @@ class PreferencesRenderersController extends Disposable { } const setting = this._settingsNavigator.current(); - const shownInEditableRenderer = this._editablePreferencesRenderer.editPreference(setting); + const shownInEditableRenderer = this._editablePreferencesRenderer.editPreference(setting!); if (!shownInEditableRenderer) { - this.defaultPreferencesRenderer.editPreference(setting); + this.defaultPreferencesRenderer.editPreference(setting!); } } - private _filterOrSearchPreferences(filter: string, preferencesRenderer: IPreferencesRenderer, provider: ISearchProvider, groupId: string, groupLabel: string, groupOrder: number, token?: CancellationToken): Promise { + private _filterOrSearchPreferences(filter: string, preferencesRenderer: IPreferencesRenderer, provider: ISearchProvider, groupId: string, groupLabel: string, groupOrder: number, token?: CancellationToken): Promise { if (!preferencesRenderer) { - return Promise.resolve(null); + return Promise.resolve(undefined); } const model = preferencesRenderer.preferencesModel; @@ -597,10 +597,10 @@ class PreferencesRenderersController extends Disposable { }); } - private _filterOrSearchPreferencesModel(filter: string, model: ISettingsEditorModel, provider: ISearchProvider, groupId: string, groupLabel: string, groupOrder: number, token?: CancellationToken): Promise { + private _filterOrSearchPreferencesModel(filter: string, model: ISettingsEditorModel, provider: ISearchProvider, groupId: string, groupLabel: string, groupOrder: number, token?: CancellationToken): Promise { const searchP = provider ? provider.searchModel(model, token) : Promise.resolve(null); return searchP - .then(null, err => { + .then(null, err => { if (isPromiseCanceledError(err)) { return Promise.reject(err); } else { @@ -616,7 +616,7 @@ class PreferencesRenderersController extends Disposable { this.telemetryService.publicLog('defaultSettings.searchError', { message, filter }); this.logService.info('Setting search error: ' + message); } - return null; + return undefined; } }) .then(searchResult => { @@ -631,7 +631,7 @@ class PreferencesRenderersController extends Disposable { result: searchResult, order: groupOrder }) : - model.updateResultGroup(groupId, null); + model.updateResultGroup(groupId, undefined); if (filterResult) { filterResult.query = filter; @@ -642,7 +642,7 @@ class PreferencesRenderersController extends Disposable { }); } - private consolidateAndUpdate(defaultFilterResult: IFilterResult, editableFilterResult: IFilterResult): void { + private consolidateAndUpdate(defaultFilterResult: IFilterResult | undefined, editableFilterResult: IFilterResult | undefined): void { const defaultPreferencesFilteredGroups = defaultFilterResult ? defaultFilterResult.filteredGroups : this._getAllPreferences(this._defaultPreferencesRenderer); const editablePreferencesFilteredGroups = editableFilterResult ? editableFilterResult.filteredGroups : this._getAllPreferences(this._editablePreferencesRenderer); const consolidatedSettings = this._consolidateSettings(editablePreferencesFilteredGroups, defaultPreferencesFilteredGroups); @@ -671,7 +671,7 @@ class PreferencesRenderersController extends Disposable { return preferencesRenderer ? (preferencesRenderer.preferencesModel).settingsGroups : []; } - private _focusPreference(preference: ISetting, preferencesRenderer: IPreferencesRenderer): void { + private _focusPreference(preference: ISetting | null, preferencesRenderer: IPreferencesRenderer): void { if (preference && preferencesRenderer) { preferencesRenderer.focusPreference(preference); } @@ -719,7 +719,7 @@ class PreferencesRenderersController extends Disposable { this.telemetryService.publicLog('defaultSettingsActions.copySetting', data); } - private _findSetting(filterResult: IFilterResult, key: string): { groupIdx: number, settingIdx: number, overallSettingIdx: number } { + private _findSetting(filterResult: IFilterResult, key: string): { groupIdx: number, settingIdx: number, overallSettingIdx: number } | undefined { let overallSettingIdx = 0; for (let groupIdx = 0; groupIdx < filterResult.filteredGroups.length; groupIdx++) { @@ -734,7 +734,7 @@ class PreferencesRenderersController extends Disposable { } } - return null; + return undefined; } private _consolidateSettings(editableSettingsGroups: ISettingsGroup[], defaultSettingsGroups: ISettingsGroup[]): ISetting[] { @@ -779,7 +779,6 @@ class SideBySidePreferencesWidget extends Widget { private readonly _onDidSettingsTargetChange = new Emitter(); readonly onDidSettingsTargetChange: Event = this._onDidSettingsTargetChange.event; - private lastFocusedEditor: BaseEditor; private splitview: SplitView; private isVisible: boolean; @@ -811,7 +810,6 @@ class SideBySidePreferencesWidget extends Widget { this.defaultPreferencesEditor = this._register(this.instantiationService.createInstance(DefaultPreferencesEditor)); this.defaultPreferencesEditor.create(this.defaultPreferencesEditorContainer); - (this.defaultPreferencesEditor.getControl()).onDidFocusEditorWidget(() => this.lastFocusedEditor = this.defaultPreferencesEditor); this.splitview.addView({ element: this.defaultPreferencesEditorContainer, @@ -850,17 +848,17 @@ class SideBySidePreferencesWidget extends Widget { setInput(defaultPreferencesEditorInput: DefaultPreferencesEditorInput, editablePreferencesEditorInput: EditorInput, options: EditorOptions, token: CancellationToken): Promise<{ defaultPreferencesRenderer?: IPreferencesRenderer, editablePreferencesRenderer?: IPreferencesRenderer }> { this.getOrCreateEditablePreferencesEditor(editablePreferencesEditorInput); - this.settingsTargetsWidget.settingsTarget = this.getSettingsTarget(editablePreferencesEditorInput.getResource()); + this.settingsTargetsWidget.settingsTarget = this.getSettingsTarget(editablePreferencesEditorInput.getResource()!); return Promise.all([ - this.updateInput(this.defaultPreferencesEditor, defaultPreferencesEditorInput, DefaultSettingsEditorContribution.ID, editablePreferencesEditorInput.getResource(), options, token), - this.updateInput(this.editablePreferencesEditor, editablePreferencesEditorInput, SettingsEditorContribution.ID, defaultPreferencesEditorInput.getResource(), options, token) + this.updateInput(this.defaultPreferencesEditor, defaultPreferencesEditorInput, DefaultSettingsEditorContribution.ID, editablePreferencesEditorInput.getResource()!, options, token), + this.updateInput(this.editablePreferencesEditor, editablePreferencesEditorInput, SettingsEditorContribution.ID, defaultPreferencesEditorInput.getResource()!, options, token) ]) .then(([defaultPreferencesRenderer, editablePreferencesRenderer]) => { if (token.isCancellationRequested) { return {}; } - this.defaultPreferencesHeader.textContent = defaultPreferencesRenderer && this.getDefaultPreferencesHeaderText((defaultPreferencesRenderer.preferencesModel).target); + this.defaultPreferencesHeader.textContent = withUndefinedAsNull(defaultPreferencesRenderer && this.getDefaultPreferencesHeaderText((defaultPreferencesRenderer.preferencesModel).target)); return { defaultPreferencesRenderer, editablePreferencesRenderer }; }); } @@ -887,12 +885,12 @@ class SideBySidePreferencesWidget extends Widget { } focus(): void { - if (this.lastFocusedEditor) { - this.lastFocusedEditor.focus(); + if (this.editablePreferencesEditor) { + this.editablePreferencesEditor.focus(); } } - getControl(): IEditorControl { + getControl(): IEditorControl | null { return this.editablePreferencesEditor ? this.editablePreferencesEditor.getControl() : null; } @@ -922,25 +920,23 @@ class SideBySidePreferencesWidget extends Widget { return this.editablePreferencesEditor; } const descriptor = Registry.as(EditorExtensions.Editors).getEditor(editorInput); - const editor = descriptor.instantiate(this.instantiationService); + const editor = descriptor!.instantiate(this.instantiationService); this.editablePreferencesEditor = editor; this.editablePreferencesEditor.create(this.editablePreferencesEditorContainer); this.editablePreferencesEditor.setVisible(this.isVisible, this.group); - (this.editablePreferencesEditor.getControl()).onDidFocusEditorWidget(() => this.lastFocusedEditor = this.editablePreferencesEditor); - this.lastFocusedEditor = this.editablePreferencesEditor; this.layout(); return editor; } - private updateInput(editor: BaseEditor, input: EditorInput, editorContributionId: string, associatedPreferencesModelUri: URI, options: EditorOptions, token: CancellationToken): Promise> { + private updateInput(editor: BaseEditor, input: EditorInput, editorContributionId: string, associatedPreferencesModelUri: URI, options: EditorOptions, token: CancellationToken): Promise | undefined> { return editor.setInput(input, options, token) - .then(() => { + .then(() => { if (token.isCancellationRequested) { return undefined; } - return (editor.getControl()).getContribution(editorContributionId).updatePreferencesRenderer(associatedPreferencesModelUri); + return withNullAsUndefined((editor.getControl()).getContribution(editorContributionId).updatePreferencesRenderer(associatedPreferencesModelUri)); }); } @@ -965,11 +961,9 @@ class SideBySidePreferencesWidget extends Widget { private disposeEditors(): void { if (this.defaultPreferencesEditor) { this.defaultPreferencesEditor.dispose(); - this.defaultPreferencesEditor = null; } if (this.editablePreferencesEditor) { this.editablePreferencesEditor.dispose(); - this.editablePreferencesEditor = null; } } @@ -1017,7 +1011,7 @@ export class DefaultPreferencesEditor extends BaseTextEditor { private showReadonlyHint(editor: ICodeEditor): void { const messageController = MessageController.get(editor); if (!messageController.isVisible()) { - messageController.showMessage(nls.localize('defaultEditorReadonly', "Edit in the right hand side editor to override defaults."), editor.getSelection().getPosition()); + messageController.showMessage(nls.localize('defaultEditorReadonly', "Edit in the right hand side editor to override defaults."), editor.getSelection()!.getPosition()); } } @@ -1043,17 +1037,17 @@ export class DefaultPreferencesEditor extends BaseTextEditor { setInput(input: DefaultPreferencesEditorInput, options: EditorOptions, token: CancellationToken): Promise { return super.setInput(input, options, token) - .then(() => this.input.resolve() - .then(editorModel => { + .then(() => this.input!.resolve() + .then(editorModel => { if (token.isCancellationRequested) { return undefined; } - return editorModel.load(); + return editorModel!.load(); }) .then(editorModel => { if (token.isCancellationRequested) { - return undefined; + return; } this.getControl().setModel((editorModel).textEditorModel); @@ -1079,13 +1073,13 @@ export class DefaultPreferencesEditor extends BaseTextEditor { interface ISettingsEditorContribution extends editorCommon.IEditorContribution { - updatePreferencesRenderer(associatedPreferencesModelUri: URI): Promise>; + updatePreferencesRenderer(associatedPreferencesModelUri: URI): Promise | null>; } abstract class AbstractSettingsEditorContribution extends Disposable implements ISettingsEditorContribution { - private preferencesRendererCreationPromise: Promise>; + private preferencesRendererCreationPromise: Promise | null> | null; constructor(protected editor: ICodeEditor, @IInstantiationService protected instantiationService: IInstantiationService, @@ -1118,15 +1112,15 @@ abstract class AbstractSettingsEditorContribution extends Disposable implements } private _hasAssociatedPreferencesModelChanged(associatedPreferencesModelUri: URI): Promise { - return this.preferencesRendererCreationPromise.then(preferencesRenderer => { - return !(preferencesRenderer && preferencesRenderer.getAssociatedPreferencesModel() && preferencesRenderer.getAssociatedPreferencesModel().uri.toString() === associatedPreferencesModelUri.toString()); + return this.preferencesRendererCreationPromise!.then(preferencesRenderer => { + return !(preferencesRenderer && preferencesRenderer.getAssociatedPreferencesModel() && preferencesRenderer.getAssociatedPreferencesModel().uri!.toString() === associatedPreferencesModelUri.toString()); }); } - private _updatePreferencesRenderer(associatedPreferencesModelUri: URI): Promise> { + private _updatePreferencesRenderer(associatedPreferencesModelUri: URI): Promise | null> { return this.preferencesService.createPreferencesEditorModel(associatedPreferencesModelUri) .then(associatedPreferencesEditorModel => { - return this.preferencesRendererCreationPromise.then(preferencesRenderer => { + return this.preferencesRendererCreationPromise!.then(preferencesRenderer => { if (preferencesRenderer) { const associatedPreferencesModel = preferencesRenderer.getAssociatedPreferencesModel(); if (associatedPreferencesModel) { @@ -1160,7 +1154,7 @@ abstract class AbstractSettingsEditorContribution extends Disposable implements super.dispose(); } - protected abstract _createPreferencesRenderer(): Promise>; + protected abstract _createPreferencesRenderer(): Promise | null> | null; abstract getId(): string; } @@ -1172,9 +1166,9 @@ class DefaultSettingsEditorContribution extends AbstractSettingsEditorContributi return DefaultSettingsEditorContribution.ID; } - protected _createPreferencesRenderer(): Promise> { - return this.preferencesService.createPreferencesEditorModel(this.editor.getModel().uri) - .then(editorModel => { + protected _createPreferencesRenderer(): Promise | null> | null { + return this.preferencesService.createPreferencesEditorModel(this.editor.getModel()!.uri) + .then(editorModel => { if (editorModel instanceof DefaultSettingsEditorModel && this.editor.getModel()) { const preferencesRenderer = this.instantiationService.createInstance(DefaultSettingsRenderer, this.editor, editorModel); preferencesRenderer.render(); @@ -1202,10 +1196,10 @@ class SettingsEditorContribution extends AbstractSettingsEditorContribution impl return SettingsEditorContribution.ID; } - protected _createPreferencesRenderer(): Promise> { + protected _createPreferencesRenderer(): Promise | null> | null { if (this.isSettingsModel()) { - return this.preferencesService.createPreferencesEditorModel(this.editor.getModel().uri) - .then(settingsModel => { + return this.preferencesService.createPreferencesEditorModel(this.editor.getModel()!.uri) + .then(settingsModel => { if (settingsModel instanceof SettingsEditorModel && this.editor.getModel()) { switch (settingsModel.configurationTarget) { case ConfigurationTarget.USER: diff --git a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts similarity index 92% rename from src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts rename to src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts index d1d6e77df2..98a8d5505e 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesRenderers.ts @@ -15,7 +15,7 @@ import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorE import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { IModelDeltaDecoration, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model'; +import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import * as nls from 'vs/nls'; import { ConfigurationTarget, IConfigurationService, overrideIdentifierFromKey } from 'vs/platform/configuration/common/configuration'; @@ -26,7 +26,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { RangeHighlightDecorations } from 'vs/workbench/browser/parts/editor/rangeDecorations'; -import { DefaultSettingsHeaderWidget, EditPreferenceWidget, SettingsGroupTitleWidget, SettingsHeaderWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; +import { DefaultSettingsHeaderWidget, EditPreferenceWidget, SettingsGroupTitleWidget, SettingsHeaderWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; import { IFilterResult, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorModel, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; import { DefaultSettingsEditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; @@ -38,13 +38,13 @@ export interface IPreferencesRenderer extends IDisposable { onFocusPreference: Event; onClearFocusPreference: Event; - onUpdatePreference?: Event<{ key: string, value: any, source: T }>; + onUpdatePreference: Event<{ key: string, value: any, source: T }>; render(): void; updatePreference(key: string, value: any, source: T): void; focusPreference(setting: T): void; clearFocus(setting: T): void; - filterPreferences(filterResult: IFilterResult): void; + filterPreferences(filterResult: IFilterResult | undefined): void; editPreference(setting: T): boolean; } @@ -65,7 +65,7 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend private readonly _onUpdatePreference: Emitter<{ key: string, value: any, source: IIndexedSetting }> = new Emitter<{ key: string, value: any, source: IIndexedSetting }>(); readonly onUpdatePreference: Event<{ key: string, value: any, source: IIndexedSetting }> = this._onUpdatePreference.event; - private filterResult: IFilterResult; + private filterResult: IFilterResult | undefined; constructor(protected editor: ICodeEditor, readonly preferencesModel: SettingsEditorModel, @IPreferencesService protected preferencesService: IPreferencesService, @@ -77,7 +77,7 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend this.highlightMatchesRenderer = this._register(instantiationService.createInstance(HighlightMatchesRenderer, editor)); this.editSettingActionRenderer = this._register(this.instantiationService.createInstance(EditSettingRenderer, this.editor, this.preferencesModel, this.settingHighlighter)); this._register(this.editSettingActionRenderer.onUpdateSetting(({ key, value, source }) => this._updatePreference(key, value, source))); - this._register(this.editor.getModel().onDidChangeContent(() => this.modelChangeDelayer.trigger(() => this.onModelChanged()))); + this._register(this.editor.getModel()!.onDidChangeContent(() => this.modelChangeDelayer.trigger(() => this.onModelChanged()))); } @@ -126,7 +126,7 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend private onSettingUpdated(setting: ISetting) { this.editor.focus(); - setting = this.getSetting(setting); + setting = this.getSetting(setting)!; if (setting) { // TODO:@sandy Selection range should be template range this.editor.setSelection(setting.valueRange); @@ -134,22 +134,22 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend } } - private getSetting(setting: ISetting): ISetting { + private getSetting(setting: ISetting): ISetting | undefined { const { key, overrideOf } = setting; if (overrideOf) { const setting = this.getSetting(overrideOf); - for (const override of setting.overrides) { + for (const override of setting!.overrides!) { if (override.key === key) { return override; } } - return null; + return undefined; } return this.preferencesModel.getPreference(key); } - filterPreferences(filterResult: IFilterResult): void { + filterPreferences(filterResult: IFilterResult | undefined): void { this.filterResult = filterResult; this.settingHighlighter.clear(true); this.highlightMatchesRenderer.render(filterResult ? filterResult.matches : []); @@ -171,7 +171,7 @@ export class UserSettingsRenderer extends Disposable implements IPreferencesRend editPreference(setting: ISetting): boolean { const editableSetting = this.getSetting(setting); - return editableSetting && this.editSettingActionRenderer.activateOnSetting(editableSetting); + return !!(editableSetting && this.editSettingActionRenderer.activateOnSetting(editableSetting)); } } @@ -231,7 +231,7 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR private hiddenAreasRenderer: HiddenAreasRenderer; private editSettingActionRenderer: EditSettingRenderer; private bracesHidingRenderer: BracesHidingRenderer; - private filterResult: IFilterResult; + private filterResult: IFilterResult | undefined; private readonly _onUpdatePreference: Emitter<{ key: string, value: any, source: IIndexedSetting }> = new Emitter<{ key: string, value: any, source: IIndexedSetting }>(); readonly onUpdatePreference: Event<{ key: string, value: any, source: IIndexedSetting }> = this._onUpdatePreference.event; @@ -273,28 +273,28 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR this.settingsGroupTitleRenderer.render(this.preferencesModel.settingsGroups); this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this._associatedPreferencesModel); this.settingHighlighter.clear(true); - this.bracesHidingRenderer.render(null, this.preferencesModel.settingsGroups); + this.bracesHidingRenderer.render(undefined, this.preferencesModel.settingsGroups); this.settingsGroupTitleRenderer.showGroup(0); this.hiddenAreasRenderer.render(); } - filterPreferences(filterResult: IFilterResult): void { + filterPreferences(filterResult: IFilterResult | undefined): void { this.filterResult = filterResult; if (filterResult) { this.filteredMatchesRenderer.render(filterResult, this.preferencesModel.settingsGroups); - this.settingsGroupTitleRenderer.render(null); + this.settingsGroupTitleRenderer.render(undefined); this.settingsHeaderRenderer.render(filterResult); this.settingHighlighter.clear(true); this.bracesHidingRenderer.render(filterResult, this.preferencesModel.settingsGroups); this.editSettingActionRenderer.render(filterResult.filteredGroups, this._associatedPreferencesModel); } else { this.settingHighlighter.clear(true); - this.filteredMatchesRenderer.render(null, this.preferencesModel.settingsGroups); - this.settingsHeaderRenderer.render(null); + this.filteredMatchesRenderer.render(undefined, this.preferencesModel.settingsGroups); + this.settingsHeaderRenderer.render(undefined); this.settingsGroupTitleRenderer.render(this.preferencesModel.settingsGroups); this.settingsGroupTitleRenderer.showGroup(0); - this.bracesHidingRenderer.render(null, this.preferencesModel.settingsGroups); + this.bracesHidingRenderer.render(undefined, this.preferencesModel.settingsGroups); this.editSettingActionRenderer.render(this.preferencesModel.settingsGroups, this._associatedPreferencesModel); } @@ -311,22 +311,22 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR } } - private getSetting(setting: ISetting): ISetting { + private getSetting(setting: ISetting): ISetting | undefined { const { key, overrideOf } = setting; if (overrideOf) { const setting = this.getSetting(overrideOf); - for (const override of setting.overrides) { + for (const override of setting!.overrides!) { if (override.key === key) { return override; } } - return null; + return undefined; } const settingsGroups = this.filterResult ? this.filterResult.filteredGroups : this.preferencesModel.settingsGroups; return this.getPreference(key, settingsGroups); } - private getPreference(key: string, settingsGroups: ISettingsGroup[]): ISetting { + private getPreference(key: string, settingsGroups: ISettingsGroup[]): ISetting | undefined { for (const group of settingsGroups) { for (const section of group.sections) { for (const setting of section.settings) { @@ -336,7 +336,7 @@ export class DefaultSettingsRenderer extends Disposable implements IPreferencesR } } } - return null; + return undefined; } clearFocus(setting: ISetting): void { @@ -356,14 +356,14 @@ export interface HiddenAreasProvider { } export class BracesHidingRenderer extends Disposable implements HiddenAreasProvider { - private _result: IFilterResult; + private _result: IFilterResult | undefined; private _settingsGroups: ISettingsGroup[]; constructor(private editor: ICodeEditor) { super(); } - render(result: IFilterResult, settingsGroups: ISettingsGroup[]): void { + render(result: IFilterResult | undefined, settingsGroups: ISettingsGroup[]): void { this._result = result; this._settingsGroups = settingsGroups; } @@ -403,7 +403,7 @@ export class BracesHidingRenderer extends Disposable implements HiddenAreasProvi } // Closing square brace - const lineCount = this.editor.getModel().getLineCount(); + const lineCount = this.editor.getModel()!.getLineCount(); hiddenAreas.push({ startLineNumber: lineCount, startColumn: 1, @@ -428,7 +428,7 @@ class DefaultSettingsHeaderRenderer extends Disposable { this.onClick = this.settingsHeaderWidget.onClick; } - render(filterResult: IFilterResult) { + render(filterResult: IFilterResult | undefined) { const hasSettings = !filterResult || filterResult.filteredGroups.length > 0; this.settingsHeaderWidget.toggleMessage(hasSettings); } @@ -458,7 +458,7 @@ export class SettingsGroupTitleRenderer extends Disposable implements HiddenArea return hiddenAreas; } - render(settingsGroups: ISettingsGroup[]) { + render(settingsGroups: ISettingsGroup[] | undefined) { this.disposeWidgets(); if (!settingsGroups) { return; @@ -503,7 +503,7 @@ export class SettingsGroupTitleRenderer extends Disposable implements HiddenArea const index = this.hiddenGroups.indexOf(group); if (collapsed) { const currentPosition = this.editor.getPosition(); - if (group.range.startLineNumber <= currentPosition.lineNumber && group.range.endLineNumber >= currentPosition.lineNumber) { + if (group.range.startLineNumber <= currentPosition!.lineNumber && group.range.endLineNumber >= currentPosition!.lineNumber) { this.editor.setPosition({ lineNumber: group.range.startLineNumber - 1, column: 1 }); } this.hiddenGroups.push(group); @@ -555,19 +555,18 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr super(); } - render(result: IFilterResult, allSettingsGroups: ISettingsGroup[]): void { - const model = this.editor.getModel(); + render(result: IFilterResult | undefined, allSettingsGroups: ISettingsGroup[]): void { this.hiddenAreas = []; if (result) { - this.hiddenAreas = this.computeHiddenRanges(result.filteredGroups, result.allGroups, model); - this.decorationIds = this.editor.deltaDecorations(this.decorationIds, result.matches.map(match => this.createDecoration(match, model))); + this.hiddenAreas = this.computeHiddenRanges(result.filteredGroups, result.allGroups); + this.decorationIds = this.editor.deltaDecorations(this.decorationIds, result.matches.map(match => this.createDecoration(match))); } else { - this.hiddenAreas = this.computeHiddenRanges(null, allSettingsGroups, model); + this.hiddenAreas = this.computeHiddenRanges(undefined, allSettingsGroups); this.decorationIds = this.editor.deltaDecorations(this.decorationIds, []); } } - private createDecoration(range: IRange, model: ITextModel): IModelDeltaDecoration { + private createDecoration(range: IRange): IModelDeltaDecoration { return { range, options: FilteredMatchesRenderer._FIND_MATCH @@ -579,7 +578,7 @@ export class FilteredMatchesRenderer extends Disposable implements HiddenAreasPr className: 'findMatch' }); - private computeHiddenRanges(filteredGroups: ISettingsGroup[], allSettingsGroups: ISettingsGroup[], model: ITextModel): IRange[] { + private computeHiddenRanges(filteredGroups: ISettingsGroup[] | undefined, allSettingsGroups: ISettingsGroup[]): IRange[] { // Hide the contents of hidden groups const notMatchesRanges: IRange[] = []; if (filteredGroups) { @@ -612,8 +611,7 @@ export class HighlightMatchesRenderer extends Disposable { } render(matches: IRange[]): void { - const model = this.editor.getModel(); - this.decorationIds = this.editor.deltaDecorations(this.decorationIds, matches.map(match => this.createDecoration(match, model))); + this.decorationIds = this.editor.deltaDecorations(this.decorationIds, matches.map(match => this.createDecoration(match))); } private static readonly _FIND_MATCH = ModelDecorationOptions.register({ @@ -621,7 +619,7 @@ export class HighlightMatchesRenderer extends Disposable { className: 'findMatch' }); - private createDecoration(range: IRange, model: ITextModel): IModelDeltaDecoration { + private createDecoration(range: IRange): IModelDeltaDecoration { return { range, options: HighlightMatchesRenderer._FIND_MATCH @@ -676,7 +674,7 @@ class EditSettingRenderer extends Disposable { this.settingsGroups = settingsGroups; this.associatedPreferencesModel = associatedPreferencesModel; - const settings = this.getSettings(this.editor.getPosition().lineNumber); + const settings = this.getSettings(this.editor.getPosition()!.lineNumber); if (settings.length) { this.showEditPreferencesWidget(this.editPreferenceWidgetForCursorPosition, settings); } @@ -713,9 +711,9 @@ class EditSettingRenderer extends Disposable { this.toggleEditPreferencesForMouseMoveDelayer.trigger(() => this.toggleEditPreferenceWidgetForMouseMove(mouseMoveEvent)); } - private getEditPreferenceWidgetUnderMouse(mouseMoveEvent: IEditorMouseEvent): EditPreferenceWidget { + private getEditPreferenceWidgetUnderMouse(mouseMoveEvent: IEditorMouseEvent): EditPreferenceWidget | undefined { if (mouseMoveEvent.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN) { - const line = mouseMoveEvent.target.position.lineNumber; + const line = mouseMoveEvent.target.position!.lineNumber; if (this.editPreferenceWidgetForMouseMove.getLine() === line && this.editPreferenceWidgetForMouseMove.isVisible()) { return this.editPreferenceWidgetForMouseMove; } @@ -723,7 +721,7 @@ class EditSettingRenderer extends Disposable { return this.editPreferenceWidgetForCursorPosition; } } - return null; + return undefined; } private toggleEditPreferenceWidgetForMouseMove(mouseMoveEvent: IEditorMouseEvent): void { @@ -797,9 +795,9 @@ class EditSettingRenderer extends Disposable { break; } if (lineNumber >= setting.range.startLineNumber && lineNumber <= setting.range.endLineNumber) { - if (!this.isDefaultSettings() && setting.overrides.length) { + if (!this.isDefaultSettings() && setting.overrides!.length) { // Only one level because override settings cannot have override settings - for (const overrideSetting of setting.overrides) { + for (const overrideSetting of setting.overrides!) { if (lineNumber >= overrideSetting.range.startLineNumber && lineNumber <= overrideSetting.range.endLineNumber) { settings.push({ ...overrideSetting, index, groupId: group.id }); } @@ -850,9 +848,9 @@ class EditSettingRenderer extends Disposable { private toAbsoluteCoords(position: Position): { x: number, y: number } { const positionCoords = this.editor.getScrolledVisiblePosition(position); - const editorCoords = getDomNodePagePosition(this.editor.getDomNode()); - const x = editorCoords.left + positionCoords.left; - const y = editorCoords.top + positionCoords.top + positionCoords.height; + const editorCoords = getDomNodePagePosition(this.editor.getDomNode()!); + const x = editorCoords.left + positionCoords!.left; + const y = editorCoords.top + positionCoords!.top + positionCoords!.height; return { x, y: y + 10 }; } @@ -930,7 +928,7 @@ class SettingHighlighter extends Disposable { const highlighter = fix ? this.fixedHighlighter : this.volatileHighlighter; highlighter.highlightRange({ range: setting.valueRange, - resource: this.editor.getModel().uri + resource: this.editor.getModel()!.uri }, this.editor); this.editor.revealLineInCenterIfOutsideViewport(setting.valueRange.startLineNumber, editorCommon.ScrollType.Smooth); @@ -956,7 +954,7 @@ class WorkspaceConfigurationRenderer extends Disposable { @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService ) { super(); - this._register(this.editor.getModel().onDidChangeContent(() => this.renderingDelayer.trigger(() => this.render(this.associatedSettingsEditorModel)))); + this._register(this.editor.getModel()!.onDidChangeContent(() => this.renderingDelayer.trigger(() => this.render(this.associatedSettingsEditorModel)))); } render(associatedSettingsEditorModel: IPreferencesEditorModel): void { @@ -978,7 +976,7 @@ class WorkspaceConfigurationRenderer extends Disposable { } } } - this.decorationIds = this.editor.deltaDecorations(this.decorationIds, ranges.map(range => this.createDecoration(range, this.editor.getModel()))); + this.decorationIds = this.editor.deltaDecorations(this.decorationIds, ranges.map(range => this.createDecoration(range))); } } @@ -987,7 +985,7 @@ class WorkspaceConfigurationRenderer extends Disposable { inlineClassName: 'dim-configuration' }); - private createDecoration(range: IRange, model: ITextModel): IModelDeltaDecoration { + private createDecoration(range: IRange): IModelDeltaDecoration { return { range, options: WorkspaceConfigurationRenderer._DIM_CONFIGURATION_ diff --git a/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts similarity index 96% rename from src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts rename to src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts index db849107d9..2100b8c41e 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/preferencesWidgets.ts @@ -226,13 +226,17 @@ export class SettingsGroupTitleWidget extends Widget implements IViewZone { if (this.settingsGroup.range.startLineNumber - 3 !== 1) { this.editor.focus(); const lineNumber = this.settingsGroup.range.startLineNumber - 2; - this.editor.setPosition({ lineNumber, column: this.editor.getModel().getLineMinColumn(lineNumber) }); + if (this.editor.hasModel()) { + this.editor.setPosition({ lineNumber, column: this.editor.getModel().getLineMinColumn(lineNumber) }); + } } break; case KeyCode.DownArrow: const lineNumber = this.isCollapsed() ? this.settingsGroup.range.startLineNumber : this.settingsGroup.range.startLineNumber - 1; this.editor.focus(); - this.editor.setPosition({ lineNumber, column: this.editor.getModel().getLineMinColumn(lineNumber) }); + if (this.editor.hasModel()) { + this.editor.setPosition({ lineNumber, column: this.editor.getModel().getLineMinColumn(lineNumber) }); + } break; } } @@ -286,7 +290,7 @@ export class SettingsGroupTitleWidget extends Widget implements IViewZone { export class FolderSettingsActionItem extends BaseActionItem { - private _folder: IWorkspaceFolder; + private _folder: IWorkspaceFolder | null; private _folderSettingCounts = new Map(); private container: HTMLElement; @@ -308,17 +312,21 @@ export class FolderSettingsActionItem extends BaseActionItem { this.disposables.push(this.contextService.onDidChangeWorkspaceFolders(() => this.onWorkspaceFoldersChanged())); } - get folder(): IWorkspaceFolder { + get folder(): IWorkspaceFolder | null { return this._folder; } - set folder(folder: IWorkspaceFolder) { + set folder(folder: IWorkspaceFolder | null) { this._folder = folder; this.update(); } setCount(settingsTarget: URI, count: number): void { - const folder = this.contextService.getWorkspaceFolder(settingsTarget).uri; + const workspaceFolder = this.contextService.getWorkspaceFolder(settingsTarget); + if (!workspaceFolder) { + throw new Error('unknown folder'); + } + const folder = workspaceFolder.uri; this._folderSettingCounts.set(folder.toString(), count); this.update(); } @@ -374,8 +382,8 @@ export class FolderSettingsActionItem extends BaseActionItem { private onWorkspaceFoldersChanged(): void { const oldFolder = this._folder; const workspace = this.contextService.getWorkspace(); - if (this._folder) { - this._folder = workspace.folders.filter(folder => folder.uri.toString() === this._folder.uri.toString())[0] || workspace.folders[0]; + if (oldFolder) { + this._folder = workspace.folders.filter(folder => folder.uri.toString() === oldFolder.uri.toString())[0] || workspace.folders[0]; } this._folder = this._folder ? this._folder : workspace.folders.length === 1 ? workspace.folders[0] : null; @@ -384,7 +392,7 @@ export class FolderSettingsActionItem extends BaseActionItem { if (this._action.checked) { if ((oldFolder || !this._folder) || (!oldFolder || this._folder) - || (oldFolder && this._folder && oldFolder.uri.toString() === this._folder.uri.toString())) { + || (oldFolder && this._folder && (oldFolder as IWorkspaceFolder).uri.toString() === (this._folder as IWorkspaceFolder).uri.toString())) { this._action.run(this._folder); } } @@ -625,9 +633,10 @@ export class SearchWidget extends Widget { const focusTracker = this._register(DOM.trackFocus(this.inputBox.inputElement)); this._register(focusTracker.onDidFocus(() => this._onFocus.fire())); - if (this.options.focusKey) { - this._register(focusTracker.onDidFocus(() => this.options.focusKey.set(true))); - this._register(focusTracker.onDidBlur(() => this.options.focusKey.set(false))); + const focusKey = this.options.focusKey; + if (focusKey) { + this._register(focusTracker.onDidFocus(() => focusKey.set(true))); + this._register(focusTracker.onDidBlur(() => focusKey.set(false))); } } diff --git a/src/vs/workbench/parts/preferences/browser/settingsLayout.ts b/src/vs/workbench/contrib/preferences/browser/settingsLayout.ts similarity index 100% rename from src/vs/workbench/parts/preferences/browser/settingsLayout.ts rename to src/vs/workbench/contrib/preferences/browser/settingsLayout.ts diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts similarity index 91% rename from src/vs/workbench/parts/preferences/browser/settingsTree.ts rename to src/vs/workbench/contrib/preferences/browser/settingsTree.ts index 4df9459574..4c0d2986b9 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTree.ts @@ -29,7 +29,6 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { ISpliceable } from 'vs/base/common/sequence'; import { escapeRegExpCharacters, startsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; -import { IAccessibilityProvider, ITree } from 'vs/base/parts/tree/browser/tree'; import { localize } from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandService } from 'vs/platform/commands/common/commands'; @@ -41,10 +40,10 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorBackground, errorForeground, focusBorder, foreground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler, attachStyler } from 'vs/platform/theme/common/styler'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; -import { ITOCEntry } from 'vs/workbench/parts/preferences/browser/settingsLayout'; -import { ISettingsEditorViewState, settingKeyToDisplayFormat, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement } from 'vs/workbench/parts/preferences/browser/settingsTreeModels'; -import { ExcludeSettingWidget, IExcludeChangeEvent, IExcludeDataItem, settingsHeaderForeground, settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/parts/preferences/browser/settingsWidgets'; -import { SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/parts/preferences/common/preferences'; +import { ITOCEntry } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; +import { ISettingsEditorViewState, settingKeyToDisplayFormat, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeNewExtensionsElement, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; +import { ExcludeSettingWidget, IExcludeChangeEvent, IExcludeDataItem, settingsHeaderForeground, settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsSelectListBorder, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; +import { SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; const $ = DOM.$; @@ -100,20 +99,20 @@ export function resolveExtensionsSettings(groups: ISettingsGroup[]): ITOCEntry { } function _resolveSettingsTree(tocData: ITOCEntry, allSettings: Set): ITOCEntry { - let children: ITOCEntry[]; + let children: ITOCEntry[] | undefined; if (tocData.children) { children = tocData.children .map(child => _resolveSettingsTree(child, allSettings)) .filter(child => (child.children && child.children.length) || (child.settings && child.settings.length)); } - let settings: ISetting[]; + let settings: ISetting[] | undefined; if (tocData.settings) { settings = arrays.flatten(tocData.settings.map(pattern => getMatchingSettings(allSettings, pattern))); } if (!children && !settings) { - return null; + throw new Error(`TOC node has no child groups or settings: ${tocData.id}`); } return { @@ -252,10 +251,12 @@ export interface ISettingOverrideClickEvent { export abstract class AbstractSettingRenderer implements ITreeRenderer { /** To override */ - templateId = undefined; + abstract get templateId(): string; static readonly CONTROL_CLASS = 'setting-control-focus-target'; static readonly CONTROL_SELECTOR = '.' + AbstractSettingRenderer.CONTROL_CLASS; + static readonly CONTENTS_CLASS = 'setting-item-contents'; + static readonly CONTENTS_SELECTOR = '.' + AbstractSettingRenderer.CONTENTS_CLASS; static readonly SETTING_KEY_ATTR = 'data-key'; static readonly SETTING_ID_ATTR = 'data-id'; @@ -296,9 +297,11 @@ export abstract class AbstractSettingRenderer implements ITreeRenderer { @@ -436,7 +439,7 @@ export abstract class AbstractSettingRenderer implements ITreeRenderer this._onDidChangeSetting.fire({ key: element.setting.key, value, type: template.context.valueType }); + const onChange = value => this._onDidChangeSetting.fire({ key: element.setting.key, value, type: template.context!.valueType }); template.deprecationWarningElement.innerText = element.setting.deprecationMessage || ''; this.renderValue(element, template, onChange); @@ -456,7 +459,7 @@ export abstract class AbstractSettingRenderer implements ITreeRenderertemplate.controlElement.firstElementChild) { itemElement.setAttribute('role', 'combobox'); label += modifiedText; } @@ -584,7 +587,7 @@ export class SettingNewExtensionsRenderer implements ITreeRenderer { if (template.context) { @@ -618,9 +621,9 @@ export class SettingComplexRenderer extends AbstractSettingRenderer implements I renderTemplate(container: HTMLElement): ISettingComplexItemTemplate { const common = this.renderCommonTemplate(null, container, 'complex'); - const openSettingsButton = new Button(common.controlElement, { title: true, buttonBackground: null, buttonHoverBackground: null }); + const openSettingsButton = new Button(common.controlElement, { title: true, buttonBackground: undefined, buttonHoverBackground: undefined }); common.toDispose.push(openSettingsButton); - common.toDispose.push(openSettingsButton.onDidClick(() => template.onChange(null))); + common.toDispose.push(openSettingsButton.onDidClick(() => template.onChange!())); openSettingsButton.label = localize('editInSettingsJson', "Edit in settings.json"); openSettingsButton.element.classList.add('edit-in-settings-button'); @@ -729,9 +732,9 @@ export class SettingExcludeRenderer extends AbstractSettingRenderer implements I export class SettingTextRenderer extends AbstractSettingRenderer implements ITreeRenderer { templateId = SETTINGS_TEXT_TEMPLATE_ID; - renderTemplate(container: HTMLElement): ISettingTextItemTemplate { - const common = this.renderCommonTemplate(null, container, 'text'); - const validationErrorMessageElement = DOM.append(container, $('.setting-item-validation-message')); + renderTemplate(_container: HTMLElement): ISettingTextItemTemplate { + const common = this.renderCommonTemplate(null, _container, 'text'); + const validationErrorMessageElement = DOM.append(common.containerElement, $('.setting-item-validation-message')); const inputBox = new InputBox(common.controlElement, this._contextViewService); common.toDispose.push(inputBox); @@ -767,7 +770,7 @@ export class SettingTextRenderer extends AbstractSettingRenderer implements ITre protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, onChange: (value: string) => void): void { const label = this.setElementAriaLabels(dataElement, SETTINGS_TEXT_TEMPLATE_ID, template); - template.onChange = null; + template.onChange = undefined; template.inputBox.value = dataElement.value; template.onChange = value => { renderValidations(dataElement, template, false, label); onChange(value); }; @@ -781,7 +784,7 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre renderTemplate(container: HTMLElement): ISettingEnumItemTemplate { const common = this.renderCommonTemplate(null, container, 'enum'); - const selectBox = new SelectBox([], undefined, this._contextViewService, undefined, { useCustomDrawn: true }); + const selectBox = new SelectBox([], 0, this._contextViewService, undefined, { useCustomDrawn: true }); common.toDispose.push(selectBox); common.toDispose.push(attachSelectBoxStyler(selectBox, this._themeService, { @@ -824,7 +827,7 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre const enumDescriptions = dataElement.setting.enumDescriptions; const enumDescriptionsAreMarkdown = dataElement.setting.enumDescriptionsAreMarkdown; - const displayOptions = dataElement.setting.enum + const displayOptions = dataElement.setting.enum! .map(String) .map(escapeInvisibleChars) .map((data, index) => { @@ -839,10 +842,17 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre const label = this.setElementAriaLabels(dataElement, SETTINGS_ENUM_TEMPLATE_ID, template); template.selectBox.setAriaLabel(label); - const idx = dataElement.setting.enum.indexOf(dataElement.value); - template.onChange = null; + let idx = dataElement.setting.enum!.indexOf(dataElement.value); + if (idx === -1) { + idx = dataElement.setting.enum!.indexOf(dataElement.defaultValue); + if (idx === -1) { + idx = 0; + } + } + + template.onChange = undefined; template.selectBox.select(idx); - template.onChange = idx => onChange(dataElement.setting.enum[idx]); + template.onChange = idx => onChange(dataElement.setting.enum![idx]); template.enumDescriptionElement.innerHTML = ''; } @@ -851,9 +861,9 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre export class SettingNumberRenderer extends AbstractSettingRenderer implements ITreeRenderer { templateId = SETTINGS_NUMBER_TEMPLATE_ID; - renderTemplate(container: HTMLElement): ISettingNumberItemTemplate { - const common = super.renderCommonTemplate(null, container, 'number'); - const validationErrorMessageElement = DOM.append(container, $('.setting-item-validation-message')); + renderTemplate(_container: HTMLElement): ISettingNumberItemTemplate { + const common = super.renderCommonTemplate(null, _container, 'number'); + const validationErrorMessageElement = DOM.append(common.containerElement, $('.setting-item-validation-message')); const inputBox = new InputBox(common.controlElement, this._contextViewService, { type: 'number' }); common.toDispose.push(inputBox); @@ -886,7 +896,7 @@ export class SettingNumberRenderer extends AbstractSettingRenderer implements IT super.renderSettingElement(element, index, templateData); } - protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingNumberItemTemplate, onChange: (value: number) => void): void { + protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingNumberItemTemplate, onChange: (value: number | null) => void): void { const numParseFn = (dataElement.valueType === 'integer' || dataElement.valueType === 'nullable-integer') ? parseInt : parseFloat; @@ -895,9 +905,12 @@ export class SettingNumberRenderer extends AbstractSettingRenderer implements IT const label = this.setElementAriaLabels(dataElement, SETTINGS_NUMBER_TEMPLATE_ID, template); - template.onChange = null; + template.onChange = undefined; template.inputBox.value = dataElement.value; - template.onChange = value => { renderValidations(dataElement, template, false, label); onChange(nullNumParseFn(value)); }; + template.onChange = value => { + renderValidations(dataElement, template, false, label); + onChange(nullNumParseFn(value)); + }; renderValidations(dataElement, template, true, label); } @@ -906,9 +919,11 @@ export class SettingNumberRenderer extends AbstractSettingRenderer implements IT export class SettingBoolRenderer extends AbstractSettingRenderer implements ITreeRenderer { templateId = SETTINGS_BOOL_TEMPLATE_ID; - renderTemplate(container: HTMLElement): ISettingBoolItemTemplate { - DOM.addClass(container, 'setting-item'); - DOM.addClass(container, 'setting-item-bool'); + renderTemplate(_container: HTMLElement): ISettingBoolItemTemplate { + DOM.addClass(_container, 'setting-item'); + DOM.addClass(_container, 'setting-item-bool'); + + const container = DOM.append(_container, $(AbstractSettingRenderer.CONTENTS_SELECTOR)); const titleElement = DOM.append(container, $('.setting-item-title')); const categoryElement = DOM.append(titleElement, $('span.setting-item-category')); @@ -925,7 +940,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre const deprecationWarningElement = DOM.append(container, $('.setting-item-deprecation-message')); const toDispose: IDisposable[] = []; - const checkbox = new Checkbox({ actionClassName: 'setting-value-checkbox', isChecked: true, title: '', inputActiveOptionBorder: null }); + const checkbox = new Checkbox({ actionClassName: 'setting-value-checkbox', isChecked: true, title: '', inputActiveOptionBorder: undefined }); controlElement.appendChild(checkbox.domNode); toDispose.push(checkbox); toDispose.push(checkbox.onChange(() => { @@ -944,7 +959,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre // Toggle target checkbox if (targetElement.tagName.toLowerCase() !== 'a' && targetId === template.checkbox.domNode.id) { template.checkbox.checked = template.checkbox.checked ? false : true; - template.onChange(checkbox.checked); + template.onChange!(checkbox.checked); } DOM.EventHelper.stop(e); })); @@ -988,7 +1003,7 @@ export class SettingBoolRenderer extends AbstractSettingRenderer implements ITre } protected renderValue(dataElement: SettingsTreeSettingElement, template: ISettingBoolItemTemplate, onChange: (value: boolean) => void): void { - template.onChange = null; + template.onChange = undefined; template.checkbox.checked = dataElement.value; template.onChange = onChange; @@ -1062,18 +1077,18 @@ export class SettingTreeRenderers { } showContextMenu(element: SettingsTreeSettingElement, settingDOMElement: HTMLElement): void { - const toolbarElement: HTMLElement = settingDOMElement.querySelector('.toolbar-toggle-more'); + const toolbarElement = settingDOMElement.querySelector('.toolbar-toggle-more'); if (toolbarElement) { this._contextMenuService.showContextMenu({ getActions: () => this.settingActions, - getAnchor: () => toolbarElement, + getAnchor: () => toolbarElement, getActionsContext: () => element }); } } - getSettingDOMElementForDOMElement(domElement: HTMLElement): HTMLElement { - const parent = DOM.findParentWithClass(domElement, 'setting-item'); + getSettingDOMElementForDOMElement(domElement: HTMLElement): HTMLElement | null { + const parent = DOM.findParentWithClass(domElement, AbstractSettingRenderer.CONTENTS_CLASS); if (parent) { return parent; } @@ -1085,12 +1100,12 @@ export class SettingTreeRenderers { return treeContainer.querySelectorAll(`[${AbstractSettingRenderer.SETTING_KEY_ATTR}="${key}"]`); } - getKeyForDOMElementInSetting(element: HTMLElement): string { + getKeyForDOMElementInSetting(element: HTMLElement): string | null { const settingElement = this.getSettingDOMElementForDOMElement(element); return settingElement && settingElement.getAttribute(AbstractSettingRenderer.SETTING_KEY_ATTR); } - getIdForDOMElementInSetting(element: HTMLElement): string { + getIdForDOMElementInSetting(element: HTMLElement): string | null { const settingElement = this.getSettingDOMElementForDOMElement(element); return settingElement && settingElement.getAttribute(AbstractSettingRenderer.SETTING_ID_ATTR); } @@ -1103,11 +1118,11 @@ function renderValidations(dataElement: SettingsTreeSettingElement, template: IS DOM.addClass(template.containerElement, 'invalid-input'); template.validationErrorMessageElement.innerText = errMsg; const validationError = localize('validationError', "Validation Error."); - template.inputBox.inputElement.parentElement.setAttribute('aria-label', [originalAriaLabel, validationError, errMsg].join(' ')); + template.inputBox.inputElement.parentElement!.setAttribute('aria-label', [originalAriaLabel, validationError, errMsg].join(' ')); if (!calledOnStartup) { ariaAlert(validationError + ' ' + errMsg); } return; } else { - template.inputBox.inputElement.parentElement.setAttribute('aria-label', originalAriaLabel); + template.inputBox.inputElement.parentElement!.setAttribute('aria-label', originalAriaLabel); } } DOM.removeClass(template.containerElement, 'invalid-input'); @@ -1201,27 +1216,6 @@ export class SettingsTreeFilter implements ITreeFilter { } } -export class SettingsAccessibilityProvider implements IAccessibilityProvider { - getAriaLabel(tree: ITree, element: SettingsTreeElement): string { - if (!element) { - return ''; - } - - if (element instanceof SettingsTreeSettingElement) { - if (element.valueType === 'boolean') { - return ''; - } - return localize('settingRowAriaLabel', "{0} {1}, Setting", element.displayCategory, element.displayLabel); - } - - if (element instanceof SettingsTreeGroupElement) { - return localize('groupRowAriaLabel', "{0}, group", element.label); - } - - return ''; - } -} - class SettingsTreeDelegate implements IListVirtualDelegate { getHeight(element: SettingsTreeElement): number { if (element instanceof SettingsTreeGroupElement) { @@ -1232,7 +1226,9 @@ class SettingsTreeDelegate implements IListVirtualDelegate { // Links appear inside other elements in markdown. CSS opacity acts like a mask. So we have to dynamically compute the description color to avoid // applying an opacity to the link color. const fgWithOpacity = new Color(new RGBA(foregroundColor.rgba.r, foregroundColor.rgba.g, foregroundColor.rgba.b, 0.9)); - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description { color: ${fgWithOpacity}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description { color: ${fgWithOpacity}; }`); } const errorColor = theme.getColor(errorForeground); if (errorColor) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-deprecation-message { color: ${errorColor}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-deprecation-message { color: ${errorColor}; }`); } const invalidInputBackground = theme.getColor(inputValidationErrorBackground); if (invalidInputBackground) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-validation-message { background-color: ${invalidInputBackground}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-validation-message { background-color: ${invalidInputBackground}; }`); } const invalidInputForeground = theme.getColor(inputValidationErrorForeground); if (invalidInputForeground) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-validation-message { color: ${invalidInputForeground}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-validation-message { color: ${invalidInputForeground}; }`); } const invalidInputBorder = theme.getColor(inputValidationErrorBorder); if (invalidInputBorder) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-validation-message { border-style:solid; border-width: 1px; border-color: ${invalidInputBorder}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-validation-message { border-style:solid; border-width: 1px; border-color: ${invalidInputBorder}; }`); collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.invalid-input .setting-item-control .monaco-inputbox.idle { outline-width: 0; border-style:solid; border-width: 1px; border-color: ${invalidInputBorder}; }`); } @@ -1356,7 +1352,7 @@ export class SettingsTree extends ObjectTree { const focusBorderColor = theme.getColor(focusBorder); if (focusBorderColor) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown a:focus { outline-color: ${focusBorderColor} }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:focus { outline-color: ${focusBorderColor} }`); } })); diff --git a/src/vs/workbench/parts/preferences/browser/settingsTreeModels.ts b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts similarity index 94% rename from src/vs/workbench/parts/preferences/browser/settingsTreeModels.ts rename to src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts index c13ea877e2..22fb43d7ac 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTreeModels.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsTreeModels.ts @@ -9,10 +9,10 @@ import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { SettingsTarget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; -import { ITOCEntry, knownAcronyms } from 'vs/workbench/parts/preferences/browser/settingsLayout'; +import { SettingsTarget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; +import { ITOCEntry, knownAcronyms } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; import { IExtensionSetting, ISearchResult, ISetting, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; -import { MODIFIED_SETTING_TAG } from 'vs/workbench/parts/preferences/common/preferences'; +import { MODIFIED_SETTING_TAG } from 'vs/workbench/contrib/preferences/common/preferences'; export const ONLINE_SERVICES_SETTING_TAG = 'usesOnlineServices'; @@ -24,7 +24,7 @@ export interface ISettingsEditorViewState { export abstract class SettingsTreeElement { id: string; - parent: SettingsTreeGroupElement; + parent?: SettingsTreeGroupElement; /** * Index assigned in display order, used for paging. @@ -130,7 +130,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { } private initLabel(): void { - const displayKeyFormat = settingKeyToDisplayFormat(this.setting.key, this.parent.id); + const displayKeyFormat = settingKeyToDisplayFormat(this.setting.key, this.parent!.id); this._displayLabel = displayKeyFormat.label; this._displayCategory = displayKeyFormat.category; } @@ -161,7 +161,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { } if (this.setting.tags) { - this.setting.tags.forEach(tag => this.tags.add(tag)); + this.setting.tags.forEach(tag => this.tags!.add(tag)); } } @@ -207,7 +207,7 @@ export class SettingsTreeSettingElement extends SettingsTreeElement { if (this.tags) { let hasFilteredTag = true; tagFilters.forEach(tag => { - hasFilteredTag = hasFilteredTag && this.tags.has(tag); + hasFilteredTag = hasFilteredTag && this.tags!.has(tag); }); return hasFilteredTag; } else { @@ -261,12 +261,12 @@ export class SettingsTreeModel { } } - getElementById(id: string): SettingsTreeElement { - return this._treeElementsById.get(id); + getElementById(id: string): SettingsTreeElement | null { + return this._treeElementsById.get(id) || null; } - getElementsByName(name: string): SettingsTreeSettingElement[] { - return this._treeElementsBySettingName.get(name); + getElementsByName(name: string): SettingsTreeSettingElement[] | null { + return this._treeElementsBySettingName.get(name) || null; } updateElementsByName(name: string): void { @@ -274,7 +274,7 @@ export class SettingsTreeModel { return; } - this._treeElementsBySettingName.get(name).forEach(element => { + this._treeElementsBySettingName.get(name)!.forEach(element => { const inspectResult = inspectSetting(element.setting.key, this._viewState.settingsTarget, this._configurationService); element.update(inspectResult); }); @@ -428,7 +428,7 @@ export const enum SearchResultIdx { export class SearchResultModel extends SettingsTreeModel { private rawSearchResults: ISearchResult[]; - private cachedUniqueSearchResults: ISearchResult[]; + private cachedUniqueSearchResults: ISearchResult[] | undefined; private newExtensionSearchResults: ISearchResult; readonly id = 'searchResultModel'; @@ -473,8 +473,8 @@ export class SearchResultModel extends SettingsTreeModel { return this.rawSearchResults; } - setResult(order: SearchResultIdx, result: ISearchResult): void { - this.cachedUniqueSearchResults = null; + setResult(order: SearchResultIdx, result: ISearchResult | null): void { + this.cachedUniqueSearchResults = undefined; this.rawSearchResults = this.rawSearchResults || []; if (!result) { delete this.rawSearchResults[order]; diff --git a/src/vs/workbench/parts/preferences/browser/settingsWidgets.ts b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts similarity index 96% rename from src/vs/workbench/parts/preferences/browser/settingsWidgets.ts rename to src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts index d54b55d1eb..f845345409 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsWidgets.ts @@ -63,16 +63,16 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const link = theme.getColor(textLinkForeground); if (link) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown a { color: ${link}; }`); - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown a > code { color: ${link}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a { color: ${link}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a > code { color: ${link}; }`); collector.addRule(`.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a { color: ${link}; }`); collector.addRule(`.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a > code { color: ${link}; }`); } const activeLink = theme.getColor(textLinkActiveForeground); if (activeLink) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown a:hover, .settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown a:active { color: ${activeLink}; }`); - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown a:hover > code, .settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown a:active > code { color: ${activeLink}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:hover, .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:active { color: ${activeLink}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:hover > code, .settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:active > code { color: ${activeLink}; }`); collector.addRule(`.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a:hover, .monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a:active { color: ${activeLink}; }`); collector.addRule(`.monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a:hover > code, .monaco-select-box-dropdown-container > .select-box-details-pane > .select-box-description-markdown a:active > code { color: ${activeLink}; }`); } @@ -127,7 +127,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const modifiedItemIndicatorColor = theme.getColor(modifiedItemIndicator); if (modifiedItemIndicatorColor) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item > .setting-item-modified-indicator { border-color: ${modifiedItemIndicatorColor}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item-contents > .setting-item-modified-indicator { border-color: ${modifiedItemIndicatorColor}; }`); } }); diff --git a/src/vs/workbench/parts/preferences/browser/tocTree.ts b/src/vs/workbench/contrib/preferences/browser/tocTree.ts similarity index 82% rename from src/vs/workbench/parts/preferences/browser/tocTree.ts rename to src/vs/workbench/contrib/preferences/browser/tocTree.ts index dbb646aa4a..29d35a96a7 100644 --- a/src/vs/workbench/parts/preferences/browser/tocTree.ts +++ b/src/vs/workbench/contrib/preferences/browser/tocTree.ts @@ -5,7 +5,7 @@ import * as DOM from 'vs/base/browser/dom'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; -import { DefaultStyleController } from 'vs/base/browser/ui/list/listWidget'; +import { DefaultStyleController, IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IObjectTreeOptions, ObjectTree } from 'vs/base/browser/ui/tree/objectTree'; import { ITreeElement, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { Iterator } from 'vs/base/common/iterator'; @@ -13,15 +13,16 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { attachStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { SettingsTreeFilter } from 'vs/workbench/parts/preferences/browser/settingsTree'; -import { ISettingsEditorViewState, SearchResultModel, SettingsTreeElement, SettingsTreeGroupElement, SettingsTreeSettingElement } from 'vs/workbench/parts/preferences/browser/settingsTreeModels'; -import { settingsHeaderForeground } from 'vs/workbench/parts/preferences/browser/settingsWidgets'; +import { SettingsTreeFilter } from 'vs/workbench/contrib/preferences/browser/settingsTree'; +import { ISettingsEditorViewState, SearchResultModel, SettingsTreeElement, SettingsTreeGroupElement, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; +import { settingsHeaderForeground } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; +import { localize } from 'vs/nls'; const $ = DOM.$; export class TOCTreeModel { - private _currentSearchModel: SearchResultModel; + private _currentSearchModel: SearchResultModel | null; private _settingsTreeRoot: SettingsTreeGroupElement; constructor(private _viewState: ISettingsEditorViewState) { @@ -36,7 +37,11 @@ export class TOCTreeModel { this.update(); } - set currentSearchModel(model: SearchResultModel) { + get currentSearchModel(): SearchResultModel | null { + return this._currentSearchModel; + } + + set currentSearchModel(model: SearchResultModel | null) { this._currentSearchModel = model; this.update(); } @@ -60,7 +65,7 @@ export class TOCTreeModel { const childCount = group.children .filter(child => child instanceof SettingsTreeGroupElement) - .reduce((acc, cur) => acc + (cur).count, 0); + .reduce((acc, cur) => acc + (cur).count!, 0); group.count = childCount + this.getGroupCount(group); } @@ -149,6 +154,30 @@ export function createTOCIterator(model: TOCTreeModel | SettingsTreeGroupElement }); } +class SettingsAccessibilityProvider implements IAccessibilityProvider { + getAriaLabel(element: SettingsTreeElement): string { + if (!element) { + return ''; + } + + if (element instanceof SettingsTreeGroupElement) { + return localize('groupRowAriaLabel', "{0}, group", element.label); + } + + return ''; + } + + getAriaLevel(element: SettingsTreeGroupElement): number { + let i = 1; + while (element instanceof SettingsTreeGroupElement && element.parent) { + i++; + element = element.parent; + } + + return i; + } +} + export class TOCTree extends ObjectTree { constructor( container: HTMLElement, @@ -168,7 +197,8 @@ export class TOCTree extends ObjectTree { return e.id; } }, - styleController: new DefaultStyleController(DOM.createStyleSheet(container), treeClass) + styleController: new DefaultStyleController(DOM.createStyleSheet(container), treeClass), + accessibilityProvider: instantiationService.createInstance(SettingsAccessibilityProvider) }; super(container, diff --git a/src/vs/workbench/parts/preferences/common/preferences.ts b/src/vs/workbench/contrib/preferences/common/preferences.ts similarity index 89% rename from src/vs/workbench/parts/preferences/common/preferences.ts rename to src/vs/workbench/contrib/preferences/common/preferences.ts index 4b491d4967..86e459eb6a 100644 --- a/src/vs/workbench/parts/preferences/common/preferences.ts +++ b/src/vs/workbench/contrib/preferences/common/preferences.ts @@ -5,11 +5,11 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { join } from 'vs/base/common/paths'; import { ISettingsEditorModel, ISearchResult } from 'vs/workbench/services/preferences/common/preferences'; import { IEditor } from 'vs/workbench/common/editor'; import { IKeybindingItemEntry } from 'vs/workbench/services/preferences/common/keybindingsEditorModel'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { Event } from 'vs/base/common/event'; export interface IWorkbenchSettingsConfiguration { workbench: { @@ -36,16 +36,18 @@ export interface IPreferencesSearchService { _serviceBrand: any; getLocalSearchProvider(filter: string): ISearchProvider; - getRemoteSearchProvider(filter: string, newExtensionsOnly?: boolean): ISearchProvider; + getRemoteSearchProvider(filter: string, newExtensionsOnly?: boolean): ISearchProvider | undefined; } export interface ISearchProvider { - searchModel(preferencesModel: ISettingsEditorModel, token?: CancellationToken): Promise; + searchModel(preferencesModel: ISettingsEditorModel, token?: CancellationToken): Promise; } export interface IKeybindingsEditor extends IEditor { - activeKeybindingEntry: IKeybindingItemEntry; + readonly activeKeybindingEntry: IKeybindingItemEntry; + readonly onDefineWhenExpression: Event; + readonly onLayout: Event; search(filter: string): void; focusSearch(): void; @@ -53,7 +55,11 @@ export interface IKeybindingsEditor extends IEditor { focusKeybindings(): void; recordSearchKeys(): void; toggleSortByPrecedence(): void; - defineKeybinding(keybindingEntry: IKeybindingItemEntry): Promise; + layoutColumns(columns: HTMLElement[]): void; + selectKeybinding(keybindingEntry: IKeybindingItemEntry): void; + defineKeybinding(keybindingEntry: IKeybindingItemEntry): Promise; + defineWhenExpression(keybindingEntry: IKeybindingItemEntry): void; + updateKeybinding(keybindingEntry: IKeybindingItemEntry, key: string, when: string | undefined): Promise; removeKeybinding(keybindingEntry: IKeybindingItemEntry): Promise; resetKeybinding(keybindingEntry: IKeybindingItemEntry): Promise; copyKeybinding(keybindingEntry: IKeybindingItemEntry): void; @@ -88,6 +94,7 @@ export const KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS = 'keybindings.edit export const KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS = 'keybindings.editor.recordSearchKeys'; export const KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE = 'keybindings.editor.toggleSortByPrecedence'; export const KEYBINDINGS_EDITOR_COMMAND_DEFINE = 'keybindings.editor.defineKeybinding'; +export const KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN = 'keybindings.editor.defineWhenExpression'; export const KEYBINDINGS_EDITOR_COMMAND_REMOVE = 'keybindings.editor.removeKeybinding'; export const KEYBINDINGS_EDITOR_COMMAND_RESET = 'keybindings.editor.resetKeybinding'; export const KEYBINDINGS_EDITOR_COMMAND_COPY = 'keybindings.editor.copyKeybindingEntry'; @@ -98,8 +105,6 @@ export const KEYBINDINGS_EDITOR_CLEAR_INPUT = 'keybindings.editor.showDefaultKey export const KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS = 'keybindings.editor.showDefaultKeybindings'; export const KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS = 'keybindings.editor.showUserKeybindings'; -// {{SQL CARBON EDIT}} -export const FOLDER_SETTINGS_PATH = join('.azuredatastudio', 'settings.json'); export const DEFAULT_SETTINGS_EDITOR_SETTING = 'workbench.settings.openDefaultSettings'; export const MODIFIED_SETTING_TAG = 'modified'; diff --git a/src/vs/workbench/parts/preferences/common/preferencesContribution.ts b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts similarity index 95% rename from src/vs/workbench/parts/preferences/common/preferencesContribution.ts rename to src/vs/workbench/contrib/preferences/common/preferencesContribution.ts index 1cd6cf804e..f547ea3963 100644 --- a/src/vs/workbench/parts/preferences/common/preferencesContribution.ts +++ b/src/vs/workbench/contrib/preferences/common/preferencesContribution.ts @@ -21,7 +21,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IEditorInput } from 'vs/workbench/common/editor'; import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { FOLDER_SETTINGS_PATH, IPreferencesService, USE_SPLIT_JSON_SETTING } from 'vs/workbench/services/preferences/common/preferences'; const schemaRegistry = Registry.as(JSONContributionRegistry.Extensions.JSONContribution); @@ -61,7 +61,7 @@ export class PreferencesContribution implements IWorkbenchContribution { } } - private onEditorOpening(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions, group: IEditorGroup): IOpenEditorOverride { + private onEditorOpening(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined { const resource = editor.getResource(); if ( !resource || @@ -108,7 +108,7 @@ export class PreferencesContribution implements IWorkbenchContribution { private start(): void { this.textModelResolverService.registerTextModelContentProvider('vscode', { - provideTextContent: (uri: URI): Promise => { + provideTextContent: (uri: URI): Promise | null => { if (uri.scheme !== 'vscode') { return null; } @@ -123,7 +123,7 @@ export class PreferencesContribution implements IWorkbenchContribution { }); } - private getSchemaModel(uri: URI): ITextModel { + private getSchemaModel(uri: URI): ITextModel | null { let schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()]; if (schema) { const modelContent = JSON.stringify(schema); diff --git a/src/vs/workbench/parts/preferences/common/smartSnippetInserter.ts b/src/vs/workbench/contrib/preferences/common/smartSnippetInserter.ts similarity index 100% rename from src/vs/workbench/parts/preferences/common/smartSnippetInserter.ts rename to src/vs/workbench/contrib/preferences/common/smartSnippetInserter.ts diff --git a/src/vs/workbench/parts/preferences/electron-browser/media/check-inverse.svg b/src/vs/workbench/contrib/preferences/electron-browser/media/check-inverse.svg similarity index 100% rename from src/vs/workbench/parts/preferences/electron-browser/media/check-inverse.svg rename to src/vs/workbench/contrib/preferences/electron-browser/media/check-inverse.svg diff --git a/src/vs/workbench/parts/preferences/electron-browser/media/check.svg b/src/vs/workbench/contrib/preferences/electron-browser/media/check.svg similarity index 100% rename from src/vs/workbench/parts/preferences/electron-browser/media/check.svg rename to src/vs/workbench/contrib/preferences/electron-browser/media/check.svg diff --git a/src/vs/workbench/parts/preferences/electron-browser/media/configure-inverse.svg b/src/vs/workbench/contrib/preferences/electron-browser/media/configure-inverse.svg similarity index 100% rename from src/vs/workbench/parts/preferences/electron-browser/media/configure-inverse.svg rename to src/vs/workbench/contrib/preferences/electron-browser/media/configure-inverse.svg diff --git a/src/vs/workbench/parts/preferences/electron-browser/media/configure.svg b/src/vs/workbench/contrib/preferences/electron-browser/media/configure.svg similarity index 100% rename from src/vs/workbench/parts/preferences/electron-browser/media/configure.svg rename to src/vs/workbench/contrib/preferences/electron-browser/media/configure.svg diff --git a/src/vs/workbench/parts/preferences/electron-browser/media/edit-json-inverse.svg b/src/vs/workbench/contrib/preferences/electron-browser/media/edit-json-inverse.svg similarity index 100% rename from src/vs/workbench/parts/preferences/electron-browser/media/edit-json-inverse.svg rename to src/vs/workbench/contrib/preferences/electron-browser/media/edit-json-inverse.svg diff --git a/src/vs/workbench/parts/preferences/electron-browser/media/edit-json.svg b/src/vs/workbench/contrib/preferences/electron-browser/media/edit-json.svg similarity index 100% rename from src/vs/workbench/parts/preferences/electron-browser/media/edit-json.svg rename to src/vs/workbench/contrib/preferences/electron-browser/media/edit-json.svg diff --git a/src/vs/workbench/parts/preferences/electron-browser/media/preferences-editor-inverse.svg b/src/vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg similarity index 100% rename from src/vs/workbench/parts/preferences/electron-browser/media/preferences-editor-inverse.svg rename to src/vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg diff --git a/src/vs/workbench/parts/preferences/electron-browser/media/preferences-editor.svg b/src/vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg similarity index 100% rename from src/vs/workbench/parts/preferences/electron-browser/media/preferences-editor.svg rename to src/vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg diff --git a/src/vs/workbench/parts/preferences/electron-browser/media/settingsEditor2.css b/src/vs/workbench/contrib/preferences/electron-browser/media/settingsEditor2.css similarity index 85% rename from src/vs/workbench/parts/preferences/electron-browser/media/settingsEditor2.css rename to src/vs/workbench/contrib/preferences/electron-browser/media/settingsEditor2.css index 3f3638d223..6293aa7fb5 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/media/settingsEditor2.css +++ b/src/vs/workbench/contrib/preferences/electron-browser/media/settingsEditor2.css @@ -122,31 +122,9 @@ text-decoration: underline; } -.settings-editor.narrow-width > .settings-body .settings-tree-container .monaco-list-rows { - margin-left: 0px; -} - -.settings-editor > .settings-body .settings-tree-container .monaco-list-rows { - max-width: 1000px; - margin: auto; -} - -.settings-editor > .settings-body .settings-tree-container .monaco-list-row { - line-height: 1.4em !important; /* TODO */ - padding-left: 208px; - padding-right: 24px; - /* box-sizing: border-box; */ -} - -.settings-editor > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-row { - position: relative; -} - -.settings-editor.no-toc-search > .settings-body .settings-tree-container .monaco-list-row, -.settings-editor.narrow-width > .settings-body .settings-tree-container .monaco-list-row { - /* 3 margin + 20 padding + 2 border */ - width: calc(100% - 25px); - padding-left: 25px; +.settings-editor.no-toc-search > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-contents, +.settings-editor.narrow-width > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-contents { + padding-left: 33px; } .settings-editor > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-twistie { @@ -162,14 +140,15 @@ .settings-editor > .settings-body > .settings-tree-container .shadow.top { left: 50%; - max-width: 952px; /* 1000 - 24*2 padding */ + max-width: 952px; + /* 1000 - 24*2 padding */ margin-left: -476px; z-index: 1000; } .settings-editor > .settings-body .settings-tree-container .setting-toolbar-container { position: absolute; - left: -23px; + left: -32px; top: 11px; bottom: 0px; width: 26px; @@ -270,7 +249,6 @@ padding-left: 31px; } -.settings-editor > .settings-body .settings-tree-container .monaco-list-rows, .settings-editor > .settings-body .settings-toc-wrapper { height: 100%; max-width: 1000px; @@ -283,94 +261,109 @@ } .settings-editor > .settings-body > .settings-tree-container .monaco-list-row { + line-height: 1.4em !important; + + /* so validation messages don't get clipped */ overflow: visible; - /* so validation messages dont get clipped */ cursor: default; } -.settings-editor > .settings-body > .settings-tree-container .setting-item { - padding-top: 12px; - padding-bottom: 18px; - box-sizing: border-box; - white-space: normal; - height: 100%; +.settings-editor > .settings-body .settings-tree-container .monaco-list-rows { + overflow: visible !important; /* Allow validation errors to flow out of the tree container. Override inline style from ScrollableElement. */ } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-title { +.settings-editor > .settings-body .settings-tree-container .monaco-list-row .monaco-tl-contents { + max-width: 1000px; + margin: auto; + box-sizing: border-box; + padding-left: 217px; + padding-right: 20px; + overflow: visible; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents { + position: relative; + padding-top: 12px; + padding-bottom: 18px; + white-space: normal; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-title { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - display: inline-block; /* size to contents for hover to show context button */ + display: inline-block; + /* size to contents for hover to show context button */ } -.settings-editor > .settings-body > .settings-tree-container .setting-item > .setting-item-modified-indicator { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-modified-indicator { display: none; } -.settings-editor > .settings-body > .settings-tree-container .setting-item.is-configured > .setting-item-modified-indicator { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents.is-configured .setting-item-modified-indicator { display: block; content: ' '; position: absolute; width: 6px; border-left-width: 2px; border-left-style: solid; - left: 0px; + left: -9px; top: 15px; bottom: 16px; } -.settings-editor > .settings-body > .settings-tree-container .setting-item-bool.is-configured > .setting-item-modified-indicator { +.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-item-contents.is-configured .setting-item-modified-indicator { bottom: 23px; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-title .setting-item-overrides { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-title .setting-item-overrides { opacity: 0.5; font-style: italic; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-title .setting-item-overrides a.modified-scope { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-title .setting-item-overrides a.modified-scope { text-decoration: underline; cursor: pointer; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-label { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-label { margin-right: 7px; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-cat-label-container { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-cat-label-container { float: left; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-label, -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-category { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-label, +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-category { font-weight: 600; user-select: text; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-category { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-category { opacity: 0.9; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-deprecation-message { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-deprecation-message { margin-top: 3px; user-select: text; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description { margin-top: -1px; user-select: text; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-deprecation-message { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-deprecation-message { position: absolute; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-validation-message { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-validation-message { display: none; } -.settings-editor > .settings-body > .settings-tree-container .setting-item.invalid-input .setting-item-validation-message { +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-contents.invalid-input .setting-item-validation-message { display: block; position: absolute; padding: 5px; @@ -392,35 +385,35 @@ -webkit-appearance: none !important; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown * { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown * { margin: 0px; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown a:focus { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:focus { outline: 1px solid -webkit-focus-ring-color; outline-offset: -1px; text-decoration: underline; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown a:hover { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown a:hover { text-decoration: underline; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown code { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-description-markdown code { line-height: 15px; /** For some reason, this is needed, otherwise will take up 20px height */ - font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-enumDescription { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-enumDescription { display: none; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-enumDescription { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-enumDescription { display: block; } -.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-bool { +.settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-item-contents { padding-bottom: 26px; } @@ -449,7 +442,7 @@ background: url('check-inverse.svg') center center no-repeat; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-value { margin-top: 9px; display: flex; } @@ -471,15 +464,15 @@ width: 320px; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value .edit-in-settings-button, -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value .edit-in-settings-button:hover, -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-value .edit-in-settings-button:active { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-value .edit-in-settings-button, +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-value .edit-in-settings-button:hover, +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .setting-item-value .edit-in-settings-button:active { text-align: left; text-decoration: underline; padding-left: 0px; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .monaco-select-box { +.settings-editor > .settings-body > .settings-tree-container .setting-item-contents .monaco-select-box { width: initial; font: inherit; height: 26px; @@ -495,12 +488,6 @@ padding: 4px 10px; } -.settings-editor > .settings-body > .settings-tree-container .group-title, -.settings-editor > .settings-body > .settings-tree-container .setting-item { - padding-left: 9px; - padding-right: 9px; -} - .settings-editor > .settings-body > .settings-tree-container .group-title { cursor: default; } diff --git a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts b/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts similarity index 88% rename from src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts rename to src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts index 765d8435fe..91b4060c8c 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts +++ b/src/vs/workbench/contrib/preferences/electron-browser/preferences.contribution.ts @@ -12,7 +12,8 @@ import { Context as SuggestContext } from 'vs/editor/contrib/suggest/suggest'; import * as nls from 'vs/nls'; import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { WorkbenchStateContext } from 'vs/workbench/common/contextkeys'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -26,16 +27,17 @@ import { Extensions, IWorkbenchActionRegistry } from 'vs/workbench/common/action import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { EditorInput, Extensions as EditorInputExtensions, IEditorInputFactory, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { ResourceContextKey } from 'vs/workbench/common/resources'; -import { KeybindingsEditor } from 'vs/workbench/parts/preferences/browser/keybindingsEditor'; -import { ConfigureLanguageBasedSettingsAction, OpenDefaultKeybindingsFileAction, OpenFolderSettingsAction, OpenGlobalKeybindingsAction, OpenGlobalKeybindingsFileAction, OpenGlobalSettingsAction, OpenRawDefaultSettingsAction, OpenSettings2Action, OpenSettingsJsonAction, OpenWorkspaceSettingsAction, OPEN_FOLDER_SETTINGS_COMMAND } from 'vs/workbench/parts/preferences/browser/preferencesActions'; -import { PreferencesEditor } from 'vs/workbench/parts/preferences/browser/preferencesEditor'; -import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, IKeybindingsEditor, IPreferencesSearchService, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED, SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST, SETTINGS_EDITOR_COMMAND_SEARCH, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, SETTINGS_COMMAND_OPEN_SETTINGS } from 'vs/workbench/parts/preferences/common/preferences'; -import { PreferencesContribution } from 'vs/workbench/parts/preferences/common/preferencesContribution'; -import { PreferencesSearchService } from 'vs/workbench/parts/preferences/electron-browser/preferencesSearch'; -import { SettingsEditor2 } from 'vs/workbench/parts/preferences/electron-browser/settingsEditor2'; +import { KeybindingsEditor } from 'vs/workbench/contrib/preferences/browser/keybindingsEditor'; +import { ConfigureLanguageBasedSettingsAction, OpenDefaultKeybindingsFileAction, OpenFolderSettingsAction, OpenGlobalKeybindingsAction, OpenGlobalKeybindingsFileAction, OpenGlobalSettingsAction, OpenRawDefaultSettingsAction, OpenSettings2Action, OpenSettingsJsonAction, OpenWorkspaceSettingsAction, OPEN_FOLDER_SETTINGS_COMMAND, OPEN_FOLDER_SETTINGS_LABEL } from 'vs/workbench/contrib/preferences/browser/preferencesActions'; +import { PreferencesEditor } from 'vs/workbench/contrib/preferences/browser/preferencesEditor'; +import { CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, CONTEXT_KEYBINDING_FOCUS, CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_JSON_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, IKeybindingsEditor, IPreferencesSearchService, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_RECORD_SEARCH_KEYS, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_SEARCH, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_SORTBY_PRECEDENCE, KEYBINDINGS_EDITOR_SHOW_DEFAULT_KEYBINDINGS, KEYBINDINGS_EDITOR_SHOW_USER_KEYBINDINGS, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FILTER_MODIFIED, SETTINGS_EDITOR_COMMAND_FILTER_ONLINE, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST, SETTINGS_EDITOR_COMMAND_SEARCH, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU, SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, SETTINGS_COMMAND_OPEN_SETTINGS, KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN } from 'vs/workbench/contrib/preferences/common/preferences'; +import { PreferencesContribution } from 'vs/workbench/contrib/preferences/common/preferencesContribution'; +import { PreferencesSearchService } from 'vs/workbench/contrib/preferences/electron-browser/preferencesSearch'; +import { SettingsEditor2 } from 'vs/workbench/contrib/preferences/electron-browser/settingsEditor2'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { DefaultPreferencesEditorInput, KeybindingsEditorInput, PreferencesEditorInput, SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; +import { ExplorerRootContext, ExplorerFolderContext } from 'vs/workbench/contrib/files/common/files'; registerSingleton(IPreferencesSearchService, PreferencesSearchService, true); @@ -86,7 +88,7 @@ interface ISerializedPreferencesEditorInput { // Register Preferences Editor Input Factory class PreferencesEditorInputFactory implements IEditorInputFactory { - serialize(editorInput: EditorInput): string { + serialize(editorInput: EditorInput): string | null { const input = editorInput; if (input.details && input.master) { @@ -114,7 +116,7 @@ class PreferencesEditorInputFactory implements IEditorInputFactory { return null; } - deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput { + deserialize(instantiationService: IInstantiationService, serializedEditorInput: string): EditorInput | null { const deserialized: ISerializedPreferencesEditorInput = JSON.parse(serializedEditorInput); const registry = Registry.as(EditorInputExtensions.EditorInputFactories); @@ -230,6 +232,19 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: KEYBINDINGS_EDITOR_COMMAND_DEFINE_WHEN, + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDING_FOCUS), + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_E), + handler: (accessor, args: any) => { + const control = accessor.get(IEditorService).activeControl as IKeybindingsEditor; + if (control && control instanceof KeybindingsEditor && control.activeKeybindingEntry.keybindingItem.keybinding) { + control.defineWhenExpression(control.activeKeybindingEntry); + } + } +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: KEYBINDINGS_EDITOR_COMMAND_REMOVE, weight: KeybindingWeight.WorkbenchContrib, @@ -365,8 +380,8 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon id: OpenGlobalKeybindingsAction.ID, title: OpenGlobalKeybindingsAction.LABEL, iconLocation: { - light: URI.parse(require.toUrl(`vs/workbench/parts/preferences/electron-browser/media/preferences-editor.svg`)), - dark: URI.parse(require.toUrl(`vs/workbench/parts/preferences/electron-browser/media/preferences-editor-inverse.svg`)) + light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg`)) } }, when: ResourceContextKey.Resource.isEqualTo(URI.file(environmentService.appKeybindingsPath).toString()), @@ -381,8 +396,8 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon id: commandId, title: OpenSettings2Action.LABEL, iconLocation: { - light: URI.parse(require.toUrl(`vs/workbench/parts/preferences/electron-browser/media/preferences-editor.svg`)), - dark: URI.parse(require.toUrl(`vs/workbench/parts/preferences/electron-browser/media/preferences-editor-inverse.svg`)) + light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg`)) } }, when: ResourceContextKey.Resource.isEqualTo(URI.file(environmentService.appSettingsPath).toString()), @@ -405,11 +420,11 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon id: commandId, title: OpenSettings2Action.LABEL, iconLocation: { - light: URI.parse(require.toUrl(`vs/workbench/parts/preferences/electron-browser/media/preferences-editor.svg`)), - dark: URI.parse(require.toUrl(`vs/workbench/parts/preferences/electron-browser/media/preferences-editor-inverse.svg`)) + light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg`)) } }, - when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.workspaceSettingsResource.toString()), new RawContextKey('workbenchState', '').isEqualTo('workspace')), + when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.workspaceSettingsResource!.toString()), WorkbenchStateContext.isEqualTo('workspace')), group: 'navigation', order: 1 }); @@ -433,11 +448,11 @@ class PreferencesActionsContribution extends Disposable implements IWorkbenchCon id: commandId, title: OpenSettings2Action.LABEL, iconLocation: { - light: URI.parse(require.toUrl(`vs/workbench/parts/preferences/electron-browser/media/preferences-editor.svg`)), - dark: URI.parse(require.toUrl(`vs/workbench/parts/preferences/electron-browser/media/preferences-editor-inverse.svg`)) + light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/preferences-editor-inverse.svg`)) } }, - when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.getFolderSettingsResource(folder.uri).toString())), + when: ContextKeyExpr.and(ResourceContextKey.Resource.isEqualTo(this.preferencesService.getFolderSettingsResource(folder.uri)!.toString())), group: 'navigation', order: 1 }); @@ -461,9 +476,10 @@ CommandsRegistry.registerCommand(OpenFolderSettingsAction.ID, serviceAccessor => MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: OpenFolderSettingsAction.ID, - title: { value: `${category}: ${OpenFolderSettingsAction.LABEL}`, original: 'Preferences: Open Folder Settings' }, + title: { value: OpenFolderSettingsAction.LABEL, original: 'Preferences: Open Folder Settings' }, + category: nls.localize('preferencesCategory', "Preferences") }, - when: new RawContextKey('workbenchState', '').isEqualTo('workspace') + when: WorkbenchStateContext.isEqualTo('workspace') }); CommandsRegistry.registerCommand(OpenWorkspaceSettingsAction.ID, serviceAccessor => { @@ -472,9 +488,10 @@ CommandsRegistry.registerCommand(OpenWorkspaceSettingsAction.ID, serviceAccessor MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: OpenWorkspaceSettingsAction.ID, - title: { value: `${category}: ${OpenWorkspaceSettingsAction.LABEL}`, original: 'Preferences: Open Workspace Settings' }, + title: { value: OpenWorkspaceSettingsAction.LABEL, original: 'Preferences: Open Workspace Settings' }, + category: nls.localize('preferencesCategory', "Preferences") }, - when: new RawContextKey('workbenchState', '').notEqualsTo('empty') + when: WorkbenchStateContext.notEqualsTo('empty') }); KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -498,8 +515,8 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: OpenGlobalKeybindingsFileAction.ID, title: OpenGlobalKeybindingsFileAction.LABEL, iconLocation: { - light: URI.parse(require.toUrl(`vs/workbench/parts/preferences/electron-browser/media/edit-json.svg`)), - dark: URI.parse(require.toUrl(`vs/workbench/parts/preferences/electron-browser/media/edit-json-inverse.svg`)) + light: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/edit-json.svg`)), + dark: URI.parse(require.toUrl(`vs/workbench/contrib/preferences/electron-browser/media/edit-json-inverse.svg`)) } }, when: ContextKeyExpr.and(CONTEXT_KEYBINDINGS_EDITOR), @@ -538,7 +555,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { abstract class SettingsCommand extends Command { - protected getPreferencesEditor(accessor: ServicesAccessor): PreferencesEditor | SettingsEditor2 { + protected getPreferencesEditor(accessor: ServicesAccessor): PreferencesEditor | SettingsEditor2 | null { const activeControl = accessor.get(IEditorService).activeControl; if (activeControl instanceof PreferencesEditor || activeControl instanceof SettingsEditor2) { return activeControl; @@ -586,7 +603,7 @@ class FocusSettingsFileEditorCommand extends SettingsCommand { const preferencesEditor = this.getPreferencesEditor(accessor); if (preferencesEditor instanceof PreferencesEditor) { preferencesEditor.focusSettingsFileEditor(); - } else { + } else if (preferencesEditor) { preferencesEditor.focusSettings(); } } @@ -736,8 +753,8 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { id: SETTINGS_EDITOR_COMMAND_SWITCH_TO_JSON, title: nls.localize('openSettingsJson', "Open Settings (JSON)"), iconLocation: { - dark: URI.parse(require.toUrl('vs/workbench/parts/preferences/electron-browser/media/edit-json-inverse.svg')), - light: URI.parse(require.toUrl('vs/workbench/parts/preferences/electron-browser/media/edit-json.svg')) + dark: URI.parse(require.toUrl('vs/workbench/contrib/preferences/electron-browser/media/edit-json-inverse.svg')), + light: URI.parse(require.toUrl('vs/workbench/contrib/preferences/electron-browser/media/edit-json.svg')) } }, group: 'navigation', @@ -773,3 +790,13 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { CONTEXT_SETTINGS_JSON_EDITOR.toNegated() ) }); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: '2_workspace', + order: 20, + command: { + id: OPEN_FOLDER_SETTINGS_COMMAND, + title: OPEN_FOLDER_SETTINGS_LABEL + }, + when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext) +}); diff --git a/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts b/src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts similarity index 92% rename from src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts rename to src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts index 702d5d5113..7a17fbb45c 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts +++ b/src/vs/workbench/contrib/preferences/electron-browser/preferencesSearch.ts @@ -11,7 +11,6 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IMatch, or, matchesContiguousSubString, matchesPrefix, matchesCamelCase, matchesWords } from 'vs/base/common/filters'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IRequestService } from 'vs/platform/request/node/request'; @@ -19,13 +18,15 @@ import { asJson } from 'vs/base/node/request'; import { Disposable } from 'vs/base/common/lifecycle'; import { IExtensionManagementService, ILocalExtension, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ILogService } from 'vs/platform/log/common/log'; -import { IPreferencesSearchService, ISearchProvider, IWorkbenchSettingsConfiguration } from 'vs/workbench/parts/preferences/common/preferences'; +import { IPreferencesSearchService, ISearchProvider, IWorkbenchSettingsConfiguration } from 'vs/workbench/contrib/preferences/common/preferences'; import { CancellationToken } from 'vs/base/common/cancellation'; import { canceled } from 'vs/base/common/errors'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { nullRange } from 'vs/workbench/services/preferences/common/preferencesModels'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; export interface IEndpointDetails { - urlBase: string; + urlBase?: string; key?: string; } @@ -35,7 +36,7 @@ export class PreferencesSearchService extends Disposable implements IPreferences private _installedExtensions: Promise; constructor( - @IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @@ -76,14 +77,14 @@ export class PreferencesSearchService extends Disposable implements IPreferences } } - getRemoteSearchProvider(filter: string, newExtensionsOnly = false): ISearchProvider { + getRemoteSearchProvider(filter: string, newExtensionsOnly = false): ISearchProvider | undefined { const opts: IRemoteSearchProviderOptions = { filter, newExtensionsOnly, endpoint: this._endpoint }; - return this.remoteSearchAllowed && this.instantiationService.createInstance(RemoteSearchProvider, opts, this._installedExtensions); + return this.remoteSearchAllowed ? this.instantiationService.createInstance(RemoteSearchProvider, opts, this._installedExtensions) : undefined; } getLocalSearchProvider(filter: string): LocalSearchProvider { @@ -164,7 +165,7 @@ class RemoteSearchProvider implements ISearchProvider { private static readonly MAX_REQUESTS = 10; private static readonly NEW_EXTENSIONS_MIN_SCORE = 1; - private _remoteSearchP: Promise; + private _remoteSearchP: Promise; constructor(private options: IRemoteSearchProviderOptions, private installedExtensions: Promise, @IEnvironmentService private readonly environmentService: IEnvironmentService, @@ -176,8 +177,8 @@ class RemoteSearchProvider implements ISearchProvider { Promise.resolve(null); } - searchModel(preferencesModel: ISettingsEditorModel, token?: CancellationToken): Promise { - return this._remoteSearchP.then(remoteResult => { + searchModel(preferencesModel: ISettingsEditorModel, token?: CancellationToken): Promise { + return this._remoteSearchP.then((remoteResult) => { if (!remoteResult) { return null; } @@ -260,20 +261,25 @@ class RemoteSearchProvider implements ISearchProvider { } const requestType = details.body ? 'post' : 'get'; + const headers = { + 'User-Agent': 'request', + 'Content-Type': 'application/json; charset=utf-8', + }; + + if (this.options.endpoint.key) { + headers['api-key'] = this.options.endpoint.key; + } + const start = Date.now(); return this.requestService.request({ type: requestType, url: details.url, data: details.body, - headers: { - 'User-Agent': 'request', - 'Content-Type': 'application/json; charset=utf-8', - 'api-key': this.options.endpoint.key - }, + headers, timeout: 5000 }, CancellationToken.None).then(context => { - if (context.res.statusCode >= 300) { - throw new Error(`${details} returned status code: ${context.res.statusCode}`); + if (typeof context.res.statusCode === 'number' && context.res.statusCode >= 300) { + throw new Error(`${JSON.stringify(details)} returned status code: ${context.res.statusCode}`); } return asJson(context); @@ -290,8 +296,8 @@ class RemoteSearchProvider implements ISearchProvider { const defaultValue = value ? JSON.parse(value) : value; const packageName = r['packagename']; - let extensionName: string; - let extensionPublisher: string; + let extensionName: string | undefined; + let extensionPublisher: string | undefined; if (packageName && packageName.indexOf('##') >= 0) { [extensionPublisher, extensionName] = packageName.split('##'); } @@ -319,8 +325,7 @@ class RemoteSearchProvider implements ISearchProvider { duration, timestamp, scoredResults, - context: result['@odata.context'], - extensions: details.extensions + context: result['@odata.context'] }; }); } @@ -374,8 +379,7 @@ class RemoteSearchProvider implements ISearchProvider { return { url, body, - hasMoreFilters, - extensions + hasMoreFilters }; } @@ -423,12 +427,12 @@ function remoteSettingToISetting(remoteSetting: IRemoteSetting): IExtensionSetti return { description: remoteSetting.description.split('\n'), descriptionIsMarkdown: false, - descriptionRanges: null, + descriptionRanges: [], key: remoteSetting.key, - keyRange: null, + keyRange: nullRange, value: remoteSetting.defaultValue, - range: null, - valueRange: null, + range: nullRange, + valueRange: nullRange, overrides: [], extensionName: remoteSetting.extensionName, extensionPublisher: remoteSetting.extensionPublisher @@ -533,16 +537,6 @@ class SettingMatches { } private toKeyRange(setting: ISetting, match: IMatch): IRange { - if (!setting.keyRange) { - // No source range? Return fake range, don't care - return { - startLineNumber: 0, - startColumn: 0, - endLineNumber: 0, - endColumn: 0, - }; - } - return { startLineNumber: setting.keyRange.startLineNumber, startColumn: setting.keyRange.startColumn + match.start, @@ -552,16 +546,6 @@ class SettingMatches { } private toDescriptionRange(setting: ISetting, match: IMatch, lineIndex: number): IRange { - if (!setting.keyRange) { - // No source range? Return fake range, don't care - return { - startLineNumber: 0, - startColumn: 0, - endLineNumber: 0, - endColumn: 0, - }; - } - return { startLineNumber: setting.descriptionRanges[lineIndex].startLineNumber, startColumn: setting.descriptionRanges[lineIndex].startColumn + match.start, @@ -571,16 +555,6 @@ class SettingMatches { } private toValueRange(setting: ISetting, match: IMatch): IRange { - if (!setting.keyRange) { - // No source range? Return fake range, don't care - return { - startLineNumber: 0, - startColumn: 0, - endLineNumber: 0, - endColumn: 0, - }; - } - return { startLineNumber: setting.valueRange.startLineNumber, startColumn: setting.valueRange.startColumn + match.start + 1, diff --git a/src/vs/workbench/parts/preferences/electron-browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/electron-browser/settingsEditor2.ts similarity index 85% rename from src/vs/workbench/parts/preferences/electron-browser/settingsEditor2.ts rename to src/vs/workbench/contrib/preferences/electron-browser/settingsEditor2.ts index 4568f272f0..a50a199ccb 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/electron-browser/settingsEditor2.ts @@ -28,15 +28,15 @@ import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IEditor, IEditorMemento } from 'vs/workbench/common/editor'; -import { attachSuggestEnabledInputBoxStyler, SuggestEnabledInput } from 'vs/workbench/parts/codeEditor/electron-browser/suggestEnabledInput'; -import { SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; -import { commonlyUsedData, tocData } from 'vs/workbench/parts/preferences/browser/settingsLayout'; -import { AbstractSettingRenderer, ISettingLinkClickEvent, ISettingOverrideClickEvent, resolveExtensionsSettings, resolveSettingsTree, SettingsTree, SettingTreeRenderers } from 'vs/workbench/parts/preferences/browser/settingsTree'; -import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/parts/preferences/browser/settingsTreeModels'; -import { settingsTextInputBorder } from 'vs/workbench/parts/preferences/browser/settingsWidgets'; -import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/parts/preferences/browser/tocTree'; -import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/parts/preferences/common/preferences'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { attachSuggestEnabledInputBoxStyler, SuggestEnabledInput } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; +import { SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/contrib/preferences/browser/preferencesWidgets'; +import { commonlyUsedData, tocData } from 'vs/workbench/contrib/preferences/browser/settingsLayout'; +import { AbstractSettingRenderer, ISettingLinkClickEvent, ISettingOverrideClickEvent, resolveExtensionsSettings, resolveSettingsTree, SettingsTree, SettingTreeRenderers } from 'vs/workbench/contrib/preferences/browser/settingsTree'; +import { ISettingsEditorViewState, parseQuery, SearchResultIdx, SearchResultModel, SettingsTreeElement, SettingsTreeGroupChild, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; +import { settingsTextInputBorder } from 'vs/workbench/contrib/preferences/browser/settingsWidgets'; +import { createTOCIterator, TOCTree, TOCTreeModel } from 'vs/workbench/contrib/preferences/browser/tocTree'; +import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, IPreferencesSearchService, ISearchProvider, MODIFIED_SETTING_TAG, SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU } from 'vs/workbench/contrib/preferences/common/preferences'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPreferencesService, ISearchResult, ISettingsEditorModel, ISettingsEditorOptions, SettingsEditorOptions, SettingValueType } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; @@ -56,6 +56,10 @@ function createGroupIterator(group: SettingsTreeGroupElement): Iterator; private localSearchDelayer: Delayer; private remoteSearchThrottle: ThrottledDelayer; - private searchInProgress: CancellationTokenSource; + private searchInProgress: CancellationTokenSource | null; private settingFastUpdateDelayer: Delayer; private settingSlowUpdateDelayer: Delayer; - private pendingSettingUpdate: { key: string, value: any }; + private pendingSettingUpdate: { key: string, value: any } | null; private readonly viewState: ISettingsEditorViewState; - private _searchResultModel: SearchResultModel; + private _searchResultModel: SearchResultModel | null; private tocRowFocused: IContextKey; private inSettingsEditorContextKey: IContextKey; @@ -124,7 +128,7 @@ export class SettingsEditor2 extends BaseEditor { private editorMemento: IEditorMemento; - private tocFocusedElement: SettingsTreeGroupElement; + private tocFocusedElement: SettingsTreeGroupElement | null; private settingsTreeScrollTop = 0; constructor( @@ -176,18 +180,19 @@ export class SettingsEditor2 extends BaseEditor { return this.searchResultModel || this.settingsTreeModel; } - private get searchResultModel(): SearchResultModel { + private get searchResultModel(): SearchResultModel | null { return this._searchResultModel; } - private set searchResultModel(value: SearchResultModel) { + private set searchResultModel(value: SearchResultModel | null) { this._searchResultModel = value; DOM.toggleClass(this.rootElement, 'search-mode', !!this._searchResultModel); } - private get currentSettingsContextMenuKeyBindingLabel() { - return this.keybindingService.lookupKeybinding(SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU).getAriaLabel(); + private get currentSettingsContextMenuKeyBindingLabel(): string { + const keybinding = this.keybindingService.lookupKeybinding(SETTINGS_EDITOR_COMMAND_SHOW_CONTEXT_MENU); + return (keybinding && keybinding.getAriaLabel()) || ''; } createEditor(parent: HTMLElement): void { @@ -229,7 +234,7 @@ export class SettingsEditor2 extends BaseEditor { } private restoreCachedState(): void { - const cachedState = this.editorMemento.loadEditorState(this.group, this.input); + const cachedState = this.group && this.input && this.editorMemento.loadEditorState(this.group, this.input); if (cachedState && typeof cachedState.target === 'object') { cachedState.target = URI.revive(cachedState.target); } @@ -264,14 +269,14 @@ export class SettingsEditor2 extends BaseEditor { clearInput(): void { this.inSettingsEditorContextKey.set(false); - this.editorMemento.clearEditorState(this.input, this.group); + if (this.input) { + this.editorMemento.clearEditorState(this.input, this.group); + } + super.clearInput(); } layout(dimension: DOM.Dimension): void { - // const firstEl = this.settingsTree.getFirstVisibleElement(); - // const firstElTop = this.settingsTree.getRelativeTop(firstEl); - this.layoutTrees(dimension); const innerWidth = dimension.width - 24 * 2; // 24px padding on left and right @@ -314,7 +319,12 @@ export class SettingsEditor2 extends BaseEditor { } showContextMenu(): void { - const settingDOMElement = this.settingRenderers.getSettingDOMElementForDOMElement(this.getActiveElementInSettingsTree()); + const activeElement = this.getActiveElementInSettingsTree(); + if (!activeElement) { + return; + } + + const settingDOMElement = this.settingRenderers.getSettingDOMElementForDOMElement(activeElement); if (!settingDOMElement) { return; } @@ -382,9 +392,10 @@ export class SettingsEditor2 extends BaseEditor { this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder, badgeForeground }, colors => { const background = colors.badgeBackground ? colors.badgeBackground.toString() : null; const border = colors.contrastBorder ? colors.contrastBorder.toString() : null; + const foreground = colors.badgeForeground ? colors.badgeForeground.toString() : null; this.countElement.style.backgroundColor = background; - this.countElement.style.color = colors.badgeForeground.toString(); + this.countElement.style.color = foreground; this.countElement.style.borderWidth = border ? '1px' : null; this.countElement.style.borderStyle = border ? 'solid' : null; @@ -411,6 +422,10 @@ export class SettingsEditor2 extends BaseEditor { const elements = this.currentSettingsModel.getElementsByName(evt.targetKey); if (elements && elements[0]) { let sourceTop = this.settingsTree.getRelativeTop(evt.source); + if (typeof sourceTop !== 'number') { + return; + } + if (sourceTop < 0) { // e.g. clicked a searched element, now the search has been cleared sourceTop = 0.5; @@ -434,12 +449,12 @@ export class SettingsEditor2 extends BaseEditor { } } - switchToSettingsFile(): Promise { + switchToSettingsFile(): Promise { const query = parseQuery(this.searchWidget.getValue()); return this.openSettingsFile(query.query); } - private openSettingsFile(query?: string): Promise { + private openSettingsFile(query?: string): Promise { const currentSettingsTarget = this.settingsTargetsWidget.settingsTarget; const options: ISettingsEditorOptions = { query }; @@ -488,57 +503,57 @@ export class SettingsEditor2 extends BaseEditor { this.createTOC(bodyContainer); - // this.createFocusSink( - // bodyContainer, - // e => { - // if (DOM.findParentWithClass(e.relatedTarget, 'settings-editor-tree')) { - // if (this.settingsTree.getScrollPosition() > 0) { - // const firstElement = this.settingsTree.getFirstVisibleElement(); - // this.settingsTree.reveal(firstElement, 0.1); - // return true; - // } - // } else { - // const firstControl = this.settingsTree.getHTMLElement().querySelector(SettingsRenderer.CONTROL_SELECTOR); - // if (firstControl) { - // (firstControl).focus(); - // } - // } + this.createFocusSink( + bodyContainer, + e => { + if (DOM.findParentWithClass(e.relatedTarget, 'settings-editor-tree')) { + if (this.settingsTree.scrollTop > 0) { + const firstElement = this.settingsTree.firstVisibleElement; + this.settingsTree.reveal(firstElement, 0.1); + return true; + } + } else { + const firstControl = this.settingsTree.getHTMLElement().querySelector(AbstractSettingRenderer.CONTROL_SELECTOR); + if (firstControl) { + (firstControl).focus(); + } + } - // return false; - // }, - // 'settings list focus helper'); + return false; + }, + 'settings list focus helper'); this.createSettingsTree(bodyContainer); - // this.createFocusSink( - // bodyContainer, - // e => { - // if (DOM.findParentWithClass(e.relatedTarget, 'settings-editor-tree')) { - // if (this.settingsTree.getScrollPosition() < 1) { - // const lastElement = this.settingsTree.getLastVisibleElement(); - // this.settingsTree.reveal(lastElement, 0.9); - // return true; - // } - // } + this.createFocusSink( + bodyContainer, + e => { + if (DOM.findParentWithClass(e.relatedTarget, 'settings-editor-tree')) { + if (this.settingsTree.scrollTop < this.settingsTree.scrollHeight) { + const lastElement = this.settingsTree.lastVisibleElement; + this.settingsTree.reveal(lastElement, 0.9); + return true; + } + } - // return false; - // }, - // 'settings list focus helper' - // ); + return false; + }, + 'settings list focus helper' + ); } - // private createFocusSink(container: HTMLElement, callback: (e: any) => boolean, label: string): HTMLElement { - // const listFocusSink = DOM.append(container, $('.settings-tree-focus-sink')); - // listFocusSink.setAttribute('aria-label', label); - // listFocusSink.tabIndex = 0; - // this._register(DOM.addDisposableListener(listFocusSink, 'focus', (e: any) => { - // if (e.relatedTarget && callback(e)) { - // e.relatedTarget.focus(); - // } - // })); + private createFocusSink(container: HTMLElement, callback: (e: any) => boolean, label: string): HTMLElement { + const listFocusSink = DOM.append(container, $('.settings-tree-focus-sink')); + listFocusSink.setAttribute('aria-label', label); + listFocusSink.tabIndex = 0; + this._register(DOM.addDisposableListener(listFocusSink, 'focus', (e: any) => { + if (e.relatedTarget && callback(e)) { + e.relatedTarget.focus(); + } + })); - // return listFocusSink; - // } + return listFocusSink; + } private createTOC(parent: HTMLElement): void { this.tocTreeModel = new TOCTreeModel(this.viewState); @@ -549,7 +564,7 @@ export class SettingsEditor2 extends BaseEditor { this.viewState)); this._register(this.tocTree.onDidChangeFocus(e => { - const element: SettingsTreeGroupElement = e.elements[0]; + const element: SettingsTreeGroupElement | null = e.elements[0]; if (this.tocFocusedElement === element) { return; } @@ -558,14 +573,11 @@ export class SettingsEditor2 extends BaseEditor { this.tocTree.setSelection(element ? [element] : []); if (this.searchResultModel) { if (this.viewState.filterToCategory !== element) { - this.viewState.filterToCategory = element; - // see https://github.com/Microsoft/vscode/issues/66796 - setTimeout(() => { - this.renderTree(); - this.settingsTree.scrollTop = 0; - }, 0); + this.viewState.filterToCategory = element || undefined; + this.renderTree(); + this.settingsTree.scrollTop = 0; } - } else if (element && (!e.browserEvent || !(e.browserEvent).fromScroll)) { + } else if (element && (!e.browserEvent || !(e.browserEvent).fromScroll)) { this.settingsTree.reveal(element, 0); } })); @@ -663,25 +675,29 @@ export class SettingsEditor2 extends BaseEditor { return; } - // Hack, see https://github.com/Microsoft/vscode/issues/64749 - const settingItems = this.settingsTree.getHTMLElement().querySelectorAll('.setting-item'); - const firstEl = settingItems[1] || settingItems[0]; - if (!firstEl) { - return; - } - - const firstSettingId = this.settingRenderers.getIdForDOMElementInSetting(firstEl); - const elementToSync = this.settingsTreeModel.getElementById(firstSettingId); + const elementToSync = this.settingsTree.firstVisibleElement; const element = elementToSync instanceof SettingsTreeSettingElement ? elementToSync.parent : elementToSync instanceof SettingsTreeGroupElement ? elementToSync : null; + // It's possible for this to be called when the TOC and settings tree are out of sync - e.g. when the settings tree has deferred a refresh because + // it is focused. So, bail if element doesn't exist in the TOC. + let nodeExists = true; + try { this.tocTree.getNode(element); } catch (e) { nodeExists = false; } + if (!nodeExists) { + return; + } + if (element && this.tocTree.getSelection()[0] !== element) { const ancestors = this.getAncestors(element); ancestors.forEach(e => this.tocTree.expand(e)); this.tocTree.reveal(element); const elementTop = this.tocTree.getRelativeTop(element); + if (typeof elementTop !== 'number') { + return; + } + this.tocTree.collapseAll(); ancestors.forEach(e => this.tocTree.expand(e)); @@ -696,7 +712,7 @@ export class SettingsEditor2 extends BaseEditor { this.tocTree.setSelection([element]); const fakeKeyboardEvent = new KeyboardEvent('keydown'); - (fakeKeyboardEvent).fromScroll = true; + (fakeKeyboardEvent).fromScroll = true; this.tocTree.setFocus([element], fakeKeyboardEvent); } } @@ -732,14 +748,14 @@ export class SettingsEditor2 extends BaseEditor { } return this.configurationService.updateValue(key, value, overrides, configurationTarget) - .then(() => this.renderTree(key, isManualReset)) .then(() => { + this.renderTree(key, isManualReset); const reportModifiedProps = { key, query: this.searchWidget.getValue(), searchResults: this.searchResultModel && this.searchResultModel.getUniqueResults(), rawResults: this.searchResultModel && this.searchResultModel.getRawResults(), - showConfiguredOnly: this.viewState.tagFilters && this.viewState.tagFilters.has(MODIFIED_SETTING_TAG), + showConfiguredOnly: !!this.viewState.tagFilters && this.viewState.tagFilters.has(MODIFIED_SETTING_TAG), isReset: typeof value === 'undefined', settingsTarget: this.settingsTargetsWidget.settingsTarget as SettingsTarget }; @@ -748,17 +764,17 @@ export class SettingsEditor2 extends BaseEditor { }); } - private reportModifiedSetting(props: { key: string, query: string, searchResults: ISearchResult[], rawResults: ISearchResult[], showConfiguredOnly: boolean, isReset: boolean, settingsTarget: SettingsTarget }): void { + private reportModifiedSetting(props: { key: string, query: string, searchResults: ISearchResult[] | null, rawResults: ISearchResult[] | null, showConfiguredOnly: boolean, isReset: boolean, settingsTarget: SettingsTarget }): void { this.pendingSettingUpdate = null; - const remoteResult = props.searchResults && props.searchResults[SearchResultIdx.Remote]; - const localResult = props.searchResults && props.searchResults[SearchResultIdx.Local]; - - let groupId = undefined; - let nlpIndex = undefined; - let displayIndex = undefined; + let groupId: string | undefined = undefined; + let nlpIndex: number | undefined = undefined; + let displayIndex: number | undefined = undefined; if (props.searchResults) { - const localIndex = arrays.firstIndex(localResult.filterMatches, m => m.setting.key === props.key); + const remoteResult = props.searchResults[SearchResultIdx.Remote]; + const localResult = props.searchResults[SearchResultIdx.Local]; + + const localIndex = arrays.firstIndex(localResult!.filterMatches, m => m.setting.key === props.key); groupId = localIndex >= 0 ? 'local' : 'remote'; @@ -848,7 +864,7 @@ export class SettingsEditor2 extends BaseEditor { }); } - private onConfigUpdate(keys?: string[], forceRefresh = false): Promise { + private onConfigUpdate(keys?: string[], forceRefresh = false): void { if (keys && this.settingsTreeModel) { return this.updateElementsByKey(keys); } @@ -870,9 +886,9 @@ export class SettingsEditor2 extends BaseEditor { } const commonlyUsed = resolveSettingsTree(commonlyUsedData, dividedGroups.core); - resolvedSettingsRoot.children.unshift(commonlyUsed.tree); + resolvedSettingsRoot.children!.unshift(commonlyUsed.tree); - resolvedSettingsRoot.children.push(resolveExtensionsSettings(dividedGroups.extension || [])); + resolvedSettingsRoot.children!.push(resolveExtensionsSettings(dividedGroups.extension || [])); if (this.searchResultModel) { this.searchResultModel.updateChildren(); @@ -882,11 +898,12 @@ export class SettingsEditor2 extends BaseEditor { this.settingsTreeModel.update(resolvedSettingsRoot); // Make sure that all extensions' settings are included in search results - const cachedState = this.editorMemento.loadEditorState(this.group, this.input); + const cachedState = this.group && this.input && this.editorMemento.loadEditorState(this.group, this.input); if (cachedState && cachedState.searchQuery) { this.triggerSearch(cachedState.searchQuery); } else { - return this.renderTree(undefined, forceRefresh); + this.renderTree(undefined, forceRefresh); + this.refreshTOCTree(); } } else { this.settingsTreeModel = this.instantiationService.createInstance(SettingsTreeModel, this.viewState); @@ -898,23 +915,19 @@ export class SettingsEditor2 extends BaseEditor { this.tocTree.collapseAll(); } - - return Promise.resolve(undefined); } - private updateElementsByKey(keys: string[]): Promise { + private updateElementsByKey(keys: string[]): void { if (keys.length) { if (this.searchResultModel) { - keys.forEach(key => this.searchResultModel.updateElementsByName(key)); + keys.forEach(key => this.searchResultModel!.updateElementsByName(key)); } if (this.settingsTreeModel) { keys.forEach(key => this.settingsTreeModel.updateElementsByName(key)); } - return Promise.all( - keys.map(key => this.renderTree(key))) - .then(() => { }); + keys.forEach(key => this.renderTree(key)); } else { return this.renderTree(); } @@ -926,14 +939,24 @@ export class SettingsEditor2 extends BaseEditor { null; } - private renderTree(key?: string, force = false): Promise { + private renderTree(key?: string, force = false): void { if (!force && key && this.scheduledRefreshes.has(key)) { this.updateModifiedLabelForKey(key); - return Promise.resolve(undefined); + return; + } + + // If the context view is focused, delay rendering settings + if (this.contextViewFocused()) { + const element = document.querySelector('.context-view'); + if (element) { + this.scheduleRefresh(element as HTMLElement, key); + } + return; } // If a setting control is currently focused, schedule a refresh for later - const focusedSetting = this.settingRenderers.getSettingDOMElementForDOMElement(this.getActiveElementInSettingsTree()); + const activeElement = this.getActiveElementInSettingsTree(); + const focusedSetting = activeElement && this.settingRenderers.getSettingDOMElementForDOMElement(activeElement); if (focusedSetting && !force) { // If a single setting is being refreshed, it's ok to refresh now if that is not the focused setting if (key) { @@ -943,11 +966,11 @@ export class SettingsEditor2 extends BaseEditor { this.updateModifiedLabelForKey(key); this.scheduleRefresh(focusedSetting, key); - return Promise.resolve(null); + return; } } else { this.scheduleRefresh(focusedSetting); - return Promise.resolve(null); + return; } } @@ -960,15 +983,17 @@ export class SettingsEditor2 extends BaseEditor { this.refreshTree(); } else { // Refresh requested for a key that we don't know about - return Promise.resolve(null); + return; } } else { this.refreshTree(); } - this.tocTreeModel.update(); - this.refreshTOCTree(); - return Promise.resolve(undefined); + return; + } + + private contextViewFocused(): boolean { + return !!DOM.findParentWithClass(document.activeElement, 'context-view'); } private refreshTree(): void { @@ -979,6 +1004,7 @@ export class SettingsEditor2 extends BaseEditor { private refreshTOCTree(): void { if (this.isVisible()) { + this.tocTreeModel.update(); this.tocTree.setChildren(null, createTOCIterator(this.tocTreeModel, this.tocTree)); } } @@ -988,7 +1014,7 @@ export class SettingsEditor2 extends BaseEditor { const isModified = dataElements && dataElements[0] && dataElements[0].isConfigured; // all elements are either configured or not const elements = this.settingRenderers.getDOMElementsForSettingKey(this.settingsTree.getHTMLElement(), key); if (elements && elements[0]) { - DOM.toggleClass(elements[0], 'is-configured', isModified); + DOM.toggleClass(elements[0], 'is-configured', !!isModified); } } @@ -997,12 +1023,12 @@ export class SettingsEditor2 extends BaseEditor { this.delayedFilterLogging.cancel(); this.triggerSearch(query.replace(/›/g, ' ')).then(() => { if (query && this.searchResultModel) { - this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(query, this.searchResultModel.getUniqueResults())); + this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(query, this.searchResultModel!.getUniqueResults())); } }); } - private parseSettingFromJSON(query: string): string { + private parseSettingFromJSON(query: string): string | null { const match = query.match(/"([a-zA-Z.]+)": /); return match && match[1]; } @@ -1012,7 +1038,7 @@ export class SettingsEditor2 extends BaseEditor { if (query) { const parsedQuery = parseQuery(query); query = parsedQuery.query; - parsedQuery.tags.forEach(tag => this.viewState.tagFilters.add(tag)); + parsedQuery.tags.forEach(tag => this.viewState.tagFilters!.add(tag)); } if (query && query !== '@') { @@ -1033,7 +1059,7 @@ export class SettingsEditor2 extends BaseEditor { this.searchInProgress = null; } - this.viewState.filterToCategory = null; + this.viewState.filterToCategory = undefined; this.tocTreeModel.currentSearchModel = this.searchResultModel; this.onSearchModeToggled(); @@ -1053,7 +1079,7 @@ export class SettingsEditor2 extends BaseEditor { this.refreshTOCTree(); } - return Promise.resolve(null); + return Promise.resolve(); } /** @@ -1131,18 +1157,18 @@ export class SettingsEditor2 extends BaseEditor { if (result && !result.exactMatch) { this.remoteSearchThrottle.trigger(() => { return searchInProgress && !searchInProgress.token.isCancellationRequested ? - this.remoteSearchPreferences(query, this.searchInProgress.token) : - Promise.resolve(null); + this.remoteSearchPreferences(query, this.searchInProgress!.token) : + Promise.resolve(); }); } }); } else { - return Promise.resolve(null); + return Promise.resolve(); } }); } - private localFilterPreferences(query: string, token?: CancellationToken): Promise { + private localFilterPreferences(query: string, token?: CancellationToken): Promise { const localSearchProvider = this.preferencesSearchService.getLocalSearchProvider(query); return this.filterOrSearchPreferences(query, SearchResultIdx.Local, localSearchProvider, token); } @@ -1157,7 +1183,7 @@ export class SettingsEditor2 extends BaseEditor { ]).then(() => { }); } - private filterOrSearchPreferences(query: string, type: SearchResultIdx, searchProvider: ISearchProvider, token?: CancellationToken): Promise { + private filterOrSearchPreferences(query: string, type: SearchResultIdx, searchProvider?: ISearchProvider, token?: CancellationToken): Promise { return this._filterOrSearchPreferencesModel(query, this.defaultSettingsEditorModel, searchProvider, token).then(result => { if (token && token.isCancellationRequested) { // Handle cancellation like this because cancellation is lost inside the search provider due to async/await @@ -1175,10 +1201,12 @@ export class SettingsEditor2 extends BaseEditor { } this.tocTree.setSelection([]); - this.viewState.filterToCategory = null; + this.viewState.filterToCategory = undefined; this.tocTree.expandAll(); - return this.renderTree(undefined, true).then(() => result); + this.renderTree(undefined, true); + this.refreshTOCTree(); + return result; }); } @@ -1203,7 +1231,7 @@ export class SettingsEditor2 extends BaseEditor { } } - private _filterOrSearchPreferencesModel(filter: string, model: ISettingsEditorModel, provider: ISearchProvider, token?: CancellationToken): Promise { + private _filterOrSearchPreferencesModel(filter: string, model: ISettingsEditorModel, provider?: ISearchProvider, token?: CancellationToken): Promise { const searchP = provider ? provider.searchModel(model, token) : Promise.resolve(null); return searchP .then(null, err => { @@ -1222,7 +1250,7 @@ export class SettingsEditor2 extends BaseEditor { this.telemetryService.publicLog('settingsEditor.searchError', { message, filter }); this.logService.info('Setting search error: ' + message); } - return null; + return Promise.resolve(null); } }); } @@ -1242,7 +1270,9 @@ export class SettingsEditor2 extends BaseEditor { if (this.isVisible()) { const searchQuery = this.searchWidget.getValue().trim(); const target = this.settingsTargetsWidget.settingsTarget as SettingsTarget; - this.editorMemento.saveEditorState(this.group, this.input, { searchQuery, target }); + if (this.group && this.input) { + this.editorMemento.saveEditorState(this.group, this.input, { searchQuery, target }); + } } super.saveState(); diff --git a/src/vs/workbench/parts/preferences/test/browser/keybindingsEditorContribution.test.ts b/src/vs/workbench/contrib/preferences/test/browser/keybindingsEditorContribution.test.ts similarity index 95% rename from src/vs/workbench/parts/preferences/test/browser/keybindingsEditorContribution.test.ts rename to src/vs/workbench/contrib/preferences/test/browser/keybindingsEditorContribution.test.ts index 19b57071ea..cd7dbbad43 100644 --- a/src/vs/workbench/parts/preferences/test/browser/keybindingsEditorContribution.test.ts +++ b/src/vs/workbench/contrib/preferences/test/browser/keybindingsEditorContribution.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { KeybindingEditorDecorationsRenderer } from 'vs/workbench/parts/preferences/browser/keybindingsEditorContribution'; +import { KeybindingEditorDecorationsRenderer } from 'vs/workbench/contrib/preferences/browser/keybindingsEditorContribution'; suite('KeybindingsEditorContribution', () => { diff --git a/src/vs/workbench/parts/preferences/test/browser/settingsTreeModels.test.ts b/src/vs/workbench/contrib/preferences/test/browser/settingsTreeModels.test.ts similarity index 98% rename from src/vs/workbench/parts/preferences/test/browser/settingsTreeModels.test.ts rename to src/vs/workbench/contrib/preferences/test/browser/settingsTreeModels.test.ts index 9e903b7f3a..4fd119f459 100644 --- a/src/vs/workbench/parts/preferences/test/browser/settingsTreeModels.test.ts +++ b/src/vs/workbench/contrib/preferences/test/browser/settingsTreeModels.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { settingKeyToDisplayFormat, parseQuery, IParsedQuery } from 'vs/workbench/parts/preferences/browser/settingsTreeModels'; +import { settingKeyToDisplayFormat, parseQuery, IParsedQuery } from 'vs/workbench/contrib/preferences/browser/settingsTreeModels'; suite('SettingsTree', () => { test('settingKeyToDisplayFormat', () => { diff --git a/src/vs/workbench/parts/preferences/test/common/smartSnippetInserter.test.ts b/src/vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts similarity index 98% rename from src/vs/workbench/parts/preferences/test/common/smartSnippetInserter.test.ts rename to src/vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts index aae0e6a884..9e1557a81e 100644 --- a/src/vs/workbench/parts/preferences/test/common/smartSnippetInserter.test.ts +++ b/src/vs/workbench/contrib/preferences/test/common/smartSnippetInserter.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { SmartSnippetInserter } from 'vs/workbench/parts/preferences/common/smartSnippetInserter'; +import { SmartSnippetInserter } from 'vs/workbench/contrib/preferences/common/smartSnippetInserter'; import { TextModel } from 'vs/editor/common/model/textModel'; import { Position } from 'vs/editor/common/core/position'; diff --git a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts similarity index 77% rename from src/vs/workbench/parts/quickopen/browser/commandsHandler.ts rename to src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts index 97db8ae0e6..ffd4a1ff7e 100644 --- a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import * as arrays from 'vs/base/common/arrays'; import * as types from 'vs/base/common/types'; -import { language, LANGUAGE_DEFAULT } from 'vs/base/common/platform'; +import { Language } from 'vs/base/common/platform'; import { Action } from 'vs/base/common/actions'; import { Mode, IEntryRunContext, IAutoFocus, IModel, IQuickNavigateConfiguration } from 'vs/base/parts/quickopen/common/quickOpen'; import { QuickOpenEntryGroup, IHighlight, QuickOpenModel, QuickOpenEntry } from 'vs/base/parts/quickopen/browser/quickOpenModel'; @@ -31,6 +31,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Disposable } from 'vs/base/common/lifecycle'; +import { timeout } from 'vs/base/common/async'; export const ALL_COMMANDS_PREFIX = '>'; @@ -90,7 +91,7 @@ class CommandsHistory extends Disposable { private load(): void { const raw = this.storageService.get(CommandsHistory.PREF_KEY_CACHE, StorageScope.GLOBAL); - let serializedCache: ISerializedCommandHistory; + let serializedCache: ISerializedCommandHistory | undefined; if (raw) { try { serializedCache = JSON.parse(raw); @@ -110,14 +111,14 @@ class CommandsHistory extends Disposable { entries.forEach(entry => commandHistory.set(entry.key, entry.value)); } - commandCounter = this.storageService.getInteger(CommandsHistory.PREF_KEY_COUNTER, StorageScope.GLOBAL, commandCounter); + commandCounter = this.storageService.getNumber(CommandsHistory.PREF_KEY_COUNTER, StorageScope.GLOBAL, commandCounter); } push(commandId: string): void { commandHistory.set(commandId, commandCounter++); // set counter to command } - peek(commandId: string): number { + peek(commandId: string): number | undefined { return commandHistory.peek(commandId); } @@ -213,14 +214,14 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { private description: string; private alias: string; private labelLowercase: string; - private keybindingAriaLabel: string; + private readonly keybindingAriaLabel?: string; constructor( private commandId: string, private keybinding: ResolvedKeybinding, private label: string, alias: string, - highlights: { label: IHighlight[], alias: IHighlight[] }, + highlights: { label: IHighlight[], alias?: IHighlight[] }, private onBeforeRun: (commandId: string) => void, @INotificationService private readonly notificationService: INotificationService, @ITelemetryService protected telemetryService: ITelemetryService @@ -228,15 +229,15 @@ abstract class BaseCommandEntry extends QuickOpenEntryGroup { super(); this.labelLowercase = this.label.toLowerCase(); - this.keybindingAriaLabel = keybinding ? keybinding.getAriaLabel() : undefined; + this.keybindingAriaLabel = keybinding ? keybinding.getAriaLabel() || undefined : undefined; if (this.label !== alias) { this.alias = alias; } else { - highlights.alias = null; + highlights.alias = undefined; } - this.setHighlights(highlights.label, null, highlights.alias); + this.setHighlights(highlights.label, undefined, highlights.alias); } getCommandId(): string { @@ -376,6 +377,7 @@ export class CommandsHandler extends QuickOpenHandler { private commandHistoryEnabled: boolean; private commandsHistory: CommandsHistory; + private extensionsRegistered: boolean; constructor( @IEditorService private readonly editorService: IEditorService, @@ -389,6 +391,8 @@ export class CommandsHandler extends QuickOpenHandler { this.commandsHistory = this.instantiationService.createInstance(CommandsHistory); + this.extensionService.whenInstalledExtensionsRegistered().then(() => this.extensionsRegistered = true); + this.configurationService.onDidChangeConfiguration(e => this.updateConfiguration()); this.updateConfiguration(); } @@ -398,89 +402,96 @@ export class CommandsHandler extends QuickOpenHandler { } getResults(searchValue: string, token: CancellationToken): Promise { + if (this.extensionsRegistered) { + return this.doGetResults(searchValue, token); + } - // wait for extensions being registered to cover all commands - // also from extensions - return this.extensionService.whenInstalledExtensionsRegistered().then(() => { - if (token.isCancellationRequested) { - return new QuickOpenModel([]); + // If extensions are not yet registered, we wait for a little moment to give them + // a chance to register so that the complete set of commands shows up as result + // We do not want to delay functionality beyond that time though to keep the commands + // functional. + return Promise.race([timeout(800), this.extensionService.whenInstalledExtensionsRegistered().then(() => undefined)]).then(() => this.doGetResults(searchValue, token)); + } + + private doGetResults(searchValue: string, token: CancellationToken): Promise { + if (token.isCancellationRequested) { + return Promise.resolve(new QuickOpenModel([])); + } + + searchValue = searchValue.trim(); + + // Remember as last command palette input + lastCommandPaletteInput = searchValue; + + // Editor Actions + const activeTextEditorWidget = this.editorService.activeTextEditorWidget; + let editorActions: IEditorAction[] = []; + if (activeTextEditorWidget && types.isFunction(activeTextEditorWidget.getSupportedActions)) { + editorActions = activeTextEditorWidget.getSupportedActions(); + } + + const editorEntries = this.editorActionsToEntries(editorActions, searchValue); + + // Other Actions + const menu = this.editorService.invokeWithinEditorContext(accessor => this.menuService.createMenu(MenuId.CommandPalette, accessor.get(IContextKeyService))); + const menuActions = menu.getActions().reduce((r, [, actions]) => [...r, ...actions], []).filter(action => action instanceof MenuItemAction) as MenuItemAction[]; + const commandEntries = this.menuItemActionsToEntries(menuActions, searchValue); + menu.dispose(); + + // Concat + let entries = [...editorEntries, ...commandEntries]; + + // Remove duplicates + entries = arrays.distinct(entries, entry => `${entry.getLabel()}${entry.getGroupLabel()}${entry.getCommandId()}`); + + // Handle label clashes + const commandLabels = new Set(); + entries.forEach(entry => { + const commandLabel = `${entry.getLabel()}${entry.getGroupLabel()}`; + if (commandLabels.has(commandLabel)) { + entry.setDescription(entry.getCommandId()); + } else { + commandLabels.add(commandLabel); } - - searchValue = searchValue.trim(); - - // Remember as last command palette input - lastCommandPaletteInput = searchValue; - - // Editor Actions - const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - let editorActions: IEditorAction[] = []; - if (activeTextEditorWidget && types.isFunction(activeTextEditorWidget.getSupportedActions)) { - editorActions = activeTextEditorWidget.getSupportedActions(); - } - - const editorEntries = this.editorActionsToEntries(editorActions, searchValue); - - // Other Actions - const menu = this.editorService.invokeWithinEditorContext(accessor => this.menuService.createMenu(MenuId.CommandPalette, accessor.get(IContextKeyService))); - const menuActions = menu.getActions().reduce((r, [, actions]) => [...r, ...actions], []).filter(action => action instanceof MenuItemAction) as MenuItemAction[]; - const commandEntries = this.menuItemActionsToEntries(menuActions, searchValue); - menu.dispose(); - - // Concat - let entries = [...editorEntries, ...commandEntries]; - - // Remove duplicates - entries = arrays.distinct(entries, entry => `${entry.getLabel()}${entry.getGroupLabel()}${entry.getCommandId()}`); - - // Handle label clashes - const commandLabels = new Set(); - entries.forEach(entry => { - const commandLabel = `${entry.getLabel()}${entry.getGroupLabel()}`; - if (commandLabels.has(commandLabel)) { - entry.setDescription(entry.getCommandId()); - } else { - commandLabels.add(commandLabel); - } - }); - - // Sort by MRU order and fallback to name otherwie - entries = entries.sort((elementA, elementB) => { - const counterA = this.commandsHistory.peek(elementA.getCommandId()); - const counterB = this.commandsHistory.peek(elementB.getCommandId()); - - if (counterA && counterB) { - return counterA > counterB ? -1 : 1; // use more recently used command before older - } - - if (counterA) { - return -1; // first command was used, so it wins over the non used one - } - - if (counterB) { - return 1; // other command was used so it wins over the command - } - - // both commands were never used, so we sort by name - return elementA.getSortLabel().localeCompare(elementB.getSortLabel()); - }); - - // Introduce group marker border between recently used and others - // only if we have recently used commands in the result set - const firstEntry = entries[0]; - if (firstEntry && this.commandsHistory.peek(firstEntry.getCommandId())) { - firstEntry.setGroupLabel(nls.localize('recentlyUsed', "recently used")); - for (let i = 1; i < entries.length; i++) { - const entry = entries[i]; - if (!this.commandsHistory.peek(entry.getCommandId())) { - entry.setShowBorder(true); - entry.setGroupLabel(nls.localize('morecCommands', "other commands")); - break; - } - } - } - - return new QuickOpenModel(entries); }); + + // Sort by MRU order and fallback to name otherwie + entries = entries.sort((elementA, elementB) => { + const counterA = this.commandsHistory.peek(elementA.getCommandId()); + const counterB = this.commandsHistory.peek(elementB.getCommandId()); + + if (counterA && counterB) { + return counterA > counterB ? -1 : 1; // use more recently used command before older + } + + if (counterA) { + return -1; // first command was used, so it wins over the non used one + } + + if (counterB) { + return 1; // other command was used so it wins over the command + } + + // both commands were never used, so we sort by name + return elementA.getSortLabel().localeCompare(elementB.getSortLabel()); + }); + + // Introduce group marker border between recently used and others + // only if we have recently used commands in the result set + const firstEntry = entries[0]; + if (firstEntry && this.commandsHistory.peek(firstEntry.getCommandId())) { + firstEntry.setGroupLabel(nls.localize('recentlyUsed', "recently used")); + for (let i = 1; i < entries.length; i++) { + const entry = entries[i]; + if (!this.commandsHistory.peek(entry.getCommandId())) { + entry.setShowBorder(true); + entry.setGroupLabel(nls.localize('morecCommands', "other commands")); + break; + } + } + } + + return Promise.resolve(new QuickOpenModel(entries)); } private editorActionsToEntries(actions: IEditorAction[], searchValue: string): EditorActionCommandEntry[] { @@ -495,7 +506,7 @@ export class CommandsHandler extends QuickOpenHandler { if (label) { // Alias for non default languages - const alias = (language !== LANGUAGE_DEFAULT) ? action.alias : null; + const alias = !Language.isDefaultVariant() ? action.alias : null; const labelHighlights = wordFilter(searchValue, label); const aliasHighlights = alias ? wordFilter(searchValue, alias) : null; @@ -529,8 +540,8 @@ export class CommandsHandler extends QuickOpenHandler { const labelHighlights = wordFilter(searchValue, label); // Add an 'alias' in original language when running in different locale - const aliasTitle = (language !== LANGUAGE_DEFAULT && typeof action.item.title !== 'string') ? action.item.title.original : null; - const aliasCategory = (language !== LANGUAGE_DEFAULT && category && typeof action.item.category !== 'string') ? action.item.category.original : null; + const aliasTitle = (!Language.isDefaultVariant() && typeof action.item.title !== 'string') ? action.item.title.original : null; + const aliasCategory = (!Language.isDefaultVariant() && category && action.item.category && typeof action.item.category !== 'string') ? action.item.category.original : null; let alias; if (aliasTitle && category) { alias = aliasCategory ? `${aliasCategory}: ${aliasTitle}` : `${category}: ${aliasTitle}`; @@ -549,7 +560,7 @@ export class CommandsHandler extends QuickOpenHandler { } getAutoFocus(searchValue: string, context: { model: IModel, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus { - let autoFocusPrefixMatch = searchValue.trim(); + let autoFocusPrefixMatch: string | undefined = searchValue.trim(); if (autoFocusPrefixMatch && this.commandHistoryEnabled) { const firstEntry = context.model && context.model.entries[0]; diff --git a/src/vs/workbench/parts/quickopen/browser/gotoLineHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts similarity index 82% rename from src/vs/workbench/parts/quickopen/browser/gotoLineHandler.ts rename to src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts index 8bf786a41a..df1511593b 100644 --- a/src/vs/workbench/parts/quickopen/browser/gotoLineHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts @@ -19,7 +19,7 @@ import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { IEditorOptions, RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; import { isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { Event } from 'vs/base/common/event'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -40,6 +40,10 @@ export class GotoLineAction extends QuickOpenAction { run(): Promise { let activeTextEditorWidget = this.editorService.activeTextEditorWidget; + if (!activeTextEditorWidget) { + return Promise.resolve(); + } + if (isDiffEditor(activeTextEditorWidget)) { activeTextEditorWidget = activeTextEditorWidget.getModifiedEditor(); } @@ -61,7 +65,7 @@ export class GotoLineAction extends QuickOpenAction { if (restoreOptions) { Event.once(this._quickOpenService.onHide)(() => { - activeTextEditorWidget.updateOptions(restoreOptions); + activeTextEditorWidget!.updateOptions(restoreOptions!); }); } @@ -92,14 +96,17 @@ class GotoLineEntry extends EditorQuickOpenEntry { // Inform user about valid range if input is invalid const maxLineNumber = this.getMaxLineNumber(); - if (this.invalidRange(maxLineNumber)) { - const currentLine = this.editorService.activeTextEditorWidget.getPosition().lineNumber; + if (this.editorService.activeTextEditorWidget && this.invalidRange(maxLineNumber)) { + const position = this.editorService.activeTextEditorWidget.getPosition(); + if (position) { + const currentLine = position.lineNumber; - if (maxLineNumber > 0) { - return nls.localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}. Type a line number between 1 and {1} to navigate to.", currentLine, maxLineNumber); + if (maxLineNumber > 0) { + return nls.localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}. Type a line number between 1 and {1} to navigate to.", currentLine, maxLineNumber); + } + + return nls.localize('gotoLineLabelEmpty', "Current Line: {0}. Type a line number to navigate to.", currentLine); } - - return nls.localize('gotoLineLabelEmpty', "Current Line: {0}. Type a line number to navigate to.", currentLine); } // Input valid, indicate action @@ -112,6 +119,9 @@ class GotoLineEntry extends EditorQuickOpenEntry { private getMaxLineNumber(): number { const activeTextEditorWidget = this.editorService.activeTextEditorWidget; + if (!activeTextEditorWidget) { + return -1; + } let model = activeTextEditorWidget.getModel(); if (model && (model).modified && (model).original) { @@ -129,8 +139,8 @@ class GotoLineEntry extends EditorQuickOpenEntry { return this.runPreview(); } - getInput(): IEditorInput { - return this.editorService.activeEditor; + getInput(): IEditorInput | null { + return this.editorService.activeEditor || null; } getOptions(pinned?: boolean): ITextEditorOptions { @@ -150,7 +160,7 @@ class GotoLineEntry extends EditorQuickOpenEntry { // Check for sideBySide use const sideBySide = context.keymods.ctrlCmd; if (sideBySide) { - this.editorService.openEditor(this.getInput(), this.getOptions(context.keymods.alt), SIDE_GROUP); + this.editorService.openEditor(this.getInput()!, this.getOptions(context.keymods.alt), SIDE_GROUP); } // Apply selection and focus @@ -180,7 +190,7 @@ class GotoLineEntry extends EditorQuickOpenEntry { activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth); // Decorate if possible - if (types.isFunction(activeTextEditorWidget.changeDecorations)) { + if (this.editorService.activeControl && types.isFunction(activeTextEditorWidget.changeDecorations)) { this.handler.decorateOutline(range, activeTextEditorWidget, this.editorService.activeControl.group); } } @@ -208,8 +218,8 @@ export class GotoLineHandler extends QuickOpenHandler { static readonly ID = 'workbench.picker.line'; - private rangeHighlightDecorationId: IEditorLineDecoration; - private lastKnownEditorViewState: IEditorViewState; + private rangeHighlightDecorationId: IEditorLineDecoration | null; + private lastKnownEditorViewState: IEditorViewState | null; constructor(@IEditorService private readonly editorService: IEditorService) { super(); @@ -217,9 +227,11 @@ export class GotoLineHandler extends QuickOpenHandler { getAriaLabel(): string { if (this.editorService.activeTextEditorWidget) { - const currentLine = this.editorService.activeTextEditorWidget.getPosition().lineNumber; - - return nls.localize('gotoLineLabelEmpty', "Current Line: {0}. Type a line number to navigate to.", currentLine); + const position = this.editorService.activeTextEditorWidget.getPosition(); + if (position) { + const currentLine = position.lineNumber; + return nls.localize('gotoLineLabelEmpty', "Current Line: {0}. Type a line number to navigate to.", currentLine); + } } return nls.localize('cannotRunGotoLine', "Open a text file first to go to a line."); @@ -231,7 +243,9 @@ export class GotoLineHandler extends QuickOpenHandler { // Remember view state to be able to restore on cancel if (!this.lastKnownEditorViewState) { const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - this.lastKnownEditorViewState = activeTextEditorWidget.saveViewState(); + if (activeTextEditorWidget) { + this.lastKnownEditorViewState = activeTextEditorWidget.saveViewState(); + } } return Promise.resolve(new QuickOpenModel([new GotoLineEntry(searchValue, this.editorService, this)])); @@ -288,14 +302,15 @@ export class GotoLineHandler extends QuickOpenHandler { } clearDecorations(): void { - if (this.rangeHighlightDecorationId) { + const rangeHighlightDecorationId = this.rangeHighlightDecorationId; + if (rangeHighlightDecorationId) { this.editorService.visibleControls.forEach(editor => { - if (editor.group.id === this.rangeHighlightDecorationId.groupId) { + if (editor.group && editor.group.id === rangeHighlightDecorationId.groupId) { const editorControl = editor.getControl(); editorControl.changeDecorations(changeAccessor => { changeAccessor.deltaDecorations([ - this.rangeHighlightDecorationId.lineDecorationId, - this.rangeHighlightDecorationId.rangeHighlightId + rangeHighlightDecorationId.lineDecorationId, + rangeHighlightDecorationId.rangeHighlightId ], []); }); } diff --git a/src/vs/workbench/parts/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts similarity index 90% rename from src/vs/workbench/parts/quickopen/browser/gotoSymbolHandler.ts rename to src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts index 8cf8c9cd0b..6acf231624 100644 --- a/src/vs/workbench/parts/quickopen/browser/gotoSymbolHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts @@ -22,7 +22,7 @@ import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry'; import { GroupIdentifier, IEditorInput } from 'vs/workbench/common/editor'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { asPromise } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; @@ -82,9 +82,9 @@ class OutlineModel extends QuickOpenModel { this.entries.forEach((entry: SymbolEntry) => { // Clear all state first - entry.setGroupLabel(null); + entry.setGroupLabel(undefined); entry.setShowBorder(false); - entry.setHighlights(null); + entry.setHighlights([]); entry.setHidden(false); // Filter by search @@ -128,7 +128,7 @@ class OutlineModel extends QuickOpenModel { // Update previous result with count if (currentResult) { - currentResult.setGroupLabel(this.renderGroupLabel(currentType, typeCounter)); + currentResult.setGroupLabel(typeof currentType === 'number' ? this.renderGroupLabel(currentType, typeCounter) : undefined); } currentType = result.getKind(); @@ -146,7 +146,7 @@ class OutlineModel extends QuickOpenModel { // Update previous result with count if (currentResult) { - currentResult.setGroupLabel(this.renderGroupLabel(currentType, typeCounter)); + currentResult.setGroupLabel(typeof currentType === 'number' ? this.renderGroupLabel(currentType, typeCounter) : undefined); } } @@ -288,8 +288,8 @@ class SymbolEntry extends EditorQuickOpenEntryGroup { return this.range; } - getInput(): IEditorInput { - return this.editorService.activeEditor; + getInput(): IEditorInput | null { + return this.editorService.activeEditor || null; } getOptions(pinned?: boolean): ITextEditorOptions { @@ -312,7 +312,7 @@ class SymbolEntry extends EditorQuickOpenEntryGroup { // Check for sideBySide use const sideBySide = context.keymods.ctrlCmd; if (sideBySide) { - this.editorService.openEditor(this.getInput(), this.getOptions(context.keymods.alt), SIDE_GROUP); + this.editorService.openEditor(this.getInput()!, this.getOptions(context.keymods.alt), SIDE_GROUP); } // Apply selection and focus @@ -337,7 +337,7 @@ class SymbolEntry extends EditorQuickOpenEntryGroup { activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth); // Decorate if possible - if (types.isFunction(activeTextEditorWidget.changeDecorations)) { + if (this.editorService.activeControl && types.isFunction(activeTextEditorWidget.changeDecorations)) { this.handler.decorateOutline(this.range, range, activeTextEditorWidget, this.editorService.activeControl.group); } } @@ -365,11 +365,11 @@ export class GotoSymbolHandler extends QuickOpenHandler { static readonly ID = 'workbench.picker.filesymbols'; - private rangeHighlightDecorationId: IEditorLineDecoration; - private lastKnownEditorViewState: IEditorViewState; + private rangeHighlightDecorationId?: IEditorLineDecoration; + private lastKnownEditorViewState: IEditorViewState | null; - private cachedOutlineRequest: Promise; - private pendingOutlineRequest: CancellationTokenSource; + private cachedOutlineRequest?: Promise; + private pendingOutlineRequest?: CancellationTokenSource; constructor( @IEditorService private readonly editorService: IEditorService @@ -386,11 +386,11 @@ export class GotoSymbolHandler extends QuickOpenHandler { private onDidActiveEditorChange(): void { this.clearOutlineRequest(); - this.lastKnownEditorViewState = undefined; + this.lastKnownEditorViewState = null; this.rangeHighlightDecorationId = undefined; } - getResults(searchValue: string, token: CancellationToken): Promise { + getResults(searchValue: string, token: CancellationToken): Promise { searchValue = searchValue.trim(); // Support to cancel pending outline requests @@ -401,11 +401,17 @@ export class GotoSymbolHandler extends QuickOpenHandler { // Remember view state to be able to restore on cancel if (!this.lastKnownEditorViewState) { const activeTextEditorWidget = this.editorService.activeTextEditorWidget; - this.lastKnownEditorViewState = activeTextEditorWidget.saveViewState(); + if (activeTextEditorWidget) { + this.lastKnownEditorViewState = activeTextEditorWidget.saveViewState(); + } } // Resolve Outline Model return this.getOutline().then(outline => { + if (!outline) { + return outline; + } + if (token.isCancellationRequested) { return outline; } @@ -469,20 +475,20 @@ export class GotoSymbolHandler extends QuickOpenHandler { const label = strings.trim(element.name); // Show parent scope as description - const description: string = element.containerName; + const description = element.containerName || ''; const icon = symbolKindToCssClass(element.kind); // Add results.push(new SymbolEntry(i, label, element.kind, description, `symbol-icon ${icon}`, - element.range, element.selectionRange, null, this.editorService, this + element.range, element.selectionRange, [], this.editorService, this )); } return results; } - private getOutline(): Promise { + private getOutline(): Promise { if (!this.cachedOutlineRequest) { this.cachedOutlineRequest = this.doGetActiveOutline(); } @@ -499,7 +505,7 @@ export class GotoSymbolHandler extends QuickOpenHandler { } if (model && types.isFunction((model).getLanguageIdentifier)) { - return Promise.resolve(asPromise(() => getDocumentSymbols(model, true, this.pendingOutlineRequest.token)).then(entries => { + return Promise.resolve(asPromise(() => getDocumentSymbols(model, true, this.pendingOutlineRequest!.token)).then(entries => { return new OutlineModel(this.toQuickOpenEntries(entries)); })); } @@ -515,7 +521,7 @@ export class GotoSymbolHandler extends QuickOpenHandler { if (this.rangeHighlightDecorationId) { deleteDecorations.push(this.rangeHighlightDecorationId.lineDecorationId); deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId); - this.rangeHighlightDecorationId = null; + this.rangeHighlightDecorationId = undefined; } const newDecorations: IModelDeltaDecoration[] = [ @@ -555,20 +561,21 @@ export class GotoSymbolHandler extends QuickOpenHandler { } private clearDecorations(): void { - if (this.rangeHighlightDecorationId) { + const rangeHighlightDecorationId = this.rangeHighlightDecorationId; + if (rangeHighlightDecorationId) { this.editorService.visibleControls.forEach(editor => { - if (editor.group.id === this.rangeHighlightDecorationId.groupId) { + if (editor.group && editor.group.id === rangeHighlightDecorationId.groupId) { const editorControl = editor.getControl(); editorControl.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => { changeAccessor.deltaDecorations([ - this.rangeHighlightDecorationId.lineDecorationId, - this.rangeHighlightDecorationId.rangeHighlightId + rangeHighlightDecorationId.lineDecorationId, + rangeHighlightDecorationId.rangeHighlightId ], []); }); } }); - this.rangeHighlightDecorationId = null; + this.rangeHighlightDecorationId = undefined; } } @@ -598,6 +605,6 @@ export class GotoSymbolHandler extends QuickOpenHandler { this.pendingOutlineRequest = undefined; } - this.cachedOutlineRequest = null; + this.cachedOutlineRequest = undefined; } } diff --git a/src/vs/workbench/parts/quickopen/browser/helpHandler.ts b/src/vs/workbench/contrib/quickopen/browser/helpHandler.ts similarity index 100% rename from src/vs/workbench/parts/quickopen/browser/helpHandler.ts rename to src/vs/workbench/contrib/quickopen/browser/helpHandler.ts diff --git a/src/vs/workbench/parts/quickopen/browser/quickopen.contribution.ts b/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts similarity index 93% rename from src/vs/workbench/parts/quickopen/browser/quickopen.contribution.ts rename to src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts index 9a1a8ea752..3456d4137e 100644 --- a/src/vs/workbench/parts/quickopen/browser/quickopen.contribution.ts +++ b/src/vs/workbench/contrib/quickopen/browser/quickopen.contribution.ts @@ -10,11 +10,11 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { GotoSymbolAction, GOTO_SYMBOL_PREFIX, SCOPE_PREFIX, GotoSymbolHandler } from 'vs/workbench/parts/quickopen/browser/gotoSymbolHandler'; -import { ShowAllCommandsAction, ALL_COMMANDS_PREFIX, ClearCommandHistoryAction, CommandsHandler } from 'vs/workbench/parts/quickopen/browser/commandsHandler'; -import { GotoLineAction, GOTO_LINE_PREFIX, GotoLineHandler } from 'vs/workbench/parts/quickopen/browser/gotoLineHandler'; -import { HELP_PREFIX, HelpHandler } from 'vs/workbench/parts/quickopen/browser/helpHandler'; -import { VIEW_PICKER_PREFIX, OpenViewPickerAction, QuickOpenViewPickerAction, ViewPickerHandler } from 'vs/workbench/parts/quickopen/browser/viewPickerHandler'; +import { GotoSymbolAction, GOTO_SYMBOL_PREFIX, SCOPE_PREFIX, GotoSymbolHandler } from 'vs/workbench/contrib/quickopen/browser/gotoSymbolHandler'; +import { ShowAllCommandsAction, ALL_COMMANDS_PREFIX, ClearCommandHistoryAction, CommandsHandler } from 'vs/workbench/contrib/quickopen/browser/commandsHandler'; +import { GotoLineAction, GOTO_LINE_PREFIX, GotoLineHandler } from 'vs/workbench/contrib/quickopen/browser/gotoLineHandler'; +import { HELP_PREFIX, HelpHandler } from 'vs/workbench/contrib/quickopen/browser/helpHandler'; +import { VIEW_PICKER_PREFIX, OpenViewPickerAction, QuickOpenViewPickerAction, ViewPickerHandler } from 'vs/workbench/contrib/quickopen/browser/viewPickerHandler'; import { inQuickOpenContext, getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; @@ -86,7 +86,7 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen GotoLineHandler, GotoLineHandler.ID, GOTO_LINE_PREFIX, - null, + undefined, [ { prefix: GOTO_LINE_PREFIX, @@ -123,7 +123,7 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen HelpHandler, HelpHandler.ID, HELP_PREFIX, - null, + undefined, nls.localize('helpDescription', "Show Help") ) ); diff --git a/src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts b/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts similarity index 85% rename from src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts rename to src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts index 9f173b1770..91206ce74c 100644 --- a/src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts +++ b/src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts @@ -8,15 +8,15 @@ import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel import { QuickOpenModel, QuickOpenEntryGroup, QuickOpenEntry } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { QuickOpenHandler, QuickOpenAction } from 'vs/workbench/browser/quickopen'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { IOutputService } from 'vs/workbench/parts/output/common/output'; -import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal'; +import { IOutputService } from 'vs/workbench/contrib/output/common/output'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/common/terminal'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { Action } from 'vs/base/common/actions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { fuzzyContains, stripWildcards } from 'vs/base/common/strings'; import { matchesFuzzy } from 'vs/base/common/filters'; -import { ViewsRegistry, ViewContainer, IViewsService, IViewContainersRegistry, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; +import { IViewsRegistry, ViewContainer, IViewsService, IViewContainersRegistry, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -133,11 +133,11 @@ export class ViewPickerHandler extends QuickOpenHandler { const viewEntries: ViewEntry[] = []; const getViewEntriesForViewlet = (viewlet: ViewletDescriptor, viewContainer: ViewContainer): ViewEntry[] => { - const views = ViewsRegistry.getViews(viewContainer); + const views = Registry.as(ViewExtensions.ViewsRegistry).getViews(viewContainer); const result: ViewEntry[] = []; if (views.length) { for (const view of views) { - if (this.contextKeyService.contextMatchesRules(view.when)) { + if (this.contextKeyService.contextMatchesRules(view.when || null)) { result.push(new ViewEntry(view.name, viewlet.name, () => this.viewsService.openView(view.id, true))); } } @@ -147,7 +147,11 @@ export class ViewPickerHandler extends QuickOpenHandler { // Viewlets const viewlets = this.viewletService.getViewlets(); - viewlets.forEach((viewlet, index) => viewEntries.push(new ViewEntry(viewlet.name, nls.localize('views', "Side Bar"), () => this.viewletService.openViewlet(viewlet.id, true)))); + viewlets.forEach((viewlet, index) => { + if (this.hasToShowViewlet(viewlet)) { + viewEntries.push(new ViewEntry(viewlet.name, nls.localize('views', "Side Bar"), () => this.viewletService.openViewlet(viewlet.id, true))); + } + }); // Panels const panels = this.panelService.getPanels(); @@ -155,7 +159,7 @@ export class ViewPickerHandler extends QuickOpenHandler { // Viewlet Views viewlets.forEach((viewlet, index) => { - const viewContainer: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).get(viewlet.id); + const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(viewlet.id); if (viewContainer) { const viewEntriesForViewlet: ViewEntry[] = getViewEntriesForViewlet(viewlet, viewContainer); viewEntries.push(...viewEntriesForViewlet); @@ -189,6 +193,15 @@ export class ViewPickerHandler extends QuickOpenHandler { return viewEntries; } + private hasToShowViewlet(viewlet: ViewletDescriptor): boolean { + const viewContainer = Registry.as(ViewExtensions.ViewContainersRegistry).get(viewlet.id); + if (viewContainer && viewContainer.hideIfEmpty) { + const viewsCollection = this.viewsService.getViewDescriptors(viewContainer); + return !!viewsCollection && viewsCollection.activeViewDescriptors.length > 0; + } + return true; + } + getAutoFocus(searchValue: string, context: { model: IModel, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus { return { autoFocusFirstEntry: !!searchValue || !!context.quickNavigateConfiguration diff --git a/src/vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts b/src/vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution.ts similarity index 89% rename from src/vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts rename to src/vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution.ts index 17ebaad2f0..70b96574e0 100644 --- a/src/vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts +++ b/src/vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution.ts @@ -21,10 +21,10 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { equals } from 'vs/base/common/objects'; interface IConfiguration extends IWindowsConfiguration { - update: { channel: string; }; + update: { mode: string; }; telemetry: { enableCrashReporter: boolean }; keyboard: { touchbar: { enabled: boolean } }; - workbench: { tree: { horizontalScrolling: boolean }, useExperimentalGridLayout: boolean }; + workbench: { list: { horizontalScrolling: boolean }, useExperimentalGridLayout: boolean }; files: { useExperimentalFileWatcher: boolean, watcherExclude: object }; } @@ -34,7 +34,7 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo private nativeTabs: boolean; private nativeFullScreen: boolean; private clickThroughInactive: boolean; - private updateChannel: string; + private updateMode: string; private enableCrashReporter: boolean; private touchbarEnabled: boolean; private treeHorizontalScrolling: boolean; @@ -84,8 +84,8 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo } // Update channel - if (config.update && typeof config.update.channel === 'string' && config.update.channel !== this.updateChannel) { - this.updateChannel = config.update.channel; + if (config.update && typeof config.update.mode === 'string' && config.update.mode !== this.updateMode) { + this.updateMode = config.update.mode; changed = true; } @@ -116,8 +116,8 @@ export class SettingsChangeRelauncher extends Disposable implements IWorkbenchCo } // Tree horizontal scrolling support - if (config.workbench && config.workbench.tree && typeof config.workbench.tree.horizontalScrolling === 'boolean' && config.workbench.tree.horizontalScrolling !== this.treeHorizontalScrolling) { - this.treeHorizontalScrolling = config.workbench.tree.horizontalScrolling; + if (config.workbench && config.workbench.list && typeof config.workbench.list.horizontalScrolling === 'boolean' && config.workbench.list.horizontalScrolling !== this.treeHorizontalScrolling) { + this.treeHorizontalScrolling = config.workbench.list.horizontalScrolling; changed = true; } @@ -167,11 +167,22 @@ export class WorkspaceChangeExtHostRelauncher extends Disposable implements IWor constructor( @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IExtensionService extensionService: IExtensionService + @IExtensionService extensionService: IExtensionService, + @IWindowService windowSevice: IWindowService, + @IEnvironmentService environmentService: IEnvironmentService ) { super(); - this.extensionHostRestarter = this._register(new RunOnceScheduler(() => extensionService.restartExtensionHost(), 10)); + this.extensionHostRestarter = this._register(new RunOnceScheduler(() => { + if (!!environmentService.extensionTestsLocationURI) { + return; // no restart when in tests: see https://github.com/Microsoft/vscode/issues/66936 + } + if (windowSevice.getConfiguration().remoteAuthority) { + windowSevice.reloadWindow(); // TODO aeschli, workaround + } else { + extensionService.restartExtensionHost(); + } + }, 10)); this.contextService.getCompleteWorkspace() .then(workspace => { diff --git a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts similarity index 99% rename from src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts rename to src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index 9e1b6c83bd..0b5c5dcc97 100644 --- a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -17,7 +17,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { URI } from 'vs/base/common/uri'; -import { ISCMService, ISCMRepository } from 'vs/workbench/services/scm/common/scm'; +import { ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { registerThemingParticipant, ITheme, ICssStyleCollector, themeColorFromId, IThemeService } from 'vs/platform/theme/common/themeService'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; @@ -37,7 +37,7 @@ import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { Action, IAction, ActionRunner } from 'vs/base/common/actions'; import { IActionBarOptions, ActionsOrientation, IActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { basename } from 'vs/base/common/paths'; +import { basename } from 'vs/base/common/resources'; import { MenuId, IMenuService, IMenu, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { MenuItemActionItem, fillInActionBarActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { IChange, IEditorModel, ScrollType, IEditorContribution, IDiffEditorModel } from 'vs/editor/common/editorCommon'; @@ -156,7 +156,7 @@ function getChangeType(change: IChange): ChangeType { } } -function getChangeTypeColor(theme: ITheme, changeType: ChangeType): Color | null { +function getChangeTypeColor(theme: ITheme, changeType: ChangeType): Color | undefined { switch (changeType) { case ChangeType.Modify: return theme.getColor(editorGutterModifiedBackground); case ChangeType.Add: return theme.getColor(editorGutterAddedBackground); @@ -208,7 +208,7 @@ class DirtyDiffWidget extends PeekViewWidget { this.create(); if (editor.hasModel()) { - this.title = basename(editor.getModel().uri.fsPath); + this.title = basename(editor.getModel().uri); } else { this.title = ''; } @@ -265,8 +265,8 @@ class DirtyDiffWidget extends PeekViewWidget { protected _fillHead(container: HTMLElement): void { super._fillHead(container); - const previous = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowPreviousChangeAction(), 'show-previous-change octicon octicon-chevron-up'); - const next = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowNextChangeAction(), 'show-next-change octicon octicon-chevron-down'); + const previous = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowPreviousChangeAction(), 'show-previous-change chevron-up'); + const next = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowNextChangeAction(), 'show-next-change chevron-down'); this._disposables.push(previous); this._disposables.push(next); diff --git a/src/vs/workbench/parts/scm/electron-browser/media/check-inverse.svg b/src/vs/workbench/contrib/scm/browser/media/check-inverse.svg similarity index 100% rename from src/vs/workbench/parts/scm/electron-browser/media/check-inverse.svg rename to src/vs/workbench/contrib/scm/browser/media/check-inverse.svg diff --git a/src/vs/workbench/parts/scm/electron-browser/media/check.svg b/src/vs/workbench/contrib/scm/browser/media/check.svg similarity index 100% rename from src/vs/workbench/parts/scm/electron-browser/media/check.svg rename to src/vs/workbench/contrib/scm/browser/media/check.svg diff --git a/src/vs/workbench/parts/scm/electron-browser/media/dirtydiffDecorator.css b/src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css similarity index 100% rename from src/vs/workbench/parts/scm/electron-browser/media/dirtydiffDecorator.css rename to src/vs/workbench/contrib/scm/browser/media/dirtydiffDecorator.css diff --git a/src/vs/workbench/parts/scm/electron-browser/media/icon-dark.svg b/src/vs/workbench/contrib/scm/browser/media/icon-dark.svg similarity index 100% rename from src/vs/workbench/parts/scm/electron-browser/media/icon-dark.svg rename to src/vs/workbench/contrib/scm/browser/media/icon-dark.svg diff --git a/src/vs/workbench/parts/scm/electron-browser/media/icon-light.svg b/src/vs/workbench/contrib/scm/browser/media/icon-light.svg similarity index 100% rename from src/vs/workbench/parts/scm/electron-browser/media/icon-light.svg rename to src/vs/workbench/contrib/scm/browser/media/icon-light.svg diff --git a/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css similarity index 99% rename from src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css rename to src/vs/workbench/contrib/scm/browser/media/scmViewlet.css index dae83c3740..69a28c6ee1 100644 --- a/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css +++ b/src/vs/workbench/contrib/scm/browser/media/scmViewlet.css @@ -13,6 +13,8 @@ } .scm-viewlet .empty-message { + box-sizing: border-box; + height: 100%; padding: 10px 22px 0 22px; } diff --git a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts similarity index 82% rename from src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts rename to src/vs/workbench/contrib/scm/browser/scm.contribution.ts index de6a591393..c9d71512a3 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -8,46 +8,44 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { DirtyDiffWorkbenchController } from './dirtydiffDecorator'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from 'vs/workbench/browser/viewlet'; -import { VIEWLET_ID } from 'vs/workbench/parts/scm/common/scm'; +import { VIEWLET_ID, ISCMRepository, ISCMService } from 'vs/workbench/contrib/scm/common/scm'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { StatusUpdater, StatusBarController } from './scmActivity'; -import { SCMViewlet } from 'vs/workbench/parts/scm/electron-browser/scmViewlet'; +import { SCMViewlet } from 'vs/workbench/contrib/scm/browser/scmViewlet'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ContextKeyDefinedExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { ISCMRepository } from 'vs/workbench/services/scm/common/scm'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { SCMService } from 'vs/workbench/contrib/scm/common/scmService'; class OpenSCMViewletAction extends ShowViewletAction { static readonly ID = VIEWLET_ID; static LABEL = localize('toggleGitViewlet', "Show Git"); - constructor(id: string, label: string, @IViewletService viewletService: IViewletService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IPartService partService: IPartService) { - super(id, label, VIEWLET_ID, viewletService, editorGroupService, partService); + constructor(id: string, label: string, @IViewletService viewletService: IViewletService, @IEditorGroupsService editorGroupService: IEditorGroupsService, @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService) { + super(id, label, VIEWLET_ID, viewletService, editorGroupService, layoutService); } } Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(DirtyDiffWorkbenchController, LifecyclePhase.Restored); -const viewletDescriptor = new ViewletDescriptor( +Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( SCMViewlet, VIEWLET_ID, localize('source control', "Source Control"), 'scm', // {{SQL CARBON EDIT}} 12 -); - -Registry.as(ViewletExtensions.Viewlets) - .registerViewlet(viewletDescriptor); +)); Registry.as(WorkbenchExtensions.Workbench) .registerWorkbenchContribution(StatusUpdater, LifecyclePhase.Restored); @@ -78,6 +76,11 @@ Registry.as(ConfigurationExtensions.Configuration).regis description: localize('alwaysShowProviders', "Controls whether to always show the Source Control Provider section."), default: false }, + 'scm.providers.visible': { + type: 'number', + description: localize('providersVisible', "Controls how many providers are visible in the Source Control Provider section. Set to `0` to be able to manually resize the view."), + default: 10 + }, 'scm.diffDecorations': { type: 'string', enum: ['all', 'gutter', 'overview', 'none'], @@ -129,6 +132,8 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const args = repository.provider.acceptInputCommand.arguments; const commandService = accessor.get(ICommandService); - return commandService.executeCommand(id, ...args); + return commandService.executeCommand(id, ...(args || [])); } -}); \ No newline at end of file +}); + +registerSingleton(ISCMService, SCMService); diff --git a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts b/src/vs/workbench/contrib/scm/browser/scmActivity.ts similarity index 96% rename from src/vs/workbench/parts/scm/electron-browser/scmActivity.ts rename to src/vs/workbench/contrib/scm/browser/scmActivity.ts index 3d74ed6ec5..89b988b193 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmActivity.ts +++ b/src/vs/workbench/contrib/scm/browser/scmActivity.ts @@ -4,11 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { basename } from 'vs/base/common/paths'; +import { basename } from 'vs/base/common/resources'; import { IDisposable, dispose, Disposable, combinedDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; -import { VIEWLET_ID } from 'vs/workbench/parts/scm/common/scm'; -import { ISCMService, ISCMRepository } from 'vs/workbench/services/scm/common/scm'; +import { VIEWLET_ID, ISCMService, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -185,7 +184,7 @@ export class StatusBarController implements IWorkbenchContribution { const commands = repository.provider.statusBarCommands || []; const label = repository.provider.rootUri - ? `${basename(repository.provider.rootUri.fsPath)} (${repository.provider.label})` + ? `${basename(repository.provider.rootUri)} (${repository.provider.label})` : repository.provider.label; const disposables = commands.map(c => this.statusbarService.addEntry({ diff --git a/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts b/src/vs/workbench/contrib/scm/browser/scmMenus.ts similarity index 99% rename from src/vs/workbench/parts/scm/electron-browser/scmMenus.ts rename to src/vs/workbench/contrib/scm/browser/scmMenus.ts index a253b5353b..38a7358d85 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts +++ b/src/vs/workbench/contrib/scm/browser/scmMenus.ts @@ -10,7 +10,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; import { IAction } from 'vs/base/common/actions'; import { fillInContextMenuActions, fillInActionBarActions } from 'vs/platform/actions/browser/menuItemActionItem'; -import { ISCMProvider, ISCMResource, ISCMResourceGroup } from 'vs/workbench/services/scm/common/scm'; +import { ISCMProvider, ISCMResource, ISCMResourceGroup } from 'vs/workbench/contrib/scm/common/scm'; import { isSCMResource } from './scmUtil'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { equals } from 'vs/base/common/arrays'; diff --git a/src/vs/workbench/parts/scm/electron-browser/scmUtil.ts b/src/vs/workbench/contrib/scm/browser/scmUtil.ts similarity index 84% rename from src/vs/workbench/parts/scm/electron-browser/scmUtil.ts rename to src/vs/workbench/contrib/scm/browser/scmUtil.ts index a3be0dbe0e..121a06aa91 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmUtil.ts +++ b/src/vs/workbench/contrib/scm/browser/scmUtil.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ISCMResourceGroup, ISCMResource } from 'vs/workbench/services/scm/common/scm'; +import { ISCMResourceGroup, ISCMResource } from 'vs/workbench/contrib/scm/common/scm'; export function isSCMResource(element: ISCMResourceGroup | ISCMResource): element is ISCMResource { return !!(element as ISCMResource).sourceUri; diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts similarity index 62% rename from src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts rename to src/vs/workbench/contrib/scm/browser/scmViewlet.ts index 07f5abd48a..20eb47e0c4 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewlet.ts @@ -6,18 +6,17 @@ import 'vs/css!./media/scmViewlet'; import { localize } from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; -import { domEvent, stop } from 'vs/base/browser/event'; -import { basename } from 'vs/base/common/paths'; -import { IDisposable, dispose, combinedDisposable, Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { PanelViewlet, ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; -import { append, $, addClass, toggleClass, trackFocus, Dimension, addDisposableListener, removeClass } from 'vs/base/browser/dom'; +import { domEvent } from 'vs/base/browser/event'; +import { basename } from 'vs/base/common/resources'; +import { IDisposable, dispose, combinedDisposable, Disposable } from 'vs/base/common/lifecycle'; +import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { append, $, addClass, toggleClass, trackFocus, removeClass, addClasses } from 'vs/base/browser/dom'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { List } from 'vs/base/browser/ui/list/listWidget'; import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent, IKeyboardNavigationLabelProvider, IIdentityProvider } from 'vs/base/browser/ui/list/list'; -import { VIEWLET_ID, VIEW_CONTAINER } from 'vs/workbench/parts/scm/common/scm'; +import { VIEWLET_ID, ISCMService, ISCMRepository, ISCMResourceGroup, ISCMResource, InputValidationType, VIEW_CONTAINER } from 'vs/workbench/contrib/scm/common/scm'; import { ResourceLabels, IResourceLabel, IResourceLabelsContainer } from 'vs/workbench/browser/labels'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; -import { ISCMService, ISCMRepository, ISCMResourceGroup, ISCMResource, InputValidationType } from 'vs/workbench/services/scm/common/scm'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; @@ -28,7 +27,7 @@ import { MenuItemAction, IMenuService, MenuId, IMenu } from 'vs/platform/actions import { IAction, Action, IActionItem, ActionRunner } from 'vs/base/common/actions'; import { fillInContextMenuActions, ContextAwareMenuItemActionItem, fillInActionBarActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { SCMMenus } from './scmMenus'; -import { ActionBar, IActionItemProvider, Separator, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, IActionItemProvider, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; import { isSCMResource } from './scmUtil'; import { attachBadgeStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; @@ -36,7 +35,6 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { Command } from 'vs/editor/common/modes'; import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; -import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { format } from 'vs/base/common/strings'; import { ISpliceable, ISequence, ISplice } from 'vs/base/common/sequence'; import { firstIndex, equals } from 'vs/base/common/arrays'; @@ -44,11 +42,13 @@ import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ThrottledDelayer } from 'vs/base/common/async'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; -import { IViewDescriptorRef, PersistentContributableViewsModel, IAddedViewDescriptorRef } from 'vs/workbench/browser/parts/views/views'; -import { IViewDescriptor, IViewsViewlet, IView } from 'vs/workbench/common/views'; -import { IPanelDndController, Panel } from 'vs/base/browser/ui/splitview/panelview'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import * as platform from 'vs/base/common/platform'; +import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IViewsRegistry, IViewDescriptor, Extensions } from 'vs/workbench/common/views'; +import { Registry } from 'vs/platform/registry/common/platform'; export interface ISpliceEvent { index: number; @@ -58,13 +58,14 @@ export interface ISpliceEvent { export interface IViewModel { readonly repositories: ISCMRepository[]; - readonly selectedRepositories: ISCMRepository[]; readonly onDidSplice: Event>; + readonly visibleRepositories: ISCMRepository[]; + readonly onDidChangeVisibleRepositories: Event; + setVisibleRepositories(repositories: ISCMRepository[]): void; + isVisible(): boolean; readonly onDidChangeVisibility: Event; - - hide(repository: ISCMRepository): void; } class ProvidersListDelegate implements IListVirtualDelegate { @@ -85,11 +86,11 @@ class StatusBarAction extends Action { private commandService: ICommandService ) { super(`statusbaraction{${command.id}}`, command.title, '', true); - this.tooltip = command.tooltip; + this.tooltip = command.tooltip || ''; } run(): Promise { - return this.commandService.executeCommand(this.command.id, ...this.command.arguments); + return this.commandService.executeCommand(this.command.id, ...(this.command.arguments || [])); } } @@ -171,20 +172,13 @@ class ProviderRenderer implements IListRenderer this.statusbarService.addEntry({ - // text: c.title, - // tooltip: `${repository.provider.label} - ${c.tooltip}`, - // command: c.id, - // arguments: c.arguments - // }, MainThreadStatusBarAlignment.LEFT, 10000)); - const actions: IAction[] = []; const disposeActions = () => dispose(actions); disposables.push({ dispose: disposeActions }); @@ -199,7 +193,7 @@ class ProviderRenderer implements IListRenderer; - private visibilityDisposables: IDisposable[] = []; - - private previousSelection: ISCMRepository[] | undefined = undefined; - private _onSelectionChange = new Emitter(); - readonly onSelectionChange: Event = this._onSelectionChange.event; constructor( protected viewModel: IViewModel, + options: IViewletPanelOptions, @IKeybindingService protected keybindingService: IKeybindingService, @IContextMenuService protected contextMenuService: IContextMenuService, @ISCMService protected scmService: ISCMService, @@ -235,29 +228,7 @@ class MainPanel extends ViewletPanel { @IMenuService private readonly menuService: IMenuService, @IConfigurationService configurationService: IConfigurationService ) { - super({ id: 'scm.mainPanel', title: localize('scm providers', "Source Control Providers") }, keybindingService, contextMenuService, configurationService); - this.updateBodySize(); - } - - focus(): void { - super.focus(); - this.list.domFocus(); - } - - hide(repository: ISCMRepository): void { - const selectedElements = this.list.getSelectedElements(); - const index = selectedElements.indexOf(repository); - - if (index === -1) { - return; - } - - const selection = this.list.getSelection(); - this.list.setSelection([...selection.slice(0, index), ...selection.slice(index + 1)]); - } - - getSelection(): ISCMRepository[] { - return this.list.getSelectedElements(); + super(options, keybindingService, contextMenuService, configurationService); } protected renderBody(container: HTMLElement): void { @@ -265,38 +236,39 @@ class MainPanel extends ViewletPanel { const renderer = this.instantiationService.createInstance(ProviderRenderer); const identityProvider = { getId: r => r.provider.id }; - this.list = this.instantiationService.createInstance(WorkbenchList, container, delegate, [renderer], { identityProvider }) as WorkbenchList; + this.list = this.instantiationService.createInstance(WorkbenchList, container, delegate, [renderer], { + identityProvider, + horizontalScrolling: false + }) as WorkbenchList; renderer.onDidRenderElement(e => this.list.updateWidth(this.viewModel.repositories.indexOf(e)), null, this.disposables); this.list.onSelectionChange(this.onListSelectionChange, this, this.disposables); + this.list.onFocusChange(this.onListFocusChange, this, this.disposables); this.list.onContextMenu(this.onListContextMenu, this, this.disposables); - this.viewModel.onDidChangeVisibility(this.onDidChangeVisibility, this, this.disposables); - this.onDidChangeVisibility(this.viewModel.isVisible()); + this.viewModel.onDidChangeVisibleRepositories(this.updateListSelection, this, this.disposables); + + this.viewModel.onDidSplice(({ index, deleteCount, elements }) => this.splice(index, deleteCount, elements), null, this.disposables); + this.splice(0, 0, this.viewModel.repositories); this.disposables.push(this.list); - } - private onDidChangeVisibility(visible: boolean): void { - if (visible) { - this.viewModel.onDidSplice(({ index, deleteCount, elements }) => this.splice(index, deleteCount, elements), null, this.visibilityDisposables); - this.splice(0, 0, this.viewModel.repositories); - } else { - this.visibilityDisposables = dispose(this.visibilityDisposables); - this.splice(0, this.list.length); - } + this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('scm.providers.visible')) { + this.updateBodySize(); + } + }, this.disposables); + + this.updateListSelection(); } private splice(index: number, deleteCount: number, repositories: ISCMRepository[] = []): void { - const wasEmpty = this.list.length === 0; - this.list.splice(index, deleteCount, repositories); - this.updateBodySize(); - // Automatically select the first one - if (wasEmpty && this.list.length > 0) { - this.restoreSelection(); - } + const empty = this.list.length === 0; + toggleClass(this.element, 'empty', empty); + + this.updateBodySize(); } protected layoutBody(height: number, width: number): void { @@ -304,16 +276,12 @@ class MainPanel extends ViewletPanel { } private updateBodySize(): void { - const count = this.viewModel.repositories.length; + const visibleCount = this.configurationService.getValue('scm.providers.visible'); + const empty = this.list.length === 0; + const size = Math.min(this.viewModel.repositories.length, visibleCount) * 22; - if (count <= 5) { - const size = count * 22; - this.minimumBodySize = size; - this.maximumBodySize = size; - } else { - this.minimumBodySize = 5 * 22; - this.maximumBodySize = Number.POSITIVE_INFINITY; - } + this.minimumBodySize = visibleCount === 0 ? 22 : size; + this.maximumBodySize = visibleCount === 0 ? Number.POSITIVE_INFINITY : empty ? Number.POSITIVE_INFINITY : size; } private onListContextMenu(e: IListContextMenuEvent): void { @@ -348,39 +316,39 @@ class MainPanel extends ViewletPanel { } private onListSelectionChange(e: IListEvent): void { - // select one repository if the selected one is gone - if (e.elements.length === 0 && this.list.length > 0) { - this.restoreSelection(); - return; + if (e.elements.length > 0 && e.browserEvent) { + const scrollTop = this.list.scrollTop; + this.viewModel.setVisibleRepositories(e.elements); + this.list.scrollTop = scrollTop; } - - if (e.elements.length > 0) { - this.previousSelection = e.elements; - } - - this._onSelectionChange.fire(e.elements); } - private restoreSelection(): void { - let selection: number[]; + private onListFocusChange(e: IListEvent): void { + if (e.elements.length > 0) { + e.elements[0].focus(); + } + } - if (this.previousSelection) { - selection = this.previousSelection - .map(r => this.viewModel.repositories.indexOf(r)) - .filter(i => i > -1); + private updateListSelection(): void { + const set = new Set(); + + for (const repository of this.viewModel.visibleRepositories) { + set.add(repository); } - if (!selection || selection.length === 0) { - selection = [0]; + const selection: number[] = []; + + for (let i = 0; i < this.list.length; i++) { + if (set.has(this.list.element(i))) { + selection.push(i); + } } this.list.setSelection(selection); - this.list.setFocus([selection[0]]); - } - dispose(): void { - this.visibilityDisposables = dispose(this.visibilityDisposables); - super.dispose(); + if (selection.length > 0) { + this.list.setFocus([selection[0]]); + } } } @@ -530,13 +498,13 @@ class ResourceRenderer implements IListRenderer if (icon) { template.decorationIcon.style.display = ''; template.decorationIcon.style.backgroundImage = `url('${icon}')`; - template.decorationIcon.title = resource.decorations.tooltip; + template.decorationIcon.title = resource.decorations.tooltip || ''; } else { template.decorationIcon.style.display = 'none'; template.decorationIcon.style.backgroundImage = ''; } - template.element.setAttribute('data-tooltip', resource.decorations.tooltip); + template.element.setAttribute('data-tooltip', resource.decorations.tooltip || ''); template.elementDisposable = combinedDisposable(disposables); } @@ -575,7 +543,7 @@ const scmResourceIdentityProvider = new class implements IIdentityProvider { getKeyboardNavigationLabel(e: ISCMResourceGroup | ISCMResource) { if (isSCMResource(e)) { - return basename(e.sourceUri.fsPath); + return basename(e.sourceUri); } else { return e.label; } @@ -741,9 +709,9 @@ export class RepositoryPanel extends ViewletPanel { protected contextKeyService: IContextKeyService; constructor( - id: string, readonly repository: ISCMRepository, - private viewModel: IViewModel, + private readonly viewModel: IViewModel, + options: IViewletPanelOptions, @IKeybindingService protected keybindingService: IKeybindingService, @IThemeService protected themeService: IThemeService, @IContextMenuService protected contextMenuService: IContextMenuService, @@ -756,12 +724,14 @@ export class RepositoryPanel extends ViewletPanel { @IContextKeyService contextKeyService: IContextKeyService, @IMenuService protected menuService: IMenuService ) { - super({ id, title: repository.provider.label }, keybindingService, contextMenuService, configurationService); - this.menus = instantiationService.createInstance(SCMMenus, repository.provider); + super(options, keybindingService, contextMenuService, configurationService); + + this.menus = instantiationService.createInstance(SCMMenus, this.repository.provider); + this.disposables.push(this.menus); this.menus.onDidChangeTitle(this._onDidChangeTitleArea.fire, this._onDidChangeTitleArea, this.disposables); this.contextKeyService = contextKeyService.createScoped(this.element); - this.contextKeyService.createKey('scmRepository', repository); + this.contextKeyService.createKey('scmRepository', this.repository); } render(): void { @@ -774,7 +744,7 @@ export class RepositoryPanel extends ViewletPanel { let type: string; if (this.repository.provider.rootUri) { - title = basename(this.repository.provider.rootUri.fsPath); + title = basename(this.repository.provider.rootUri); type = this.repository.provider.label; } else { title = this.repository.provider.label; @@ -783,25 +753,7 @@ export class RepositoryPanel extends ViewletPanel { super.renderHeaderTitle(container, title); addClass(container, 'scm-provider'); - append(container, $('span.type', null, type)); - const onContextMenu = Event.map(stop(domEvent(container, 'contextmenu')), e => new StandardMouseEvent(e)); - onContextMenu(this.onContextMenu, this, this.disposables); - } - - private onContextMenu(event: StandardMouseEvent): void { - if (this.viewModel.selectedRepositories.length <= 1) { - return; - } - - this.contextMenuService.showContextMenu({ - getAnchor: () => ({ x: event.posx, y: event.posy }), - getActions: () => [{ - id: `scm.hideRepository`, - label: localize('hideRepository', "Hide"), - enabled: true, - run: () => this.viewModel.hide(this.repository) - }], - }); + append(container, $('span.type', undefined, type)); } protected renderBody(container: HTMLElement): void { @@ -822,7 +774,7 @@ export class RepositoryPanel extends ViewletPanel { const validationDelayer = new ThrottledDelayer(200); const validate = () => { - return this.repository.input.validateInput(this.inputBox.value, this.inputBox.inputElement.selectionStart).then(result => { + return this.repository.input.validateInput(this.inputBox.value, this.inputBox.inputElement.selectionStart || 0).then(result => { if (!result) { this.inputBox.inputElement.removeAttribute('aria-invalid'); this.inputBox.hideMessage(); @@ -887,7 +839,8 @@ export class RepositoryPanel extends ViewletPanel { this.list = this.instantiationService.createInstance(WorkbenchList, this.listContainer, delegate, renderers, { identityProvider: scmResourceIdentityProvider, - keyboardNavigationLabelProvider: scmKeyboardNavigationLabelProvider + keyboardNavigationLabelProvider: scmKeyboardNavigationLabelProvider, + horizontalScrolling: false }) as WorkbenchList; Event.chain(this.list.onDidOpen) @@ -917,7 +870,7 @@ export class RepositoryPanel extends ViewletPanel { } } - layoutBody(height: number = this.cachedHeight, width: number = this.cachedWidth): void { + layoutBody(height: number | undefined = this.cachedHeight, width: number | undefined = this.cachedWidth): void { if (height === undefined) { return; } @@ -952,6 +905,8 @@ export class RepositoryPanel extends ViewletPanel { } else { this.list.domFocus(); } + + this.repository.focus(); } } @@ -963,9 +918,9 @@ export class RepositoryPanel extends ViewletPanel { return this.menus.getTitleSecondaryActions(); } - getActionItem(action: IAction): IActionItem { + getActionItem(action: IAction): IActionItem | null { if (!(action instanceof MenuItemAction)) { - return undefined; + return null; } return new ContextAwareMenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService); @@ -1033,29 +988,53 @@ export class RepositoryPanel extends ViewletPanel { } } -class SCMPanelDndController implements IPanelDndController { +class RepositoryViewDescriptor implements IViewDescriptor { - canDrag(panel: Panel): boolean { - return !(panel instanceof MainPanel) && !(panel instanceof RepositoryPanel); - } + private static counter = 0; - canDrop(panel: Panel, overPanel: Panel): boolean { - return !(overPanel instanceof MainPanel) && !(overPanel instanceof RepositoryPanel); + readonly id: string; + readonly name: string; + readonly ctorDescriptor: { ctor: any, arguments?: any[] }; + readonly canToggleVisibility = true; + readonly order = -500; + readonly workspace = true; + + constructor(readonly repository: ISCMRepository, viewModel: IViewModel, readonly hideByDefault: boolean) { + const repoId = repository.provider.rootUri ? repository.provider.rootUri.toString() : `#${RepositoryViewDescriptor.counter++}`; + this.id = `scm:repository:${repository.provider.label}:${repoId}`; + this.name = repository.provider.rootUri ? basename(repository.provider.rootUri) : repository.provider.label; + + this.ctorDescriptor = { ctor: RepositoryPanel, arguments: [repository, viewModel] }; } } -export class SCMViewlet extends PanelViewlet implements IViewModel, IViewsViewlet { +class MainPanelDescriptor implements IViewDescriptor { + readonly id = MainPanel.ID; + readonly name = MainPanel.TITLE; + readonly ctorDescriptor: { ctor: any, arguments?: any[] }; + readonly canToggleVisibility = true; + readonly hideByDefault = true; + readonly order = -1000; + readonly workspace = true; + + constructor(viewModel: IViewModel) { + this.ctorDescriptor = { ctor: MainPanel, arguments: [viewModel] }; + } +} + +export class SCMViewlet extends ViewContainerViewlet implements IViewModel { + + private static readonly STATE_KEY = 'workbench.scm.views.state'; + + private repositoryCount = 0; private el: HTMLElement; + private message: HTMLElement; private menus: SCMMenus; - private mainPanel: MainPanel | null = null; - private cachedMainPanelHeight: number | undefined; - private mainPanelDisposable: IDisposable = Disposable.None; private _repositories: ISCMRepository[] = []; - private repositoryPanels: RepositoryPanel[] = []; - private singlePanelTitleActionsDisposable: IDisposable = Disposable.None; - private disposables: IDisposable[] = []; - private lastFocusedRepository: ISCMRepository | undefined; + + private mainPanelDescriptor = new MainPanelDescriptor(this); + private viewDescriptors: RepositoryViewDescriptor[] = []; private _onDidSplice = new Emitter>(); readonly onDidSplice: Event> = this._onDidSplice.event; @@ -1063,77 +1042,103 @@ export class SCMViewlet extends PanelViewlet implements IViewModel, IViewsViewle private _height: number | undefined = undefined; get height(): number | undefined { return this._height; } - get repositories(): ISCMRepository[] { return this._repositories; } - get selectedRepositories(): ISCMRepository[] { return this.repositoryPanels.map(p => p.repository); } + get repositories(): ISCMRepository[] { + return this._repositories; + } - private contributedViews: PersistentContributableViewsModel; - private contributedViewDisposables: IDisposable[] = []; + get visibleRepositories(): ISCMRepository[] { + return this.panels.filter(panel => panel instanceof RepositoryPanel) + .map(panel => (panel as RepositoryPanel).repository); + } + + get onDidChangeVisibleRepositories(): Event { + const modificationEvent = Event.debounce(Event.any(this.viewsModel.onDidAdd, this.viewsModel.onDidRemove), () => null, 0); + return Event.map(modificationEvent, () => this.visibleRepositories); + } + + setVisibleRepositories(repositories: ISCMRepository[]): void { + const visibleViewDescriptors = this.viewsModel.visibleViewDescriptors; + + const toSetVisible = this.viewsModel.viewDescriptors + .filter(d => d instanceof RepositoryViewDescriptor && repositories.indexOf(d.repository) > -1 && visibleViewDescriptors.indexOf(d) === -1); + + const toSetInvisible = visibleViewDescriptors + .filter(d => d instanceof RepositoryViewDescriptor && repositories.indexOf(d.repository) === -1); + + let size: number | undefined; + const oneToOne = toSetVisible.length === 1 && toSetInvisible.length === 1; + + for (const viewDescriptor of toSetInvisible) { + if (oneToOne) { + const panel = this.panels.filter(panel => panel.id === viewDescriptor.id)[0]; + + if (panel) { + size = this.getPanelSize(panel); + } + } + + this.viewsModel.setVisible(viewDescriptor.id, false); + } + + for (const viewDescriptor of toSetVisible) { + this.viewsModel.setVisible(viewDescriptor.id, true, size); + } + } constructor( - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITelemetryService telemetryService: ITelemetryService, @ISCMService protected scmService: ISCMService, @IInstantiationService protected instantiationService: IInstantiationService, @IContextViewService protected contextViewService: IContextViewService, - @IContextKeyService contextKeyService: IContextKeyService, @IKeybindingService protected keybindingService: IKeybindingService, @INotificationService protected notificationService: INotificationService, @IContextMenuService protected contextMenuService: IContextMenuService, @IThemeService protected themeService: IThemeService, @ICommandService protected commandService: ICommandService, @IStorageService storageService: IStorageService, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @IExtensionService extensionService: IExtensionService, + @IWorkspaceContextService protected contextService: IWorkspaceContextService, ) { - super(VIEWLET_ID, { showHeaderInTitleWhenSingleView: true, dnd: new SCMPanelDndController() }, configurationService, partService, contextMenuService, telemetryService, themeService, storageService); + super(VIEWLET_ID, SCMViewlet.STATE_KEY, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); this.menus = instantiationService.createInstance(SCMMenus, undefined); - this.menus.onDidChangeTitle(this.updateTitleArea, this, this.disposables); + this.menus.onDidChangeTitle(this.updateTitleArea, this, this.toDispose); - this.contributedViews = instantiationService.createInstance(PersistentContributableViewsModel, VIEW_CONTAINER, 'scm.views'); - this.disposables.push(this.contributedViews); + this.message = $('.empty-message', { tabIndex: 0 }, localize('no open repo', "No source control providers registered.")); + + configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('scm.alwaysShowProviders')) { + this.onDidChangeRepositories(); + } + }, this.toDispose); } create(parent: HTMLElement): void { super.create(parent); + this.el = parent; - addClass(this.el, 'scm-viewlet'); - addClass(this.el, 'empty'); - append(parent, $('div.empty-message', null, localize('no open repo', "No source control providers registered."))); + addClasses(parent, 'scm-viewlet', 'empty'); + append(parent, this.message); - this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); - this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); + this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.toDispose); + this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.toDispose); this.scmService.repositories.forEach(r => this.onDidAddRepository(r)); - - const onDidUpdateConfiguration = Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.alwaysShowProviders')); - onDidUpdateConfiguration(this.onDidChangeRepositories, this, this.disposables); - - this.onDidChangeRepositories(); - - this.contributedViews.onDidAdd(this.onDidAddContributedViews, this, this.disposables); - this.contributedViews.onDidRemove(this.onDidRemoveContributedViews, this, this.disposables); - - let index = this.getContributedViewsStartIndex(); - const contributedViews: IAddedViewDescriptorRef[] = this.contributedViews.visibleViewDescriptors.map(viewDescriptor => { - const size = this.contributedViews.getSize(viewDescriptor.id); - const collapsed = this.contributedViews.isCollapsed(viewDescriptor.id); - return { viewDescriptor, index: index++, size, collapsed }; - }); - if (contributedViews.length) { - this.onDidAddContributedViews(contributedViews); - } - - this.onDidSashChange(this.saveContributedViewSizes, this, this.disposables); } private onDidAddRepository(repository: ISCMRepository): void { const index = this._repositories.length; this._repositories.push(repository); - this._onDidSplice.fire({ index, deleteCount: 0, elements: [repository] }); - this.onDidChangeRepositories(); - if (!this.mainPanel) { - this.onSelectionChange(this.repositories); - } + const viewDescriptor = new RepositoryViewDescriptor(repository, this, false); + Registry.as(Extensions.ViewsRegistry).registerViews([viewDescriptor], VIEW_CONTAINER); + this.viewDescriptors.push(viewDescriptor); + + this._onDidSplice.fire({ index, deleteCount: 0, elements: [repository] }); + this.updateTitleArea(); + + this.onDidChangeRepositories(); } private onDidRemoveRepository(repository: ISCMRepository): void { @@ -1143,80 +1148,64 @@ export class SCMViewlet extends PanelViewlet implements IViewModel, IViewsViewle return; } - this._repositories.splice(index, 1); - this._onDidSplice.fire({ index, deleteCount: 1, elements: [] }); - this.onDidChangeRepositories(); + Registry.as(Extensions.ViewsRegistry).deregisterViews([this.viewDescriptors[index]], VIEW_CONTAINER); - if (!this.mainPanel) { - this.onSelectionChange(this.repositories); - } + this._repositories.splice(index, 1); + this.viewDescriptors.splice(index, 1); + + this._onDidSplice.fire({ index, deleteCount: 1, elements: [] }); + this.updateTitleArea(); + + this.onDidChangeRepositories(); } private onDidChangeRepositories(): void { - toggleClass(this.el, 'empty', this.scmService.repositories.length === 0); + const repositoryCount = this.repositories.length; - if (this.scmService.repositories.length === 0) { - this.el.tabIndex = 0; - } else { - this.el.removeAttribute('tabIndex'); + const viewsRegistry = Registry.as(Extensions.ViewsRegistry); + if (this.repositoryCount === 0 && repositoryCount !== 0) { + viewsRegistry.registerViews([this.mainPanelDescriptor], VIEW_CONTAINER); + } else if (this.repositoryCount !== 0 && repositoryCount === 0) { + viewsRegistry.deregisterViews([this.mainPanelDescriptor], VIEW_CONTAINER); } - const shouldMainPanelAlwaysBeVisible = this.configurationService.getValue('scm.alwaysShowProviders'); - const shouldMainPanelBeVisible = shouldMainPanelAlwaysBeVisible || this.scmService.repositories.length > 1; + const alwaysShowProviders = this.configurationService.getValue('scm.alwaysShowProviders') || false; - if (!!this.mainPanel === shouldMainPanelBeVisible) { - return; + if (alwaysShowProviders && repositoryCount > 0) { + this.viewsModel.setVisible(MainPanel.ID, true); + } else if (!alwaysShowProviders && repositoryCount === 1) { + this.viewsModel.setVisible(MainPanel.ID, false); + } else if (this.repositoryCount < 2 && repositoryCount >= 2) { + this.viewsModel.setVisible(MainPanel.ID, true); + } else if (this.repositoryCount >= 2 && repositoryCount === 1) { + this.viewsModel.setVisible(MainPanel.ID, false); } - if (shouldMainPanelBeVisible) { - this.mainPanel = this.instantiationService.createInstance(MainPanel, this); - this.mainPanel.render(); - this.addPanels([{ panel: this.mainPanel, size: this.mainPanel.minimumSize, index: 0 }]); - - const selectionChangeDisposable = this.mainPanel.onSelectionChange(this.onSelectionChange, this); - this.onSelectionChange(this.mainPanel.getSelection()); - - this.mainPanelDisposable = toDisposable(() => { - this.removePanels([this.mainPanel]); - selectionChangeDisposable.dispose(); - this.mainPanel.dispose(); - }); - } else { - this.mainPanelDisposable.dispose(); - this.mainPanelDisposable = Disposable.None; - this.mainPanel = null; - } - } - - private getContributedViewsStartIndex(): number { - return (this.mainPanel ? 1 : 0) + this.repositoryPanels.length; + toggleClass(this.el, 'empty', repositoryCount === 0); + this.repositoryCount = repositoryCount; } focus(): void { - if (this.scmService.repositories.length === 0) { - this.el.focus(); + if (this.repositoryCount === 0) { + this.message.focus(); } else { - super.focus(); + const repository = this.visibleRepositories[0]; + + if (repository) { + const panel = this.panels + .filter(panel => panel instanceof RepositoryPanel && panel.repository === repository)[0] as RepositoryPanel | undefined; + + if (panel) { + panel.focus(); + } else { + super.focus(); + } + } else { + super.focus(); + } } } - setVisible(visible: boolean): void { - super.setVisible(visible); - - if (!visible) { - this.cachedMainPanelHeight = this.getPanelSize(this.mainPanel); - } - - const start = this.getContributedViewsStartIndex(); - - for (let i = 0; i < this.contributedViews.visibleViewDescriptors.length; i++) { - const panel = this.panels[start + i] as ViewletPanel; - panel.setVisible(visible); - } - - this.repositoryPanels.forEach(panel => panel.setVisible(visible)); - } - getOptimalWidth(): number { return 400; } @@ -1224,7 +1213,7 @@ export class SCMViewlet extends PanelViewlet implements IViewModel, IViewsViewle getTitle(): string { const title = localize('source control', "Source Control"); - if (this.repositories.length === 1) { + if (this.visibleRepositories.length === 1) { const [repository] = this.repositories; return localize('viewletTitle', "{0}: {1}", title, repository.provider.label); } else { @@ -1232,282 +1221,33 @@ export class SCMViewlet extends PanelViewlet implements IViewModel, IViewsViewle } } + getActionItem(action: IAction): IActionItem | null { + if (!(action instanceof MenuItemAction)) { + return null; + } + + return new ContextAwareMenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService); + } + getActions(): IAction[] { - if (this.isSingleView()) { - return this.panels[0].getActions(); + if (this.repositories.length > 0) { + return super.getActions(); } return this.menus.getTitleActions(); } getSecondaryActions(): IAction[] { - if (this.isSingleView()) { - return this.panels[0].getSecondaryActions(); - } else { - return this.menus.getTitleSecondaryActions(); + if (this.repositories.length > 0) { + return super.getSecondaryActions(); } + + return this.menus.getTitleSecondaryActions(); } - getActionItem(action: IAction): IActionItem { - if (!(action instanceof MenuItemAction)) { - return undefined; + getActionsContext(): any { + if (this.visibleRepositories.length === 1) { + return this.repositories[0].provider; } - - return new ContextAwareMenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService); - } - - private didLayout = false; - layout(dimension: Dimension): void { - super.layout(dimension); - this._height = dimension.height; - - if (this.didLayout) { - // this.saveViewSizes(); - } else { - this.didLayout = true; - this.restoreContributedViewSizes(); - } - } - - movePanel(from: ViewletPanel, to: ViewletPanel): void { - const start = this.getContributedViewsStartIndex(); - const fromIndex = firstIndex(this.panels, panel => panel === from) - start; - const toIndex = firstIndex(this.panels, panel => panel === to) - start; - const fromViewDescriptor = this.contributedViews.viewDescriptors[fromIndex]; - const toViewDescriptor = this.contributedViews.viewDescriptors[toIndex]; - - super.movePanel(from, to); - this.contributedViews.move(fromViewDescriptor.id, toViewDescriptor.id); - } - - private onSelectionChange(repositories: ISCMRepository[]): void { - const wasSingleView = this.isSingleView(); - const contributableViewsHeight = this.getContributableViewsSize(); - - // Collect unselected panels - const panelsToRemove = this.repositoryPanels - .filter(p => repositories.every(r => p.repository !== r)); - - // Collect panels still selected - const repositoryPanels = this.repositoryPanels - .filter(p => repositories.some(r => p.repository === r)); - - // Collect new selected panels - const newRepositoryPanels = repositories - .filter(r => this.repositoryPanels.every(p => p.repository !== r)) - .map((r, index) => { - const panel = this.instantiationService.createInstance(RepositoryPanel, `scm.repository.${r.provider.label}.${index}`, r, this); - panel.render(); - panel.setVisible(true); - return panel; - }); - - // Add new selected panels - let index = repositoryPanels.length + (this.mainPanel ? 1 : 0); - this.repositoryPanels = [...repositoryPanels, ...newRepositoryPanels]; - newRepositoryPanels.forEach(panel => { - this.addPanels([{ panel, size: panel.minimumSize, index: index++ }]); - panel.repository.focus(); - panel.onDidFocus(() => this.lastFocusedRepository = panel.repository); - - if (this.lastFocusedRepository === panel.repository) { - panel.focus(); - } - }); - - // Remove unselected panels - this.removePanels(panelsToRemove); - - // Restore main panel height - if (this.isVisible() && typeof this.cachedMainPanelHeight === 'number') { - this.resizePanel(this.mainPanel, this.cachedMainPanelHeight); - this.cachedMainPanelHeight = undefined; - } - - // Resize all panels equally - const height = typeof this.height === 'number' ? this.height : 1000; - const mainPanelHeight = this.getPanelSize(this.mainPanel); - const size = (height - mainPanelHeight - contributableViewsHeight) / repositories.length; - for (const panel of this.repositoryPanels) { - this.resizePanel(panel, size); - } - - // Resize contributed view sizes - this.restoreContributedViewSizes(); - - // React to menu changes for single view mode - if (wasSingleView !== this.isSingleView()) { - this.singlePanelTitleActionsDisposable.dispose(); - - if (this.isSingleView()) { - this.singlePanelTitleActionsDisposable = this.panels[0].onDidChangeTitleArea(this.updateTitleArea, this); - } - - this.updateTitleArea(); - } - - if (this.isVisible()) { - panelsToRemove.forEach(p => p.repository.setSelected(false)); - newRepositoryPanels.forEach(p => p.repository.setSelected(true)); - } - } - - private getContributableViewsSize(): number { - let value = 0; - - for (let i = this.getContributedViewsStartIndex(); i < this.length; i++) { - value += this.getPanelSize(this.panels[i]); - } - - return value; - } - - onDidAddContributedViews(added: IAddedViewDescriptorRef[]): void { - const start = this.getContributedViewsStartIndex(); - const panelsToAdd: { panel: ViewletPanel, size: number, index: number }[] = []; - - for (const { viewDescriptor, collapsed, index, size } of added) { - const panel = this.instantiationService.createInstance(viewDescriptor.ctor, { - id: viewDescriptor.id, - title: viewDescriptor.name, - actionRunner: this.getActionRunner(), - expanded: !collapsed - }) as ViewletPanel; - panel.render(); - panel.setVisible(true); - const contextMenuDisposable = addDisposableListener(panel.draggableElement, 'contextmenu', e => { - e.stopPropagation(); - e.preventDefault(); - this.onViewHeaderContextMenu(new StandardMouseEvent(e), viewDescriptor); - }); - - const collapseDisposable = Event.latch(Event.map(panel.onDidChange, () => !panel.isExpanded()))(collapsed => { - this.contributedViews.setCollapsed(viewDescriptor.id, collapsed); - }); - - this.contributedViewDisposables.splice(index, 0, combinedDisposable([contextMenuDisposable, collapseDisposable])); - panelsToAdd.push({ panel, size: size || panel.minimumSize, index: start + index }); - } - - this.addPanels(panelsToAdd); - } - - private onViewHeaderContextMenu(event: StandardMouseEvent, viewDescriptor: IViewDescriptor): void { - const actions: IAction[] = []; - actions.push({ - id: `${viewDescriptor.id}.removeView`, - label: localize('hideView', "Hide"), - enabled: viewDescriptor.canToggleVisibility, - run: () => this.contributedViews.setVisible(viewDescriptor.id, !this.contributedViews.isVisible(viewDescriptor.id)) - }); - - const otherActions = this.getContextMenuActions(); - if (otherActions.length) { - actions.push(...[new Separator(), ...otherActions]); - } - - let anchor: { x: number, y: number } = { x: event.posx, y: event.posy }; - this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, - getActions: () => actions - }); - } - - getContextMenuActions(): IAction[] { - const result: IAction[] = []; - const viewToggleActions = this.contributedViews.viewDescriptors.map(viewDescriptor => ({ - id: `${viewDescriptor.id}.toggleVisibility`, - label: viewDescriptor.name, - checked: this.contributedViews.isVisible(viewDescriptor.id), - enabled: viewDescriptor.canToggleVisibility, - run: () => this.contributedViews.setVisible(viewDescriptor.id, !this.contributedViews.isVisible(viewDescriptor.id)) - })); - - result.push(...viewToggleActions); - const parentActions = super.getContextMenuActions(); - if (viewToggleActions.length && parentActions.length) { - result.push(new Separator()); - } - result.push(...parentActions); - return result; - } - - onDidRemoveContributedViews(removed: IViewDescriptorRef[]): void { - removed = removed.sort((a, b) => b.index - a.index); - const start = this.getContributedViewsStartIndex(); - const panelsToRemove: ViewletPanel[] = []; - - for (const { index } of removed) { - const [disposable] = this.contributedViewDisposables.splice(index, 1); - disposable.dispose(); - panelsToRemove.push(this.panels[start + index]); - } - - this.removePanels(panelsToRemove); - dispose(panelsToRemove); - } - - private saveContributedViewSizes(): void { - const start = this.getContributedViewsStartIndex(); - - for (let i = 0; i < this.contributedViews.viewDescriptors.length; i++) { - const viewDescriptor = this.contributedViews.viewDescriptors[i]; - const size = this.getPanelSize(this.panels[start + i]); - - this.contributedViews.setSize(viewDescriptor.id, size); - } - } - - private restoreContributedViewSizes(): void { - if (!this.didLayout) { - return; - } - - const start = this.getContributedViewsStartIndex(); - - for (let i = 0; i < this.contributedViews.viewDescriptors.length; i++) { - const panel = this.panels[start + i]; - const viewDescriptor = this.contributedViews.viewDescriptors[i]; - const size = this.contributedViews.getSize(viewDescriptor.id); - - if (typeof size === 'number') { - this.resizePanel(panel, size); - } - } - } - - protected isSingleView(): boolean { - return super.isSingleView() && this.repositoryPanels.length + this.contributedViews.visibleViewDescriptors.length === 1; - } - - openView(id: string, focus?: boolean): IView { - if (focus) { - this.focus(); - } - let panel = this.panels.filter(panel => panel instanceof ViewletPanel && panel.id === id)[0]; - if (!panel) { - this.contributedViews.setVisible(id, true); - } - panel = this.panels.filter(panel => panel instanceof ViewletPanel && panel.id === id)[0]; - panel.setExpanded(true); - if (focus) { - panel.focus(); - } - return panel; - } - - hide(repository: ISCMRepository): void { - if (!this.mainPanel) { - return; - } - - this.mainPanel.hide(repository); - } - - dispose(): void { - this.disposables = dispose(this.disposables); - this.contributedViewDisposables = dispose(this.contributedViewDisposables); - this.mainPanelDisposable.dispose(); - super.dispose(); } } diff --git a/src/vs/workbench/services/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts similarity index 88% rename from src/vs/workbench/services/scm/common/scm.ts rename to src/vs/workbench/contrib/scm/common/scm.ts index 3eaf8cf1cd..a12473d3dc 100644 --- a/src/vs/workbench/services/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -3,6 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { Registry } from 'vs/platform/registry/common/platform'; +import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; @@ -11,6 +13,9 @@ import { Command } from 'vs/editor/common/modes'; import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { ISequence } from 'vs/base/common/sequence'; +export const VIEWLET_ID = 'workbench.view.scm'; +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); + export interface IBaselineResourceProvider { getBaselineResource(resource: URI): Promise; } @@ -63,7 +68,7 @@ export interface ISCMProvider extends IDisposable { readonly statusBarCommands?: Command[]; readonly onDidChange: Event; - getOriginalResource(uri: URI): Promise; + getOriginalResource(uri: URI): Promise; } export const enum InputValidationType { diff --git a/src/vs/workbench/services/scm/common/scmService.ts b/src/vs/workbench/contrib/scm/common/scmService.ts similarity index 100% rename from src/vs/workbench/services/scm/common/scmService.ts rename to src/vs/workbench/contrib/scm/common/scmService.ts diff --git a/src/vs/workbench/parts/search/browser/media/CollapseAll.svg b/src/vs/workbench/contrib/search/browser/media/CollapseAll.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/CollapseAll.svg rename to src/vs/workbench/contrib/search/browser/media/CollapseAll.svg diff --git a/src/vs/workbench/parts/search/browser/media/CollapseAll_inverse.svg b/src/vs/workbench/contrib/search/browser/media/CollapseAll_inverse.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/CollapseAll_inverse.svg rename to src/vs/workbench/contrib/search/browser/media/CollapseAll_inverse.svg diff --git a/src/vs/workbench/parts/search/browser/media/Refresh.svg b/src/vs/workbench/contrib/search/browser/media/Refresh.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/Refresh.svg rename to src/vs/workbench/contrib/search/browser/media/Refresh.svg diff --git a/src/vs/workbench/parts/search/browser/media/Refresh_inverse.svg b/src/vs/workbench/contrib/search/browser/media/Refresh_inverse.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/Refresh_inverse.svg rename to src/vs/workbench/contrib/search/browser/media/Refresh_inverse.svg diff --git a/src/vs/workbench/parts/search/browser/media/action-remove-dark.svg b/src/vs/workbench/contrib/search/browser/media/action-remove-dark.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/action-remove-dark.svg rename to src/vs/workbench/contrib/search/browser/media/action-remove-dark.svg diff --git a/src/vs/workbench/parts/search/browser/media/action-remove.svg b/src/vs/workbench/contrib/search/browser/media/action-remove.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/action-remove.svg rename to src/vs/workbench/contrib/search/browser/media/action-remove.svg diff --git a/src/vs/workbench/parts/search/browser/media/clear-search-results-dark.svg b/src/vs/workbench/contrib/search/browser/media/clear-search-results-dark.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/clear-search-results-dark.svg rename to src/vs/workbench/contrib/search/browser/media/clear-search-results-dark.svg diff --git a/src/vs/workbench/parts/search/browser/media/clear-search-results.svg b/src/vs/workbench/contrib/search/browser/media/clear-search-results.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/clear-search-results.svg rename to src/vs/workbench/contrib/search/browser/media/clear-search-results.svg diff --git a/src/vs/workbench/parts/search/browser/media/ellipsis-inverse.svg b/src/vs/workbench/contrib/search/browser/media/ellipsis-inverse.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/ellipsis-inverse.svg rename to src/vs/workbench/contrib/search/browser/media/ellipsis-inverse.svg diff --git a/src/vs/workbench/parts/search/browser/media/ellipsis.svg b/src/vs/workbench/contrib/search/browser/media/ellipsis.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/ellipsis.svg rename to src/vs/workbench/contrib/search/browser/media/ellipsis.svg diff --git a/src/vs/workbench/parts/search/browser/media/excludeSettings-dark.svg b/src/vs/workbench/contrib/search/browser/media/excludeSettings-dark.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/excludeSettings-dark.svg rename to src/vs/workbench/contrib/search/browser/media/excludeSettings-dark.svg diff --git a/src/vs/workbench/parts/search/browser/media/excludeSettings.svg b/src/vs/workbench/contrib/search/browser/media/excludeSettings.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/excludeSettings.svg rename to src/vs/workbench/contrib/search/browser/media/excludeSettings.svg diff --git a/src/vs/workbench/parts/search/browser/media/expando-collapsed-dark.svg b/src/vs/workbench/contrib/search/browser/media/expando-collapsed-dark.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/expando-collapsed-dark.svg rename to src/vs/workbench/contrib/search/browser/media/expando-collapsed-dark.svg diff --git a/src/vs/workbench/parts/search/browser/media/expando-collapsed.svg b/src/vs/workbench/contrib/search/browser/media/expando-collapsed.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/expando-collapsed.svg rename to src/vs/workbench/contrib/search/browser/media/expando-collapsed.svg diff --git a/src/vs/workbench/parts/search/browser/media/expando-expanded-dark.svg b/src/vs/workbench/contrib/search/browser/media/expando-expanded-dark.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/expando-expanded-dark.svg rename to src/vs/workbench/contrib/search/browser/media/expando-expanded-dark.svg diff --git a/src/vs/workbench/parts/search/browser/media/expando-expanded.svg b/src/vs/workbench/contrib/search/browser/media/expando-expanded.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/expando-expanded.svg rename to src/vs/workbench/contrib/search/browser/media/expando-expanded.svg diff --git a/src/vs/workbench/parts/search/browser/media/replace-all-inverse.svg b/src/vs/workbench/contrib/search/browser/media/replace-all-inverse.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/replace-all-inverse.svg rename to src/vs/workbench/contrib/search/browser/media/replace-all-inverse.svg diff --git a/src/vs/workbench/parts/search/browser/media/replace-all.svg b/src/vs/workbench/contrib/search/browser/media/replace-all.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/replace-all.svg rename to src/vs/workbench/contrib/search/browser/media/replace-all.svg diff --git a/src/vs/workbench/parts/search/browser/media/replace-inverse.svg b/src/vs/workbench/contrib/search/browser/media/replace-inverse.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/replace-inverse.svg rename to src/vs/workbench/contrib/search/browser/media/replace-inverse.svg diff --git a/src/vs/workbench/parts/search/browser/media/replace.svg b/src/vs/workbench/contrib/search/browser/media/replace.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/replace.svg rename to src/vs/workbench/contrib/search/browser/media/replace.svg diff --git a/src/vs/workbench/parts/search/electron-browser/media/search-dark.svg b/src/vs/workbench/contrib/search/browser/media/search-dark.svg similarity index 100% rename from src/vs/workbench/parts/search/electron-browser/media/search-dark.svg rename to src/vs/workbench/contrib/search/browser/media/search-dark.svg diff --git a/src/vs/workbench/parts/search/electron-browser/media/search.contribution.css b/src/vs/workbench/contrib/search/browser/media/search.contribution.css similarity index 100% rename from src/vs/workbench/parts/search/electron-browser/media/search.contribution.css rename to src/vs/workbench/contrib/search/browser/media/search.contribution.css diff --git a/src/vs/workbench/parts/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css similarity index 99% rename from src/vs/workbench/parts/search/browser/media/searchview.css rename to src/vs/workbench/contrib/search/browser/media/searchview.css index bb375a9e77..19c83963a0 100644 --- a/src/vs/workbench/parts/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -51,6 +51,7 @@ .search-view .search-widget .monaco-findInput { display: inline-block; vertical-align: middle; + width: 100%; } .search-view .search-widget .replace-container { @@ -182,6 +183,7 @@ .search-view .linematch > .match { overflow: hidden; text-overflow: ellipsis; + white-space: pre; } .search-view .linematch .matchLineNum { diff --git a/src/vs/workbench/parts/search/browser/media/stop-inverse.svg b/src/vs/workbench/contrib/search/browser/media/stop-inverse.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/stop-inverse.svg rename to src/vs/workbench/contrib/search/browser/media/stop-inverse.svg diff --git a/src/vs/workbench/parts/search/browser/media/stop.svg b/src/vs/workbench/contrib/search/browser/media/stop.svg similarity index 100% rename from src/vs/workbench/parts/search/browser/media/stop.svg rename to src/vs/workbench/contrib/search/browser/media/stop.svg diff --git a/src/vs/workbench/parts/search/browser/openAnythingHandler.ts b/src/vs/workbench/contrib/search/browser/openAnythingHandler.ts similarity index 94% rename from src/vs/workbench/parts/search/browser/openAnythingHandler.ts rename to src/vs/workbench/contrib/search/browser/openAnythingHandler.ts index 412385b9fb..18612dd172 100644 --- a/src/vs/workbench/parts/search/browser/openAnythingHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openAnythingHandler.ts @@ -10,11 +10,11 @@ import * as types from 'vs/base/common/types'; import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; import { QuickOpenEntry, QuickOpenModel, QuickOpenItemAccessor } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; -import { FileEntry, OpenFileHandler, FileQuickOpenModel } from 'vs/workbench/parts/search/browser/openFileHandler'; -import * as openSymbolHandler from 'vs/workbench/parts/search/browser/openSymbolHandler'; +import { FileEntry, OpenFileHandler, FileQuickOpenModel } from 'vs/workbench/contrib/search/browser/openFileHandler'; +import * as openSymbolHandler from 'vs/workbench/contrib/search/browser/openSymbolHandler'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkbenchSearchConfiguration } from 'vs/workbench/parts/search/common/search'; +import { IWorkbenchSearchConfiguration } from 'vs/workbench/contrib/search/common/search'; import { IRange } from 'vs/editor/common/core/range'; import { compareItemsByScore, scoreItem, ScorerCache, prepareQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -120,7 +120,7 @@ export class OpenAnythingHandler extends QuickOpenHandler { } // Combine results. - const mergedResults: QuickOpenEntry[] = [].concat(...results.map(r => r.entries)); + const mergedResults: QuickOpenEntry[] = ([] as QuickOpenEntry[]).concat(...results.map(r => r.entries)); // Sort const compare = (elementA: QuickOpenEntry, elementB: QuickOpenEntry) => compareItemsByScore(elementA, elementB, query, true, QuickOpenItemAccessor, this.scorerCache); @@ -132,7 +132,7 @@ export class OpenAnythingHandler extends QuickOpenHandler { entry.setRange(searchWithRange ? searchWithRange.range : null); const itemScore = scoreItem(entry, query, true, QuickOpenItemAccessor, this.scorerCache); - entry.setHighlights(itemScore.labelMatch, itemScore.descriptionMatch); + entry.setHighlights(itemScore.labelMatch || [], itemScore.descriptionMatch); } }); @@ -165,7 +165,7 @@ export class OpenAnythingHandler extends QuickOpenHandler { return this.openFileHandler.hasShortResponseTime() && this.openSymbolHandler.hasShortResponseTime(); } - private extractRange(value: string): ISearchWithRange { + private extractRange(value: string): ISearchWithRange | null { if (!value) { return null; } @@ -211,7 +211,7 @@ export class OpenAnythingHandler extends QuickOpenHandler { } } - if (range) { + if (patternMatch && range) { return { search: value.substr(0, patternMatch.index), // clear range suffix from search value range: range diff --git a/src/vs/workbench/parts/search/browser/openFileHandler.ts b/src/vs/workbench/contrib/search/browser/openFileHandler.ts similarity index 93% rename from src/vs/workbench/parts/search/browser/openFileHandler.ts rename to src/vs/workbench/contrib/search/browser/openFileHandler.ts index 75a9168067..6ce11dff93 100644 --- a/src/vs/workbench/parts/search/browser/openFileHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openFileHandler.ts @@ -5,11 +5,11 @@ import * as errors from 'vs/base/common/errors'; import * as nls from 'vs/nls'; -import * as paths from 'vs/base/common/paths'; +import { isAbsolute } from 'vs/base/common/path'; import * as objects from 'vs/base/common/objects'; import { defaultGenerator } from 'vs/base/common/idGenerator'; import { URI } from 'vs/base/common/uri'; -import * as resources from 'vs/base/common/resources'; +import { basename, dirname } from 'vs/base/common/resources'; import { IIconLabelValueOptions } from 'vs/base/browser/ui/iconLabel/iconLabel'; import { IModeService } from 'vs/editor/common/services/modeService'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; @@ -18,16 +18,16 @@ import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/work import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen'; import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { QuickOpenHandler, EditorQuickOpenEntry } from 'vs/workbench/browser/quickopen'; -import { QueryBuilder, IFileQueryBuilderOptions } from 'vs/workbench/parts/search/common/queryBuilder'; +import { QueryBuilder, IFileQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; import { EditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ISearchService, IFileSearchStats, IFileQuery, ISearchComplete } from 'vs/platform/search/common/search'; +import { ISearchService, IFileSearchStats, IFileQuery, ISearchComplete } from 'vs/workbench/services/search/common/search'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IRange } from 'vs/editor/common/core/range'; -import { getOutOfWorkspaceEditorResources } from 'vs/workbench/parts/search/common/search'; +import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { prepareQuery, IPreparedQuery } from 'vs/base/parts/quickopen/common/quickOpenScorer'; import { IFileService } from 'vs/platform/files/common/files'; @@ -43,7 +43,7 @@ export class FileQuickOpenModel extends QuickOpenModel { } export class FileEntry extends EditorQuickOpenEntry { - private range: IRange; + private range: IRange | null; constructor( private resource: URI, @@ -85,7 +85,7 @@ export class FileEntry extends EditorQuickOpenEntry { return this.resource; } - setRange(range: IRange): void { + setRange(range: IRange | null): void { this.range = range; } @@ -97,14 +97,11 @@ export class FileEntry extends EditorQuickOpenEntry { const input: IResourceInput = { resource: this.resource, options: { - pinned: !this.configurationService.getValue().workbench.editor.enablePreviewFromQuickOpen + pinned: !this.configurationService.getValue().workbench.editor.enablePreviewFromQuickOpen, + selection: this.range ? this.range : undefined } }; - if (this.range) { - input.options.selection = this.range; - } - return input; } } @@ -177,8 +174,8 @@ export class OpenFileHandler extends QuickOpenHandler { if (!token.isCancellationRequested) { for (const fileMatch of complete.results) { - const label = paths.basename(fileMatch.resource.fsPath); - const description = this.labelService.getUriLabel(resources.dirname(fileMatch.resource), { relative: true }); + const label = basename(fileMatch.resource); + const description = this.labelService.getUriLabel(dirname(fileMatch.resource), { relative: true }); results.push(this.instantiationService.createInstance(FileEntry, fileMatch.resource, label, description, iconClass)); } @@ -188,14 +185,14 @@ export class OpenFileHandler extends QuickOpenHandler { }); } - private getAbsolutePathResult(query: IPreparedQuery): Promise { - if (paths.isAbsolute(query.original)) { + private getAbsolutePathResult(query: IPreparedQuery): Promise { + if (isAbsolute(query.original)) { const resource = URI.file(query.original); return this.fileService.resolveFile(resource).then(stat => stat.isDirectory ? undefined : resource, error => undefined); } - return Promise.resolve(null); + return Promise.resolve(undefined); } private doResolveQueryOptions(query: IPreparedQuery, cacheKey?: string, maxSortedResults?: number): IFileQueryBuilderOptions { @@ -273,7 +270,7 @@ export class CacheState { private loadingPhase = LoadingPhase.Created; private promise: Promise; - constructor(cacheQuery: (cacheKey: string) => IFileQuery, private doLoad: (query: IFileQuery) => Promise, private doDispose: (cacheKey: string) => Promise, private previous: CacheState) { + constructor(cacheQuery: (cacheKey: string) => IFileQuery, private doLoad: (query: IFileQuery) => Promise, private doDispose: (cacheKey: string) => Promise, private previous: CacheState | null) { this.query = cacheQuery(this._cacheKey); if (this.previous) { const current = objects.assign({}, this.query, { cacheKey: null }); diff --git a/src/vs/workbench/parts/search/browser/openSymbolHandler.ts b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts similarity index 95% rename from src/vs/workbench/parts/search/browser/openSymbolHandler.ts rename to src/vs/workbench/contrib/search/browser/openSymbolHandler.ts index d7d89d3525..86496ea8cc 100644 --- a/src/vs/workbench/parts/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/contrib/search/browser/openSymbolHandler.ts @@ -18,8 +18,8 @@ import { symbolKindToCssClass } from 'vs/editor/common/modes'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkspaceSymbolProvider, getWorkspaceSymbols, IWorkspaceSymbol } from 'vs/workbench/parts/search/common/search'; -import { basename } from 'vs/base/common/paths'; +import { IWorkspaceSymbolProvider, getWorkspaceSymbols, IWorkspaceSymbol } from 'vs/workbench/contrib/search/common/search'; +import { basename } from 'vs/base/common/resources'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ILabelService } from 'vs/platform/label/common/label'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -27,7 +27,7 @@ import { Schemas } from 'vs/base/common/network'; import { IOpenerService } from 'vs/platform/opener/common/opener'; class SymbolEntry extends EditorQuickOpenEntry { - private bearingResolve: Promise; + private bearingResolve: Promise; constructor( private bearing: IWorkspaceSymbol, @@ -48,17 +48,17 @@ class SymbolEntry extends EditorQuickOpenEntry { return nls.localize('entryAriaLabel', "{0}, symbols picker", this.getLabel()); } - getDescription(): string { + getDescription(): string | null { const containerName = this.bearing.containerName; if (this.bearing.location.uri) { if (containerName) { - return `${containerName} — ${basename(this.bearing.location.uri.fsPath)}`; + return `${containerName} — ${basename(this.bearing.location.uri)}`; } return this.labelService.getUriLabel(this.bearing.location.uri, { relative: true }); } - return containerName; + return containerName || null; } getIcon(): string { @@ -105,7 +105,7 @@ class SymbolEntry extends EditorQuickOpenEntry { }; if (this.bearing.location.range) { - input.options.selection = Range.collapseToStart(this.bearing.location.range); + input.options!.selection = Range.collapseToStart(this.bearing.location.range); } return input; @@ -206,7 +206,7 @@ export class OpenSymbolHandler extends QuickOpenHandler { } const entry = this.instantiationService.createInstance(SymbolEntry, element, provider); - entry.setHighlights(filters.matchesFuzzy2(searchValue, entry.getLabel())); + entry.setHighlights(filters.matchesFuzzy2(searchValue, entry.getLabel()) || []); bucket.push(entry); } } diff --git a/src/vs/workbench/parts/search/browser/patternInputWidget.ts b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts similarity index 97% rename from src/vs/workbench/parts/search/browser/patternInputWidget.ts rename to src/vs/workbench/contrib/search/browser/patternInputWidget.ts index 807ce35390..ab4e731437 100644 --- a/src/vs/workbench/parts/search/browser/patternInputWidget.ts +++ b/src/vs/workbench/contrib/search/browser/patternInputWidget.ts @@ -14,7 +14,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { Event as CommonEvent, Emitter } from 'vs/base/common/event'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachInputBoxStyler, attachCheckboxStyler } from 'vs/platform/theme/common/styler'; -import { ContextScopedHistoryInputBox } from 'vs/platform/widget/browser/contextScopedHistoryWidget'; +import { ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export interface IOptions { @@ -36,7 +36,7 @@ export class PatternInputWidget extends Widget { private ariaLabel: string; private domNode: HTMLElement; - inputBox: HistoryInputBox; + protected inputBox: HistoryInputBox; private _onSubmit = this._register(new Emitter()); onSubmit: CommonEvent = this._onSubmit.event; diff --git a/src/vs/workbench/parts/search/browser/replaceContributions.ts b/src/vs/workbench/contrib/search/browser/replaceContributions.ts similarity index 88% rename from src/vs/workbench/parts/search/browser/replaceContributions.ts rename to src/vs/workbench/contrib/search/browser/replaceContributions.ts index 0925848221..bcbb154a60 100644 --- a/src/vs/workbench/parts/search/browser/replaceContributions.ts +++ b/src/vs/workbench/contrib/search/browser/replaceContributions.ts @@ -3,8 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IReplaceService } from 'vs/workbench/parts/search/common/replace'; -import { ReplaceService, ReplacePreviewContentProvider } from 'vs/workbench/parts/search/browser/replaceService'; +import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; +import { ReplaceService, ReplacePreviewContentProvider } from 'vs/workbench/contrib/search/browser/replaceService'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; diff --git a/src/vs/workbench/parts/search/browser/replaceService.ts b/src/vs/workbench/contrib/search/browser/replaceService.ts similarity index 98% rename from src/vs/workbench/parts/search/browser/replaceService.ts rename to src/vs/workbench/contrib/search/browser/replaceService.ts index fe73f9d005..0de14a73e9 100644 --- a/src/vs/workbench/parts/search/browser/replaceService.ts +++ b/src/vs/workbench/contrib/search/browser/replaceService.ts @@ -8,11 +8,11 @@ import * as errors from 'vs/base/common/errors'; import { URI } from 'vs/base/common/uri'; import * as network from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IReplaceService } from 'vs/workbench/parts/search/common/replace'; +import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { Match, FileMatch, FileMatchOrMatch, ISearchWorkbenchService } from 'vs/workbench/parts/search/common/searchModel'; +import { Match, FileMatch, FileMatchOrMatch, ISearchWorkbenchService } from 'vs/workbench/contrib/search/common/searchModel'; import { IProgressRunner } from 'vs/platform/progress/common/progress'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; diff --git a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts b/src/vs/workbench/contrib/search/browser/search.contribution.ts similarity index 87% rename from src/vs/workbench/parts/search/electron-browser/search.contribution.ts rename to src/vs/workbench/contrib/search/browser/search.contribution.ts index c026ca0016..c8f8e0bc47 100644 --- a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts +++ b/src/vs/workbench/contrib/search/browser/search.contribution.ts @@ -3,7 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { relative } from 'path'; +import 'vs/css!./media/search.contribution'; + import { Action } from 'vs/base/common/actions'; import { distinct } from 'vs/base/common/arrays'; import { illegalArgument } from 'vs/base/common/errors'; @@ -13,7 +14,6 @@ import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { dirname } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import 'vs/css!./media/search.contribution'; import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { getSelectionSearchString } from 'vs/editor/contrib/find/findController'; @@ -32,30 +32,36 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IListService, WorkbenchListFocusContextKey, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ISearchConfigurationProperties, VIEW_ID, ISearchConfiguration } from 'vs/platform/search/common/search'; +import { ISearchConfigurationProperties, ISearchConfiguration, VIEWLET_ID, PANEL_ID, VIEW_ID, VIEW_CONTAINER } from 'vs/workbench/services/search/common/search'; import { defaultQuickOpenContextKey } from 'vs/workbench/browser/parts/quickopen/quickopen'; import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ResourceContextKey } from 'vs/workbench/common/resources'; -import { getMultiSelectedResources } from 'vs/workbench/parts/files/browser/files'; -import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition } from 'vs/workbench/parts/files/common/files'; -import { OpenAnythingHandler } from 'vs/workbench/parts/search/browser/openAnythingHandler'; -import { OpenSymbolHandler } from 'vs/workbench/parts/search/browser/openSymbolHandler'; -import { registerContributions as replaceContributions } from 'vs/workbench/parts/search/browser/replaceContributions'; -import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FindInFilesAction, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand } from 'vs/workbench/parts/search/browser/searchActions'; -import { registerContributions as searchWidgetContributions } from 'vs/workbench/parts/search/browser/searchWidget'; -import * as Constants from 'vs/workbench/parts/search/common/constants'; -import { getWorkspaceSymbols } from 'vs/workbench/parts/search/common/search'; -import { FileMatchOrMatch, ISearchWorkbenchService, RenderableMatch, SearchWorkbenchService } from 'vs/workbench/parts/search/common/searchModel'; +import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; +import { ExplorerFolderContext, ExplorerRootContext, FilesExplorerFocusCondition } from 'vs/workbench/contrib/files/common/files'; +import { OpenAnythingHandler } from 'vs/workbench/contrib/search/browser/openAnythingHandler'; +import { OpenSymbolHandler } from 'vs/workbench/contrib/search/browser/openSymbolHandler'; +import { registerContributions as replaceContributions } from 'vs/workbench/contrib/search/browser/replaceContributions'; +import { clearHistoryCommand, ClearSearchResultsAction, CloseReplaceAction, CollapseDeepestExpandedLevelAction, copyAllCommand, copyMatchCommand, copyPathCommand, FindInFilesAction, FocusNextInputAction, FocusNextSearchResultAction, FocusPreviousInputAction, FocusPreviousSearchResultAction, focusSearchListCommand, getSearchView, openSearchView, OpenSearchViewletAction, RefreshAction, RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction, ReplaceInFilesAction, toggleCaseSensitiveCommand, toggleRegexCommand, toggleWholeWordCommand } from 'vs/workbench/contrib/search/browser/searchActions'; +import { registerContributions as searchWidgetContributions } from 'vs/workbench/contrib/search/browser/searchWidget'; +import * as Constants from 'vs/workbench/contrib/search/common/constants'; +import { getWorkspaceSymbols } from 'vs/workbench/contrib/search/common/search'; +import { FileMatchOrMatch, ISearchWorkbenchService, RenderableMatch, SearchWorkbenchService } from 'vs/workbench/contrib/search/common/searchModel'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel'; import { ViewletDescriptor, ViewletRegistry, Extensions as ViewletExtensions } from 'vs/workbench/browser/viewlet'; -import { SearchView } from 'vs/workbench/parts/search/browser/searchView'; +import { ISearchHistoryService, SearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; +import { SearchViewlet } from 'vs/workbench/contrib/search/browser/searchViewlet'; +import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; +import { IViewsRegistry, Extensions as ViewExtensions } from 'vs/workbench/common/views'; +import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true); +registerSingleton(ISearchHistoryService, SearchHistoryService, true); + replaceContributions(); searchWidgetContributions(); @@ -81,7 +87,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.CtrlCmd | KeyCode.UpArrow, handler: (accessor, args: any) => { const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); - searchView.focusPreviousInputBox(); + if (searchView) { + searchView.focusPreviousInputBox(); + } } }); @@ -95,8 +103,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }, handler: (accessor, args: any) => { const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); - const tree: WorkbenchObjectTree = searchView.getControl(); - searchView.open(tree.getFocus()[0], false, true, true); + if (searchView) { + const tree: WorkbenchObjectTree = searchView.getControl(); + searchView.open(tree.getFocus()[0], false, true, true); + } } }); @@ -107,7 +117,9 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyCode.Escape, handler: (accessor, args: any) => { const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); - searchView.cancelSearch(); + if (searchView) { + searchView.cancelSearch(); + } } }); @@ -121,8 +133,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ }, handler: (accessor, args: any) => { const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); - const tree: WorkbenchObjectTree = searchView.getControl(); - accessor.get(IInstantiationService).createInstance(RemoveAction, searchView, tree, tree.getFocus()[0]).run(); + if (searchView) { + const tree: WorkbenchObjectTree = searchView.getControl(); + accessor.get(IInstantiationService).createInstance(RemoveAction, tree, tree.getFocus()[0]).run(); + } } }); @@ -133,8 +147,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.KEY_1, handler: (accessor, args: any) => { const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); - const tree: WorkbenchObjectTree = searchView.getControl(); - accessor.get(IInstantiationService).createInstance(ReplaceAction, tree, tree.getFocus()[0], searchView).run(); + if (searchView) { + const tree: WorkbenchObjectTree = searchView.getControl(); + accessor.get(IInstantiationService).createInstance(ReplaceAction, tree, tree.getFocus()[0], searchView).run(); + } } }); @@ -146,8 +162,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter], handler: (accessor, args: any) => { const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); - const tree: WorkbenchObjectTree = searchView.getControl(); - accessor.get(IInstantiationService).createInstance(ReplaceAllAction, searchView, tree.getFocus()[0]).run(); + if (searchView) { + const tree: WorkbenchObjectTree = searchView.getControl(); + accessor.get(IInstantiationService).createInstance(ReplaceAllAction, searchView, tree.getFocus()[0]).run(); + } } }); @@ -159,8 +177,10 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.Enter], handler: (accessor, args: any) => { const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); - const tree: WorkbenchObjectTree = searchView.getControl(); - accessor.get(IInstantiationService).createInstance(ReplaceAllInFolderAction, tree, tree.getFocus()[0]).run(); + if (searchView) { + const tree: WorkbenchObjectTree = searchView.getControl(); + accessor.get(IInstantiationService).createInstance(ReplaceAllInFolderAction, tree, tree.getFocus()[0]).run(); + } } }); @@ -344,9 +364,10 @@ const searchInFolderCommand: ICommandHandler = (accessor, resource?: URI) => { const viewletService = accessor.get(IViewletService); const panelService = accessor.get(IPanelService); const fileService = accessor.get(IFileService); + const configurationService = accessor.get(IConfigurationService); const resources = getMultiSelectedResources(resource, listService, accessor.get(IEditorService)); - return openSearchView(viewletService, panelService, true).then(searchView => { + return openSearchView(viewletService, panelService, configurationService, true).then(searchView => { if (resources && resources.length) { return fileService.resolveFiles(resources.map(resource => ({ resource }))).then(results => { const folders: URI[] = []; @@ -357,7 +378,7 @@ const searchInFolderCommand: ICommandHandler = (accessor, resource?: URI) => { } }); - searchView.searchInFolders(distinct(folders, folder => folder.toString()), (from, to) => relative(from, to)); + searchView.searchInFolders(distinct(folders, folder => folder.toString())); }); } @@ -392,8 +413,8 @@ const FIND_IN_WORKSPACE_ID = 'filesExplorer.findInWorkspace'; CommandsRegistry.registerCommand({ id: FIND_IN_WORKSPACE_ID, handler: (accessor) => { - return openSearchView(accessor.get(IViewletService), accessor.get(IPanelService), true).then(searchView => { - searchView.searchInFolders(null, (from, to) => relative(from, to)); + return openSearchView(accessor.get(IViewletService), accessor.get(IPanelService), accessor.get(IConfigurationService), true).then(searchView => { + searchView.searchInFolders(null); }); } }); @@ -449,6 +470,14 @@ class ShowAllSymbolsAction extends Action { } } +Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( + SearchViewlet, + VIEWLET_ID, + nls.localize('name', "Search"), + 'search', + 1 +)); + class RegisterSearchViewContribution implements IWorkbenchContribution { constructor( @@ -456,32 +485,26 @@ class RegisterSearchViewContribution implements IWorkbenchContribution { @IPanelService panelService: IPanelService, @IConfigurationService configurationService: IConfigurationService ) { + const viewsRegistry = Registry.as(ViewExtensions.ViewsRegistry); const updateSearchViewLocation = (open: boolean) => { const config = configurationService.getValue(); if (config.search.location === 'panel') { - Registry.as(ViewletExtensions.Viewlets).deregisterViewlet(VIEW_ID); + viewsRegistry.deregisterViews(viewsRegistry.getViews(VIEW_CONTAINER), VIEW_CONTAINER); Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescriptor( - SearchView, - VIEW_ID, + SearchPanel, + PANEL_ID, nls.localize('name', "Search"), 'search', 10 )); if (open) { - panelService.openPanel(VIEW_ID); + panelService.openPanel(PANEL_ID); } } else { - Registry.as(PanelExtensions.Panels).deregisterPanel(VIEW_ID); - Registry.as(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor( - SearchView, - VIEW_ID, - nls.localize('name', "Search"), - 'search', - // {{SQL CARBON EDIT}} - Change from 1 to 11 - 11 - )); + Registry.as(PanelExtensions.Panels).deregisterPanel(PANEL_ID); + viewsRegistry.registerViews([{ id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: { ctor: SearchView }, canToggleVisibility: false }], VIEW_CONTAINER); if (open) { - viewletService.openViewlet(VIEW_ID); + viewletService.openViewlet(VIEWLET_ID); } } }; @@ -501,7 +524,7 @@ const registry = Registry.as(ActionExtensions.Workbenc // Show Search and Find in Files are redundant, but we can't break keybindings by removing one. So it's the same action, same keybinding, registered to different IDs. // Show Search 'when' is redundant but if the two conflict with exactly the same keybinding and 'when' clause, then they can show up as "unbound" - #51780 -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSearchViewletAction, VIEW_ID, OpenSearchViewletAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_F }, Constants.SearchViewVisibleKey.toNegated()), 'View: Show Search', nls.localize('view', "View")); +registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSearchViewletAction, VIEWLET_ID, OpenSearchViewletAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_F }, Constants.SearchViewVisibleKey.toNegated()), 'View: Show Search', nls.localize('view', "View")); registry.registerWorkbenchAction(new SyncActionDescriptor(FindInFilesAction, Constants.FindInFilesActionId, nls.localize('findInFiles', "Find in Files"), { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_F }), 'Find in Files', category); MenuRegistry.appendMenuItem(MenuId.MenubarEditMenu, { group: '4_find_global', @@ -619,6 +642,11 @@ configurationRegistry.registerConfiguration({ deprecationMessage: nls.localize('useRipgrepDeprecated', "Deprecated. Consider \"search.usePCRE2\" for advanced regex feature support."), default: true }, + 'search.maintainFileSearchCache': { + type: 'boolean', + description: nls.localize('search.maintainFileSearchCache', "When enabled, the searchService process will be kept alive instead of being shut down after an hour of inactivity. This will keep the file search cache in memory."), + default: false + }, 'search.useIgnoreFiles': { type: 'boolean', markdownDescription: nls.localize('useIgnoreFiles', "Controls whether to use `.gitignore` and `.ignore` files when searching for files."), @@ -726,7 +754,7 @@ registerLanguageCommand('_executeWorkspaceSymbolProvider', function (accessor, a MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { group: '3_views', command: { - id: VIEW_ID, + id: VIEWLET_ID, title: nls.localize({ key: 'miViewSearch', comment: ['&& denotes a mnemonic'] }, "&&Search") }, order: 2 diff --git a/src/vs/workbench/parts/search/browser/searchActions.ts b/src/vs/workbench/contrib/search/browser/searchActions.ts similarity index 79% rename from src/vs/workbench/parts/search/browser/searchActions.ts rename to src/vs/workbench/contrib/search/browser/searchActions.ts index b5fde198a9..086b47f878 100644 --- a/src/vs/workbench/parts/search/browser/searchActions.ts +++ b/src/vs/workbench/contrib/search/browser/searchActions.ts @@ -9,7 +9,7 @@ import { INavigator } from 'vs/base/common/iterator'; import { createKeybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { Schemas } from 'vs/base/common/network'; -import { normalize } from 'vs/base/common/paths'; +import { normalize } from 'vs/base/common/path'; import { isWindows, OS } from 'vs/base/common/platform'; import { repeat } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; @@ -17,74 +17,86 @@ import * as nls from 'vs/nls'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ICommandHandler } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; -import { ISearchConfiguration, ISearchHistoryService, VIEW_ID } from 'vs/platform/search/common/search'; -import { SearchView } from 'vs/workbench/parts/search/browser/searchView'; -import * as Constants from 'vs/workbench/parts/search/common/constants'; -import { IReplaceService } from 'vs/workbench/parts/search/common/replace'; -import { FileMatch, FileMatchOrMatch, FolderMatch, Match, RenderableMatch, searchMatchComparer, SearchResult } from 'vs/workbench/parts/search/common/searchModel'; +import { getSelectionKeyboardEvent, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; +import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; +import * as Constants from 'vs/workbench/contrib/search/common/constants'; +import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; +import { BaseFolderMatch, FileMatch, FileMatchOrMatch, FolderMatch, Match, RenderableMatch, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { ISearchConfiguration, VIEWLET_ID, PANEL_ID } from 'vs/workbench/services/search/common/search'; +import { ISearchHistoryService } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; +import { SearchViewlet } from 'vs/workbench/contrib/search/browser/searchViewlet'; +import { SearchPanel } from 'vs/workbench/contrib/search/browser/searchPanel'; export function isSearchViewFocused(viewletService: IViewletService, panelService: IPanelService): boolean { const searchView = getSearchView(viewletService, panelService); const activeElement = document.activeElement; - return searchView && activeElement && DOM.isAncestor(activeElement, searchView.getContainer()); + return !!(searchView && activeElement && DOM.isAncestor(activeElement, searchView.getContainer())); } -export function appendKeyBindingLabel(label: string, keyBinding: number | ResolvedKeybinding, keyBindingService2: IKeybindingService): string { - if (typeof keyBinding === 'number') { - const resolvedKeybindings = keyBindingService2.resolveKeybinding(createKeybinding(keyBinding, OS)); - return doAppendKeyBindingLabel(label, resolvedKeybindings.length > 0 ? resolvedKeybindings[0] : null); +export function appendKeyBindingLabel(label: string, inputKeyBinding: number | ResolvedKeybinding | undefined, keyBindingService2: IKeybindingService): string { + if (typeof inputKeyBinding === 'number') { + const keybinding = createKeybinding(inputKeyBinding, OS); + if (keybinding) { + const resolvedKeybindings = keyBindingService2.resolveKeybinding(keybinding); + return doAppendKeyBindingLabel(label, resolvedKeybindings.length > 0 ? resolvedKeybindings[0] : undefined); + } + return doAppendKeyBindingLabel(label, undefined); } else { - return doAppendKeyBindingLabel(label, keyBinding); + return doAppendKeyBindingLabel(label, inputKeyBinding); } } -export function openSearchView(viewletService: IViewletService, panelService: IPanelService, focus?: boolean): Promise { - if (viewletService.getViewlets().filter(v => v.id === VIEW_ID).length) { - return viewletService.openViewlet(VIEW_ID, focus).then(viewlet => viewlet); +export function openSearchView(viewletService: IViewletService, panelService: IPanelService, configurationService: IConfigurationService, focus?: boolean): Promise { + if (configurationService.getValue().search.location === 'panel') { + return Promise.resolve((panelService.openPanel(PANEL_ID, focus) as SearchPanel).getSearchView()); } - - return Promise.resolve(panelService.openPanel(VIEW_ID, focus) as SearchView); + return viewletService.openViewlet(VIEWLET_ID, focus).then(viewlet => (viewlet as SearchViewlet).getSearchView()); } -export function getSearchView(viewletService: IViewletService, panelService: IPanelService): SearchView { +export function getSearchView(viewletService: IViewletService, panelService: IPanelService): SearchView | null { const activeViewlet = viewletService.getActiveViewlet(); - if (activeViewlet && activeViewlet.getId() === VIEW_ID) { - return activeViewlet; + if (activeViewlet && activeViewlet.getId() === VIEWLET_ID) { + return (activeViewlet as SearchViewlet).getSearchView(); } const activePanel = panelService.getActivePanel(); - if (activePanel && activePanel.getId() === VIEW_ID) { - return activePanel; + if (activePanel && activePanel.getId() === PANEL_ID) { + return (activePanel as SearchPanel).getSearchView(); } - return undefined; + return null; } -function doAppendKeyBindingLabel(label: string, keyBinding: ResolvedKeybinding): string { +function doAppendKeyBindingLabel(label: string, keyBinding: ResolvedKeybinding | undefined): string { return keyBinding ? label + ' (' + keyBinding.getLabel() + ')' : label; } export const toggleCaseSensitiveCommand = (accessor: ServicesAccessor) => { const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); - searchView.toggleCaseSensitive(); + if (searchView) { + searchView.toggleCaseSensitive(); + } }; export const toggleWholeWordCommand = (accessor: ServicesAccessor) => { const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); - searchView.toggleWholeWords(); + if (searchView) { + + searchView.toggleWholeWords(); + } }; export const toggleRegexCommand = (accessor: ServicesAccessor) => { const searchView = getSearchView(accessor.get(IViewletService), accessor.get(IPanelService)); - searchView.toggleRegex(); + if (searchView) { + searchView.toggleRegex(); + } }; export class FocusNextInputAction extends Action { @@ -100,7 +112,9 @@ export class FocusNextInputAction extends Action { run(): Promise { const searchView = getSearchView(this.viewletService, this.panelService); - searchView.focusNextInputBox(); + if (searchView) { + searchView.focusNextInputBox(); + } return Promise.resolve(null); } } @@ -118,21 +132,23 @@ export class FocusPreviousInputAction extends Action { run(): Promise { const searchView = getSearchView(this.viewletService, this.panelService); - searchView.focusPreviousInputBox(); + if (searchView) { + searchView.focusPreviousInputBox(); + } return Promise.resolve(null); } } export abstract class FindOrReplaceInFilesAction extends Action { - constructor(id: string, label: string, protected viewletService: IViewletService, protected panelService: IPanelService, + constructor(id: string, label: string, protected viewletService: IViewletService, protected panelService: IPanelService, protected configurationService: IConfigurationService, private expandSearchReplaceWidget: boolean ) { super(id, label); } run(): Promise { - return openSearchView(this.viewletService, this.panelService, false).then(openedView => { + return openSearchView(this.viewletService, this.panelService, this.configurationService, false).then(openedView => { const searchAndReplaceWidget = openedView.searchAndReplaceWidget; searchAndReplaceWidget.toggleReplace(this.expandSearchReplaceWidget); @@ -148,9 +164,10 @@ export class FindInFilesAction extends FindOrReplaceInFilesAction { constructor(id: string, label: string, @IViewletService viewletService: IViewletService, - @IPanelService panelService: IPanelService + @IPanelService panelService: IPanelService, + @IConfigurationService configurationService: IConfigurationService ) { - super(id, label, viewletService, panelService, /*expandSearchReplaceWidget=*/false); + super(id, label, viewletService, panelService, configurationService, /*expandSearchReplaceWidget=*/false); } } @@ -161,9 +178,10 @@ export class OpenSearchViewletAction extends FindOrReplaceInFilesAction { constructor(id: string, label: string, @IViewletService viewletService: IViewletService, @IPanelService panelService: IPanelService, - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + @IConfigurationService configurationService: IConfigurationService ) { - super(id, label, viewletService, panelService, /*expandSearchReplaceWidget=*/false); + super(id, label, viewletService, panelService, configurationService, /*expandSearchReplaceWidget=*/false); } run(): Promise { @@ -191,9 +209,10 @@ export class ReplaceInFilesAction extends FindOrReplaceInFilesAction { constructor(id: string, label: string, @IViewletService viewletService: IViewletService, - @IPanelService panelService: IPanelService + @IPanelService panelService: IPanelService, + @IConfigurationService configurationService: IConfigurationService ) { - super(id, label, viewletService, panelService, /*expandSearchReplaceWidget=*/true); + super(id, label, viewletService, panelService, configurationService, /*expandSearchReplaceWidget=*/true); } } @@ -208,8 +227,10 @@ export class CloseReplaceAction extends Action { run(): Promise { const searchView = getSearchView(this.viewletService, this.panelService); - searchView.searchAndReplaceWidget.toggleReplace(false); - searchView.searchAndReplaceWidget.focus(); + if (searchView) { + searchView.searchAndReplaceWidget.toggleReplace(false); + searchView.searchAndReplaceWidget.focus(); + } return Promise.resolve(null); } } @@ -219,7 +240,7 @@ export class RefreshAction extends Action { static readonly ID: string = 'search.action.refreshSearchResults'; static LABEL: string = nls.localize('RefreshAction.label', "Refresh"); - private searchView: SearchView; + private searchView: SearchView | null; constructor(id: string, label: string, @IViewletService private readonly viewletService: IViewletService, @@ -230,7 +251,7 @@ export class RefreshAction extends Action { } get enabled(): boolean { - return this.searchView.isSearchSubmitted(); + return !!this.searchView && this.searchView.isSearchSubmitted(); } update(): void { @@ -242,7 +263,8 @@ export class RefreshAction extends Action { if (searchView) { searchView.onQueryChanged(); } - return Promise.resolve(null); + + return Promise.resolve(); } } @@ -261,7 +283,7 @@ export class CollapseDeepestExpandedLevelAction extends Action { update(): void { const searchView = getSearchView(this.viewletService, this.panelService); - this.enabled = searchView && searchView.hasSearchResults(); + this.enabled = !!searchView && searchView.hasSearchResults(); } run(): Promise { @@ -276,7 +298,7 @@ export class CollapseDeepestExpandedLevelAction extends Action { const navigator = viewer.navigate(); let node = navigator.first(); let collapseFileMatchLevel = false; - if (node instanceof FolderMatch) { + if (node instanceof BaseFolderMatch) { while (node = navigator.next()) { if (node instanceof Match) { collapseFileMatchLevel = true; @@ -318,7 +340,7 @@ export class ClearSearchResultsAction extends Action { update(): void { const searchView = getSearchView(this.viewletService, this.panelService); - this.enabled = searchView && (!searchView.allSearchFieldsClear() || searchView.hasSearchResults()); + this.enabled = !!searchView && (!searchView.allSearchFieldsClear() || searchView.hasSearchResults()); } run(): Promise { @@ -326,7 +348,7 @@ export class ClearSearchResultsAction extends Action { if (searchView) { searchView.clearSearchResults(); } - return Promise.resolve(null); + return Promise.resolve(); } } @@ -345,7 +367,7 @@ export class CancelSearchAction extends Action { update(): void { const searchView = getSearchView(this.viewletService, this.panelService); - this.enabled = searchView && searchView.isSearching(); + this.enabled = !!searchView && searchView.isSearching(); } run(): Promise { @@ -364,13 +386,14 @@ export class FocusNextSearchResultAction extends Action { constructor(id: string, label: string, @IViewletService private readonly viewletService: IViewletService, - @IPanelService private readonly panelService: IPanelService + @IPanelService private readonly panelService: IPanelService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, label); } run(): Promise { - return openSearchView(this.viewletService, this.panelService).then(searchView => { + return openSearchView(this.viewletService, this.panelService, this.configurationService).then(searchView => { searchView.selectNextMatch(); }); } @@ -382,13 +405,14 @@ export class FocusPreviousSearchResultAction extends Action { constructor(id: string, label: string, @IViewletService private readonly viewletService: IViewletService, - @IPanelService private readonly panelService: IPanelService + @IPanelService private readonly panelService: IPanelService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, label); } run(): Promise { - return openSearchView(this.viewletService, this.panelService).then(searchView => { + return openSearchView(this.viewletService, this.panelService, this.configurationService).then(searchView => { searchView.selectPreviousMatch(); }); } @@ -406,8 +430,8 @@ export abstract class AbstractSearchAndReplaceAction extends Action { getNextElementAfterRemoved(viewer: WorkbenchObjectTree, element: RenderableMatch): RenderableMatch { const navigator: INavigator = viewer.navigate(element); - if (element instanceof FolderMatch) { - while (!!navigator.next() && !(navigator.current() instanceof FolderMatch)) { } + if (element instanceof BaseFolderMatch) { + while (!!navigator.next() && !(navigator.current() instanceof BaseFolderMatch)) { } } else if (element instanceof FileMatch) { while (!!navigator.next() && !(navigator.current() instanceof FileMatch)) { } } else { @@ -434,7 +458,7 @@ export abstract class AbstractSearchAndReplaceAction extends Action { // If the previous element is a File or Folder, expand it and go to its last child. // Spell out the two cases, would be too easy to create an infinite loop, like by adding another level... - if (element instanceof Match && previousElement && previousElement instanceof FolderMatch) { + if (element instanceof Match && previousElement && previousElement instanceof BaseFolderMatch) { navigator.next(); viewer.expand(previousElement); previousElement = navigator.previous(); @@ -455,7 +479,6 @@ export class RemoveAction extends AbstractSearchAndReplaceAction { static LABEL = nls.localize('RemoveAction.label', "Dismiss"); constructor( - private viewlet: SearchView, private viewer: WorkbenchObjectTree, private element: RenderableMatch ) { @@ -470,27 +493,12 @@ export class RemoveAction extends AbstractSearchAndReplaceAction { if (nextFocusElement) { this.viewer.reveal(nextFocusElement); - this.viewer.setFocus([nextFocusElement], getKeyboardEventForEditorOpen()); - } - - let elementToRefresh: FolderMatch | FileMatch | SearchResult; - const element = this.element; - if (element instanceof FolderMatch) { - const parent = element.parent(); - parent.remove(element); - elementToRefresh = parent; - } else if (element instanceof FileMatch) { - const parent = element.parent(); - parent.remove(element); - elementToRefresh = parent; - } else if (element instanceof Match) { - const parent = element.parent(); - parent.remove(element); - elementToRefresh = parent.count() === 0 ? parent.parent() : parent; + this.viewer.setFocus([nextFocusElement], getSelectionKeyboardEvent()); } + this.element.parent().remove(this.element); this.viewer.domFocus(); - this.viewlet.refreshTree({ elements: [elementToRefresh] }); + return Promise.resolve(); } } @@ -522,7 +530,7 @@ export class ReplaceAllAction extends AbstractSearchAndReplaceAction { const nextFocusElement = this.getElementToFocusAfterRemoved(tree, this.fileMatch); return this.fileMatch.parent().replace(this.fileMatch).then(() => { if (nextFocusElement) { - tree.setFocus([nextFocusElement], getKeyboardEventForEditorOpen()); + tree.setFocus([nextFocusElement], getSelectionKeyboardEvent()); } tree.domFocus(); @@ -545,7 +553,7 @@ export class ReplaceAllInFolderAction extends AbstractSearchAndReplaceAction { const nextFocusElement = this.getElementToFocusAfterRemoved(this.viewer, this.folderMatch); return this.folderMatch.replaceAll().then(() => { if (nextFocusElement) { - this.viewer.setFocus([nextFocusElement], getKeyboardEventForEditorOpen()); + this.viewer.setFocus([nextFocusElement], getSelectionKeyboardEvent()); } this.viewer.domFocus(); }); @@ -570,7 +578,7 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { return this.element.parent().replace(this.element).then(() => { const elementToFocus = this.getElementToFocusAfterReplace(); if (elementToFocus) { - this.viewer.setFocus([elementToFocus], getKeyboardEventForEditorOpen()); + this.viewer.setFocus([elementToFocus], getSelectionKeyboardEvent()); } return this.getElementToShowReplacePreview(elementToFocus); @@ -589,7 +597,7 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { private getElementToFocusAfterReplace(): Match { const navigator: INavigator = this.viewer.navigate(); let fileMatched = false; - let elementToFocus = null; + let elementToFocus: any = null; do { elementToFocus = navigator.current(); if (elementToFocus instanceof Match) { @@ -613,7 +621,7 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { return elementToFocus; } - private async getElementToShowReplacePreview(elementToFocus: FileMatchOrMatch): Promise { + private async getElementToShowReplacePreview(elementToFocus: FileMatchOrMatch): Promise { if (this.hasSameParent(elementToFocus)) { return elementToFocus; } @@ -639,7 +647,7 @@ export class ReplaceAction extends AbstractSearchAndReplaceAction { } function uriToClipboardString(resource: URI): string { - return resource.scheme === Schemas.file ? normalize(normalizeDriveLetter(resource.fsPath), true) : resource.toString(); + return resource.scheme === Schemas.file ? normalize(normalizeDriveLetter(resource.fsPath)) : resource.toString(); } export const copyPathCommand: ICommandHandler = (accessor, fileMatch: FileMatch | FolderMatch) => { @@ -688,7 +696,7 @@ function fileMatchToString(fileMatch: FileMatch, maxMatches: number): { text: st }; } -function folderMatchToString(folderMatch: FolderMatch, maxMatches: number): { text: string, count: number } { +function folderMatchToString(folderMatch: FolderMatch | BaseFolderMatch, maxMatches: number): { text: string, count: number } { const fileResults: string[] = []; let numMatches = 0; @@ -710,12 +718,12 @@ const maxClipboardMatches = 1e4; export const copyMatchCommand: ICommandHandler = (accessor, match: RenderableMatch) => { const clipboardService = accessor.get(IClipboardService); - let text: string; + let text: string | undefined; if (match instanceof Match) { text = matchToString(match); } else if (match instanceof FileMatch) { text = fileMatchToString(match, maxClipboardMatches).text; - } else if (match instanceof FolderMatch) { + } else if (match instanceof BaseFolderMatch) { text = folderMatchToString(match, maxClipboardMatches).text; } @@ -724,7 +732,7 @@ export const copyMatchCommand: ICommandHandler = (accessor, match: RenderableMat } }; -function allFolderMatchesToString(folderMatches: FolderMatch[], maxMatches: number): string { +function allFolderMatchesToString(folderMatches: Array, maxMatches: number): string { const folderResults: string[] = []; let numMatches = 0; folderMatches = folderMatches.sort(searchMatchComparer); @@ -745,10 +753,12 @@ export const copyAllCommand: ICommandHandler = accessor => { const clipboardService = accessor.get(IClipboardService); const searchView = getSearchView(viewletService, panelService); - const root = searchView.searchResult; + if (searchView) { + const root = searchView.searchResult; - const text = allFolderMatchesToString(root.folderMatches(), maxClipboardMatches); - clipboardService.writeText(text); + const text = allFolderMatchesToString(root.folderMatches(), maxClipboardMatches); + clipboardService.writeText(text); + } }; export const clearHistoryCommand: ICommandHandler = accessor => { @@ -759,17 +769,8 @@ export const clearHistoryCommand: ICommandHandler = accessor => { export const focusSearchListCommand: ICommandHandler = accessor => { const viewletService = accessor.get(IViewletService); const panelService = accessor.get(IPanelService); - openSearchView(viewletService, panelService).then(searchView => { + const configurationService = accessor.get(IConfigurationService); + openSearchView(viewletService, panelService, configurationService).then(searchView => { searchView.moveFocusToResults(); }); }; - -export function getKeyboardEventForEditorOpen(options: IEditorOptions = {}): KeyboardEvent { - const fakeKeyboardEvent = new KeyboardEvent('keydown'); - if (options.preserveFocus) { - // fake double click - (fakeKeyboardEvent).detail = 2; - } - - return fakeKeyboardEvent; -} diff --git a/src/vs/workbench/contrib/search/browser/searchPanel.ts b/src/vs/workbench/contrib/search/browser/searchPanel.ts new file mode 100644 index 0000000000..d589eb2c94 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/searchPanel.ts @@ -0,0 +1,70 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { PANEL_ID } from 'vs/workbench/services/search/common/search'; +import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; +import { Panel } from 'vs/workbench/browser/panel'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { localize } from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; +import { IAction } from 'vs/base/common/actions'; + +export class SearchPanel extends Panel { + + private readonly searchView: SearchView; + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super(PANEL_ID, telemetryService, themeService, storageService); + this.searchView = this._register(instantiationService.createInstance(SearchView, { id: PANEL_ID, title: localize('search', "Search") })); + this._register(this.searchView.onDidChangeTitleArea(() => this.updateTitleArea())); + this._register(this.onDidChangeVisibility(visible => this.searchView.setVisible(visible))); + } + + create(parent: HTMLElement): void { + dom.addClass(parent, 'monaco-panel-view'); + this.searchView.render(); + dom.append(parent, this.searchView.element); + this.searchView.setExpanded(true); + this.searchView.headerVisible = false; + } + + public getTitle(): string { + return this.searchView.title; + } + + public layout(dimension: dom.Dimension): void { + this.searchView.width = dimension.width; + this.searchView.layout(dimension.height); + } + + public focus(): void { + this.searchView.focus(); + } + + getActions(): IAction[] { + return this.searchView.getActions(); + } + + getSecondaryActions(): IAction[] { + return this.searchView.getSecondaryActions(); + } + + saveState(): void { + this.searchView.saveState(); + super.saveState(); + } + + getSearchView(): SearchView | null { + return this.searchView; + } +} diff --git a/src/vs/workbench/parts/search/browser/searchResultsView.ts b/src/vs/workbench/contrib/search/browser/searchResultsView.ts similarity index 82% rename from src/vs/workbench/parts/search/browser/searchResultsView.ts rename to src/vs/workbench/contrib/search/browser/searchResultsView.ts index 53d2dc151f..cd632e731e 100644 --- a/src/vs/workbench/parts/search/browser/searchResultsView.ts +++ b/src/vs/workbench/contrib/search/browser/searchResultsView.ts @@ -8,24 +8,28 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; -import { ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; +import { ITreeNode, ITreeRenderer, ITreeDragAndDrop, ITreeDragOverReaction } from 'vs/base/browser/ui/tree/tree'; import { IAction } from 'vs/base/common/actions'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; -import * as paths from 'vs/base/common/paths'; +import * as paths from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { FileKind } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILabelService } from 'vs/platform/label/common/label'; -import { ISearchConfigurationProperties } from 'vs/platform/search/common/search'; +import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels'; -import { RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction } from 'vs/workbench/parts/search/browser/searchActions'; -import { SearchView } from 'vs/workbench/parts/search/browser/searchView'; -import { FileMatch, FolderMatch, Match, RenderableMatch, SearchModel, BaseFolderMatch } from 'vs/workbench/parts/search/common/searchModel'; +import { RemoveAction, ReplaceAction, ReplaceAllAction, ReplaceAllInFolderAction } from 'vs/workbench/contrib/search/browser/searchActions'; +import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; +import { FileMatch, FolderMatch, Match, RenderableMatch, SearchModel, BaseFolderMatch } from 'vs/workbench/contrib/search/common/searchModel'; +import { IDragAndDropData } from 'vs/base/browser/dnd'; +import { fillResourceDataTransfers } from 'vs/workbench/browser/dnd'; +import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; +import { URI } from 'vs/base/common/uri'; interface IFolderMatchTemplate { label: IResourceLabel; @@ -131,7 +135,7 @@ export class FolderMatchRenderer extends Disposable implements ITreeRenderer 0) { actions.push(this.instantiationService.createInstance(ReplaceAllAction, this.searchView, fileMatch)); } - actions.push(new RemoveAction(this.searchView, this.searchView.getControl(), fileMatch)); + actions.push(new RemoveAction(this.searchView.getControl(), fileMatch)); templateData.actions.push(actions, { icon: true, label: false }); } - disposeElement(element: ITreeNode, index: number, templateData: IFolderMatchTemplate): void { + disposeElement(element: ITreeNode, index: number, templateData: IFileMatchTemplate): void { } - disposeTemplate(templateData: IFolderMatchTemplate): void { + disposeTemplate(templateData: IFileMatchTemplate): void { dispose(templateData.disposables); } } @@ -267,9 +271,9 @@ export class MatchRenderer extends Disposable implements ITreeRenderer { + constructor( + @IInstantiationService private instantiationService: IInstantiationService + ) { } + + onDragOver(data: IDragAndDropData, targetElement: RenderableMatch, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction { + return false; + } + + getDragURI(element: RenderableMatch): string | null { + if (element instanceof FileMatch) { + return element.remove.toString(); + } + + return null; + } + + getDragLabel?(elements: RenderableMatch[]): string | undefined { + if (elements.length > 1) { + return String(elements.length); + } + + const element = elements[0]; + return element instanceof FileMatch ? + resources.basename(element.resource()) : + undefined; + } + + onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void { + const elements = (data as ElementsDragAndDropData).elements; + const resources: URI[] = elements + .filter(e => e instanceof FileMatch) + .map((fm: FileMatch) => fm.resource()); + + if (resources.length) { + // Apply some datatransfer types to allow for dragging the element outside of the application + this.instantiationService.invokeFunction(fillResourceDataTransfers, resources, originalEvent); + } + } + + drop(data: IDragAndDropData, targetElement: RenderableMatch, targetIndex: number, originalEvent: DragEvent): void { } } diff --git a/src/vs/workbench/parts/search/browser/searchView.ts b/src/vs/workbench/contrib/search/browser/searchView.ts similarity index 92% rename from src/vs/workbench/parts/search/browser/searchView.ts rename to src/vs/workbench/contrib/search/browser/searchView.ts index 3cb287e6f5..8f940b0a95 100644 --- a/src/vs/workbench/parts/search/browser/searchView.ts +++ b/src/vs/workbench/contrib/search/browser/searchView.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import * as aria from 'vs/base/browser/ui/aria/aria'; @@ -17,7 +16,6 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Iterator } from 'vs/base/common/iterator'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import * as paths from 'vs/base/common/paths'; import * as env from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; @@ -33,39 +31,40 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie import { IConfirmation, IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TreeResourceNavigator2, WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; +import { TreeResourceNavigator2, WorkbenchObjectTree, getSelectionKeyboardEvent } from 'vs/platform/list/browser/listService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IProgressService } from 'vs/platform/progress/common/progress'; -import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ISearchHistoryService, ISearchHistoryValues, ITextQuery, SearchErrorCode, VIEW_ID } from 'vs/platform/search/common/search'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IPatternInfo, ISearchComplete, ISearchConfiguration, ISearchConfigurationProperties, ITextQuery, SearchErrorCode, VIEW_ID } from 'vs/workbench/services/search/common/search'; +import { ISearchHistoryService, ISearchHistoryValues } from 'vs/workbench/contrib/search/common/searchHistoryService'; import { diffInserted, diffInsertedOutline, diffRemoved, diffRemovedOutline, editorFindMatchHighlight, editorFindMatchHighlightBorder, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { OpenFileFolderAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; -import { ResourceLabels } from 'vs/workbench/browser/labels'; -import { Viewlet } from 'vs/workbench/browser/viewlet'; +import { ResourceLabels, IResourceLabelsContainer } from 'vs/workbench/browser/labels'; import { IEditor } from 'vs/workbench/common/editor'; -import { IPanel } from 'vs/workbench/common/panel'; -import { IViewlet } from 'vs/workbench/common/viewlet'; -import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/parts/search/browser/patternInputWidget'; -import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, getKeyboardEventForEditorOpen, RefreshAction } from 'vs/workbench/parts/search/browser/searchActions'; -import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate } from 'vs/workbench/parts/search/browser/searchResultsView'; -import { ISearchWidgetOptions, SearchWidget } from 'vs/workbench/parts/search/browser/searchWidget'; -import * as Constants from 'vs/workbench/parts/search/common/constants'; -import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder'; -import { IReplaceService } from 'vs/workbench/parts/search/common/replace'; -import { getOutOfWorkspaceEditorResources } from 'vs/workbench/parts/search/common/search'; -import { FileMatch, FileMatchOrMatch, FolderMatch, IChangeEvent, ISearchWorkbenchService, Match, RenderableMatch, searchMatchComparer, SearchModel, SearchResult } from 'vs/workbench/parts/search/common/searchModel'; +import { ExcludePatternInputWidget, PatternInputWidget } from 'vs/workbench/contrib/search/browser/patternInputWidget'; +import { CancelSearchAction, ClearSearchResultsAction, CollapseDeepestExpandedLevelAction, RefreshAction } from 'vs/workbench/contrib/search/browser/searchActions'; +import { FileMatchRenderer, FolderMatchRenderer, MatchRenderer, SearchAccessibilityProvider, SearchDelegate, SearchDND } from 'vs/workbench/contrib/search/browser/searchResultsView'; +import { ISearchWidgetOptions, SearchWidget } from 'vs/workbench/contrib/search/browser/searchWidget'; +import * as Constants from 'vs/workbench/contrib/search/common/constants'; +import { ITextQueryBuilderOptions, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; +import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; +import { getOutOfWorkspaceEditorResources } from 'vs/workbench/contrib/search/common/search'; +import { FileMatch, FileMatchOrMatch, FolderMatch, IChangeEvent, ISearchWorkbenchService, Match, RenderableMatch, searchMatchComparer, SearchModel, SearchResult, BaseFolderMatch } from 'vs/workbench/contrib/search/common/searchModel'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IPreferencesService, ISettingsEditorOptions } from 'vs/workbench/services/preferences/common/preferences'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { relativePath } from 'vs/base/common/resources'; +import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { Memento } from 'vs/workbench/common/memento'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; const $ = dom.$; -export class SearchView extends Viewlet implements IViewlet, IPanel { +export class SearchView extends ViewletPanel { private static readonly MAX_TEXT_RESULTS = 10000; @@ -75,8 +74,10 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { private isDisposed: boolean; + private container: HTMLElement; private queryBuilder: QueryBuilder; private viewModel: SearchModel; + private memento: Memento; private viewletVisible: IContextKey; private viewletFocused: IContextKey; @@ -116,23 +117,21 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { private currentSelectedFileMatch: FileMatch; - private readonly selectCurrentMatchEmitter: Emitter; + private readonly selectCurrentMatchEmitter: Emitter; private delayedRefresh: Delayer; private changedWhileHidden: boolean; private searchWithoutFolderMessageElement: HTMLElement; - private currentSearchQ = Promise.resolve(); + private currentSearchQ = Promise.resolve(); constructor( - @IPartService partService: IPartService, - @ITelemetryService telemetryService: ITelemetryService, + options: IViewletPanelOptions, @IFileService private readonly fileService: IFileService, @IEditorService private readonly editorService: IEditorService, @IProgressService private readonly progressService: IProgressService, @INotificationService private readonly notificationService: INotificationService, @IDialogService private readonly dialogService: IDialogService, - @IStorageService storageService: IStorageService, @IContextViewService private readonly contextViewService: IContextViewService, @IInstantiationService private readonly instantiationService: IInstantiationService, @IConfigurationService configurationService: IConfigurationService, @@ -145,10 +144,13 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { @IThemeService protected themeService: IThemeService, @ISearchHistoryService private readonly searchHistoryService: ISearchHistoryService, @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService, - @IContextMenuService private readonly contextMenuService: IContextMenuService, - @IMenuService private readonly menuService: IMenuService + @IContextMenuService contextMenuService: IContextMenuService, + @IMenuService private readonly menuService: IMenuService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + @IKeybindingService keybindingService: IKeybindingService, + @IStorageService storageService: IStorageService, ) { - super(VIEW_ID, configurationService, partService, telemetryService, themeService, storageService); + super({ ...(options as IViewletPanelOptions), id: VIEW_ID, ariaHeaderLabel: nls.localize('searchView', "Search") }, keybindingService, contextMenuService, configurationService); this.viewletVisible = Constants.SearchViewVisibleKey.bindTo(contextKeyService); this.viewletFocused = Constants.SearchViewFocusedKey.bindTo(contextKeyService); @@ -164,8 +166,9 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.hasSearchResultsKey = Constants.HasSearchResults.bindTo(this.contextKeyService); this.queryBuilder = this.instantiationService.createInstance(QueryBuilder); - this.viewletState = this.getMemento(StorageScope.WORKSPACE); - this.globalMemento = this.getMemento(StorageScope.GLOBAL); + this.memento = new Memento(this.id, storageService); + this.viewletState = this.memento.getMemento(StorageScope.WORKSPACE); + this.globalMemento = this.memento.getMemento(StorageScope.GLOBAL); this._register(this.fileService.onFileChanges(e => this.onFilesChanged(e))); this._register(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDidChangeDirty(e))); @@ -178,6 +181,16 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.delayedRefresh = this._register(new Delayer(250)); + this.actions = [ + this._register(this.instantiationService.createInstance(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL)), + this._register(this.instantiationService.createInstance(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL)) + ]; + this.refreshAction = this._register(this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL)); + this.cancelAction = this._register(this.instantiationService.createInstance(CancelSearchAction, CancelSearchAction.ID, CancelSearchAction.LABEL)); + } + + getContainer(): HTMLElement { + return this.container; } get searchResult(): SearchResult { @@ -190,13 +203,11 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { } } - create(parent: HTMLElement): void { - super.create(parent); - + renderBody(parent: HTMLElement): void { this.viewModel = this._register(this.searchWorkbenchService.searchModel); - dom.addClass(parent, 'search-view'); + this.container = dom.append(parent, dom.$('.search-view')); - this.searchWidgetsContainerElement = dom.append(parent, $('.search-widgets-container')); + this.searchWidgetsContainerElement = dom.append(this.container, $('.search-widgets-container')); this.createSearchWidget(this.searchWidgetsContainerElement); const history = this.searchHistoryService.load(); @@ -273,19 +284,12 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.inputPatternExcludes.onCancel(() => this.viewModel.cancelSearch()); // Cancel search without focusing the search widget this.trackInputBox(this.inputPatternExcludes.inputFocusTracker, this.inputPatternExclusionsFocused); - this.messagesElement = dom.append(parent, $('.messages')); + this.messagesElement = dom.append(this.container, $('.messages')); if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { this.showSearchWithoutFolderMessage(); } - this.createSearchResultsView(parent); - - this.actions = [ - this._register(this.instantiationService.createInstance(ClearSearchResultsAction, ClearSearchResultsAction.ID, ClearSearchResultsAction.LABEL)), - this._register(this.instantiationService.createInstance(CollapseDeepestExpandedLevelAction, CollapseDeepestExpandedLevelAction.ID, CollapseDeepestExpandedLevelAction.LABEL)) - ]; - this.refreshAction = this._register(this.instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL)); - this.cancelAction = this._register(this.instantiationService.createInstance(CancelSearchAction, CancelSearchAction.ID, CancelSearchAction.LABEL)); + this.createSearchResultsView(this.container); if (filePatterns !== '' || patternExclusions !== '' || patternIncludes !== '' || queryDetailsExpanded !== '' || !useExcludesAndIgnoreFiles) { this.toggleQueryDetails(true, true, true); @@ -295,13 +299,11 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this._register(this.searchWidget.searchInput.onInput(() => this.updateActions())); this._register(this.searchWidget.replaceInput.onDidChange(() => this.updateActions())); - this._register(this.searchIncludePattern.inputBox.onDidChange(() => this.updateActions())); - this._register(this.searchExcludePattern.inputBox.onDidChange(() => this.updateActions())); this._register(this.onDidFocus(() => this.viewletFocused.set(true))); this._register(this.onDidBlur(() => this.viewletFocused.set(false))); - this._register(this.onDidChangeVisibility(visible => this.onVisibilityChanged(visible))); + this._register(this.onDidChangeBodyVisibility(visible => this.onVisibilityChanged(visible))); } private onVisibilityChanged(visible: boolean): void { @@ -340,16 +342,17 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { return this.inputPatternExcludes; } - private updateActions(): void { + protected updateActions(): void { for (const action of this.actions) { action.update(); this.refreshAction.update(); this.cancelAction.update(); } + super.updateActions(); } private isScreenReaderOptimized() { - const detected = browser.getAccessibilitySupport() === env.AccessibilitySupport.Enabled; + const detected = this.accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; const config = this.configurationService.getValue('editor').accessibilitySupport; return config === 'on' || (config === 'auto' && detected); } @@ -443,7 +446,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.tree.setChildren(null, this.createResultIterator(collapseResults)); } else { event.elements.forEach(element => { - if (element instanceof FolderMatch) { + if (element instanceof BaseFolderMatch) { // The folder may or may not be in the tree. Refresh the whole thing. this.tree.setChildren(null, this.createResultIterator(collapseResults)); return; @@ -496,9 +499,9 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { return Iterator.map(matchesIt, r => (>{ element: r })); } - private createIterator(match: FolderMatch | FileMatch | SearchResult, collapseResults: ISearchConfigurationProperties['collapseResults']): Iterator> { + private createIterator(match: BaseFolderMatch | FileMatch | SearchResult, collapseResults: ISearchConfigurationProperties['collapseResults']): Iterator> { return match instanceof SearchResult ? this.createResultIterator(collapseResults) : - match instanceof FolderMatch ? this.createFolderIterator(match, collapseResults) : + match instanceof BaseFolderMatch ? this.createFolderIterator(match, collapseResults) : this.createFileIterator(match); } @@ -622,7 +625,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { } }; - this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this)); + this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility } as IResourceLabelsContainer)); this.tree = this._register(>this.instantiationService.createInstance(WorkbenchObjectTree, this.resultsElement, delegate, @@ -633,7 +636,9 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { ], { identityProvider, - accessibilityProvider: this.instantiationService.createInstance(SearchAccessibilityProvider, this.viewModel) + accessibilityProvider: this.instantiationService.createInstance(SearchAccessibilityProvider, this.viewModel), + dnd: this.instantiationService.createInstance(SearchDND), + multipleSelectionSupport: false })); this._register(this.tree.onContextMenu(e => this.onContextMenu(e))); @@ -698,12 +703,12 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { selectCurrentMatch(): void { const focused = this.tree.getFocus()[0]; - const fakeKeyboardEvent = getKeyboardEventForEditorOpen({ preserveFocus: false }); + const fakeKeyboardEvent = getSelectionKeyboardEvent(undefined, false); this.tree.setSelection([focused], fakeKeyboardEvent); } selectNextMatch(): void { - const [selected]: RenderableMatch[] = this.tree.getSelection(); + const [selected] = this.tree.getSelection(); // Expand the initial selected node, if needed if (selected instanceof FileMatch) { @@ -733,15 +738,14 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { // Reveal the newly selected element if (next) { - this.tree.setFocus([next]); - this.tree.setSelection([next]); + this.tree.setFocus([next], getSelectionKeyboardEvent(undefined, false)); this.tree.reveal(next); this.selectCurrentMatchEmitter.fire(undefined); } } selectPreviousMatch(): void { - const [selected]: RenderableMatch[] = this.tree.getSelection(); + const [selected] = this.tree.getSelection(); let navigator = this.tree.navigate(selected); let prev = navigator.previous(); @@ -756,7 +760,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { // This is complicated because .last will set the navigator to the last FileMatch, // so expand it and FF to its last child this.tree.expand(prev); - let tmp: RenderableMatch; + let tmp: RenderableMatch | null; while (tmp = navigator.next()) { prev = tmp; } @@ -774,8 +778,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { // Reveal the newly selected element if (prev) { - this.tree.setFocus([prev]); - this.tree.setSelection([prev]); + this.tree.setFocus([prev], getSelectionKeyboardEvent(undefined, false)); this.tree.reveal(prev); this.selectCurrentMatchEmitter.fire(undefined); } @@ -907,8 +910,8 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.tree.layout(searchResultContainerSize, this.size.width); } - layout(dimension: dom.Dimension): void { - this.size = dimension; + protected layoutBody(height: number, width: number): void { + this.size = new dom.Dimension(width, height); this.reLayout(); } @@ -926,9 +929,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { allSearchFieldsClear(): boolean { return this.searchWidget.getReplaceValue() === '' && - this.searchWidget.searchInput.getValue() === '' && - this.searchIncludePattern.getValue() === '' && - this.searchExcludePattern.getValue() === ''; + this.searchWidget.searchInput.getValue() === ''; } hasSearchResults(): boolean { @@ -942,8 +943,6 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.showSearchWithoutFolderMessage(); } this.searchWidget.clear(); - this.searchIncludePattern.setValue(''); - this.searchExcludePattern.setValue(''); this.viewModel.cancelSearch(); this.updateActions(); } @@ -966,7 +965,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { } } - private getSearchTextFromEditor(allowUnselectedWord: boolean): string { + private getSearchTextFromEditor(allowUnselectedWord: boolean): string | null { if (!this.editorService.activeEditor) { return null; } @@ -984,7 +983,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { } } - if (!isCodeEditor(activeTextEditorWidget)) { + if (!isCodeEditor(activeTextEditorWidget) || !activeTextEditorWidget.hasModel()) { return null; } @@ -1071,20 +1070,20 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { } if (!skipLayout && this.size) { - this.layout(this.size); + this.layout(this.size.height); } } - searchInFolders(resources: URI[], pathToRelative: (from: string, to: string) => string): void { + searchInFolders(resources: URI[]): void { const folderPaths: string[] = []; const workspace = this.contextService.getWorkspace(); if (resources) { resources.forEach(resource => { - let folderPath: string; + let folderPath: string | undefined; if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { // Show relative path from the root for single-root mode - folderPath = paths.normalize(pathToRelative(workspace.folders[0].uri.fsPath, resource.fsPath)); + folderPath = relativePath(workspace.folders[0].uri, resource); // always uses forward slashes if (folderPath && folderPath !== '.') { folderPath = './' + folderPath; } @@ -1096,14 +1095,14 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { // If this root is the only one with its basename, use a relative ./ path. If there is another, use an absolute path const isUniqueFolder = workspace.folders.filter(folder => folder.name === owningRootName).length === 1; if (isUniqueFolder) { - const relativePath = paths.normalize(pathToRelative(owningFolder.uri.fsPath, resource.fsPath)); - if (relativePath === '.') { + const relPath = relativePath(owningFolder.uri, resource); // always uses forward slashes + if (relPath === '') { folderPath = `./${owningFolder.name}`; } else { - folderPath = `./${owningFolder.name}/${relativePath}`; + folderPath = `./${owningFolder.name}/${relPath}`; } } else { - folderPath = resource.fsPath; + folderPath = resource.fsPath; // TODO rob: handle on-file URIs } } } @@ -1142,20 +1141,6 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { return; } - // Validate regex is OK - if (isRegex) { - let regExp: RegExp; - try { - regExp = new RegExp(contentPattern); - } catch (e) { - return; // malformed regex - } - - if (strings.regExpLeadsToEndlessLoop(regExp)) { - return; // endless regex - } - } - const content: IPatternInfo = { pattern: contentPattern, isRegExp: isRegex, @@ -1184,7 +1169,8 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { matchLines: 1, charsPerLine }, - isSmartCase: this.configurationService.getValue().search.smartCase + isSmartCase: this.configurationService.getValue().search.smartCase, + expandPatterns: true }; const folderResources = this.contextService.getWorkspace().folders; @@ -1241,7 +1227,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.currentSearchQ = this.currentSearchQ .then(() => this.doSearch(query, options, excludePatternText, includePatternText)) - .then(() => { }, () => { }); + .then(() => undefined, () => undefined); } private doSearch(query: ITextQuery, options: ITextQueryBuilderOptions, excludePatternText: string, includePatternText: string): Thenable { @@ -1252,7 +1238,6 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { setTimeout(() => { if (this.searching) { this.updateActions(); - this.updateTitleArea(); } }, 2000); this.showEmptyStage(); @@ -1278,7 +1263,6 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.searchSubmitted = true; this.updateActions(); - this.updateTitleArea(); const hasResults = !this.viewModel.searchResult.isEmpty(); if (completed && completed.limitHit) { @@ -1358,7 +1342,6 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { } else { this.searching = false; this.updateActions(); - this.updateTitleArea(); progressRunner.done(); this.searchWidget.searchInput.showMessage({ content: e.message, type: MessageType.ERROR }); this.viewModel.searchResult.clear(); @@ -1629,7 +1612,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.inputPatternIncludes.clearHistory(); } - protected saveState(): void { + public saveState(): void { const isRegex = this.searchWidget.searchInput.getRegex(); const isWholeWords = this.searchWidget.searchInput.getWholeWords(); const isCaseSensitive = this.searchWidget.searchInput.getCaseSensitive(); @@ -1677,9 +1660,21 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { super.saveState(); } + private _toDispose: IDisposable[] = []; + protected _register(t: T): T { + if (this.isDisposed) { + console.warn('Registering disposable on object that has already been disposed.'); + t.dispose(); + } else { + this._toDispose.push(t); + } + return t; + } + dispose(): void { this.isDisposed = true; - + this.saveState(); + this._toDispose = dispose(this._toDispose); super.dispose(); } } diff --git a/src/vs/workbench/contrib/search/browser/searchViewlet.ts b/src/vs/workbench/contrib/search/browser/searchViewlet.ts new file mode 100644 index 0000000000..04ebc835e1 --- /dev/null +++ b/src/vs/workbench/contrib/search/browser/searchViewlet.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { VIEWLET_ID, VIEW_ID } from 'vs/workbench/services/search/common/search'; +import { SearchView } from 'vs/workbench/contrib/search/browser/searchView'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { ViewletRegistry, Extensions } from 'vs/workbench/browser/viewlet'; + +export class SearchViewlet extends ViewContainerViewlet { + + constructor( + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @ITelemetryService telemetryService: ITelemetryService, + @IWorkspaceContextService protected contextService: IWorkspaceContextService, + @IStorageService protected storageService: IStorageService, + @IConfigurationService configurationService: IConfigurationService, + @IInstantiationService protected instantiationService: IInstantiationService, + @IThemeService themeService: IThemeService, + @IContextMenuService contextMenuService: IContextMenuService, + @IExtensionService extensionService: IExtensionService + ) { + super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService); + } + + getTitle(): string { + return Registry.as(Extensions.Viewlets).getViewlet(this.getId()).name; + } + + getSearchView(): SearchView | null { + const view = super.getView(VIEW_ID); + return view ? view as SearchView : null; + } +} diff --git a/src/vs/workbench/parts/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts similarity index 93% rename from src/vs/workbench/parts/search/browser/searchWidget.ts rename to src/vs/workbench/contrib/search/browser/searchWidget.ts index 2e7197ea5f..c9f6ec1970 100644 --- a/src/vs/workbench/parts/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -3,7 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -15,7 +14,6 @@ import { Action } from 'vs/base/common/actions'; import { Delayer } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import * as env from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; import { CONTEXT_FIND_WIDGET_NOT_VISIBLE } from 'vs/editor/contrib/find/findModel'; import * as nls from 'vs/nls'; @@ -25,15 +23,16 @@ import { ContextKeyExpr, IContextKey, IContextKeyService } from 'vs/platform/con import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { ISearchConfigurationProperties } from 'vs/platform/search/common/search'; +import { ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import { attachFindInputBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ContextScopedFindInput, ContextScopedHistoryInputBox } from 'vs/platform/widget/browser/contextScopedHistoryWidget'; -import { appendKeyBindingLabel, isSearchViewFocused } from 'vs/workbench/parts/search/browser/searchActions'; -import * as Constants from 'vs/workbench/parts/search/common/constants'; +import { ContextScopedFindInput, ContextScopedHistoryInputBox } from 'vs/platform/browser/contextScopedHistoryWidget'; +import { appendKeyBindingLabel, isSearchViewFocused } from 'vs/workbench/contrib/search/browser/searchActions'; +import * as Constants from 'vs/workbench/contrib/search/common/constants'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; export interface ISearchWidgetOptions { value?: string; @@ -134,7 +133,8 @@ export class SearchWidget extends Widget { @IContextKeyService private readonly contextKeyService: IContextKeyService, @IKeybindingService private readonly keyBindingService: IKeybindingService, @IClipboardService private readonly clipboardServce: IClipboardService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService ) { super(); this.replaceActive = Constants.ReplaceActiveKey.bindTo(this.contextKeyService); @@ -148,7 +148,7 @@ export class SearchWidget extends Widget { this.updateAccessibilitySupport(); } }); - browser.onDidChangeAccessibilitySupport(() => this.updateAccessibilitySupport()); + this.accessibilityService.onDidChangeAccessibilitySupport(() => this.updateAccessibilitySupport()); this.updateAccessibilitySupport(); } @@ -169,7 +169,7 @@ export class SearchWidget extends Widget { } setWidth(width: number) { - this.searchInput.setWidth(width); + this.searchInput.inputBox.layout(); this.replaceInput.width = width - 28; this.replaceInput.layout(); } @@ -185,7 +185,7 @@ export class SearchWidget extends Widget { } isReplaceActive(): boolean { - return this.replaceActive.get(); + return !!this.replaceActive.get(); } getReplaceValue(): string { @@ -227,7 +227,7 @@ export class SearchWidget extends Widget { } searchInputHasFocus(): boolean { - return this.searchInputBoxFocused.get(); + return !!this.searchInputBoxFocused.get(); } replaceInputHasFocus(): boolean { @@ -253,7 +253,7 @@ export class SearchWidget extends Widget { } private isScreenReaderOptimized() { - const detected = browser.getAccessibilitySupport() === env.AccessibilitySupport.Enabled; + const detected = this.accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; const config = this.configurationService.getValue('editor').accessibilitySupport; return config === 'on' || (config === 'auto' && detected); } @@ -264,10 +264,10 @@ export class SearchWidget extends Widget { private renderToggleReplaceButton(parent: HTMLElement): void { const opts: IButtonOptions = { - buttonBackground: null, - buttonBorder: null, - buttonForeground: null, - buttonHoverBackground: null + buttonBackground: undefined, + buttonBorder: undefined, + buttonForeground: undefined, + buttonHoverBackground: undefined }; this.toggleReplaceButton = this._register(new Button(parent, opts)); this.toggleReplaceButton.element.setAttribute('aria-expanded', 'false'); @@ -390,22 +390,19 @@ export class SearchWidget extends Widget { } } - private validateSearchInput(value: string): IMessage { + private validateSearchInput(value: string): IMessage | null { if (value.length === 0) { return null; } if (!this.searchInput.getRegex()) { return null; } - let regExp: RegExp; try { - regExp = new RegExp(value); + // tslint:disable-next-line: no-unused-expression + new RegExp(value); } catch (e) { return { content: e.message }; } - if (strings.regExpLeadsToEndlessLoop(regExp)) { - return { content: nls.localize('regexp.validationFailure', "Expression matches everything") }; - } if (strings.regExpContainsBackreference(value)) { if (!this.searchConfiguration.usePCRE2) { @@ -443,14 +440,16 @@ export class SearchWidget extends Widget { else if (keyboardEvent.equals(KeyCode.UpArrow)) { const ta = this.searchInput.domNode.querySelector('textarea'); - if (ta && ta.selectionStart > 0) { + const isMultiline = !!this.searchInput.getValue().match(/\n/); + if (ta && isMultiline && ta.selectionStart > 0) { keyboardEvent.stopPropagation(); } } else if (keyboardEvent.equals(KeyCode.DownArrow)) { const ta = this.searchInput.domNode.querySelector('textarea'); - if (ta && ta.selectionEnd < ta.value.length) { + const isMultiline = !!this.searchInput.getValue().match(/\n/); + if (ta && isMultiline && ta.selectionEnd < ta.value.length) { keyboardEvent.stopPropagation(); } } diff --git a/src/vs/workbench/parts/search/common/constants.ts b/src/vs/workbench/contrib/search/common/constants.ts similarity index 100% rename from src/vs/workbench/parts/search/common/constants.ts rename to src/vs/workbench/contrib/search/common/constants.ts diff --git a/src/vs/workbench/parts/search/common/queryBuilder.ts b/src/vs/workbench/contrib/search/common/queryBuilder.ts similarity index 86% rename from src/vs/workbench/parts/search/common/queryBuilder.ts rename to src/vs/workbench/contrib/search/common/queryBuilder.ts index 9bb092f8a4..9a2e199f1f 100644 --- a/src/vs/workbench/parts/search/common/queryBuilder.ts +++ b/src/vs/workbench/contrib/search/common/queryBuilder.ts @@ -8,7 +8,7 @@ import * as collections from 'vs/base/common/collections'; import * as glob from 'vs/base/common/glob'; import { untildify } from 'vs/base/common/labels'; import { values } from 'vs/base/common/map'; -import * as paths from 'vs/base/common/paths'; +import * as path from 'vs/base/common/path'; import { isEqual } from 'vs/base/common/resources'; import * as strings from 'vs/base/common/strings'; import { URI as uri } from 'vs/base/common/uri'; @@ -16,8 +16,8 @@ import { isMultilineRegexSource } from 'vs/editor/common/model/textModelSearch'; import * as nls from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { getExcludes, ICommonQueryProps, IFileQuery, IFolderQuery, IPatternInfo, ISearchConfiguration, ITextQuery, ITextSearchPreviewOptions, pathIncludedInQuery, QueryType } from 'vs/platform/search/common/search'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { getExcludes, ICommonQueryProps, IFileQuery, IFolderQuery, IPatternInfo, ISearchConfiguration, ITextQuery, ITextSearchPreviewOptions, pathIncludedInQuery, QueryType } from 'vs/workbench/services/search/common/search'; /** * One folder to search and a glob expression that should be applied. @@ -38,7 +38,7 @@ export interface ISearchPathPattern { /** * A set of search paths and a set of glob expressions that should be applied. */ -export interface ISearchPathsResult { +export interface ISearchPathsInfo { searchPaths?: ISearchPathPattern[]; pattern?: glob.IExpression; } @@ -49,11 +49,15 @@ export interface ICommonQueryBuilderOptions { includePattern?: string; extraFileResources?: uri[]; + /** Parse the special ./ syntax supported by the searchview, and expand foo to ** /foo */ + expandPatterns?: boolean; + maxResults?: number; maxFileSize?: number; disregardIgnoreFiles?: boolean; disregardGlobalIgnoreFiles?: boolean; disregardExcludeSettings?: boolean; + disregardSearchExcludeSettings?: boolean; ignoreSymlinks?: boolean; } @@ -109,6 +113,10 @@ export class QueryBuilder { private getContentPattern(inputPattern: IPatternInfo, options: ITextQueryBuilderOptions): IPatternInfo { const searchConfig = this.configurationService.getValue(); + if (inputPattern.isRegExp) { + inputPattern.pattern = inputPattern.pattern.replace(/\r?\n/g, '\\n'); + } + const newPattern = { ...inputPattern, wordSeparators: searchConfig.editor.wordSeparators @@ -140,23 +148,34 @@ export class QueryBuilder { } private commonQuery(folderResources: uri[] = [], options: ICommonQueryBuilderOptions = {}): ICommonQueryProps { - const { searchPaths, pattern: includePattern } = this.parseSearchPaths(options.includePattern || ''); - const excludePattern = this.parseSearchPaths(options.excludePattern || ''); + let includeSearchPathsInfo: ISearchPathsInfo = {}; + if (options.includePattern) { + includeSearchPathsInfo = options.expandPatterns ? + this.parseSearchPaths(options.includePattern) : + { pattern: patternListToIExpression(options.includePattern) }; + } + + let excludeSearchPathsInfo: ISearchPathsInfo = {}; + if (options.excludePattern) { + excludeSearchPathsInfo = options.expandPatterns ? + this.parseSearchPaths(options.excludePattern) : + { pattern: patternListToIExpression(options.excludePattern) }; + } // Build folderQueries from searchPaths, if given, otherwise folderResources - const folderQueries = (searchPaths && searchPaths.length ? - searchPaths.map(searchPath => this.getFolderQueryForSearchPath(searchPath, options, excludePattern)) : - folderResources.map(uri => this.getFolderQueryForRoot(uri, options, excludePattern))) + const folderQueries = (includeSearchPathsInfo.searchPaths && includeSearchPathsInfo.searchPaths.length ? + includeSearchPathsInfo.searchPaths.map(searchPath => this.getFolderQueryForSearchPath(searchPath, options, excludeSearchPathsInfo)) : + folderResources.map(uri => this.getFolderQueryForRoot(uri, options, excludeSearchPathsInfo))) .filter(query => !!query) as IFolderQuery[]; const queryProps: ICommonQueryProps = { _reason: options._reason, folderQueries, - usingSearchPaths: !!(searchPaths && searchPaths.length), + usingSearchPaths: !!(includeSearchPathsInfo.searchPaths && includeSearchPathsInfo.searchPaths.length), extraFileResources: options.extraFileResources, - excludePattern: excludePattern.pattern, - includePattern, + excludePattern: excludeSearchPathsInfo.pattern, + includePattern: includeSearchPathsInfo.pattern, maxResults: options.maxResults }; @@ -207,10 +226,10 @@ export class QueryBuilder { * * Public for test. */ - parseSearchPaths(pattern: string): ISearchPathsResult { + parseSearchPaths(pattern: string): ISearchPathsInfo { const isSearchPath = (segment: string) => { // A segment is a search path if it is an absolute path or starts with ./, ../, .\, or ..\ - return paths.isAbsolute(segment) || /^\.\.?[\/\\]/.test(segment); + return path.isAbsolute(segment) || /^\.\.?[\/\\]/.test(segment); }; const segments = splitGlobPattern(pattern) @@ -229,7 +248,7 @@ export class QueryBuilder { return expandGlobalGlob(p); }); - const result: ISearchPathsResult = {}; + const result: ISearchPathsInfo = {}; const searchPaths = this.expandSearchPathPatterns(groups.searchPaths || []); if (searchPaths && searchPaths.length) { result.searchPaths = searchPaths; @@ -247,11 +266,11 @@ export class QueryBuilder { private getExcludesForFolder(folderConfig: ISearchConfiguration, options: ICommonQueryBuilderOptions): glob.IExpression | undefined { return options.disregardExcludeSettings ? undefined : - getExcludes(folderConfig); + getExcludes(folderConfig, !options.disregardSearchExcludeSettings); } /** - * Split search paths (./ or absolute paths in the includePatterns) into absolute paths and globs applied to those paths + * Split search paths (./ or ../ or absolute paths in the includePatterns) into absolute paths and globs applied to those paths */ private expandSearchPathPatterns(searchPaths: string[]): ISearchPathPattern[] { if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY || !searchPaths || !searchPaths.length) { @@ -297,19 +316,28 @@ export class QueryBuilder { } /** - * Takes a searchPath like `./a/foo` and expands it to absolute paths for all the workspaces it matches. + * Takes a searchPath like `./a/foo` or `../a/foo` and expands it to absolute paths for all the workspaces it matches. */ private expandOneSearchPath(searchPath: string): IOneSearchPathPattern[] { - if (paths.isAbsolute(searchPath)) { + if (path.isAbsolute(searchPath)) { // Currently only local resources can be searched for with absolute search paths. // TODO convert this to a workspace folder + pattern, so excludes will be resolved properly for an absolute path inside a workspace folder return [{ - searchPath: uri.file(paths.normalize(searchPath)) + searchPath: uri.file(path.normalize(searchPath)) }]; } if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.FOLDER) { const workspaceUri = this.workspaceContextService.getWorkspace().folders[0].uri; + + searchPath = normalizeSlashes(searchPath); + if (strings.startsWith(searchPath, '../')) { + const resolvedPath = path.posix.resolve(workspaceUri.path, searchPath); + return [{ + searchPath: workspaceUri.with({ path: resolvedPath }) + }]; + } + const cleanedPattern = normalizeGlobPattern(searchPath); return [{ searchPath: workspaceUri, @@ -364,7 +392,7 @@ export class QueryBuilder { return results; } - private getFolderQueryForSearchPath(searchPath: ISearchPathPattern, options: ICommonQueryBuilderOptions, searchPathExcludes: ISearchPathsResult): IFolderQuery | null { + private getFolderQueryForSearchPath(searchPath: ISearchPathPattern, options: ICommonQueryBuilderOptions, searchPathExcludes: ISearchPathsInfo): IFolderQuery | null { const rootConfig = this.getFolderQueryForRoot(searchPath.searchPath, options, searchPathExcludes); if (!rootConfig) { return null; @@ -378,7 +406,7 @@ export class QueryBuilder { }; } - private getFolderQueryForRoot(folder: uri, options: ICommonQueryBuilderOptions, searchPathExcludes: ISearchPathsResult): IFolderQuery | null { + private getFolderQueryForRoot(folder: uri, options: ICommonQueryBuilderOptions, searchPathExcludes: ISearchPathsInfo): IFolderQuery | null { let thisFolderExcludeSearchPathPattern: glob.IExpression | undefined; if (searchPathExcludes.searchPaths) { const thisFolderExcludeSearchPath = searchPathExcludes.searchPaths.filter(sp => isEqual(sp.searchPath, folder))[0]; diff --git a/src/vs/workbench/parts/search/common/replace.ts b/src/vs/workbench/contrib/search/common/replace.ts similarity index 97% rename from src/vs/workbench/parts/search/common/replace.ts rename to src/vs/workbench/contrib/search/common/replace.ts index a877f72cee..7acc0280f0 100644 --- a/src/vs/workbench/parts/search/common/replace.ts +++ b/src/vs/workbench/contrib/search/common/replace.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Match, FileMatch, FileMatchOrMatch } from 'vs/workbench/parts/search/common/searchModel'; +import { Match, FileMatch, FileMatchOrMatch } from 'vs/workbench/contrib/search/common/searchModel'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProgressRunner } from 'vs/platform/progress/common/progress'; diff --git a/src/vs/workbench/parts/search/common/search.ts b/src/vs/workbench/contrib/search/common/search.ts similarity index 98% rename from src/vs/workbench/parts/search/common/search.ts rename to src/vs/workbench/contrib/search/common/search.ts index 196012b83f..cf49453284 100644 --- a/src/vs/workbench/parts/search/common/search.ts +++ b/src/vs/workbench/contrib/search/common/search.ts @@ -5,7 +5,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { ISearchConfiguration, ISearchConfigurationProperties } from 'vs/platform/search/common/search'; +import { ISearchConfiguration, ISearchConfigurationProperties } from 'vs/workbench/services/search/common/search'; import { SymbolKind, Location, ProviderResult } from 'vs/editor/common/modes'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { URI } from 'vs/base/common/uri'; diff --git a/src/vs/workbench/services/search/node/searchHistoryService.ts b/src/vs/workbench/contrib/search/common/searchHistoryService.ts similarity index 77% rename from src/vs/workbench/services/search/node/searchHistoryService.ts rename to src/vs/workbench/contrib/search/common/searchHistoryService.ts index 78209b4af1..5e6334caec 100644 --- a/src/vs/workbench/services/search/node/searchHistoryService.ts +++ b/src/vs/workbench/contrib/search/common/searchHistoryService.ts @@ -4,9 +4,26 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { ISearchHistoryValues, ISearchHistoryService } from 'vs/platform/search/common/search'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { isEmptyObject } from 'vs/base/common/types'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export interface ISearchHistoryService { + _serviceBrand: any; + onDidClearHistory: Event; + clearHistory(): void; + load(): ISearchHistoryValues; + save(history: ISearchHistoryValues): void; +} + +export const ISearchHistoryService = createDecorator('searchHistoryService'); + +export interface ISearchHistoryValues { + search?: string[]; + replace?: string[]; + include?: string[]; + exclude?: string[]; +} export class SearchHistoryService implements ISearchHistoryService { _serviceBrand: any; diff --git a/src/vs/workbench/parts/search/common/searchModel.ts b/src/vs/workbench/contrib/search/common/searchModel.ts similarity index 99% rename from src/vs/workbench/parts/search/common/searchModel.ts rename to src/vs/workbench/contrib/search/common/searchModel.ts index afa962df13..af0b4b2cfd 100644 --- a/src/vs/workbench/parts/search/common/searchModel.ts +++ b/src/vs/workbench/contrib/search/common/searchModel.ts @@ -19,12 +19,12 @@ import { ModelDecorationOptions } from 'vs/editor/common/model/textModel'; import { IModelService } from 'vs/editor/common/services/modelService'; import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IProgressRunner } from 'vs/platform/progress/common/progress'; -import { ReplacePattern } from 'vs/platform/search/common/replace'; -import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange } from 'vs/platform/search/common/search'; +import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; +import { IFileMatch, IPatternInfo, ISearchComplete, ISearchProgressItem, ISearchService, ITextQuery, ITextSearchPreviewOptions, ITextSearchMatch, ITextSearchStats, resultIsMatch, ISearchRange, OneLineRange } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; -import { IReplaceService } from 'vs/workbench/parts/search/common/replace'; +import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; import { editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; export class Match { @@ -79,6 +79,7 @@ export class Match { after = this._oneLinePreviewText.substring(this._rangeInPreviewText.endColumn - 1); before = lcut(before, 26); + before = before.trimLeft(); let charsRemaining = Match.MAX_PREVIEW_CHARS - before.length; inside = inside.substr(0, charsRemaining); @@ -598,7 +599,7 @@ export class FolderMatch extends BaseFolderMatch { * and their sort order is undefined. */ export function searchMatchComparer(elementA: RenderableMatch, elementB: RenderableMatch): number { - if (elementA instanceof FolderMatch && elementB instanceof FolderMatch) { + if (elementA instanceof BaseFolderMatch && elementB instanceof BaseFolderMatch) { return elementA.index() - elementB.index(); } diff --git a/src/vs/workbench/parts/search/test/browser/mockSearchTree.ts b/src/vs/workbench/contrib/search/test/browser/mockSearchTree.ts similarity index 100% rename from src/vs/workbench/parts/search/test/browser/mockSearchTree.ts rename to src/vs/workbench/contrib/search/test/browser/mockSearchTree.ts diff --git a/src/vs/workbench/parts/search/test/browser/openFileHandler.test.ts b/src/vs/workbench/contrib/search/test/browser/openFileHandler.test.ts similarity index 97% rename from src/vs/workbench/parts/search/test/browser/openFileHandler.test.ts rename to src/vs/workbench/contrib/search/test/browser/openFileHandler.test.ts index ba0648df5c..32648ebd30 100644 --- a/src/vs/workbench/parts/search/test/browser/openFileHandler.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/openFileHandler.test.ts @@ -6,9 +6,9 @@ import * as assert from 'assert'; import * as errors from 'vs/base/common/errors'; import * as objects from 'vs/base/common/objects'; -import { CacheState } from 'vs/workbench/parts/search/browser/openFileHandler'; +import { CacheState } from 'vs/workbench/contrib/search/browser/openFileHandler'; import { DeferredPromise } from 'vs/base/test/common/utils'; -import { QueryType, IFileQuery } from 'vs/platform/search/common/search'; +import { QueryType, IFileQuery } from 'vs/workbench/services/search/common/search'; suite('CacheState', () => { diff --git a/src/vs/workbench/parts/search/test/browser/searchActions.test.ts b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts similarity index 95% rename from src/vs/workbench/parts/search/test/browser/searchActions.test.ts rename to src/vs/workbench/contrib/search/test/browser/searchActions.test.ts index ce1b3d79e3..ef7eeb7a85 100644 --- a/src/vs/workbench/parts/search/test/browser/searchActions.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchActions.test.ts @@ -14,10 +14,10 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; -import { IFileMatch } from 'vs/platform/search/common/search'; -import { ReplaceAction } from 'vs/workbench/parts/search/browser/searchActions'; -import { FileMatch, FileMatchOrMatch, Match } from 'vs/workbench/parts/search/common/searchModel'; -import { MockObjectTree } from 'vs/workbench/parts/search/test/browser/mockSearchTree'; +import { IFileMatch } from 'vs/workbench/services/search/common/search'; +import { ReplaceAction } from 'vs/workbench/contrib/search/browser/searchActions'; +import { FileMatch, FileMatchOrMatch, Match } from 'vs/workbench/contrib/search/common/searchModel'; +import { MockObjectTree } from 'vs/workbench/contrib/search/test/browser/mockSearchTree'; suite('Search Actions', () => { diff --git a/src/vs/workbench/parts/search/test/browser/searchViewlet.test.ts b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts similarity index 97% rename from src/vs/workbench/parts/search/test/browser/searchViewlet.test.ts rename to src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts index 81703472e6..a5ffa0da2e 100644 --- a/src/vs/workbench/parts/search/test/browser/searchViewlet.test.ts +++ b/src/vs/workbench/contrib/search/test/browser/searchViewlet.test.ts @@ -9,10 +9,10 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { IFileMatch, ITextSearchMatch, OneLineRange, QueryType } from 'vs/platform/search/common/search'; +import { IFileMatch, ITextSearchMatch, OneLineRange, QueryType } from 'vs/workbench/services/search/common/search'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; -import { FileMatch, Match, searchMatchComparer, SearchResult } from 'vs/workbench/parts/search/common/searchModel'; +import { FileMatch, Match, searchMatchComparer, SearchResult } from 'vs/workbench/contrib/search/common/searchModel'; import { TestContextService } from 'vs/workbench/test/workbenchTestServices'; suite('Search - Viewlet', () => { diff --git a/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts b/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts similarity index 86% rename from src/vs/workbench/parts/search/test/common/queryBuilder.test.ts rename to src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts index 60abb60ab6..b0196914af 100644 --- a/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts +++ b/src/vs/workbench/contrib/search/test/common/queryBuilder.test.ts @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; import { IExpression } from 'vs/base/common/glob'; -import * as paths from 'vs/base/common/paths'; +import { join } from 'vs/base/common/path'; import { URI as uri } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { IFolderQuery, IPatternInfo, QueryType, ITextQuery, IFileQuery } from 'vs/platform/search/common/search'; +import { IFolderQuery, IPatternInfo, QueryType, ITextQuery, IFileQuery } from 'vs/workbench/services/search/common/search'; import { IWorkspaceContextService, toWorkspaceFolders, Workspace } from 'vs/platform/workspace/common/workspace'; -import { ISearchPathsResult, QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder'; +import { ISearchPathsInfo, QueryBuilder } from 'vs/workbench/contrib/search/common/queryBuilder'; import { TestContextService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices'; const DEFAULT_EDITOR_CONFIG = {}; @@ -59,6 +59,46 @@ suite('QueryBuilder', () => { }); }); + test('normalize literal newlines', () => { + assertEqualTextQueries( + queryBuilder.text({ pattern: 'foo\nbar', isRegExp: true }), + { + folderQueries: [], + contentPattern: { + pattern: 'foo\\nbar', + isRegExp: true, + isMultiline: true + }, + type: QueryType.Text + }); + + assertEqualTextQueries( + queryBuilder.text({ pattern: 'foo\nbar', isRegExp: false }), + { + folderQueries: [], + contentPattern: { + pattern: 'foo\nbar', + isRegExp: false, + isMultiline: true + }, + type: QueryType.Text + }); + }); + + test('does not split glob pattern when expandPatterns disabled', () => { + assertEqualQueries( + queryBuilder.file([ROOT_1_URI], { includePattern: '**/foo, **/bar' }), + { + folderQueries: [{ + folder: ROOT_1_URI + }], + type: QueryType.File, + includePattern: { + '**/foo, **/bar': true + } + }); + }); + test('folderResources', () => { assertEqualTextQueries( queryBuilder.text( @@ -86,7 +126,10 @@ suite('QueryBuilder', () => { assertEqualTextQueries( queryBuilder.text( PATTERN_INFO, - [ROOT_1_URI] + [ROOT_1_URI], + { + expandPatterns: true // verify that this doesn't affect patterns from configuration + } ), { contentPattern: PATTERN_INFO, @@ -108,7 +151,53 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI], - { includePattern: './bar' } + { + includePattern: 'bar', + expandPatterns: true + } + ), + { + contentPattern: PATTERN_INFO, + folderQueries: [{ + folder: ROOT_1_URI + }], + includePattern: { + '**/bar': true, + '**/bar/**': true + }, + type: QueryType.Text + }); + + assertEqualTextQueries( + queryBuilder.text( + PATTERN_INFO, + [ROOT_1_URI], + { + includePattern: 'bar' + } + ), + { + contentPattern: PATTERN_INFO, + folderQueries: [{ + folder: ROOT_1_URI + }], + includePattern: { + 'bar': true + }, + type: QueryType.Text + }); + }); + + test('simple include with ./ syntax', () => { + + assertEqualTextQueries( + queryBuilder.text( + PATTERN_INFO, + [ROOT_1_URI], + { + includePattern: './bar', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -126,7 +215,10 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI], - { includePattern: '.\\bar' } + { + includePattern: '.\\bar', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -156,7 +248,10 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI], - { includePattern: './foo' } + { + includePattern: './foo', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -217,7 +312,10 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI, ROOT_2_URI, ROOT_3_URI], - { includePattern: './root2/src' } + { + includePattern: './root2/src', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -243,7 +341,10 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI], - { excludePattern: 'foo' } + { + excludePattern: 'foo', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -274,7 +375,10 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI], - { excludePattern: './bar' } + { + excludePattern: './bar', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -289,7 +393,10 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI], - { excludePattern: './bar/**/*.ts' } + { + excludePattern: './bar/**/*.ts', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -304,7 +411,10 @@ suite('QueryBuilder', () => { queryBuilder.text( PATTERN_INFO, [ROOT_1_URI], - { excludePattern: '.\\bar\\**\\*.ts' } + { + excludePattern: '.\\bar\\**\\*.ts', + expandPatterns: true + } ), { contentPattern: PATTERN_INFO, @@ -338,7 +448,8 @@ suite('QueryBuilder', () => { [ROOT_1_URI], { extraFileResources: [getUri('/foo/bar.js')], - excludePattern: '*.js' + excludePattern: '*.js', + expandPatterns: true } ), { @@ -356,7 +467,8 @@ suite('QueryBuilder', () => { [ROOT_1_URI], { extraFileResources: [getUri('/foo/bar.js')], - includePattern: '*.txt' + includePattern: '*.txt', + expandPatterns: true } ), { @@ -390,19 +502,19 @@ suite('QueryBuilder', () => { ].forEach(([includePattern, expectedPatterns]) => testSimpleIncludes(includePattern, expectedPatterns)); }); - function testIncludes(includePattern: string, expectedResult: ISearchPathsResult): void { + function testIncludes(includePattern: string, expectedResult: ISearchPathsInfo): void { assertEqualSearchPathResults( queryBuilder.parseSearchPaths(includePattern), expectedResult, includePattern); } - function testIncludesDataItem([includePattern, expectedResult]: [string, ISearchPathsResult]): void { + function testIncludesDataItem([includePattern, expectedResult]: [string, ISearchPathsInfo]): void { testIncludes(includePattern, expectedResult); } test('absolute includes', () => { - const cases: [string, ISearchPathsResult][] = [ + const cases: [string, ISearchPathsInfo][] = [ [ fixPath('/foo/bar'), { @@ -472,7 +584,7 @@ suite('QueryBuilder', () => { test('includes with tilde', () => { const userHome = TestEnvironmentService.userHome; - const cases: [string, ISearchPathsResult][] = [ + const cases: [string, ISearchPathsInfo][] = [ [ '~/foo/bar', { @@ -497,7 +609,7 @@ suite('QueryBuilder', () => { }); test('relative includes w/single root folder', () => { - const cases: [string, ISearchPathsResult][] = [ + const cases: [string, ISearchPathsInfo][] = [ [ './a', { @@ -547,15 +659,22 @@ suite('QueryBuilder', () => { }] } ], - // TODO @ rob - // [ - // '../', - // { - // searchPaths: [{ - // searchPath: getUri('foo/') - // }] - // } - // ] + [ + '../', + { + searchPaths: [{ + searchPath: getUri('/foo') + }] + } + ], + [ + '..\\bar', + { + searchPaths: [{ + searchPath: getUri('/foo/bar') + }] + } + ] ]; cases.forEach(testIncludesDataItem); }); @@ -565,7 +684,7 @@ suite('QueryBuilder', () => { mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: getUri(ROOT_2).fsPath }]); mockWorkspace.configuration = uri.file(fixPath('config')); - const cases: [string, ISearchPathsResult][] = [ + const cases: [string, ISearchPathsInfo][] = [ [ './root1', { @@ -606,7 +725,7 @@ suite('QueryBuilder', () => { mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath, name: ROOT_1_FOLDERNAME }, { path: getUri(ROOT_2).fsPath }]); mockWorkspace.configuration = uri.file(fixPath('config')); - const cases: [string, ISearchPathsResult][] = [ + const cases: [string, ISearchPathsInfo][] = [ [ './foldername', { @@ -634,7 +753,7 @@ suite('QueryBuilder', () => { mockWorkspace.folders = toWorkspaceFolders([{ path: ROOT_1_URI.fsPath }, { path: getUri(ROOT_2).fsPath }, { path: getUri(ROOT_3).fsPath }]); mockWorkspace.configuration = uri.file(fixPath('/config')); - const cases: [string, ISearchPathsResult][] = [ + const cases: [string, ISearchPathsInfo][] = [ [ '', { @@ -856,7 +975,7 @@ function assertEqualQueries(actual: ITextQuery | IFileQuery, expected: ITextQuer assert.deepEqual(actual, expected); } -function assertEqualSearchPathResults(actual: ISearchPathsResult, expected: ISearchPathsResult, message?: string): void { +function assertEqualSearchPathResults(actual: ISearchPathsInfo, expected: ISearchPathsInfo, message?: string): void { cleanUndefinedQueryValues(actual); assert.deepEqual(actual.pattern, expected.pattern, message); @@ -908,7 +1027,7 @@ function fixPath(...slashPathParts: string[]): string { slashPathParts.unshift('c:'); } - return paths.join(...slashPathParts); + return join(...slashPathParts); } function normalizeExpression(expression: IExpression | undefined): IExpression | undefined { diff --git a/src/vs/workbench/parts/search/test/common/searchModel.test.ts b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts similarity index 98% rename from src/vs/workbench/parts/search/test/common/searchModel.test.ts rename to src/vs/workbench/contrib/search/test/common/searchModel.test.ts index ac72e14837..b021e53b3f 100644 --- a/src/vs/workbench/parts/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchModel.test.ts @@ -14,10 +14,10 @@ import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { IFileMatch, IFileSearchStats, IFolderQuery, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchService, ITextSearchMatch, OneLineRange, TextSearchMatch } from 'vs/platform/search/common/search'; +import { IFileMatch, IFileSearchStats, IFolderQuery, ISearchComplete, ISearchProgressItem, ISearchQuery, ISearchService, ITextSearchMatch, OneLineRange, TextSearchMatch } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { SearchModel } from 'vs/workbench/parts/search/common/searchModel'; +import { SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; const nullEvent = new class { id: number; diff --git a/src/vs/workbench/parts/search/test/common/searchResult.test.ts b/src/vs/workbench/contrib/search/test/common/searchResult.test.ts similarity index 98% rename from src/vs/workbench/parts/search/test/common/searchResult.test.ts rename to src/vs/workbench/contrib/search/test/common/searchResult.test.ts index b80bfa0362..9984d40836 100644 --- a/src/vs/workbench/parts/search/test/common/searchResult.test.ts +++ b/src/vs/workbench/contrib/search/test/common/searchResult.test.ts @@ -5,9 +5,9 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import { Match, FileMatch, SearchResult, SearchModel } from 'vs/workbench/parts/search/common/searchModel'; +import { Match, FileMatch, SearchResult, SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; import { URI } from 'vs/base/common/uri'; -import { IFileMatch, TextSearchMatch, OneLineRange, ITextSearchMatch } from 'vs/platform/search/common/search'; +import { IFileMatch, TextSearchMatch, OneLineRange, ITextSearchMatch } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { Range } from 'vs/editor/common/core/range'; @@ -15,7 +15,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IReplaceService } from 'vs/workbench/parts/search/common/replace'; +import { IReplaceService } from 'vs/workbench/contrib/search/common/replace'; const lineOneRange = new OneLineRange(1, 0, 1); diff --git a/src/vs/workbench/parts/snippets/electron-browser/configureSnippets.ts b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts similarity index 85% rename from src/vs/workbench/parts/snippets/electron-browser/configureSnippets.ts rename to src/vs/workbench/contrib/snippets/browser/configureSnippets.ts index 278ebdb3e5..4baeb78862 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/configureSnippets.ts +++ b/src/vs/workbench/contrib/snippets/browser/configureSnippets.ts @@ -8,15 +8,15 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { join, basename, dirname, extname } from 'path'; +import { join, basename, dirname, extname } from 'vs/base/common/path'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { timeout } from 'vs/base/common/async'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; -import { ISnippetsService } from 'vs/workbench/parts/snippets/electron-browser/snippets.contribution'; +import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; import { values } from 'vs/base/common/map'; import { IQuickPickItem, IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { SnippetSource } from 'vs/workbench/parts/snippets/electron-browser/snippetsFile'; +import { SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IFileService } from 'vs/platform/files/common/files'; import { INotificationService } from 'vs/platform/notification/common/notification'; @@ -78,7 +78,7 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir } else { // language snippet - const mode = basename(file.location.fsPath, '.json'); + const mode = basename(file.location.fsPath).replace(/\.json$/, ''); existing.push({ label: basename(file.location.fsPath), description: `(${modeService.getLanguageName(mode)})`, @@ -120,7 +120,7 @@ async function computePicks(snippetService: ISnippetsService, envService: IEnvir return { existing, future }; } -async function createGlobalSnippetFile(defaultPath: URI, windowService: IWindowService, notificationService: INotificationService, fileService: IFileService, opener: IOpenerService) { +async function createSnippetFile(scope: string, defaultPath: URI, windowService: IWindowService, notificationService: INotificationService, fileService: IFileService, opener: IOpenerService) { await fileService.createFolder(defaultPath); await timeout(100); // ensure quick pick closes... @@ -140,7 +140,7 @@ async function createGlobalSnippetFile(defaultPath: URI, windowService: IWindowS await fileService.updateContent(resource, [ '{', - '\t// Place your global snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and ', + '\t// Place your ' + scope + ' snippets here. Each snippet is defined under a snippet name and has a scope, prefix, body and ', '\t// description. Add comma separated ids of the languages where the snippet is applicable in the scope field. If scope ', '\t// is left empty or omitted, the snippet gets applied to all languages. The prefix is what is ', '\t// used to trigger the snippet and the body will be expanded and inserted. Possible variables are: ', @@ -202,13 +202,17 @@ CommandsRegistry.registerCommand(id, async (accessor): Promise => { const picks = await computePicks(snippetService, envService, modeService); const existing: QuickPickInput[] = picks.existing; - type GlobalSnippetPick = IQuickPickItem & { uri: URI }; - const globalSnippetPicks: GlobalSnippetPick[] = [{ + type SnippetPick = IQuickPickItem & { uri: URI } & { scope: string }; + const globalSnippetPicks: SnippetPick[] = [{ + scope: nls.localize('new.global_scope', 'global'), label: nls.localize('new.global', "New Global Snippets file..."), uri: URI.file(join(envService.appSettingsHome, 'snippets')) }]; + + const workspaceSnippetPicks: SnippetPick[] = []; for (const folder of workspaceService.getWorkspace().folders) { - globalSnippetPicks.push({ + workspaceSnippetPicks.push({ + scope: nls.localize('new.workspace_scope', "{0} workspace", folder.name), label: nls.localize('new.folder', "New Snippets file for '{0}'...", folder.name), uri: folder.toResource('.vscode') }); @@ -221,13 +225,15 @@ CommandsRegistry.registerCommand(id, async (accessor): Promise => { existing.push({ type: 'separator', label: nls.localize('new.global.sep', "New Snippets") }); } - const pick = await quickInputService.pick(([] as QuickPickInput[]).concat(existing, globalSnippetPicks, picks.future), { + const pick = await quickInputService.pick(([] as QuickPickInput[]).concat(existing, globalSnippetPicks, workspaceSnippetPicks, picks.future), { placeHolder: nls.localize('openSnippet.pickLanguage', "Select Snippets File or Create Snippets"), matchOnDescription: true }); - if (globalSnippetPicks.indexOf(pick as GlobalSnippetPick) >= 0) { - return createGlobalSnippetFile((pick as GlobalSnippetPick).uri, windowService, notificationService, fileService, opener); + if (globalSnippetPicks.indexOf(pick as SnippetPick) >= 0) { + return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, windowService, notificationService, fileService, opener); + } else if (workspaceSnippetPicks.indexOf(pick as SnippetPick) >= 0) { + return createSnippetFile((pick as SnippetPick).scope, (pick as SnippetPick).uri, windowService, notificationService, fileService, opener); } else if (ISnippetPick.is(pick)) { if (pick.hint) { await createLanguageSnippetFile(pick, fileService); diff --git a/src/vs/workbench/parts/snippets/electron-browser/insertSnippet.ts b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts similarity index 89% rename from src/vs/workbench/parts/snippets/electron-browser/insertSnippet.ts rename to src/vs/workbench/contrib/snippets/browser/insertSnippet.ts index d42575739f..ff41aa2fe6 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/insertSnippet.ts +++ b/src/vs/workbench/contrib/snippets/browser/insertSnippet.ts @@ -8,11 +8,11 @@ import { registerEditorAction, ServicesAccessor, EditorAction } from 'vs/editor/ import { IModeService } from 'vs/editor/common/services/modeService'; import { LanguageId } from 'vs/editor/common/modes'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { ISnippetsService } from 'vs/workbench/parts/snippets/electron-browser/snippets.contribution'; +import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { Snippet, SnippetSource } from 'vs/workbench/parts/snippets/electron-browser/snippetsFile'; +import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { IQuickPickItem, IQuickInputService, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; interface ISnippetPick extends IQuickPickItem { @@ -54,7 +54,28 @@ class InsertSnippetAction extends EditorAction { id: 'editor.action.insertSnippet', label: nls.localize('snippet.suggestions.label', "Insert Snippet"), alias: 'Insert Snippet', - precondition: EditorContextKeys.writable + precondition: EditorContextKeys.writable, + description: { + description: `Insert Snippet`, + args: [{ + name: 'args', + schema: { + 'type': 'object', + 'properties': { + 'snippet': { + 'type': 'string' + }, + 'langId': { + 'type': 'string', + + }, + 'name': { + 'type': 'string' + } + }, + } + }] + } }); } diff --git a/src/vs/workbench/parts/snippets/electron-browser/snippetCompletionProvider.ts b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts similarity index 90% rename from src/vs/workbench/parts/snippets/electron-browser/snippetCompletionProvider.ts rename to src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts index e780190826..8c581fa1bc 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/snippetCompletionProvider.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetCompletionProvider.ts @@ -8,12 +8,12 @@ import { compare } from 'vs/base/common/strings'; import { Position } from 'vs/editor/common/core/position'; import { IRange, Range } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; -import { CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionList, LanguageId, CompletionItemInsertTextRule } from 'vs/editor/common/modes'; +import { CompletionItem, CompletionItemKind, CompletionItemProvider, CompletionList, LanguageId, CompletionItemInsertTextRule, CompletionContext, CompletionTriggerKind } from 'vs/editor/common/modes'; import { IModeService } from 'vs/editor/common/services/modeService'; import { SnippetParser } from 'vs/editor/contrib/snippet/snippetParser'; import { localize } from 'vs/nls'; -import { ISnippetsService } from 'vs/workbench/parts/snippets/electron-browser/snippets.contribution'; -import { Snippet, SnippetSource } from 'vs/workbench/parts/snippets/electron-browser/snippetsFile'; +import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; +import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; export class SnippetCompletion implements CompletionItem { @@ -73,12 +73,17 @@ export class SnippetCompletionProvider implements CompletionItemProvider { // } - provideCompletionItems(model: ITextModel, position: Position): Promise | undefined { + provideCompletionItems(model: ITextModel, position: Position, context: CompletionContext): Promise | undefined { if (position.column >= SnippetCompletionProvider._maxPrefix) { return undefined; } + if (context.triggerKind === CompletionTriggerKind.TriggerCharacter && context.triggerCharacter === ' ') { + // no snippets when suggestions have been triggered by space + return undefined; + } + const languageId = this._getLanguageIdAtPosition(model, position); return this._snippets.getSnippets(languageId).then(snippets => { diff --git a/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts similarity index 97% rename from src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts rename to src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts index 86752dae7c..dd1759d308 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippets.contribution.ts @@ -9,7 +9,7 @@ import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonCo import * as nls from 'vs/nls'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { LanguageId } from 'vs/editor/common/modes'; -import { SnippetFile, Snippet } from 'vs/workbench/parts/snippets/electron-browser/snippetsFile'; +import { SnippetFile, Snippet } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; export const ISnippetsService = createDecorator('snippetService'); diff --git a/src/vs/workbench/parts/snippets/electron-browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts similarity index 97% rename from src/vs/workbench/parts/snippets/electron-browser/snippetsFile.ts rename to src/vs/workbench/contrib/snippets/browser/snippetsFile.ts index 7673636a6d..6a6757bbe1 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/snippetsFile.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts @@ -5,14 +5,14 @@ import { parse as jsonParse } from 'vs/base/common/json'; import { forEach } from 'vs/base/common/collections'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { localize } from 'vs/nls'; -import { basename, extname } from 'path'; +import { extname, basename } from 'vs/base/common/path'; import { SnippetParser, Variable, Placeholder, Text } from 'vs/editor/contrib/snippet/snippetParser'; import { KnownSnippetVariableNames } from 'vs/editor/contrib/snippet/snippetVariables'; import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export class Snippet { @@ -166,7 +166,7 @@ export class SnippetFile { private _filepathSelect(selector: string, bucket: Snippet[]): void { // for `fooLang.json` files all snippets are accepted - if (selector === basename(this.location.path, '.json')) { + if (selector + '.json' === basename(this.location.path)) { bucket.push(...this.data); } } diff --git a/src/vs/workbench/parts/snippets/electron-browser/snippetsService.ts b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts similarity index 97% rename from src/vs/workbench/parts/snippets/electron-browser/snippetsService.ts rename to src/vs/workbench/contrib/snippets/browser/snippetsService.ts index a29a712343..657310ab25 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/snippetsService.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { basename, extname, join } from 'path'; +import { join } from 'vs/base/common/path'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { combinedDisposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { values } from 'vs/base/common/map'; @@ -21,8 +21,8 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkspace, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ISnippetsService } from 'vs/workbench/parts/snippets/electron-browser/snippets.contribution'; -import { Snippet, SnippetFile, SnippetSource } from 'vs/workbench/parts/snippets/electron-browser/snippetsFile'; +import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; +import { Snippet, SnippetFile, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService'; import { SnippetCompletionProvider } from './snippetCompletionProvider'; @@ -107,7 +107,6 @@ namespace snippetExt { }; export const point = ExtensionsRegistry.registerExtensionPoint({ - isDynamic: true, extensionPoint: 'snippets', deps: [languagesExtPoint], jsonSchema: snippetExt.snippetsContribution @@ -321,10 +320,10 @@ class SnippetsService implements ISnippetsService { } private _addSnippetFile(uri: URI, source: SnippetSource): IDisposable { - const ext = extname(uri.path); + const ext = resources.extname(uri); const key = uri.toString(); if (source === SnippetSource.User && ext === '.json') { - const langName = basename(uri.path, '.json'); + const langName = resources.basename(uri).replace(/\.json/, ''); this._files.set(key, new SnippetFile(source, uri, [langName], undefined, this._fileService)); } else if (ext === '.code-snippets') { this._files.set(key, new SnippetFile(source, uri, undefined, undefined, this._fileService)); diff --git a/src/vs/workbench/parts/snippets/electron-browser/tabCompletion.ts b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts similarity index 93% rename from src/vs/workbench/parts/snippets/electron-browser/tabCompletion.ts rename to src/vs/workbench/contrib/snippets/browser/tabCompletion.ts index 6fb7d3e1f4..89e2ed7d79 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/tabCompletion.ts +++ b/src/vs/workbench/contrib/snippets/browser/tabCompletion.ts @@ -6,8 +6,8 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { RawContextKey, IContextKeyService, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { ISnippetsService } from 'vs/workbench/parts/snippets/electron-browser/snippets.contribution'; -import { getNonWhitespacePrefix } from 'vs/workbench/parts/snippets/electron-browser/snippetsService'; +import { ISnippetsService } from './snippets.contribution'; +import { getNonWhitespacePrefix } from './snippetsService'; import { endsWith } from 'vs/base/common/strings'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import * as editorCommon from 'vs/editor/common/editorCommon'; @@ -17,8 +17,8 @@ import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2 import { showSimpleSuggestions } from 'vs/editor/contrib/suggest/suggest'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { Snippet } from 'vs/workbench/parts/snippets/electron-browser/snippetsFile'; -import { SnippetCompletion } from 'vs/workbench/parts/snippets/electron-browser/snippetCompletionProvider'; +import { Snippet } from './snippetsFile'; +import { SnippetCompletion } from './snippetCompletionProvider'; export class TabCompletionController implements editorCommon.IEditorContribution { diff --git a/src/vs/workbench/parts/snippets/test/electron-browser/snippetFile.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts similarity index 98% rename from src/vs/workbench/parts/snippets/test/electron-browser/snippetFile.test.ts rename to src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts index 671bf405d5..f02be4d25a 100644 --- a/src/vs/workbench/parts/snippets/test/electron-browser/snippetFile.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetFile.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { SnippetFile, Snippet, SnippetSource } from 'vs/workbench/parts/snippets/electron-browser/snippetsFile'; +import { SnippetFile, Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { URI } from 'vs/base/common/uri'; suite('Snippets', function () { diff --git a/src/vs/workbench/parts/snippets/test/electron-browser/snippetsRegistry.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsRegistry.test.ts similarity index 97% rename from src/vs/workbench/parts/snippets/test/electron-browser/snippetsRegistry.test.ts rename to src/vs/workbench/contrib/snippets/test/browser/snippetsRegistry.test.ts index f229d47d98..88ecb379d9 100644 --- a/src/vs/workbench/parts/snippets/test/electron-browser/snippetsRegistry.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsRegistry.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { getNonWhitespacePrefix } from 'vs/workbench/parts/snippets/electron-browser/snippetsService'; +import { getNonWhitespacePrefix } from 'vs/workbench/contrib/snippets/browser/snippetsService'; import { Position } from 'vs/editor/common/core/position'; suite('getNonWhitespacePrefix', () => { diff --git a/src/vs/workbench/parts/snippets/test/electron-browser/snippetsRewrite.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts similarity index 94% rename from src/vs/workbench/parts/snippets/test/electron-browser/snippetsRewrite.test.ts rename to src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts index 94a09c7abb..99d07eda23 100644 --- a/src/vs/workbench/parts/snippets/test/electron-browser/snippetsRewrite.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsRewrite.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Snippet, SnippetSource } from 'vs/workbench/parts/snippets/electron-browser/snippetsFile'; +import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; suite('SnippetRewrite', function () { diff --git a/src/vs/workbench/parts/snippets/test/electron-browser/snippetsService.test.ts b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts similarity index 92% rename from src/vs/workbench/parts/snippets/test/electron-browser/snippetsService.test.ts rename to src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts index 4b1874324b..3e08000710 100644 --- a/src/vs/workbench/parts/snippets/test/electron-browser/snippetsService.test.ts +++ b/src/vs/workbench/contrib/snippets/test/browser/snippetsService.test.ts @@ -4,14 +4,15 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { SnippetCompletionProvider } from 'vs/workbench/parts/snippets/electron-browser/snippetCompletionProvider'; +import { SnippetCompletionProvider } from 'vs/workbench/contrib/snippets/browser/snippetCompletionProvider'; import { Position } from 'vs/editor/common/core/position'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { ISnippetsService } from 'vs/workbench/parts/snippets/electron-browser/snippets.contribution'; -import { Snippet, SnippetSource } from 'vs/workbench/parts/snippets/electron-browser/snippetsFile'; +import { ISnippetsService } from 'vs/workbench/contrib/snippets/browser/snippets.contribution'; +import { Snippet, SnippetSource } from 'vs/workbench/contrib/snippets/browser/snippetsFile'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { CompletionContext, CompletionTriggerKind } from 'vs/editor/common/modes'; class SimpleSnippetService implements ISnippetsService { _serviceBrand: any; @@ -39,6 +40,7 @@ suite('SnippetsService', function () { let modeService: ModeServiceImpl; let snippetService: ISnippetsService; + let context: CompletionContext = { triggerKind: CompletionTriggerKind.Invoke }; setup(function () { modeService = new ModeServiceImpl(); @@ -67,7 +69,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); const model = TextModel.createFromString('', undefined, modeService.getLanguageIdentifier('fooLang')); - return provider.provideCompletionItems(model, new Position(1, 1))!.then(result => { + return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { assert.equal(result.incomplete, undefined); assert.equal(result.suggestions.length, 2); }); @@ -78,7 +80,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); const model = TextModel.createFromString('bar', undefined, modeService.getLanguageIdentifier('fooLang')); - return provider.provideCompletionItems(model, new Position(1, 4))!.then(result => { + return provider.provideCompletionItems(model, new Position(1, 4), context)!.then(result => { assert.equal(result.incomplete, undefined); assert.equal(result.suggestions.length, 1); assert.equal(result.suggestions[0].label, 'bar'); @@ -110,7 +112,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); const model = TextModel.createFromString('bar-bar', undefined, modeService.getLanguageIdentifier('fooLang')); - await provider.provideCompletionItems(model, new Position(1, 3))!.then(result => { + await provider.provideCompletionItems(model, new Position(1, 3), context)!.then(result => { assert.equal(result.incomplete, undefined); assert.equal(result.suggestions.length, 2); assert.equal(result.suggestions[0].label, 'bar'); @@ -121,7 +123,7 @@ suite('SnippetsService', function () { assert.equal(result.suggestions[1].range.startColumn, 1); }); - await provider.provideCompletionItems(model, new Position(1, 5))!.then(result => { + await provider.provideCompletionItems(model, new Position(1, 5), context)!.then(result => { assert.equal(result.incomplete, undefined); assert.equal(result.suggestions.length, 1); assert.equal(result.suggestions[0].label, 'bar-bar'); @@ -129,7 +131,7 @@ suite('SnippetsService', function () { assert.equal(result.suggestions[0].range.startColumn, 1); }); - await provider.provideCompletionItems(model, new Position(1, 6))!.then(result => { + await provider.provideCompletionItems(model, new Position(1, 6), context)!.then(result => { assert.equal(result.incomplete, undefined); assert.equal(result.suggestions.length, 2); assert.equal(result.suggestions[0].label, 'bar'); @@ -155,19 +157,19 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); let model = TextModel.createFromString('\t { + return provider.provideCompletionItems(model, new Position(1, 7), context)!.then(result => { assert.equal(result.suggestions.length, 1); model.dispose(); model = TextModel.createFromString('\t { assert.equal(result.suggestions.length, 1); assert.equal(result.suggestions[0].range.startColumn, 2); model.dispose(); model = TextModel.createFromString('a { assert.equal(result.suggestions.length, 1); assert.equal(result.suggestions[0].range.startColumn, 2); @@ -190,9 +192,9 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); let model = TextModel.createFromString('\n\t\n>/head>', undefined, modeService.getLanguageIdentifier('fooLang')); - return provider.provideCompletionItems(model, new Position(1, 1))!.then(result => { + return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { assert.equal(result.suggestions.length, 1); - return provider.provideCompletionItems(model, new Position(2, 2))!; + return provider.provideCompletionItems(model, new Position(2, 2), context)!; }).then(result => { assert.equal(result.suggestions.length, 1); }); @@ -220,7 +222,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); let model = TextModel.createFromString('', undefined, modeService.getLanguageIdentifier('fooLang')); - return provider.provideCompletionItems(model, new Position(1, 1))!.then(result => { + return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => { assert.equal(result.suggestions.length, 2); let [first, second] = result.suggestions; assert.equal(first.label, 'first'); @@ -242,13 +244,13 @@ suite('SnippetsService', function () { let model = TextModel.createFromString('p-', undefined, modeService.getLanguageIdentifier('fooLang')); - let result = await provider.provideCompletionItems(model, new Position(1, 2))!; + let result = await provider.provideCompletionItems(model, new Position(1, 2), context)!; assert.equal(result.suggestions.length, 1); - result = await provider.provideCompletionItems(model, new Position(1, 3))!; + result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; assert.equal(result.suggestions.length, 1); - result = await provider.provideCompletionItems(model, new Position(1, 3))!; + result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; assert.equal(result.suggestions.length, 1); }); @@ -266,7 +268,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); let model = TextModel.createFromString('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b', undefined, modeService.getLanguageIdentifier('fooLang')); - let result = await provider.provideCompletionItems(model, new Position(1, 158))!; + let result = await provider.provideCompletionItems(model, new Position(1, 158), context)!; assert.equal(result.suggestions.length, 1); }); @@ -285,7 +287,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); let model = TextModel.createFromString(':', undefined, modeService.getLanguageIdentifier('fooLang')); - let result = await provider.provideCompletionItems(model, new Position(1, 2))!; + let result = await provider.provideCompletionItems(model, new Position(1, 2), context)!; assert.equal(result.suggestions.length, 0); }); @@ -304,7 +306,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); let model = TextModel.createFromString('template', undefined, modeService.getLanguageIdentifier('fooLang')); - let result = await provider.provideCompletionItems(model, new Position(1, 9))!; + let result = await provider.provideCompletionItems(model, new Position(1, 9), context)!; assert.equal(result.suggestions.length, 1); assert.equal(result.suggestions[0].label, 'mytemplate'); @@ -324,7 +326,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); let model = TextModel.createFromString('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b text_after_b', undefined, modeService.getLanguageIdentifier('fooLang')); - let result = await provider.provideCompletionItems(model, new Position(1, 158))!; + let result = await provider.provideCompletionItems(model, new Position(1, 158), context)!; assert.equal(result.suggestions.length, 1); }); @@ -346,7 +348,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); let model = TextModel.createFromString('.🐷-a-b', undefined, modeService.getLanguageIdentifier('fooLang')); - let result = await provider.provideCompletionItems(model, new Position(1, 8))!; + let result = await provider.provideCompletionItems(model, new Position(1, 8), context)!; assert.equal(result.suggestions.length, 1); @@ -367,7 +369,7 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); let model = TextModel.createFromString('a ', undefined, modeService.getLanguageIdentifier('fooLang')); - let result = await provider.provideCompletionItems(model, new Position(1, 3))!; + let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; assert.equal(result.suggestions.length, 1); }); @@ -394,14 +396,14 @@ suite('SnippetsService', function () { const provider = new SnippetCompletionProvider(modeService, snippetService); let model = TextModel.createFromString(' <', undefined, modeService.getLanguageIdentifier('fooLang')); - let result = await provider.provideCompletionItems(model, new Position(1, 3))!; + let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!; assert.equal(result.suggestions.length, 1); let [first] = result.suggestions; assert.equal(first.range.startColumn, 2); model = TextModel.createFromString('1', undefined, modeService.getLanguageIdentifier('fooLang')); - result = await provider.provideCompletionItems(model, new Position(1, 2))!; + result = await provider.provideCompletionItems(model, new Position(1, 2), context)!; assert.equal(result.suggestions.length, 1); [first] = result.suggestions; diff --git a/src/vs/workbench/parts/splash/electron-browser/partsSplash.contribution.ts b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts similarity index 66% rename from src/vs/workbench/parts/splash/electron-browser/partsSplash.contribution.ts rename to src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts index dc6cd3c970..de00e033e5 100644 --- a/src/vs/workbench/parts/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/contrib/splash/electron-browser/partsSplash.contribution.ts @@ -3,22 +3,26 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { join } from 'vs/base/common/path'; import { onDidChangeFullscreen, isFullscreen } from 'vs/base/browser/browser'; import { getTotalHeight, getTotalWidth } from 'vs/base/browser/dom'; import { Color } from 'vs/base/common/color'; import { Event } from 'vs/base/common/event'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { IBroadcastService } from 'vs/platform/broadcast/electron-browser/broadcastService'; +import { IBroadcastService } from 'vs/workbench/services/broadcast/electron-browser/broadcastService'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ColorIdentifier, editorBackground, foreground } from 'vs/platform/theme/common/colorRegistry'; import { getThemeTypeSelector, IThemeService } from 'vs/platform/theme/common/themeService'; import { DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; import { Extensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import * as themes from 'vs/workbench/common/theme'; -import { IPartService, Parts, Position } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { URI } from 'vs/base/common/uri'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; class PartsSplash { @@ -26,22 +30,29 @@ class PartsSplash { private readonly _disposables: IDisposable[] = []; + private _didChangeTitleBarStyle: boolean; private _lastBaseTheme: string; private _lastBackground?: string; constructor( @IThemeService private readonly _themeService: IThemeService, - @IPartService private readonly _partService: IPartService, - @IStorageService private readonly _storageService: IStorageService, + @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, + @IFileService private readonly _fileService: IFileService, @IEnvironmentService private readonly _envService: IEnvironmentService, + @IBroadcastService private readonly _broadcastService: IBroadcastService, @ILifecycleService lifecycleService: ILifecycleService, - @IBroadcastService private readonly broadcastService: IBroadcastService + @IEditorGroupsService editorGroupsService: IEditorGroupsService, + @IConfigurationService configService: IConfigurationService, ) { lifecycleService.when(LifecyclePhase.Restored).then(_ => this._removePartsSplash()); Event.debounce(Event.any( onDidChangeFullscreen, - _partService.onEditorLayout + editorGroupsService.onDidLayout ), () => { }, 800)(this._savePartsSplash, this, this._disposables); + + configService.onDidChangeConfiguration(e => { + this._didChangeTitleBarStyle = e.affectsConfiguration('window.titleBarStyle'); + }, this, this._disposables); } dispose(): void { @@ -60,19 +71,23 @@ class PartsSplash { statusBarNoFolderBackground: this._getThemeColor(themes.STATUS_BAR_NO_FOLDER_BACKGROUND), }; const layoutInfo = !this._shouldSaveLayoutInfo() ? undefined : { - sideBarSide: this._partService.getSideBarPosition() === Position.RIGHT ? 'right' : 'left', + sideBarSide: this._layoutService.getSideBarPosition() === Position.RIGHT ? 'right' : 'left', editorPartMinWidth: DEFAULT_EDITOR_MIN_DIMENSIONS.width, - titleBarHeight: getTotalHeight(this._partService.getContainer(Parts.TITLEBAR_PART)), - activityBarWidth: getTotalWidth(this._partService.getContainer(Parts.ACTIVITYBAR_PART)), - sideBarWidth: getTotalWidth(this._partService.getContainer(Parts.SIDEBAR_PART)), - statusBarHeight: getTotalHeight(this._partService.getContainer(Parts.STATUSBAR_PART)), + titleBarHeight: getTotalHeight(this._layoutService.getContainer(Parts.TITLEBAR_PART)), + activityBarWidth: getTotalWidth(this._layoutService.getContainer(Parts.ACTIVITYBAR_PART)), + sideBarWidth: getTotalWidth(this._layoutService.getContainer(Parts.SIDEBAR_PART)), + statusBarHeight: getTotalHeight(this._layoutService.getContainer(Parts.STATUSBAR_PART)), }; - this._storageService.store('parts-splash-data', JSON.stringify({ - id: PartsSplash._splashElementId, - colorInfo, - layoutInfo, - baseTheme - }), StorageScope.GLOBAL); + this._fileService.updateContent( + URI.file(join(this._envService.userDataPath, 'rapid_render.json')), + JSON.stringify({ + id: PartsSplash._splashElementId, + colorInfo, + layoutInfo, + baseTheme + }), + { encoding: 'utf8' } + ); if (baseTheme !== this._lastBaseTheme || colorInfo.editorBackground !== this._lastBackground) { // notify the main window on background color changes: the main window sets the background color to new windows @@ -81,7 +96,7 @@ class PartsSplash { // the color needs to be in hex const backgroundColor = this._themeService.getTheme().getColor(editorBackground) || themes.WORKBENCH_BACKGROUND(this._themeService.getTheme()); - this.broadcastService.broadcast({ channel: 'vscode:changeColorTheme', payload: JSON.stringify({ baseTheme, background: Color.Format.CSS.formatHex(backgroundColor) }) }); + this._broadcastService.broadcast({ channel: 'vscode:changeColorTheme', payload: JSON.stringify({ baseTheme, background: Color.Format.CSS.formatHex(backgroundColor) }) }); } } @@ -92,7 +107,7 @@ class PartsSplash { } private _shouldSaveLayoutInfo(): boolean { - return !isFullscreen() && !this._envService.isExtensionDevelopment; + return !isFullscreen() && !this._envService.isExtensionDevelopment && !this._didChangeTitleBarStyle; } private _removePartsSplash(): void { diff --git a/src/vs/workbench/parts/stats/node/stats.contribution.ts b/src/vs/workbench/contrib/stats/node/stats.contribution.ts similarity index 88% rename from src/vs/workbench/parts/stats/node/stats.contribution.ts rename to src/vs/workbench/contrib/stats/node/stats.contribution.ts index 8c8e398c40..e6965b3ffa 100644 --- a/src/vs/workbench/parts/stats/node/stats.contribution.ts +++ b/src/vs/workbench/contrib/stats/node/stats.contribution.ts @@ -5,7 +5,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { WorkspaceStats } from 'vs/workbench/parts/stats/node/workspaceStats'; +import { WorkspaceStats } from 'vs/workbench/contrib/stats/node/workspaceStats'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; // Register Workspace Stats Contribution diff --git a/src/vs/workbench/parts/stats/node/workspaceStats.ts b/src/vs/workbench/contrib/stats/node/workspaceStats.ts similarity index 97% rename from src/vs/workbench/parts/stats/node/workspaceStats.ts rename to src/vs/workbench/contrib/stats/node/workspaceStats.ts index c27baf2e29..9fd76f8501 100644 --- a/src/vs/workbench/parts/stats/node/workspaceStats.ts +++ b/src/vs/workbench/contrib/stats/node/workspaceStats.ts @@ -16,10 +16,10 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { endsWith } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; import { INotificationService, Severity, IPromptChoice } from 'vs/platform/notification/common/notification'; -import { extname, join } from 'path'; -import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; +import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { joinPath } from 'vs/base/common/resources'; const SshProtocolMatcher = /^([^@:]+@)?([^:]+):/; const SshUrlMatcher = /^([^@:]+@)?([^:]+):(.+)$/; @@ -327,17 +327,21 @@ export class WorkspaceStats implements IWorkbenchContribution { const state = this.contextService.getWorkbenchState(); const workspace = this.contextService.getWorkspace(); + function createHash(uri: URI): string { + return crypto.createHash('sha1').update(uri.scheme === Schemas.file ? uri.fsPath : uri.toString()).digest('hex'); + } + let workspaceId: string | undefined; switch (state) { case WorkbenchState.EMPTY: workspaceId = undefined; break; case WorkbenchState.FOLDER: - workspaceId = crypto.createHash('sha1').update(workspace.folders[0].uri.scheme === Schemas.file ? workspace.folders[0].uri.fsPath : workspace.folders[0].uri.toString()).digest('hex'); + workspaceId = createHash(workspace.folders[0].uri); break; case WorkbenchState.WORKSPACE: if (workspace.configuration) { - workspaceId = crypto.createHash('sha1').update(workspace.configuration.fsPath).digest('hex'); + workspaceId = createHash(workspace.configuration); } } @@ -358,7 +362,7 @@ export class WorkspaceStats implements IWorkbenchContribution { } return this.fileService.resolveFiles(folders.map(resource => ({ resource }))).then((files: IResolveFileResult[]) => { - const names = ([]).concat(...files.map(result => result.success ? (result.stat.children || []) : [])).map(c => c.name); + const names = ([]).concat(...files.map(result => result.success ? (result.stat!.children || []) : [])).map(c => c.name); const nameSet = names.reduce((s, n) => s.add(n.toLowerCase()), new Set()); if (participant) { @@ -523,7 +527,7 @@ export class WorkspaceStats implements IWorkbenchContribution { // Handle top-level workspace files for local single folder workspace if (state === WorkbenchState.FOLDER && workspace.folders[0].uri.scheme === Schemas.file) { - const workspaceFiles = rootFiles.filter(name => extname(name) === `.${WORKSPACE_EXTENSION}`); + const workspaceFiles = rootFiles.filter(hasWorkspaceFileExtension); if (workspaceFiles.length > 0) { this.doHandleWorkspaceFiles(workspace.folders[0].uri, workspaceFiles); } @@ -547,7 +551,7 @@ export class WorkspaceStats implements IWorkbenchContribution { this.notificationService.prompt(Severity.Info, localize('workspaceFound', "This folder contains a workspace file '{0}'. Do you want to open it? [Learn more]({1}) about workspace files.", workspaceFile, 'https://go.microsoft.com/fwlink/?linkid=2025315'), [{ label: localize('openWorkspace', "Open Workspace"), - run: () => this.windowService.openWindow([URI.file(join(folder.fsPath, workspaceFile))]) + run: () => this.windowService.openWindow([{ uri: joinPath(folder, workspaceFile), typeHint: 'file' }]) }, doNotShowAgain]); } @@ -560,7 +564,7 @@ export class WorkspaceStats implements IWorkbenchContribution { workspaces.map(workspace => ({ label: workspace } as IQuickPickItem)), { placeHolder: localize('selectToOpen', "Select a workspace to open") }).then(pick => { if (pick) { - this.windowService.openWindow([URI.file(join(folder.fsPath, pick.label))]); + this.windowService.openWindow([{ uri: joinPath(folder, pick.label), typeHint: 'file' }]); } }); } @@ -654,7 +658,7 @@ export class WorkspaceStats implements IWorkbenchContribution { }); return this.fileService.resolveFiles(uris.map(resource => ({ resource }))).then( results => { - const names = ([]).concat(...results.map(result => result.success ? (result.stat.children || []) : [])).map(c => c.name); + const names = ([]).concat(...results.map(result => result.success ? (result.stat!.children || []) : [])).map(c => c.name); const referencesAzure = WorkspaceStats.searchArray(names, /azure/i); if (referencesAzure) { tags['node'] = true; diff --git a/src/vs/workbench/parts/stats/test/workspaceStats.test.ts b/src/vs/workbench/contrib/stats/test/workspaceStats.test.ts similarity index 99% rename from src/vs/workbench/parts/stats/test/workspaceStats.test.ts rename to src/vs/workbench/contrib/stats/test/workspaceStats.test.ts index 840b2d5071..4dd2d69eee 100644 --- a/src/vs/workbench/parts/stats/test/workspaceStats.test.ts +++ b/src/vs/workbench/contrib/stats/test/workspaceStats.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as crypto from 'crypto'; -import { getDomainsOfRemotes, getRemotes, getHashedRemotesFromConfig } from 'vs/workbench/parts/stats/node/workspaceStats'; +import { getDomainsOfRemotes, getRemotes, getHashedRemotesFromConfig } from 'vs/workbench/contrib/stats/node/workspaceStats'; function hash(value: string): string { return crypto.createHash('sha1').update(value.toString()).digest('hex'); diff --git a/src/vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution.ts b/src/vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution.ts similarity index 91% rename from src/vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution.ts rename to src/vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution.ts index 1cc46f1226..8d44199939 100644 --- a/src/vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution.ts +++ b/src/vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution.ts @@ -10,8 +10,8 @@ import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions as import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import pkg from 'vs/platform/node/package'; -import product, { ISurveyData } from 'vs/platform/node/product'; +import pkg from 'vs/platform/product/node/package'; +import product, { ISurveyData } from 'vs/platform/product/node/product'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; import { ITextFileService, StateChange } from 'vs/workbench/services/textfile/common/textfiles'; @@ -40,13 +40,13 @@ class LanguageSurvey { const date = new Date().toDateString(); - if (storageService.getInteger(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) < data.editCount) { + if (storageService.getNumber(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) < data.editCount) { textFileService.models.onModelsSaved(e => { e.forEach(event => { if (event.kind === StateChange.SAVED) { const model = modelService.getModel(event.resource); if (model && model.getModeId() === data.languageId && date !== storageService.get(EDITED_LANGUAGE_DATE_KEY, StorageScope.GLOBAL)) { - const editedCount = storageService.getInteger(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) + 1; + const editedCount = storageService.getNumber(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) + 1; storageService.store(EDITED_LANGUAGE_COUNT_KEY, editedCount, StorageScope.GLOBAL); storageService.store(EDITED_LANGUAGE_DATE_KEY, date, StorageScope.GLOBAL); } @@ -60,7 +60,7 @@ class LanguageSurvey { return; } - const sessionCount = storageService.getInteger(SESSION_COUNT_KEY, StorageScope.GLOBAL, 0) + 1; + const sessionCount = storageService.getNumber(SESSION_COUNT_KEY, StorageScope.GLOBAL, 0) + 1; storageService.store(LAST_SESSION_DATE_KEY, date, StorageScope.GLOBAL); storageService.store(SESSION_COUNT_KEY, sessionCount, StorageScope.GLOBAL); @@ -68,7 +68,7 @@ class LanguageSurvey { return; } - if (storageService.getInteger(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) < data.editCount) { + if (storageService.getNumber(EDITED_LANGUAGE_COUNT_KEY, StorageScope.GLOBAL, 0) < data.editCount) { return; } diff --git a/src/vs/workbench/parts/surveys/electron-browser/nps.contribution.ts b/src/vs/workbench/contrib/surveys/electron-browser/nps.contribution.ts similarity index 94% rename from src/vs/workbench/parts/surveys/electron-browser/nps.contribution.ts rename to src/vs/workbench/contrib/surveys/electron-browser/nps.contribution.ts index 65724bda88..e97d7b087f 100644 --- a/src/vs/workbench/parts/surveys/electron-browser/nps.contribution.ts +++ b/src/vs/workbench/contrib/surveys/electron-browser/nps.contribution.ts @@ -9,8 +9,8 @@ import { IWorkbenchContributionsRegistry, IWorkbenchContribution, Extensions as import { Registry } from 'vs/platform/registry/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import pkg from 'vs/platform/node/package'; -import product from 'vs/platform/node/product'; +import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/node/product'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; @@ -39,7 +39,7 @@ class NPSContribution implements IWorkbenchContribution { return; } - const sessionCount = (storageService.getInteger(SESSION_COUNT_KEY, StorageScope.GLOBAL, 0) || 0) + 1; + const sessionCount = (storageService.getNumber(SESSION_COUNT_KEY, StorageScope.GLOBAL, 0) || 0) + 1; storageService.store(LAST_SESSION_DATE_KEY, date, StorageScope.GLOBAL); storageService.store(SESSION_COUNT_KEY, sessionCount, StorageScope.GLOBAL); diff --git a/src/vs/workbench/parts/tasks/browser/quickOpen.ts b/src/vs/workbench/contrib/tasks/browser/quickOpen.ts similarity index 92% rename from src/vs/workbench/parts/tasks/browser/quickOpen.ts rename to src/vs/workbench/contrib/tasks/browser/quickOpen.ts index f2857887ee..19037be242 100644 --- a/src/vs/workbench/parts/tasks/browser/quickOpen.ts +++ b/src/vs/workbench/contrib/tasks/browser/quickOpen.ts @@ -13,8 +13,8 @@ import * as QuickOpen from 'vs/base/parts/quickopen/common/quickOpen'; import * as Model from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { CustomTask, ContributedTask } from 'vs/workbench/parts/tasks/common/tasks'; -import { ITaskService, ProblemMatcherRunOptions } from 'vs/workbench/parts/tasks/common/taskService'; +import { CustomTask, ContributedTask } from 'vs/workbench/contrib/tasks/common/tasks'; +import { ITaskService, ProblemMatcherRunOptions } from 'vs/workbench/contrib/tasks/common/taskService'; import { ActionBarContributor, ContributableActionProvider } from 'vs/workbench/browser/actions'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -28,7 +28,7 @@ export class TaskEntry extends Model.QuickOpenEntry { return this.task._label; } - public getDescription(): string { + public getDescription(): string | null { if (!this.taskService.needsFolderQualification()) { return null; } @@ -51,7 +51,7 @@ export class TaskEntry extends Model.QuickOpenEntry { this.taskService.run(task, options).then(undefined, reason => { // eat the error, it has already been surfaced to the user and we don't care about it here }); - if (!task.command || task.command.presentation.focus) { + if (!task.command || (task.command.presentation && task.command.presentation.focus)) { this.quickOpenService.close(); return false; } @@ -67,7 +67,7 @@ export class TaskGroupEntry extends Model.QuickOpenEntryGroup { export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler { - private tasks: Promise>; + private tasks?: Promise>; constructor( protected quickOpenService: IQuickOpenService, @@ -87,7 +87,10 @@ export abstract class QuickOpenHandler extends Quickopen.QuickOpenHandler { this.tasks = undefined; } - public getResults(input: string, token: CancellationToken): Promise { + public getResults(input: string, token: CancellationToken): Promise { + if (!this.tasks) { + return Promise.resolve(null); + } return this.tasks.then((tasks) => { let entries: Model.QuickOpenEntry[] = []; if (tasks.length === 0 || token.isCancellationRequested) { @@ -186,7 +189,7 @@ class CustomizeTaskAction extends Action { } } - private getTask(element: any): CustomTask | ContributedTask { + private getTask(element: any): CustomTask | ContributedTask | undefined { if (element instanceof TaskEntry) { return element.task; } else if (element instanceof TaskGroupEntry) { @@ -220,7 +223,7 @@ export class QuickOpenActionContributor extends ActionBarContributor { return actions; } - private getTask(context: any): CustomTask | ContributedTask { + private getTask(context: any): CustomTask | ContributedTask | undefined { if (!context) { return undefined; } diff --git a/src/vs/workbench/parts/tasks/browser/taskQuickOpen.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickOpen.ts similarity index 90% rename from src/vs/workbench/parts/tasks/browser/taskQuickOpen.ts rename to src/vs/workbench/contrib/tasks/browser/taskQuickOpen.ts index 325734acab..4a66522778 100644 --- a/src/vs/workbench/parts/tasks/browser/taskQuickOpen.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskQuickOpen.ts @@ -8,8 +8,8 @@ import * as QuickOpen from 'vs/base/parts/quickopen/common/quickOpen'; import * as Model from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { CustomTask, ContributedTask } from 'vs/workbench/parts/tasks/common/tasks'; -import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService'; +import { CustomTask, ContributedTask } from 'vs/workbench/contrib/tasks/common/tasks'; +import { ITaskService } from 'vs/workbench/contrib/tasks/common/taskService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import * as base from './quickOpen'; @@ -19,7 +19,7 @@ class TaskEntry extends base.TaskEntry { super(quickOpenService, taskService, task, highlights); } - public run(mode: QuickOpen.Mode, context: Model.IContext): boolean { + public run(mode: QuickOpen.Mode, context: QuickOpen.IEntryRunContext): boolean { if (mode === QuickOpen.Mode.PREVIEW) { return false; } diff --git a/src/vs/workbench/parts/tasks/common/problemCollectors.ts b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts similarity index 99% rename from src/vs/workbench/parts/tasks/common/problemCollectors.ts rename to src/vs/workbench/contrib/tasks/common/problemCollectors.ts index f29f1457bb..729eb45c02 100644 --- a/src/vs/workbench/parts/tasks/common/problemCollectors.ts +++ b/src/vs/workbench/contrib/tasks/common/problemCollectors.ts @@ -10,7 +10,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { ILineMatcher, createLineMatcher, ProblemMatcher, ProblemMatch, ApplyToKind, WatchingPattern, getResource } from 'vs/workbench/parts/tasks/common/problemMatcher'; +import { ILineMatcher, createLineMatcher, ProblemMatcher, ProblemMatch, ApplyToKind, WatchingPattern, getResource } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { IMarkerService, IMarkerData, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { generateUuid } from 'vs/base/common/uuid'; diff --git a/src/vs/workbench/parts/tasks/common/problemMatcher.ts b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts similarity index 99% rename from src/vs/workbench/parts/tasks/common/problemMatcher.ts rename to src/vs/workbench/contrib/tasks/common/problemMatcher.ts index 125004e2f3..a88507fa2e 100644 --- a/src/vs/workbench/parts/tasks/common/problemMatcher.ts +++ b/src/vs/workbench/contrib/tasks/common/problemMatcher.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import * as Objects from 'vs/base/common/objects'; import * as Strings from 'vs/base/common/strings'; import * as Assert from 'vs/base/common/assert'; -import * as Paths from 'vs/base/common/paths'; +import { join } from 'vs/base/common/path'; import * as Types from 'vs/base/common/types'; import * as UUID from 'vs/base/common/uuid'; import * as Platform from 'vs/base/common/platform'; @@ -188,7 +188,7 @@ export function getResource(filename: string, matcher: ProblemMatcher): URI { if (kind === FileLocationKind.Absolute) { fullPath = filename; } else if ((kind === FileLocationKind.Relative) && matcher.filePrefix) { - fullPath = Paths.join(matcher.filePrefix, filename); + fullPath = join(matcher.filePrefix, filename); } if (fullPath === undefined) { throw new Error('FileLocationKind is not actionable. Does the matcher have a filePrefix? This should never happen.'); @@ -1093,8 +1093,7 @@ const problemPatternExtPoint = ExtensionsRegistry.registerExtensionPoint; private readyPromise: Promise; - private _onMatchersChanged: Emitter = new Emitter(); + private readonly _onMatchersChanged: Emitter = new Emitter(); public get onMatcherChanged(): Event { return this._onMatchersChanged.event; } diff --git a/src/vs/workbench/parts/tasks/common/taskDefinitionRegistry.ts b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts similarity index 98% rename from src/vs/workbench/parts/tasks/common/taskDefinitionRegistry.ts rename to src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts index fd46411a18..2b604cd8c7 100644 --- a/src/vs/workbench/parts/tasks/common/taskDefinitionRegistry.ts +++ b/src/vs/workbench/contrib/tasks/common/taskDefinitionRegistry.ts @@ -11,7 +11,7 @@ import * as Objects from 'vs/base/common/objects'; import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import * as Tasks from 'vs/workbench/parts/tasks/common/tasks'; +import * as Tasks from 'vs/workbench/contrib/tasks/common/tasks'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; @@ -74,8 +74,7 @@ const taskDefinitionsExtPoint = ExtensionsRegistry.registerExtensionPoint; - }; + } | undefined; hasErrors: boolean; } @@ -57,7 +57,7 @@ export interface ITaskService { configureAction(): Action; build(): Promise; runTest(): Promise; - run(task: Task, options?: ProblemMatcherRunOptions): Promise; + run(task: Task | undefined, options?: ProblemMatcherRunOptions): Promise; inTerminal(): boolean; isActive(): Promise; getActiveTasks(): Promise; @@ -69,7 +69,7 @@ export interface ITaskService { /** * @param alias The task's name, label or defined identifier. */ - getTask(workspaceFolder: IWorkspaceFolder | string, alias: string | TaskIdentifier, compareId?: boolean): Promise; + getTask(workspaceFolder: IWorkspaceFolder | string, alias: string | TaskIdentifier, compareId?: boolean): Promise; getTasksForGroup(group: string): Promise; getRecentlyUsedTasks(): LinkedMap; createSorter(): TaskSorter; @@ -82,4 +82,6 @@ export interface ITaskService { registerTaskProvider(taskProvider: ITaskProvider): IDisposable; registerTaskSystem(scheme: string, taskSystemInfo: TaskSystemInfo): void; + + extensionCallbackTaskComplete(task: Task, result: number | undefined): Promise; } diff --git a/src/vs/workbench/parts/tasks/common/taskSystem.ts b/src/vs/workbench/contrib/tasks/common/taskSystem.ts similarity index 95% rename from src/vs/workbench/parts/tasks/common/taskSystem.ts rename to src/vs/workbench/contrib/tasks/common/taskSystem.ts index 52c29580cb..a5ebe23548 100644 --- a/src/vs/workbench/parts/tasks/common/taskSystem.ts +++ b/src/vs/workbench/contrib/tasks/common/taskSystem.ts @@ -93,7 +93,7 @@ export interface ITaskExecuteResult { } export interface ITaskResolver { - resolve(workspaceFolder: IWorkspaceFolder, identifier: string | KeyedTaskIdentifier): Task; + resolve(workspaceFolder: IWorkspaceFolder, identifier: string | KeyedTaskIdentifier | undefined): Task | undefined; } export interface TaskTerminateResponse extends TerminateResponse { @@ -122,7 +122,7 @@ export interface TaskSystemInfo { } export interface TaskSystemInfoResovler { - (workspaceFolder: IWorkspaceFolder): TaskSystemInfo; + (workspaceFolder: IWorkspaceFolder): TaskSystemInfo | undefined; } export interface ITaskSystem { @@ -136,4 +136,5 @@ export interface ITaskSystem { terminate(task: Task): Promise; terminateAll(): Promise; revealTask(task: Task): boolean; + customExecutionComplete(task: Task, result: number): Promise; } \ No newline at end of file diff --git a/src/vs/workbench/parts/tasks/common/taskTemplates.ts b/src/vs/workbench/contrib/tasks/common/taskTemplates.ts similarity index 100% rename from src/vs/workbench/parts/tasks/common/taskTemplates.ts rename to src/vs/workbench/contrib/tasks/common/taskTemplates.ts diff --git a/src/vs/workbench/parts/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts similarity index 92% rename from src/vs/workbench/parts/tasks/common/tasks.ts rename to src/vs/workbench/contrib/tasks/common/tasks.ts index 8c6d67e87a..6a00daf5a9 100644 --- a/src/vs/workbench/parts/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -8,10 +8,10 @@ import { IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import * as Objects from 'vs/base/common/objects'; import { UriComponents } from 'vs/base/common/uri'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import { ProblemMatcher } from 'vs/workbench/parts/tasks/common/problemMatcher'; +import { ProblemMatcher } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export const TASK_RUNNING_STATE = new RawContextKey('taskRunning', false); @@ -223,13 +223,14 @@ export interface PresentationOptions { export namespace PresentationOptions { export const defaults: PresentationOptions = { - echo: false, reveal: RevealKind.Always, focus: false, panel: PanelKind.Shared, showReuseMessage: true, clear: false + echo: true, reveal: RevealKind.Always, focus: false, panel: PanelKind.Shared, showReuseMessage: true, clear: false }; } export enum RuntimeType { Shell = 1, - Process = 2 + Process = 2, + CustomExecution = 3 } export namespace RuntimeType { @@ -239,6 +240,8 @@ export namespace RuntimeType { return RuntimeType.Shell; case 'process': return RuntimeType.Process; + case 'customExecution': + return RuntimeType.CustomExecution; default: return RuntimeType.Process; } @@ -350,7 +353,7 @@ export interface WorkspaceTaskSource extends BaseTaskSource { export interface ExtensionTaskSource extends BaseTaskSource { readonly kind: 'extension'; - readonly extension: string; + readonly extension?: string; readonly scope: TaskScope; readonly workspaceFolder: IWorkspaceFolder | undefined; } @@ -514,7 +517,7 @@ export abstract class CommonTask { return 'unknown'; } - public matches(key: string | KeyedTaskIdentifier, compareId: boolean = false): boolean { + public matches(key: string | KeyedTaskIdentifier | undefined, compareId: boolean = false): boolean { if (key === undefined) { return false; } @@ -594,11 +597,28 @@ export class CustomTask extends CommonTask { return this._source.customizes; } else { let type: string; - if (this.command !== undefined) { - type = this.command.runtime === RuntimeType.Shell ? 'shell' : 'process'; - } else { - type = '$composite'; + const commandRuntime = this.command ? this.command.runtime : undefined; + switch (commandRuntime) { + case RuntimeType.Shell: + type = 'shell'; + break; + + case RuntimeType.Process: + type = 'process'; + break; + + case RuntimeType.CustomExecution: + type = 'customExecution'; + break; + + case undefined: + type = '$composite'; + break; + + default: + throw new Error('Unexpected task runtime'); } + let result: KeyedTaskIdentifier = { type, _key: this._id, @@ -631,7 +651,7 @@ export class CustomTask extends CommonTask { return JSON.stringify(key); } - public getWorkspaceFolder(): IWorkspaceFolder | undefined { + public getWorkspaceFolder(): IWorkspaceFolder { return this._source.config.workspaceFolder; } @@ -861,6 +881,7 @@ export interface TaskEvent { group?: string; processId?: number; exitCode?: number; + terminalId?: number; __task?: Task; } @@ -871,12 +892,13 @@ export const enum TaskRunSource { } export namespace TaskEvent { - export function create(kind: TaskEventKind.ProcessStarted | TaskEventKind.ProcessEnded, task: Task, processIdOrExitCode: number): TaskEvent; + export function create(kind: TaskEventKind.ProcessStarted | TaskEventKind.ProcessEnded, task: Task, processIdOrExitCode?: number): TaskEvent; + export function create(kind: TaskEventKind.Start, task: Task, terminalId?: number): TaskEvent; export function create(kind: TaskEventKind.DependsOnStarted | TaskEventKind.Start | TaskEventKind.Active | TaskEventKind.Inactive | TaskEventKind.Terminated | TaskEventKind.End, task: Task): TaskEvent; export function create(kind: TaskEventKind.Changed): TaskEvent; - export function create(kind: TaskEventKind, task?: Task, processIdOrExitCode?: number): TaskEvent { + export function create(kind: TaskEventKind, task?: Task, processIdOrExitCodeOrTerminalId?: number): TaskEvent { if (task) { - let result = { + let result: TaskEvent = { kind: kind, taskId: task._id, taskName: task.configurationProperties.name, @@ -884,12 +906,15 @@ export namespace TaskEvent { group: task.configurationProperties.group, processId: undefined as number | undefined, exitCode: undefined as number | undefined, + terminalId: undefined as number | undefined, __task: task, }; - if (kind === TaskEventKind.ProcessStarted) { - result.processId = processIdOrExitCode; + if (kind === TaskEventKind.Start) { + result.terminalId = processIdOrExitCodeOrTerminalId; + } else if (kind === TaskEventKind.ProcessStarted) { + result.processId = processIdOrExitCodeOrTerminalId; } else if (kind === TaskEventKind.ProcessEnded) { - result.exitCode = processIdOrExitCode; + result.exitCode = processIdOrExitCodeOrTerminalId; } return Object.freeze(result); } else { diff --git a/src/vs/workbench/parts/tasks/electron-browser/jsonSchemaCommon.ts b/src/vs/workbench/contrib/tasks/electron-browser/jsonSchemaCommon.ts similarity index 98% rename from src/vs/workbench/parts/tasks/electron-browser/jsonSchemaCommon.ts rename to src/vs/workbench/contrib/tasks/electron-browser/jsonSchemaCommon.ts index f678c5b79b..452c342c5d 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/jsonSchemaCommon.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/jsonSchemaCommon.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { Schemas } from 'vs/workbench/parts/tasks/common/problemMatcher'; +import { Schemas } from 'vs/workbench/contrib/tasks/common/problemMatcher'; const schema: IJSONSchema = { definitions: { diff --git a/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v1.ts b/src/vs/workbench/contrib/tasks/electron-browser/jsonSchema_v1.ts similarity index 97% rename from src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v1.ts rename to src/vs/workbench/contrib/tasks/electron-browser/jsonSchema_v1.ts index 92f05c07ea..6925d59f40 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v1.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/jsonSchema_v1.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import * as Objects from 'vs/base/common/objects'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; -import { ProblemMatcherRegistry } from 'vs/workbench/parts/tasks/common/problemMatcher'; +import { ProblemMatcherRegistry } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import commonSchema from './jsonSchemaCommon'; diff --git a/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts b/src/vs/workbench/contrib/tasks/electron-browser/jsonSchema_v2.ts similarity index 95% rename from src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts rename to src/vs/workbench/contrib/tasks/electron-browser/jsonSchema_v2.ts index 9f2237d2a7..2d08ac8950 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/jsonSchema_v2.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/jsonSchema_v2.ts @@ -9,7 +9,7 @@ import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import commonSchema from './jsonSchemaCommon'; -import { ProblemMatcherRegistry } from 'vs/workbench/parts/tasks/common/problemMatcher'; +import { ProblemMatcherRegistry } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { TaskDefinitionRegistry } from '../common/taskDefinitionRegistry'; import * as ConfigurationResolverUtils from 'vs/workbench/services/configurationResolver/common/configurationResolverUtils'; import { inputsSchema } from 'vs/workbench/services/configurationResolver/common/configurationResolverSchema'; @@ -168,8 +168,8 @@ const group: IJSONSchema = { enumDescriptions: [ nls.localize('JsonSchema.tasks.group.defaultBuild', 'Marks the task as the default build task.'), nls.localize('JsonSchema.tasks.group.defaultTest', 'Marks the task as the default test task.'), - nls.localize('JsonSchema.tasks.group.build', 'Marks the task as a build task accesible through the \'Run Build Task\' command.'), - nls.localize('JsonSchema.tasks.group.test', 'Marks the task as a test task accesible through the \'Run Test Task\' command.'), + nls.localize('JsonSchema.tasks.group.build', 'Marks the task as a build task accessible through the \'Run Build Task\' command.'), + nls.localize('JsonSchema.tasks.group.test', 'Marks the task as a test task accessible through the \'Run Test Task\' command.'), nls.localize('JsonSchema.tasks.group.none', 'Assigns the task to no group') ], description: nls.localize('JsonSchema.tasks.group', 'Defines to which execution group this task belongs to. It supports "build" to add it to the build group and "test" to add it to the test group.') @@ -177,7 +177,7 @@ const group: IJSONSchema = { const taskType: IJSONSchema = { type: 'string', - enum: ['shell', 'process'], + enum: ['shell'], default: 'shell', description: nls.localize('JsonSchema.tasks.type', 'Defines whether the task is run as a process or as a command inside a shell.') }; @@ -358,7 +358,11 @@ TaskDefinitionRegistry.onReady().then(() => { }; if (taskType.required) { schema.required = taskType.required.slice(); + } else { + schema.required = []; } + // Customized tasks require that the task type be set. + schema.required.push('type'); if (taskType.properties) { for (let key of Object.keys(taskType.properties)) { let property = taskType.properties[key]; @@ -375,6 +379,10 @@ customize.properties!.customize = { type: 'string', deprecationMessage: nls.localize('JsonSchema.tasks.customize.deprecated', 'The customize property is deprecated. See the 1.14 release notes on how to migrate to the new task customization approach') }; +if (!customize.required) { + customize.required = []; +} +customize.required.push('customize'); taskDefinitions.push(customize); let definitions = Objects.deepClone(commonSchemaDefinitions); @@ -423,6 +431,17 @@ taskDescriptionProperties.isTestCommand.deprecationMessage = nls.localize( 'The property isTestCommand is deprecated. Use the group property instead. See also the 1.14 release notes.' ); +// Process tasks are almost identical schema-wise to shell tasks, but they are required to have a command +const processTask = Objects.deepClone(taskDescription); +processTask.properties!.type = { + type: 'string', + enum: ['process'], + default: 'shell', + description: nls.localize('JsonSchema.tasks.type', 'Defines whether the task is run as a process or as a command inside a shell.') +}; +processTask.required!.push('command'); + +taskDefinitions.push(processTask); taskDefinitions.push({ $ref: '#/definitions/taskDescription' @@ -434,7 +453,6 @@ tasks.items = { oneOf: taskDefinitions }; - definitionsTaskRunnerConfigurationProperties.inputs = inputsSchema.definitions!.inputs; definitions.commandConfiguration.properties!.isShellCommand = Objects.deepClone(shellCommand); diff --git a/src/vs/workbench/parts/tasks/electron-browser/media/configure-inverse.svg b/src/vs/workbench/contrib/tasks/electron-browser/media/configure-inverse.svg similarity index 100% rename from src/vs/workbench/parts/tasks/electron-browser/media/configure-inverse.svg rename to src/vs/workbench/contrib/tasks/electron-browser/media/configure-inverse.svg diff --git a/src/vs/workbench/parts/tasks/electron-browser/media/configure.svg b/src/vs/workbench/contrib/tasks/electron-browser/media/configure.svg similarity index 100% rename from src/vs/workbench/parts/tasks/electron-browser/media/configure.svg rename to src/vs/workbench/contrib/tasks/electron-browser/media/configure.svg diff --git a/src/vs/workbench/parts/tasks/electron-browser/media/status-error.svg b/src/vs/workbench/contrib/tasks/electron-browser/media/status-error.svg similarity index 100% rename from src/vs/workbench/parts/tasks/electron-browser/media/status-error.svg rename to src/vs/workbench/contrib/tasks/electron-browser/media/status-error.svg diff --git a/src/vs/workbench/parts/tasks/electron-browser/media/status-info.svg b/src/vs/workbench/contrib/tasks/electron-browser/media/status-info.svg similarity index 100% rename from src/vs/workbench/parts/tasks/electron-browser/media/status-info.svg rename to src/vs/workbench/contrib/tasks/electron-browser/media/status-info.svg diff --git a/src/vs/workbench/parts/tasks/electron-browser/media/status-warning.svg b/src/vs/workbench/contrib/tasks/electron-browser/media/status-warning.svg similarity index 100% rename from src/vs/workbench/parts/tasks/electron-browser/media/status-warning.svg rename to src/vs/workbench/contrib/tasks/electron-browser/media/status-warning.svg diff --git a/src/vs/workbench/parts/tasks/electron-browser/media/task.contribution.css b/src/vs/workbench/contrib/tasks/electron-browser/media/task.contribution.css similarity index 100% rename from src/vs/workbench/parts/tasks/electron-browser/media/task.contribution.css rename to src/vs/workbench/contrib/tasks/electron-browser/media/task.contribution.css diff --git a/src/vs/workbench/parts/tasks/electron-browser/media/task.svg b/src/vs/workbench/contrib/tasks/electron-browser/media/task.svg similarity index 100% rename from src/vs/workbench/parts/tasks/electron-browser/media/task.svg rename to src/vs/workbench/contrib/tasks/electron-browser/media/task.svg diff --git a/src/vs/workbench/parts/tasks/electron-browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/electron-browser/runAutomaticTasks.ts similarity index 98% rename from src/vs/workbench/parts/tasks/electron-browser/runAutomaticTasks.ts rename to src/vs/workbench/contrib/tasks/electron-browser/runAutomaticTasks.ts index 92c8cf8e63..945f08f210 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/runAutomaticTasks.ts @@ -6,9 +6,9 @@ import * as nls from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { ITaskService, WorkspaceFolderTaskResult } from 'vs/workbench/parts/tasks/common/taskService'; +import { ITaskService, WorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService'; import { forEach } from 'vs/base/common/collections'; -import { RunOnOptions, Task, TaskRunSource } from 'vs/workbench/parts/tasks/common/tasks'; +import { RunOnOptions, Task, TaskRunSource } from 'vs/workbench/contrib/tasks/common/tasks'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { Action } from 'vs/base/common/actions'; diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts similarity index 92% rename from src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts rename to src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts index 1ad1950725..faa4d1780b 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/task.contribution.ts @@ -8,7 +8,7 @@ import 'vs/css!./media/task.contribution'; import * as nls from 'vs/nls'; import * as semver from 'semver'; -import { QuickOpenHandler } from 'vs/workbench/parts/tasks/browser/taskQuickOpen'; +import { QuickOpenHandler } from 'vs/workbench/contrib/tasks/browser/taskQuickOpen'; import Severity from 'vs/base/common/severity'; import * as Objects from 'vs/base/common/objects'; import { URI } from 'vs/base/common/uri'; @@ -38,7 +38,7 @@ import { IFileService, IFileStat } from 'vs/platform/files/common/files'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { ProblemMatcherRegistry, NamedProblemMatcher } from 'vs/workbench/parts/tasks/common/problemMatcher'; +import { ProblemMatcherRegistry, NamedProblemMatcher } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IProgressService2, IProgressOptions, ProgressLocation } from 'vs/platform/progress/common/progress'; @@ -57,44 +57,44 @@ import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar'; import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import Constants from 'vs/workbench/parts/markers/electron-browser/constants'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import Constants from 'vs/workbench/contrib/markers/browser/constants'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IOutputService, IOutputChannelRegistry, Extensions as OutputExt, IOutputChannel } from 'vs/workbench/parts/output/common/output'; +import { IOutputService, IOutputChannelRegistry, Extensions as OutputExt, IOutputChannel } from 'vs/workbench/contrib/output/common/output'; import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions'; -import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/common/terminal'; -import { ITaskSystem, ITaskResolver, ITaskSummary, TaskExecuteKind, TaskError, TaskErrors, TaskTerminateResponse, TaskSystemInfo, ITaskExecuteResult } from 'vs/workbench/parts/tasks/common/taskSystem'; +import { ITaskSystem, ITaskResolver, ITaskSummary, TaskExecuteKind, TaskError, TaskErrors, TaskTerminateResponse, TaskSystemInfo, ITaskExecuteResult } from 'vs/workbench/contrib/tasks/common/taskSystem'; import { Task, CustomTask, ConfiguringTask, ContributedTask, InMemoryTask, TaskEvent, TaskEventKind, TaskSet, TaskGroup, GroupType, ExecutionEngine, JsonSchemaVersion, TaskSourceKind, TaskSorter, TaskIdentifier, KeyedTaskIdentifier, TASK_RUNNING_STATE, TaskRunSource -} from 'vs/workbench/parts/tasks/common/tasks'; -import { ITaskService, ITaskProvider, ProblemMatcherRunOptions, CustomizationProperties, TaskFilter, WorkspaceFolderTaskResult } from 'vs/workbench/parts/tasks/common/taskService'; -import { getTemplates as getTaskTemplates } from 'vs/workbench/parts/tasks/common/taskTemplates'; +} from 'vs/workbench/contrib/tasks/common/tasks'; +import { ITaskService, ITaskProvider, ProblemMatcherRunOptions, CustomizationProperties, TaskFilter, WorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService'; +import { getTemplates as getTaskTemplates } from 'vs/workbench/contrib/tasks/common/taskTemplates'; -import { KeyedTaskIdentifier as NKeyedTaskIdentifier, TaskDefinition } from 'vs/workbench/parts/tasks/node/tasks'; +import { KeyedTaskIdentifier as NKeyedTaskIdentifier, TaskDefinition } from 'vs/workbench/contrib/tasks/node/tasks'; import * as TaskConfig from '../node/taskConfiguration'; -import { ProcessTaskSystem } from 'vs/workbench/parts/tasks/node/processTaskSystem'; +import { ProcessTaskSystem } from 'vs/workbench/contrib/tasks/node/processTaskSystem'; import { TerminalTaskSystem } from './terminalTaskSystem'; -import { ProcessRunnerDetector } from 'vs/workbench/parts/tasks/node/processRunnerDetector'; +import { ProcessRunnerDetector } from 'vs/workbench/contrib/tasks/node/processRunnerDetector'; import { QuickOpenActionContributor } from '../browser/quickOpen'; import { Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND } from 'vs/workbench/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; -import { TaskDefinitionRegistry } from 'vs/workbench/parts/tasks/common/taskDefinitionRegistry'; +import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { RunAutomaticTasks, AllowAutomaticTaskRunning, DisallowAutomaticTaskRunning } from 'vs/workbench/parts/tasks/electron-browser/runAutomaticTasks'; +import { RunAutomaticTasks, AllowAutomaticTaskRunning, DisallowAutomaticTaskRunning } from 'vs/workbench/contrib/tasks/electron-browser/runAutomaticTasks'; let tasksCategory = nls.localize('tasksCategory', "Tasks"); @@ -119,7 +119,7 @@ class BuildStatusBarItem extends Themable implements IStatusbarItem { @IPanelService private readonly panelService: IPanelService, @IMarkerService private readonly markerService: IMarkerService, @ITaskService private readonly taskService: ITaskService, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IThemeService themeService: IThemeService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService ) { @@ -204,7 +204,7 @@ class BuildStatusBarItem extends Themable implements IStatusbarItem { callOnDispose.push(Dom.addDisposableListener(label, 'click', (e: MouseEvent) => { const panel = this.panelService.getActivePanel(); if (panel && panel.getId() === Constants.MARKERS_PANEL_ID) { - this.partService.setPanelHidden(true); + this.layoutService.setPanelHidden(true); } else { this.panelService.openPanel(Constants.MARKERS_PANEL_ID, true); } @@ -382,7 +382,7 @@ class ProblemReporter implements TaskConfig.IProblemReporter { interface WorkspaceFolderConfigurationResult { workspaceFolder: IWorkspaceFolder; - config: TaskConfig.ExternalTaskRunnerConfiguration; + config: TaskConfig.ExternalTaskRunnerConfiguration | undefined; hasErrors: boolean; } @@ -401,7 +401,7 @@ class TaskMap { } public get(workspaceFolder: IWorkspaceFolder | string): Task[] { - let result: Task[] = Types.isString(workspaceFolder) ? this._store.get(workspaceFolder) : this._store.get(workspaceFolder.uri.toString()); + let result: Task[] | undefined = Types.isString(workspaceFolder) ? this._store.get(workspaceFolder) : this._store.get(workspaceFolder.uri.toString()); if (!result) { result = []; Types.isString(workspaceFolder) ? this._store.set(workspaceFolder, result) : this._store.set(workspaceFolder.uri.toString(), result); @@ -426,7 +426,7 @@ class TaskMap { } interface TaskQuickPickEntry extends IQuickPickItem { - task: Task; + task: Task | undefined | null; } class TaskService extends Disposable implements ITaskService { @@ -449,14 +449,14 @@ class TaskService extends Disposable implements ITaskService { private _executionEngine: ExecutionEngine; private _workspaceFolders: IWorkspaceFolder[]; private _ignoredWorkspaceFolders: IWorkspaceFolder[]; - private _showIgnoreMessage: boolean; + private _showIgnoreMessage?: boolean; private _providers: Map; private _taskSystemInfos: Map; - private _workspaceTasksPromise: Promise>; + private _workspaceTasksPromise?: Promise>; - private _taskSystem: ITaskSystem; - private _taskSystemListener: IDisposable; + private _taskSystem?: ITaskSystem; + private _taskSystemListener?: IDisposable; private _recentlyUsedTasks: LinkedMap; private _taskRunningState: IContextKey; @@ -493,7 +493,7 @@ class TaskService extends Disposable implements ITaskService { this._workspaceTasksPromise = undefined; this._taskSystem = undefined; this._taskSystemListener = undefined; - this._outputChannel = this.outputService.getChannel(TaskService.OutputChannelId); + this._outputChannel = this.outputService.getChannel(TaskService.OutputChannelId)!; this._providers = new Map(); this._taskSystemInfos = new Map(); this._register(this.contextService.onDidChangeWorkspaceFolders(() => { @@ -549,8 +549,20 @@ class TaskService extends Disposable implements ITaskService { } private registerCommands(): void { - CommandsRegistry.registerCommand('workbench.action.tasks.runTask', (accessor, arg) => { - this.runTaskCommand(arg); + CommandsRegistry.registerCommand({ + id: 'workbench.action.tasks.runTask', + handler: (accessor, arg) => { + this.runTaskCommand(arg); + }, + description: { + description: 'Run Task', + args: [{ + name: 'args', + schema: { + 'type': 'string', + } + }] + } }); CommandsRegistry.registerCommand('workbench.action.tasks.reRunTask', (accessor, arg) => { @@ -706,26 +718,31 @@ class TaskService extends Disposable implements ITaskService { this._taskSystemInfos.set(key, info); } - public getTask(folder: IWorkspaceFolder | string, identifier: string | TaskIdentifier, compareId: boolean = false): Promise { - let name = Types.isString(folder) ? folder : folder.name; + public extensionCallbackTaskComplete(task: Task, result: number): Promise { + if (!this._taskSystem) { + return Promise.resolve(); + } + return this._taskSystem.customExecutionComplete(task, result); + } + + public getTask(folder: IWorkspaceFolder | string, identifier: string | TaskIdentifier, compareId: boolean = false): Promise { + const name = Types.isString(folder) ? folder : folder.name; if (this.ignoredWorkspaceFolders.some(ignored => ignored.name === name)) { return Promise.reject(new Error(nls.localize('TaskServer.folderIgnored', 'The folder {0} is ignored since it uses task version 0.1.0', name))); } - let key: string | KeyedTaskIdentifier; - if (!Types.isString(identifier)) { - key = TaskDefinition.createTaskIdentifier(identifier, console); - } else { - key = identifier; - } + const key: string | KeyedTaskIdentifier | undefined = !Types.isString(identifier) + ? TaskDefinition.createTaskIdentifier(identifier, console) + : identifier; + if (key === undefined) { return Promise.resolve(undefined); } return this.getGroupedTasks().then((map) => { - let values = map.get(folder); + const values = map.get(folder); if (!values) { return undefined; } - for (let task of values) { + for (const task of values) { if (task.matches(key, compareId)) { return task; } @@ -854,23 +871,22 @@ class TaskService extends Disposable implements ITaskService { }); } - public run(task: Task, options?: ProblemMatcherRunOptions): Promise { + public run(task: Task | undefined, options?: ProblemMatcherRunOptions): Promise { + if (!task) { + throw new TaskError(Severity.Info, nls.localize('TaskServer.noTask', 'Task to execute is undefined'), TaskErrors.TaskNotFound); + } return this.getGroupedTasks().then((grouped) => { - if (!task) { - throw new TaskError(Severity.Info, nls.localize('TaskServer.noTask', 'Requested task {0} to execute not found.', task.configurationProperties.name), TaskErrors.TaskNotFound); - } else { - let resolver = this.createResolver(grouped); - if (options && options.attachProblemMatcher && this.shouldAttachProblemMatcher(task) && !InMemoryTask.is(task)) { - return this.attachProblemMatcher(task).then((toExecute) => { - if (toExecute) { - return this.executeTask(toExecute, resolver); - } else { - return Promise.resolve(undefined); - } - }); - } - return this.executeTask(task, resolver); + let resolver = this.createResolver(grouped); + if (options && options.attachProblemMatcher && this.shouldAttachProblemMatcher(task) && !InMemoryTask.is(task)) { + return this.attachProblemMatcher(task).then((toExecute) => { + if (toExecute) { + return this.executeTask(toExecute, resolver); + } else { + return Promise.resolve(undefined); + } + }); } + return this.executeTask(task, resolver); }).then(value => value, (error) => { this.handleError(error); return Promise.reject(error); @@ -888,7 +904,7 @@ class TaskService extends Disposable implements ITaskService { return false; } if (ContributedTask.is(task)) { - return !task.hasDefinedMatchers && task.configurationProperties.problemMatchers.length === 0; + return !task.hasDefinedMatchers && !!task.configurationProperties.problemMatchers && (task.configurationProperties.problemMatchers.length === 0); } if (CustomTask.is(task)) { let configProperties: TaskConfig.ConfigurationProperties = task._source.config.element; @@ -897,9 +913,9 @@ class TaskService extends Disposable implements ITaskService { return false; } - private attachProblemMatcher(task: ContributedTask | CustomTask): Promise { + private attachProblemMatcher(task: ContributedTask | CustomTask): Promise { interface ProblemMatcherPickEntry extends IQuickPickItem { - matcher: NamedProblemMatcher; + matcher: NamedProblemMatcher | undefined; never?: boolean; learnMore?: boolean; } @@ -920,7 +936,13 @@ class TaskService extends Disposable implements ITaskService { } } if (entries.length > 0) { - entries = entries.sort((a, b) => a.label.localeCompare(b.label)); + entries = entries.sort((a, b) => { + if (a.label && b.label) { + return a.label.localeCompare(b.label); + } else { + return 0; + } + }); entries.unshift({ type: 'separator', label: nls.localize('TaskService.associate', 'associate') }); entries.unshift( { label: nls.localize('TaskService.attachProblemMatcher.continueWithout', 'Continue without scanning the task output'), matcher: undefined }, @@ -992,7 +1014,7 @@ class TaskService extends Disposable implements ITaskService { } public customize(task: ContributedTask | CustomTask, properties?: CustomizationProperties, openConfig?: boolean): Promise { - let workspaceFolder = task.getWorkspaceFolder(); + const workspaceFolder = task.getWorkspaceFolder(); if (!workspaceFolder) { return Promise.resolve(undefined); } @@ -1003,8 +1025,8 @@ class TaskService extends Disposable implements ITaskService { } let fileConfig = configuration.config; - let index: number; - let toCustomize: TaskConfig.CustomTask | TaskConfig.ConfiguringTask; + let index: number | undefined; + let toCustomize: TaskConfig.CustomTask | TaskConfig.ConfiguringTask | undefined; let taskConfig = CustomTask.is(task) ? task._source.config : undefined; if (taskConfig && taskConfig.element) { index = taskConfig.index; @@ -1014,7 +1036,7 @@ class TaskService extends Disposable implements ITaskService { }; let identifier: TaskConfig.TaskIdentifier = Objects.assign(Object.create(null), task.defines); delete identifier['_key']; - Object.keys(identifier).forEach(key => toCustomize[key] = identifier[key]); + Object.keys(identifier).forEach(key => toCustomize![key] = identifier[key]); if (task.configurationProperties.problemMatchers && task.configurationProperties.problemMatchers.length > 0 && Types.isStringArray(task.configurationProperties.problemMatchers)) { toCustomize.problemMatcher = task.configurationProperties.problemMatchers; } @@ -1030,12 +1052,12 @@ class TaskService extends Disposable implements ITaskService { } } } else { - if (toCustomize.problemMatcher === undefined && task.configurationProperties.problemMatchers === undefined || task.configurationProperties.problemMatchers.length === 0) { + if (toCustomize.problemMatcher === undefined && task.configurationProperties.problemMatchers === undefined || (task.configurationProperties.problemMatchers && task.configurationProperties.problemMatchers.length === 0)) { toCustomize.problemMatcher = []; } } - let promise: Promise; + let promise: Promise | undefined; if (!fileConfig) { let value = { version: '2.0.0', @@ -1052,7 +1074,7 @@ class TaskService extends Disposable implements ITaskService { promise = this.fileService.createFile(workspaceFolder.toResource('.vscode/tasks.json'), content).then(() => { }); } else { // We have a global task configuration - if (index === -1) { + if ((index === -1) && properties) { if (properties.problemMatcher !== undefined) { fileConfig.problemMatcher = properties.problemMatcher; promise = this.writeConfiguration(workspaceFolder, 'tasks.problemMatchers', fileConfig.problemMatcher); @@ -1098,7 +1120,7 @@ class TaskService extends Disposable implements ITaskService { }); } - private writeConfiguration(workspaceFolder: IWorkspaceFolder, key: string, value: any): Promise { + private writeConfiguration(workspaceFolder: IWorkspaceFolder, key: string, value: any): Promise | undefined { if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { return this.configurationService.updateValue(key, value, { resource: workspaceFolder.uri }, ConfigurationTarget.WORKSPACE); } else if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { @@ -1109,7 +1131,7 @@ class TaskService extends Disposable implements ITaskService { } public openConfig(task: CustomTask | undefined): Promise { - let resource: URI; + let resource: URI | undefined; if (task) { resource = task.getWorkspaceFolder().toResource(task._source.config.file); } else { @@ -1123,7 +1145,7 @@ class TaskService extends Disposable implements ITaskService { }).then(() => undefined); } - private createRunnableTask(tasks: TaskMap, group: TaskGroup): { task: Task; resolver: ITaskResolver } { + private createRunnableTask(tasks: TaskMap, group: TaskGroup): { task: Task; resolver: ITaskResolver } | undefined { interface ResolverData { id: Map; label: Map; @@ -1146,7 +1168,9 @@ class TaskService extends Disposable implements ITaskService { for (let task of tasks) { data.id.set(task._id, task); data.label.set(task._label, task); - data.identifier.set(task.configurationProperties.identifier, task); + if (task.configurationProperties.identifier) { + data.identifier.set(task.configurationProperties.identifier, task); + } if (group && task.configurationProperties.group === group) { if (task._source.kind === TaskSourceKind.Workspace) { workspaceTasks.push(task); @@ -1189,7 +1213,7 @@ class TaskService extends Disposable implements ITaskService { { reevaluateOnRerun: true }, { identifier: id, - dependsOn: extensionTasks.map((task) => { return { workspaceFolder: task.getWorkspaceFolder(), task: task._id }; }), + dependsOn: extensionTasks.map((extensionTask) => { return { workspaceFolder: extensionTask.getWorkspaceFolder()!, task: extensionTask._id }; }), name: id, } ); @@ -1213,7 +1237,9 @@ class TaskService extends Disposable implements ITaskService { } for (let task of tasks) { data.label.set(task._label, task); - data.identifier.set(task.configurationProperties.identifier, task); + if (task.configurationProperties.identifier) { + data.identifier.set(task.configurationProperties.identifier, task); + } let keyedIdentifier = task.getDefinition(true); if (keyedIdentifier !== undefined) { data.taskIdentifier.set(keyedIdentifier._key, task); @@ -1221,9 +1247,9 @@ class TaskService extends Disposable implements ITaskService { } }); return { - resolve: (workspaceFolder: IWorkspaceFolder, identifier: string | TaskIdentifier) => { + resolve: (workspaceFolder: IWorkspaceFolder, identifier: string | TaskIdentifier | undefined) => { let data = resolverData.get(workspaceFolder.uri.toString()); - if (!data) { + if (!data || !identifier) { return undefined; } if (Types.isString(identifier)) { @@ -1259,7 +1285,7 @@ class TaskService extends Disposable implements ITaskService { } if (executeResult.kind === TaskExecuteKind.Active) { let active = executeResult.active; - if (active.same) { + if (active && active.same) { let message; if (active.background) { message = nls.localize('TaskSystem.activeSame.background', 'The task \'{0}\' is already active and in background mode.', executeResult.task.getQualifiedLabel()); @@ -1322,7 +1348,8 @@ class TaskService extends Disposable implements ITaskService { this._taskSystem = new TerminalTaskSystem( this.terminalService, this.outputService, this.markerService, this.modelService, this.configurationResolverService, this.telemetryService, - this.contextService, TaskService.OutputChannelId, + this.contextService, this._windowService, + TaskService.OutputChannelId, (workspaceFolder: IWorkspaceFolder) => { if (!workspaceFolder) { return undefined; @@ -1338,13 +1365,13 @@ class TaskService extends Disposable implements ITaskService { system.hasErrors(this._configHasErrors); this._taskSystem = system; } - this._taskSystemListener = this._taskSystem.onDidStateChange((event) => { + this._taskSystemListener = this._taskSystem!.onDidStateChange((event) => { if (this._taskSystem) { this._taskRunningState.set(this._taskSystem.isActiveSync()); } this._onDidStateChange.fire(event); }); - return this._taskSystem; + return this._taskSystem!; } private getGroupedTasks(): Promise { @@ -1459,7 +1486,7 @@ class TaskService extends Disposable implements ITaskService { result.add(key, ...folderTasks.set.tasks); } unUsedConfigurations.forEach((value) => { - let configuringTask = configurations.byIdentifier[value]; + let configuringTask = configurations!.byIdentifier[value]; this._outputChannel.append(nls.localize( 'TaskService.noConfiguration', 'Error: The {0} task detection didn\'t contribute a task for the following configuration:\n{1}\nThe task will be ignored.\n', @@ -1480,7 +1507,10 @@ class TaskService extends Disposable implements ITaskService { let result: TaskMap = new TaskMap(); for (let set of contributedTaskSets) { for (let task of set.tasks) { - result.add(task.getWorkspaceFolder(), task); + const folder = task.getWorkspaceFolder(); + if (folder) { + result.add(folder, task); + } } } return result; @@ -1488,14 +1518,14 @@ class TaskService extends Disposable implements ITaskService { }); } - private getLegacyTaskConfigurations(workspaceTasks: TaskSet): IStringDictionary { - let result: IStringDictionary; - function getResult() { + private getLegacyTaskConfigurations(workspaceTasks: TaskSet): IStringDictionary | undefined { + let result: IStringDictionary | undefined; + function getResult(): IStringDictionary { if (result) { return result; } result = Object.create(null); - return result; + return result!; } for (let task of workspaceTasks.tasks) { if (CustomTask.is(task)) { @@ -1520,11 +1550,11 @@ class TaskService extends Disposable implements ITaskService { } this.updateWorkspaceTasks(runSource); if (runSource === TaskRunSource.User) { - this._workspaceTasksPromise.then(workspaceFolderTasks => { + this._workspaceTasksPromise!.then(workspaceFolderTasks => { RunAutomaticTasks.promptForPermission(this, this.storageService, this.notificationService, workspaceFolderTasks); }); } - return this._workspaceTasksPromise; + return this._workspaceTasksPromise!; } private updateWorkspaceTasks(runSource: TaskRunSource = TaskRunSource.User): void { @@ -1544,7 +1574,7 @@ class TaskService extends Disposable implements ITaskService { if (this.workspaceFolders.length === 0) { return Promise.resolve(new Map()); } else { - let promises: Promise[] = []; + let promises: Promise[] = []; for (let folder of this.workspaceFolders) { promises.push(this.computeWorkspaceFolderTasks(folder, runSource).then((value) => value, () => undefined)); } @@ -1569,9 +1599,9 @@ class TaskService extends Disposable implements ITaskService { return Promise.resolve({ workspaceFolder, set: undefined, configurations: undefined, hasErrors: workspaceFolderConfiguration ? workspaceFolderConfiguration.hasErrors : false }); } return ProblemMatcherRegistry.onReady().then((): WorkspaceFolderTaskResult => { - let taskSystemInfo: TaskSystemInfo = this._taskSystemInfos.get(workspaceFolder.uri.scheme); + let taskSystemInfo: TaskSystemInfo | undefined = this._taskSystemInfos.get(workspaceFolder.uri.scheme); let problemReporter = new ProblemReporter(this._outputChannel); - let parseResult = TaskConfig.parse(workspaceFolder, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config, problemReporter); + let parseResult = TaskConfig.parse(workspaceFolder, taskSystemInfo ? taskSystemInfo.platform : Platform.platform, workspaceFolderConfiguration.config!, problemReporter); let hasErrors = false; if (!parseResult.validationStatus.isOK()) { hasErrors = true; @@ -1581,7 +1611,7 @@ class TaskService extends Disposable implements ITaskService { problemReporter.fatal(nls.localize('TaskSystem.configurationErrors', 'Error: the provided task configuration has validation errors and can\'t not be used. Please correct the errors first.')); return { workspaceFolder, set: undefined, configurations: undefined, hasErrors }; } - let customizedTasks: { byIdentifier: IStringDictionary; }; + let customizedTasks: { byIdentifier: IStringDictionary; } | undefined; if (parseResult.configured && parseResult.configured.length > 0) { customizedTasks = { byIdentifier: Object.create(null) @@ -1613,19 +1643,26 @@ class TaskService extends Disposable implements ITaskService { if (!detectedConfig) { return { workspaceFolder, config, hasErrors }; } - let result: TaskConfig.ExternalTaskRunnerConfiguration = Objects.deepClone(config); + let result: TaskConfig.ExternalTaskRunnerConfiguration = Objects.deepClone(config)!; let configuredTasks: IStringDictionary = Object.create(null); - if (!result.tasks) { + const resultTasks = result.tasks; + if (!resultTasks) { if (detectedConfig.tasks) { result.tasks = detectedConfig.tasks; } } else { - result.tasks.forEach(task => configuredTasks[task.taskName] = task); - detectedConfig.tasks.forEach((task) => { - if (!configuredTasks[task.taskName]) { - result.tasks.push(task); + resultTasks.forEach(task => { + if (task.taskName) { + configuredTasks[task.taskName] = task; } }); + if (detectedConfig.tasks) { + detectedConfig.tasks.forEach((task) => { + if (task.taskName && !configuredTasks[task.taskName]) { + resultTasks.push(task); + } + }); + } } return { workspaceFolder, config: result, hasErrors }; }); @@ -1635,7 +1672,7 @@ class TaskService extends Disposable implements ITaskService { } else { return new ProcessRunnerDetector(workspaceFolder, this.fileService, this.contextService, this.configurationResolverService).detect(true).then((value) => { let hasErrors = this.printStderr(value.stderr); - return { workspaceFolder, config: value.config, hasErrors }; + return { workspaceFolder, config: value.config!, hasErrors }; }); } } @@ -1683,7 +1720,7 @@ class TaskService extends Disposable implements ITaskService { return TaskConfig.JsonSchemaVersion.from(config); } - private getConfiguration(workspaceFolder: IWorkspaceFolder): { config: TaskConfig.ExternalTaskRunnerConfiguration; hasParseErrors: boolean } { + private getConfiguration(workspaceFolder: IWorkspaceFolder): { config: TaskConfig.ExternalTaskRunnerConfiguration | undefined; hasParseErrors: boolean } { let result = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? Objects.deepClone(this.configurationService.getValue('tasks', { resource: workspaceFolder.uri })) : undefined; @@ -1735,10 +1772,10 @@ class TaskService extends Disposable implements ITaskService { } public configureAction(): Action { - let run = () => { this.runConfigureTasks(); return Promise.resolve(undefined); }; + const thisCapture: TaskService = this; return new class extends Action { constructor() { - super(ConfigureTaskAction.ID, ConfigureTaskAction.TEXT, undefined, true, run); + super(ConfigureTaskAction.ID, ConfigureTaskAction.TEXT, undefined, true, () => { thisCapture.runConfigureTasks(); return Promise.resolve(undefined); }); } }; } @@ -1769,7 +1806,7 @@ class TaskService extends Disposable implements ITaskService { return terminatePromise.then(res => { if (res.confirmed) { - return this._taskSystem.terminateAll().then((responses) => { + return this._taskSystem!.terminateAll().then((responses) => { let success = true; let code: number | undefined = undefined; for (let response of responses) { @@ -1781,7 +1818,7 @@ class TaskService extends Disposable implements ITaskService { } } if (success) { - this._taskSystem = null; + this._taskSystem = undefined; this.disposeTaskSystemListeners(); return false; // no veto } else if (code && code === TerminateResponseCode.ProcessNotFound) { @@ -1824,6 +1861,7 @@ class TaskService extends Disposable implements ITaskService { } else if (err instanceof Error) { let error = err; this.notificationService.error(error.message); + showOutput = false; } else if (Types.isString(err)) { this.notificationService.error(err); } else { @@ -1847,7 +1885,7 @@ class TaskService extends Disposable implements ITaskService { return []; } const TaskQuickPickEntry = (task: Task): TaskQuickPickEntry => { - let description: string; + let description: string | undefined; if (this.needsFolderQualification()) { let workspaceFolder = task.getWorkspaceFolder(); if (workspaceFolder) { @@ -1920,7 +1958,7 @@ class TaskService extends Disposable implements ITaskService { return entries; } - private showQuickPick(tasks: Promise | Task[], placeHolder: string, defaultEntry?: TaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry): Promise { + private showQuickPick(tasks: Promise | Task[], placeHolder: string, defaultEntry?: TaskQuickPickEntry, group: boolean = false, sort: boolean = false, selectedEntry?: TaskQuickPickEntry): Promise { let _createEntries = (): Promise => { if (Array.isArray(tasks)) { return Promise.resolve(this.createTaskQuickPickEntries(tasks, group, sort, selectedEntry)); @@ -2032,7 +2070,7 @@ class TaskService extends Disposable implements ITaskService { return this.handleExecuteResult(executeResult); } else { this.doRunTaskCommand(); - return undefined; + return Promise.resolve(undefined); } }); }); @@ -2254,7 +2292,7 @@ class TaskService extends Disposable implements ITaskService { } private getTaskIdentifier(arg?: any): string | KeyedTaskIdentifier | undefined { - let result: string | KeyedTaskIdentifier = undefined; + let result: string | KeyedTaskIdentifier | undefined = undefined; if (Types.isString(arg)) { result = arg; } else if (arg && Types.isString((arg as TaskIdentifier).type)) { @@ -2283,7 +2321,7 @@ class TaskService extends Disposable implements ITaskService { } return this.quickInputService.pick(getTaskTemplates(), { placeHolder: nls.localize('TaskService.template', 'Select a Task Template') }).then((selection) => { if (!selection) { - return undefined; + return Promise.resolve(undefined); } let content = selection.content; let editorConfig = this.configurationService.getValue(); @@ -2333,7 +2371,7 @@ class TaskService extends Disposable implements ITaskService { return candidate && !!candidate.task; } - let stats = this.contextService.getWorkspace().folders.map>((folder) => { + let stats = this.contextService.getWorkspace().folders.map>((folder) => { return this.fileService.resolveFile(folder.toResource('.vscode/tasks.json')).then(stat => stat, () => undefined); }); @@ -2413,7 +2451,7 @@ class TaskService extends Disposable implements ITaskService { this.runConfigureTasks(); return; } - let selectedTask: Task; + let selectedTask: Task | undefined; let selectedEntry: TaskQuickPickEntry; for (let task of tasks) { if (task.configurationProperties.group === TaskGroup.Build && task.configurationProperties.groupType === GroupType.default) { @@ -2431,7 +2469,7 @@ class TaskService extends Disposable implements ITaskService { this.showQuickPick(tasks, nls.localize('TaskService.pickDefaultBuildTask', 'Select the task to be used as the default build task'), undefined, true, false, selectedEntry). then((task) => { - if (task === undefined) { + if ((task === undefined) || (task === null)) { return; } if (task === selectedTask && CustomTask.is(task)) { @@ -2462,7 +2500,7 @@ class TaskService extends Disposable implements ITaskService { this.runConfigureTasks(); return; } - let selectedTask: Task; + let selectedTask: Task | undefined; let selectedEntry: TaskQuickPickEntry; for (let task of tasks) { @@ -2517,7 +2555,7 @@ class TaskService extends Disposable implements ITaskService { if (task === undefined || task === null) { return; } - this._taskSystem.revealTask(task); + this._taskSystem!.revealTask(task); }); } } @@ -2646,17 +2684,18 @@ let schema: IJSONSchema = { description: 'Task definition file', type: 'object', default: { - version: '0.1.0', - command: 'myCommand', - isShellCommand: false, - args: [], - showOutput: 'always', + version: '2.0.0', tasks: [ { - taskName: 'build', - showOutput: 'silent', - isBuildCommand: true, - problemMatcher: ['$tsc', '$lessCompile'] + label: 'My Task', + command: 'echo hello', + type: 'shell', + args: [], + problemMatcher: ['$tsc'], + presentation: { + reveal: 'always' + }, + group: 'build' } ] } @@ -2668,7 +2707,7 @@ schema.definitions = { ...schemaVersion1.definitions, ...schemaVersion2.definitions, }; -schema.oneOf = [...schemaVersion2.oneOf, ...schemaVersion1.oneOf]; +schema.oneOf = [...(schemaVersion2.oneOf || []), ...(schemaVersion1.oneOf || [])]; let jsonRegistry = Registry.as(jsonContributionRegistry.Extensions.JSONContribution); jsonRegistry.registerSchema(schemaId, schema); diff --git a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts similarity index 90% rename from src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts rename to src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts index 2bdff943fc..c4aebe8612 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/electron-browser/terminalTaskSystem.ts @@ -3,8 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; - +import * as path from 'vs/base/common/path'; import * as nls from 'vs/nls'; import * as Objects from 'vs/base/common/objects'; import * as Types from 'vs/base/common/types'; @@ -15,29 +14,34 @@ import { LinkedMap, Touch } from 'vs/base/common/map'; import Severity from 'vs/base/common/severity'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import * as TPath from 'vs/base/common/paths'; +import { isUNC } from 'vs/base/common/extpath'; import { win32 } from 'vs/base/node/processes'; import { IMarkerService, MarkerSeverity } from 'vs/platform/markers/common/markers'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { ProblemMatcher, ProblemMatcherRegistry /*, ProblemPattern, getResource */ } from 'vs/workbench/parts/tasks/common/problemMatcher'; +import { ProblemMatcher, ProblemMatcherRegistry /*, ProblemPattern, getResource */ } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { ITerminalService, ITerminalInstance, IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal'; -import { IOutputService, IOutputChannel } from 'vs/workbench/parts/output/common/output'; -import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind } from 'vs/workbench/parts/tasks/common/problemCollectors'; +import { ITerminalService, ITerminalInstance, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IOutputService } from 'vs/workbench/contrib/output/common/output'; +import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind } from 'vs/workbench/contrib/tasks/common/problemCollectors'; import { Task, CustomTask, ContributedTask, RevealKind, CommandOptions, ShellConfiguration, RuntimeType, PanelKind, TaskEvent, TaskEventKind, ShellQuotingOptions, ShellQuoting, CommandString, CommandConfiguration, ExtensionTaskSource, TaskScope -} from 'vs/workbench/parts/tasks/common/tasks'; +} from 'vs/workbench/contrib/tasks/common/tasks'; import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, ITaskResolver, TelemetryEvent, Triggers, TaskTerminateResponse, TaskSystemInfoResovler, TaskSystemInfo, ResolveSet, ResolvedVariables -} from 'vs/workbench/parts/tasks/common/taskSystem'; +} from 'vs/workbench/contrib/tasks/common/taskSystem'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { URI } from 'vs/base/common/uri'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { Schemas } from 'vs/base/common/network'; +import { getWindowsBuildNumber } from 'vs/workbench/contrib/terminal/node/terminal'; interface TerminalData { terminal: ITerminalInstance; @@ -59,7 +63,7 @@ class VariableResolver { return value.replace(/\$\{(.*?)\}/g, (match: string, variable: string) => { // Strip out the ${} because the map contains them variables without those characters. let result = this._values.get(match.substring(2, match.length - 1)); - if (result) { + if ((result !== undefined) && (result !== null)) { return result; } if (this._service) { @@ -144,7 +148,6 @@ export class TerminalTaskSystem implements ITaskSystem { 'win32': TerminalTaskSystem.shellQuotes['powershell'] }; - private outputChannel: IOutputChannel; private activeTasks: IStringDictionary; private terminals: IStringDictionary; private idleTaskTerminals: LinkedMap; @@ -161,10 +164,10 @@ export class TerminalTaskSystem implements ITaskSystem { private configurationResolverService: IConfigurationResolverService, private telemetryService: ITelemetryService, private contextService: IWorkspaceContextService, - outputChannelId: string, + private windowService: IWindowService, + private outputChannelId: string, taskSystemInfoResolver: TaskSystemInfoResovler) { - this.outputChannel = this.outputService.getChannel(outputChannelId); this.activeTasks = Object.create(null); this.terminals = Object.create(null); this.idleTaskTerminals = new LinkedMap(); @@ -179,11 +182,11 @@ export class TerminalTaskSystem implements ITaskSystem { } public log(value: string): void { - this.outputChannel.append(value + '\n'); + this.appendOutput(value + '\n'); } protected showOutput(): void { - this.outputService.showChannel(this.outputChannel.id, true); + this.outputService.showChannel(this.outputChannelId, true); } public run(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult { @@ -266,6 +269,18 @@ export class TerminalTaskSystem implements ITaskSystem { return Object.keys(this.activeTasks).map(key => this.activeTasks[key].task); } + public customExecutionComplete(task: Task, result: number): Promise { + let activeTerminal = this.activeTasks[task.getMapKey()]; + if (!activeTerminal) { + return Promise.reject(new Error('Expected to have a terminal for an custom execution task')); + } + + return new Promise((resolve) => { + activeTerminal.terminal.rendererExit(result); + resolve(); + }); + } + public terminate(task: Task): Promise { let activeTerminal = this.activeTasks[task.getMapKey()]; if (!activeTerminal) { @@ -273,6 +288,7 @@ export class TerminalTaskSystem implements ITaskSystem { } return new Promise((resolve, reject) => { let terminal = activeTerminal.terminal; + const onExit = terminal.onExit(() => { let task = activeTerminal.task; try { @@ -516,13 +532,15 @@ export class TerminalTaskSystem implements ITaskSystem { let processStartedSignaled = false; terminal.processReady.then(() => { if (!processStartedSignaled) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); + if (task.command.runtime !== RuntimeType.CustomExecution) { + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); + } processStartedSignaled = true; } }, (_error) => { // The process never got ready. Need to think how to handle this. }); - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task)); + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.id)); const registeredLinkMatchers = this.registerLinkMatchers(terminal, problemMatchers); const onData = terminal.onLineData((line) => { watchingProblemMatcher.processLine(line); @@ -564,7 +582,11 @@ export class TerminalTaskSystem implements ITaskSystem { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); processStartedSignaled = true; } - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); + + if (task.command.runtime !== RuntimeType.CustomExecution) { + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); + } + for (let i = 0; i < eventCounter; i++) { let event = TaskEvent.create(TaskEventKind.Inactive, task); this._onDidStateChange.fire(event); @@ -586,13 +608,15 @@ export class TerminalTaskSystem implements ITaskSystem { let processStartedSignaled = false; terminal.processReady.then(() => { if (!processStartedSignaled) { - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); + if (task.command.runtime !== RuntimeType.CustomExecution) { + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal!.processId!)); + } processStartedSignaled = true; } }, (_error) => { // The process never got ready. Need to think how to handle this. }); - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task)); + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Start, task, terminal.id)); this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Active, task)); let problemMatchers = this.resolveMatchers(resolver, task.configurationProperties.problemMatchers); let startStopProblemMatcher = new StartStopProblemCollector(problemMatchers, this.markerService, this.modelService); @@ -634,7 +658,9 @@ export class TerminalTaskSystem implements ITaskSystem { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessStarted, task, terminal.processId!)); processStartedSignaled = true; } - this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); + if (task.command.runtime !== RuntimeType.CustomExecution) { + this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, exitCode)); + } this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.Inactive, task)); this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.End, task)); resolve({ exitCode }); @@ -697,11 +723,16 @@ export class TerminalTaskSystem implements ITaskSystem { }); } + private createTerminalName(task: CustomTask | ContributedTask): string { + const needsFolderQualification = this.currentTask.workspaceFolder && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE; + return nls.localize('TerminalTaskSystem.terminalName', 'Task - {0}', needsFolderQualification ? task.getQualifiedLabel() : task.configurationProperties.name); + } + private createShellLaunchConfig(task: CustomTask | ContributedTask, variableResolver: VariableResolver, platform: Platform.Platform, options: CommandOptions, command: CommandString, args: CommandString[], waitOnExit: boolean | string): IShellLaunchConfig | undefined { let shellLaunchConfig: IShellLaunchConfig; let isShellCommand = task.command.runtime === RuntimeType.Shell; let needsFolderQualification = this.currentTask.workspaceFolder && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE; - let terminalName = nls.localize('TerminalTaskSystem.terminalName', 'Task - {0}', needsFolderQualification ? task.getQualifiedLabel() : task.configurationProperties.name); + let terminalName = this.createTerminalName(task); let originalCommand = task.command.name; if (isShellCommand) { shellLaunchConfig = { name: terminalName, executable: undefined, args: undefined, waitOnExit }; @@ -735,7 +766,7 @@ export class TerminalTaskSystem implements ITaskSystem { } windowsShellArgs = true; let basename = path.basename(shellLaunchConfig.executable!).toLowerCase(); - if (basename === 'cmd.exe' && ((options.cwd && TPath.isUNC(options.cwd)) || (!options.cwd && TPath.isUNC(process.cwd())))) { + if (basename === 'cmd.exe' && ((options.cwd && isUNC(options.cwd)) || (!options.cwd && isUNC(process.cwd())))) { return undefined; } if ((basename === 'powershell.exe') || (basename === 'pwsh.exe')) { @@ -748,7 +779,7 @@ export class TerminalTaskSystem implements ITaskSystem { toAdd.push('-c'); } } else if (basename === 'wsl.exe') { - if (!shellSpecified) { + if (!shellSpecified && (getWindowsBuildNumber() >= 17763)) { // See https://github.com/Microsoft/vscode/issues/67855 toAdd.push('-e'); } } else { @@ -783,7 +814,7 @@ export class TerminalTaskSystem implements ITaskSystem { } } } else { - let commandExecutable = CommandString.value(command); + let commandExecutable = task.command.runtime !== RuntimeType.CustomExecution ? CommandString.value(command) : undefined; let executable = !isShellCommand ? this.resolveVariable(variableResolver, '${' + TerminalTaskSystem.ProcessVarName + '}') : commandExecutable; @@ -815,23 +846,15 @@ export class TerminalTaskSystem implements ITaskSystem { if (options.cwd) { let cwd = options.cwd; - let p: typeof path; - // This must be normalized to the OS - if (platform === Platform.Platform.Windows) { - p = path.win32 as any; - } else if (platform === Platform.Platform.Linux || platform === Platform.Platform.Mac) { - p = path.posix as any; - } else { - p = path; - } - if (!p.isAbsolute(cwd)) { + if (!path.isAbsolute(cwd)) { let workspaceFolder = task.getWorkspaceFolder(); if (workspaceFolder && (workspaceFolder.uri.scheme === 'file')) { - cwd = p.join(workspaceFolder.uri.fsPath, cwd); + cwd = path.join(workspaceFolder.uri.fsPath, cwd); } } // This must be normalized to the OS - shellLaunchConfig.cwd = p.normalize(cwd); + const authority = this.windowService.getConfiguration().remoteAuthority; + shellLaunchConfig.cwd = URI.from({ scheme: authority ? REMOTE_HOST_SCHEME : Schemas.file, authority: authority, path: cwd }); } if (options.env) { shellLaunchConfig.env = options.env; @@ -842,9 +865,8 @@ export class TerminalTaskSystem implements ITaskSystem { private createTerminal(task: CustomTask | ContributedTask, resolver: VariableResolver): [ITerminalInstance | undefined, string | undefined, TaskError | undefined] { let platform = resolver.taskSystemInfo ? resolver.taskSystemInfo.platform : Platform.platform; let options = this.resolveOptions(resolver, task.command.options); + let waitOnExit: boolean | string = false; - let { command, args } = this.resolveCommandAndArgs(resolver, task.command); - let commandExecutable = CommandString.value(command); const presentationOptions = task.command.presentation; if (!presentationOptions) { throw new Error('Task presentation options should not be undefined here.'); @@ -859,10 +881,29 @@ export class TerminalTaskSystem implements ITaskSystem { waitOnExit = true; } } - this.currentTask.shellLaunchConfig = this.isRerun ? this.lastTask.getVerifiedTask().shellLaunchConfig : this.createShellLaunchConfig(task, resolver, platform, options, command, args, waitOnExit); - if (this.currentTask.shellLaunchConfig === undefined) { - return [undefined, undefined, new TaskError(Severity.Error, nls.localize('TerminalTaskSystem', 'Can\'t execute a shell command on an UNC drive using cmd.exe.'), TaskErrors.UnknownError)]; + + let commandExecutable: string | undefined; + let command: CommandString | undefined; + let args: CommandString[] | undefined; + + if (task.command.runtime === RuntimeType.CustomExecution) { + this.currentTask.shellLaunchConfig = { + isRendererOnly: true, + waitOnExit, + name: this.createTerminalName(task) + }; + } else { + let resolvedResult: { command: CommandString, args: CommandString[] } = this.resolveCommandAndArgs(resolver, task.command); + command = resolvedResult.command; + args = resolvedResult.args; + commandExecutable = CommandString.value(command); + + this.currentTask.shellLaunchConfig = this.isRerun ? this.lastTask.getVerifiedTask().shellLaunchConfig : this.createShellLaunchConfig(task, resolver, platform, options, command, args, waitOnExit); + if (this.currentTask.shellLaunchConfig === undefined) { + return [undefined, undefined, new TaskError(Severity.Error, nls.localize('TerminalTaskSystem', 'Can\'t execute a shell command on an UNC drive using cmd.exe.'), TaskErrors.UnknownError)]; + } } + let prefersSameTerminal = presentationOptions.panel === PanelKind.Dedicated; let allowsSharedTerminal = presentationOptions.panel === PanelKind.Shared; let group = presentationOptions.group; @@ -884,7 +925,7 @@ export class TerminalTaskSystem implements ITaskSystem { // (or, if the task has no group, a terminal used by a task without group). for (const taskId of this.idleTaskTerminals.keys()) { const idleTerminalId = this.idleTaskTerminals.get(taskId)!; - if (idleTerminalId && this.terminals[idleTerminalId].group === group) { + if (idleTerminalId && this.terminals[idleTerminalId] && this.terminals[idleTerminalId].group === group) { terminalId = this.idleTaskTerminals.remove(taskId); break; } @@ -895,7 +936,12 @@ export class TerminalTaskSystem implements ITaskSystem { } } if (terminalToReuse) { + if (!this.currentTask.shellLaunchConfig) { + throw new Error('Task shell launch configuration should not be undefined here.'); + } + terminalToReuse.terminal.reuseTerminal(this.currentTask.shellLaunchConfig); + if (task.command.presentation && task.command.presentation.clear) { terminalToReuse.terminal.clear(); } @@ -1058,6 +1104,12 @@ export class TerminalTaskSystem implements ITaskSystem { } private collectCommandVariables(variables: Set, command: CommandConfiguration, task: CustomTask | ContributedTask): void { + // The custom execution should have everything it needs already as it provided + // the callback. + if (command.runtime === RuntimeType.CustomExecution) { + return; + } + if (command.name === undefined) { throw new Error('Command name should never be undefined here.'); } @@ -1159,7 +1211,7 @@ export class TerminalTaskSystem implements ITaskSystem { matcher = value; } if (!matcher) { - this.outputChannel.append(nls.localize('unkownProblemMatcher', 'Problem matcher {0} can\'t be resolved. The matcher will be ignored')); + this.appendOutput(nls.localize('unkownProblemMatcher', 'Problem matcher {0} can\'t be resolved. The matcher will be ignored')); return; } let taskSystemInfo: TaskSystemInfo | undefined = resolver.taskSystemInfo; @@ -1279,4 +1331,11 @@ export class TerminalTaskSystem implements ITaskSystem { } return 'other'; } + + private appendOutput(output: string): void { + const outputChannel = this.outputService.getChannel(this.outputChannelId); + if (outputChannel) { + outputChannel.append(output); + } + } } diff --git a/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts b/src/vs/workbench/contrib/tasks/node/processRunnerDetector.ts similarity index 99% rename from src/vs/workbench/parts/tasks/node/processRunnerDetector.ts rename to src/vs/workbench/contrib/tasks/node/processRunnerDetector.ts index e54e033dd5..73f9ef9bea 100644 --- a/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts +++ b/src/vs/workbench/contrib/tasks/node/processRunnerDetector.ts @@ -5,7 +5,7 @@ import * as Collections from 'vs/base/common/collections'; import * as Objects from 'vs/base/common/objects'; -import * as Paths from 'vs/base/common/paths'; +import * as Path from 'vs/base/common/path'; import { CommandOptions, ErrorData, Source } from 'vs/base/common/processes'; import * as Strings from 'vs/base/common/strings'; import { LineData, LineProcess } from 'vs/base/node/processes'; @@ -156,7 +156,7 @@ export class ProcessRunnerDetector { this._workspaceRoot = workspaceFolder; this._stderr = []; this._stdout = []; - this._cwd = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? Paths.normalize(this._workspaceRoot.uri.fsPath, true) : ''; + this._cwd = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? Path.normalize(this._workspaceRoot.uri.fsPath) : ''; } public get stderr(): string[] { diff --git a/src/vs/workbench/parts/tasks/node/processTaskSystem.ts b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts similarity index 91% rename from src/vs/workbench/parts/tasks/node/processTaskSystem.ts rename to src/vs/workbench/contrib/tasks/node/processTaskSystem.ts index 994178fd9e..82454068a6 100644 --- a/src/vs/workbench/parts/tasks/node/processTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/node/processTaskSystem.ts @@ -15,23 +15,23 @@ import { Event, Emitter } from 'vs/base/common/event'; import { SuccessData, ErrorData } from 'vs/base/common/processes'; import { LineProcess, LineData } from 'vs/base/node/processes'; -import { IOutputService, IOutputChannel } from 'vs/workbench/parts/output/common/output'; +import { IOutputService } from 'vs/workbench/contrib/output/common/output'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { ProblemMatcher, ProblemMatcherRegistry } from 'vs/workbench/parts/tasks/common/problemMatcher'; +import { ProblemMatcher, ProblemMatcherRegistry } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind } from 'vs/workbench/parts/tasks/common/problemCollectors'; +import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEventKind } from 'vs/workbench/contrib/tasks/common/problemCollectors'; import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TelemetryEvent, Triggers, TaskTerminateResponse -} from 'vs/workbench/parts/tasks/common/taskSystem'; +} from 'vs/workbench/contrib/tasks/common/taskSystem'; import { Task, CustomTask, CommandOptions, RevealKind, CommandConfiguration, RuntimeType, TaskEvent, TaskEventKind -} from 'vs/workbench/parts/tasks/common/tasks'; +} from 'vs/workbench/contrib/tasks/common/tasks'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -48,8 +48,6 @@ export class ProcessTaskSystem implements ITaskSystem { private telemetryService: ITelemetryService; private configurationResolverService: IConfigurationResolverService; - private outputChannel: IOutputChannel; - private errorsShown: boolean; private childProcess: LineProcess | null; private activeTask: CustomTask | null; @@ -58,7 +56,7 @@ export class ProcessTaskSystem implements ITaskSystem { private readonly _onDidStateChange: Emitter; constructor(markerService: IMarkerService, modelService: IModelService, telemetryService: ITelemetryService, - outputService: IOutputService, configurationResolverService: IConfigurationResolverService, outputChannelId: string) { + outputService: IOutputService, configurationResolverService: IConfigurationResolverService, private outputChannelId: string) { this.markerService = markerService; this.modelService = modelService; this.outputService = outputService; @@ -68,7 +66,6 @@ export class ProcessTaskSystem implements ITaskSystem { this.childProcess = null; this.activeTask = null; this.activeTaskPromise = null; - this.outputChannel = this.outputService.getChannel(outputChannelId); this.errorsShown = true; this._onDidStateChange = new Emitter(); } @@ -105,6 +102,10 @@ export class ProcessTaskSystem implements ITaskSystem { return true; } + public customExecutionComplete(task: Task, result?: number): Promise { + throw new TaskError(Severity.Error, 'Custom execution task completion is never expected in the process task system.', TaskErrors.UnknownError); + } + public hasErrors(value: boolean): void { this.errorsShown = !value; } @@ -188,10 +189,10 @@ export class ProcessTaskSystem implements ITaskSystem { throw err; } else if (err instanceof Error) { let error = err; - this.outputChannel.append(error.message); + this.appendOutput(error.message); throw new TaskError(Severity.Error, error.message, TaskErrors.UnknownError); } else { - this.outputChannel.append(err.toString()); + this.appendOutput(err.toString()); throw new TaskError(Severity.Error, nls.localize('TaskRunnerSystem.unknownError', 'A unknown error has occurred while executing a task. See task output log for details.'), TaskErrors.UnknownError); } } @@ -256,7 +257,7 @@ export class ProcessTaskSystem implements ITaskSystem { let processStartedSignaled: boolean = false; const onProgress = (progress: LineData) => { let line = Strings.removeAnsiEscapeCodes(progress.line); - this.outputChannel.append(line + '\n'); + this.appendOutput(line + '\n'); watchingProblemMatcher.processLine(line); if (delayer === null) { delayer = new Async.Delayer(3000); @@ -279,7 +280,7 @@ export class ProcessTaskSystem implements ITaskSystem { this.childProcessEnded(); watchingProblemMatcher.done(); watchingProblemMatcher.dispose(); - if (processStartedSignaled) { + if (processStartedSignaled && task.command.runtime !== RuntimeType.CustomExecution) { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, success.cmdCode!)); } toDispose = dispose(toDispose!); @@ -320,7 +321,7 @@ export class ProcessTaskSystem implements ITaskSystem { let processStartedSignaled: boolean = false; const onProgress = (progress) => { let line = Strings.removeAnsiEscapeCodes(progress.line); - this.outputChannel.append(line + '\n'); + this.appendOutput(line + '\n'); startStopProblemMatcher.processLine(line); }; const startPromise = this.childProcess.start(onProgress); @@ -335,7 +336,7 @@ export class ProcessTaskSystem implements ITaskSystem { startStopProblemMatcher.done(); startStopProblemMatcher.dispose(); this.checkTerminated(task, success); - if (processStartedSignaled) { + if (processStartedSignaled && task.command.runtime !== RuntimeType.CustomExecution) { this._onDidStateChange.fire(TaskEvent.create(TaskEventKind.ProcessEnded, task, success.cmdCode!)); } this._onDidStateChange.fire(inactiveEvent); @@ -367,16 +368,16 @@ export class ProcessTaskSystem implements ITaskSystem { if (errorData.error && !errorData.terminated) { let args: string = task.command.args ? task.command.args.join(' ') : ''; this.log(nls.localize('TaskRunnerSystem.childProcessError', 'Failed to launch external program {0} {1}.', JSON.stringify(task.command.name), args)); - this.outputChannel.append(errorData.error.message); + this.appendOutput(errorData.error.message); makeVisible = true; } if (errorData.stdout) { - this.outputChannel.append(errorData.stdout); + this.appendOutput(errorData.stdout); makeVisible = true; } if (errorData.stderr) { - this.outputChannel.append(errorData.stderr); + this.appendOutput(errorData.stderr); makeVisible = true; } makeVisible = this.checkTerminated(task, errorData) || makeVisible; @@ -436,7 +437,7 @@ export class ProcessTaskSystem implements ITaskSystem { matcher = value; } if (!matcher) { - this.outputChannel.append(nls.localize('unkownProblemMatcher', 'Problem matcher {0} can\'t be resolved. The matcher will be ignored')); + this.appendOutput(nls.localize('unkownProblemMatcher', 'Problem matcher {0} can\'t be resolved. The matcher will be ignored')); return; } if (!matcher.filePrefix) { @@ -455,14 +456,24 @@ export class ProcessTaskSystem implements ITaskSystem { } public log(value: string): void { - this.outputChannel.append(value + '\n'); + this.appendOutput(value + '\n'); } private showOutput(): void { - this.outputService.showChannel(this.outputChannel.id, true); + this.outputService.showChannel(this.outputChannelId, true); + } + + private appendOutput(output: string): void { + const outputChannel = this.outputService.getChannel(this.outputChannelId); + if (outputChannel) { + outputChannel.append(output); + } } private clearOutput(): void { - this.outputChannel.clear(); + const outputChannel = this.outputService.getChannel(this.outputChannelId); + if (outputChannel) { + outputChannel.clear(); + } } } diff --git a/src/vs/workbench/parts/tasks/node/taskConfiguration.ts b/src/vs/workbench/contrib/tasks/node/taskConfiguration.ts similarity index 99% rename from src/vs/workbench/parts/tasks/node/taskConfiguration.ts rename to src/vs/workbench/contrib/tasks/node/taskConfiguration.ts index 426b8a13e6..6438f46c36 100644 --- a/src/vs/workbench/parts/tasks/node/taskConfiguration.ts +++ b/src/vs/workbench/contrib/tasks/node/taskConfiguration.ts @@ -15,14 +15,14 @@ import { ValidationStatus, IProblemReporter as IProblemReporterBase } from 'vs/b import { NamedProblemMatcher, ProblemMatcher, ProblemMatcherParser, Config as ProblemMatcherConfig, isNamedProblemMatcher, ProblemMatcherRegistry -} from 'vs/workbench/parts/tasks/common/problemMatcher'; +} from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import * as Tasks from '../common/tasks'; import { TaskDefinitionRegistry } from '../common/taskDefinitionRegistry'; -import { TaskDefinition } from 'vs/workbench/parts/tasks/node/tasks'; +import { TaskDefinition } from 'vs/workbench/contrib/tasks/node/tasks'; import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; export const enum ShellQuoting { @@ -1342,7 +1342,7 @@ namespace ConfiguringTask { } let configElement: Tasks.TaskSourceConfigElement = { workspaceFolder: context.workspaceFolder, - file: '.vscode\\tasks.json', + file: '.vscode/tasks.json', index, element: external }; @@ -1406,7 +1406,7 @@ namespace CustomTask { let result: Tasks.CustomTask = new Tasks.CustomTask( context.uuidMap.getUUID(taskName), - Objects.assign({} as Tasks.WorkspaceTaskSource, source, { config: { index, element: external, file: '.vscode\\tasks.json', workspaceFolder: context.workspaceFolder } }), + Objects.assign({} as Tasks.WorkspaceTaskSource, source, { config: { index, element: external, file: '.vscode/tasks.json', workspaceFolder: context.workspaceFolder } }), taskName, Tasks.CUSTOMIZED_TASK_TYPE, undefined, diff --git a/src/vs/workbench/parts/tasks/node/tasks.ts b/src/vs/workbench/contrib/tasks/node/tasks.ts similarity index 94% rename from src/vs/workbench/parts/tasks/node/tasks.ts rename to src/vs/workbench/contrib/tasks/node/tasks.ts index 1c9c1982d4..e8e8d9f742 100644 --- a/src/vs/workbench/parts/tasks/node/tasks.ts +++ b/src/vs/workbench/contrib/tasks/node/tasks.ts @@ -9,8 +9,8 @@ import * as crypto from 'crypto'; import * as Objects from 'vs/base/common/objects'; -import { TaskIdentifier, KeyedTaskIdentifier, TaskDefinition } from 'vs/workbench/parts/tasks/common/tasks'; -import { TaskDefinitionRegistry } from 'vs/workbench/parts/tasks/common/taskDefinitionRegistry'; +import { TaskIdentifier, KeyedTaskIdentifier, TaskDefinition } from 'vs/workbench/contrib/tasks/common/tasks'; +import { TaskDefinitionRegistry } from 'vs/workbench/contrib/tasks/common/taskDefinitionRegistry'; namespace KeyedTaskIdentifier { export function create(value: TaskIdentifier): KeyedTaskIdentifier { diff --git a/src/vs/workbench/parts/tasks/test/common/problemMatcher.test.ts b/src/vs/workbench/contrib/tasks/test/common/problemMatcher.test.ts similarity index 99% rename from src/vs/workbench/parts/tasks/test/common/problemMatcher.test.ts rename to src/vs/workbench/contrib/tasks/test/common/problemMatcher.test.ts index 36bfd50a70..43265ca601 100644 --- a/src/vs/workbench/parts/tasks/test/common/problemMatcher.test.ts +++ b/src/vs/workbench/contrib/tasks/test/common/problemMatcher.test.ts @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as matchers from 'vs/workbench/parts/tasks/common/problemMatcher'; +import * as matchers from 'vs/workbench/contrib/tasks/common/problemMatcher'; import * as assert from 'assert'; import { ValidationState, IProblemReporter, ValidationStatus } from 'vs/base/common/parsers'; diff --git a/src/vs/workbench/parts/tasks/test/electron-browser/configuration.test.ts b/src/vs/workbench/contrib/tasks/test/electron-browser/configuration.test.ts similarity index 99% rename from src/vs/workbench/parts/tasks/test/electron-browser/configuration.test.ts rename to src/vs/workbench/contrib/tasks/test/electron-browser/configuration.test.ts index 4ed6593f27..3375c204de 100644 --- a/src/vs/workbench/parts/tasks/test/electron-browser/configuration.test.ts +++ b/src/vs/workbench/contrib/tasks/test/electron-browser/configuration.test.ts @@ -9,11 +9,11 @@ import * as UUID from 'vs/base/common/uuid'; import * as Platform from 'vs/base/common/platform'; import { ValidationStatus } from 'vs/base/common/parsers'; -import { ProblemMatcher, FileLocationKind, ProblemPattern, ApplyToKind } from 'vs/workbench/parts/tasks/common/problemMatcher'; +import { ProblemMatcher, FileLocationKind, ProblemPattern, ApplyToKind } from 'vs/workbench/contrib/tasks/common/problemMatcher'; import { IWorkspaceFolder, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import * as Tasks from 'vs/workbench/parts/tasks/common/tasks'; -import { parse, ParseResult, IProblemReporter, ExternalTaskRunnerConfiguration, CustomTask } from 'vs/workbench/parts/tasks/node/taskConfiguration'; +import * as Tasks from 'vs/workbench/contrib/tasks/common/tasks'; +import { parse, ParseResult, IProblemReporter, ExternalTaskRunnerConfiguration, CustomTask } from 'vs/workbench/contrib/tasks/node/taskConfiguration'; const workspaceFolder: IWorkspaceFolder = new WorkspaceFolder({ uri: URI.file('/workspace/folderOne'), diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts new file mode 100644 index 0000000000..02cb825a16 --- /dev/null +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { LifecyclePhase, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { language } from 'vs/base/common/platform'; +import { Disposable } from 'vs/base/common/lifecycle'; +import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; +import { configurationTelemetry } from 'vs/platform/telemetry/common/telemetryUtils'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; + +export class TelemetryContribution extends Disposable implements IWorkbenchContribution { + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IActivityBarService activityBarService: IActivityBarService, + @ILifecycleService lifecycleService: ILifecycleService, + @IEditorService editorService: IEditorService, + @IKeybindingService keybindingsService: IKeybindingService, + @IWorkbenchThemeService themeService: IWorkbenchThemeService, + @IWindowService windowService: IWindowService, + @IConfigurationService configurationService: IConfigurationService, + @IViewletService viewletService: IViewletService + ) { + super(); + + const { filesToOpen, filesToCreate, filesToDiff } = windowService.getConfiguration(); + const activeViewlet = viewletService.getActiveViewlet(); + + /* __GDPR__ + "workspaceLoad" : { + "userAgent" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "windowSize.innerHeight": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "windowSize.innerWidth": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "windowSize.outerHeight": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "windowSize.outerWidth": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "emptyWorkbench": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workbench.filesToOpen": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workbench.filesToCreate": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workbench.filesToDiff": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "customKeybindingsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "theme": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "language": { "classification": "SystemMetaData", "purpose": "BusinessInsight" }, + "pinnedViewlets": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "restoredViewlet": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "restoredEditors": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "pinnedViewlets": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "startupKind": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + } + */ + telemetryService.publicLog('workspaceLoad', { + userAgent: navigator.userAgent, + windowSize: { innerHeight: window.innerHeight, innerWidth: window.innerWidth, outerHeight: window.outerHeight, outerWidth: window.outerWidth }, + emptyWorkbench: contextService.getWorkbenchState() === WorkbenchState.EMPTY, + 'workbench.filesToOpen': filesToOpen && filesToOpen.length || 0, + 'workbench.filesToCreate': filesToCreate && filesToCreate.length || 0, + 'workbench.filesToDiff': filesToDiff && filesToDiff.length || 0, + customKeybindingsCount: keybindingsService.customKeybindingsCount(), + theme: themeService.getColorTheme().id, + language, + pinnedViewlets: activityBarService.getPinnedViewletIds(), + restoredViewlet: activeViewlet ? activeViewlet.getId() : undefined, + restoredEditors: editorService.visibleEditors.length, + startupKind: lifecycleService.startupKind + }); + + // Error Telemetry + this._register(new ErrorTelemetry(telemetryService)); + + // Configuration Telemetry + this._register(configurationTelemetry(telemetryService, configurationService)); + + // Lifecycle + this._register(lifecycleService.onShutdown(() => this.dispose())); + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(TelemetryContribution, LifecyclePhase.Restored); diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/configure-inverse.svg b/src/vs/workbench/contrib/terminal/browser/media/configure-inverse.svg similarity index 100% rename from src/vs/workbench/parts/terminal/electron-browser/media/configure-inverse.svg rename to src/vs/workbench/contrib/terminal/browser/media/configure-inverse.svg diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/configure.svg b/src/vs/workbench/contrib/terminal/browser/media/configure.svg similarity index 100% rename from src/vs/workbench/parts/terminal/electron-browser/media/configure.svg rename to src/vs/workbench/contrib/terminal/browser/media/configure.svg diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/kill-inverse.svg b/src/vs/workbench/contrib/terminal/browser/media/kill-inverse.svg similarity index 100% rename from src/vs/workbench/parts/terminal/electron-browser/media/kill-inverse.svg rename to src/vs/workbench/contrib/terminal/browser/media/kill-inverse.svg diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/kill.svg b/src/vs/workbench/contrib/terminal/browser/media/kill.svg similarity index 100% rename from src/vs/workbench/parts/terminal/electron-browser/media/kill.svg rename to src/vs/workbench/contrib/terminal/browser/media/kill.svg diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/new-inverse.svg b/src/vs/workbench/contrib/terminal/browser/media/new-inverse.svg similarity index 100% rename from src/vs/workbench/parts/terminal/electron-browser/media/new-inverse.svg rename to src/vs/workbench/contrib/terminal/browser/media/new-inverse.svg diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/new.svg b/src/vs/workbench/contrib/terminal/browser/media/new.svg similarity index 100% rename from src/vs/workbench/parts/terminal/electron-browser/media/new.svg rename to src/vs/workbench/contrib/terminal/browser/media/new.svg diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/scrollbar.css b/src/vs/workbench/contrib/terminal/browser/media/scrollbar.css similarity index 100% rename from src/vs/workbench/parts/terminal/electron-browser/media/scrollbar.css rename to src/vs/workbench/contrib/terminal/browser/media/scrollbar.css diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/split-horizontal-inverse.svg b/src/vs/workbench/contrib/terminal/browser/media/split-horizontal-inverse.svg similarity index 100% rename from src/vs/workbench/parts/terminal/electron-browser/media/split-horizontal-inverse.svg rename to src/vs/workbench/contrib/terminal/browser/media/split-horizontal-inverse.svg diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/split-horizontal.svg b/src/vs/workbench/contrib/terminal/browser/media/split-horizontal.svg similarity index 100% rename from src/vs/workbench/parts/terminal/electron-browser/media/split-horizontal.svg rename to src/vs/workbench/contrib/terminal/browser/media/split-horizontal.svg diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/split-inverse.svg b/src/vs/workbench/contrib/terminal/browser/media/split-inverse.svg similarity index 100% rename from src/vs/workbench/parts/terminal/electron-browser/media/split-inverse.svg rename to src/vs/workbench/contrib/terminal/browser/media/split-inverse.svg diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/split.svg b/src/vs/workbench/contrib/terminal/browser/media/split.svg similarity index 100% rename from src/vs/workbench/parts/terminal/electron-browser/media/split.svg rename to src/vs/workbench/contrib/terminal/browser/media/split.svg diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css similarity index 98% rename from src/vs/workbench/parts/terminal/electron-browser/media/terminal.css rename to src/vs/workbench/contrib/terminal/browser/media/terminal.css index 981772d8bc..c2a7d1171e 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -157,8 +157,8 @@ .vs-dark .monaco-workbench .terminal-action.split, .hc-black .monaco-workbench .terminal-action.split { background: url('split-inverse.svg') center center no-repeat; } .vs-dark .monaco-workbench .panel.right .terminal-action.split, .hc-black .monaco-workbench .panel.right .terminal-action.split { background: url('split-horizontal-inverse.svg') center center no-repeat; } -.vs-dark.mac .monaco-workbench .panel.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events), -.hc-black.mac .monaco-workbench .panel.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events) { +.vs-dark .monaco-workbench.mac .panel.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events), +.hc-black .monaco-workbench.mac .panel.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events) { cursor: -webkit-image-set(url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=') 1x, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC') 2x) 5 8, text; } diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/widgets.css b/src/vs/workbench/contrib/terminal/browser/media/widgets.css similarity index 100% rename from src/vs/workbench/parts/terminal/electron-browser/media/widgets.css rename to src/vs/workbench/contrib/terminal/browser/media/widgets.css diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css b/src/vs/workbench/contrib/terminal/browser/media/xterm.css similarity index 98% rename from src/vs/workbench/parts/terminal/electron-browser/media/xterm.css rename to src/vs/workbench/contrib/terminal/browser/media/xterm.css index ff382c5a96..361924fdbd 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css +++ b/src/vs/workbench/contrib/terminal/browser/media/xterm.css @@ -40,7 +40,6 @@ */ .xterm { - font-family: courier-new, courier, monospace; font-feature-settings: "liga" 0; position: relative; user-select: none; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts similarity index 88% rename from src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts rename to src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts index a811789308..93c32781c5 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.contribution.ts @@ -3,36 +3,33 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import * as platform from 'vs/base/common/platform'; import 'vs/css!./media/scrollbar'; import 'vs/css!./media/terminal'; -import 'vs/css!./media/xterm'; import 'vs/css!./media/widgets'; +import 'vs/css!./media/xterm'; import * as nls from 'vs/nls'; -import * as panel from 'vs/workbench/browser/panel'; -import * as platform from 'vs/base/common/platform'; -import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; -import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TerminalCursorStyle, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, DEFAULT_LINE_HEIGHT, DEFAULT_LETTER_SPACING, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED } from 'vs/workbench/parts/terminal/common/terminal'; -import { getDefaultShell } from 'vs/workbench/parts/terminal/node/terminal'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KillTerminalAction, ClearSelectionTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, CreateNewInActiveWorkspaceTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction, AllowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand, RenameTerminalAction, SelectAllTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, TERMINAL_PICKER_PREFIX, MoveToLineStartTerminalAction, MoveToLineEndTerminalAction, SplitTerminalAction, SplitInActiveWorkspaceTerminalAction, FocusPreviousPaneTerminalAction, FocusNextPaneTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, ResizePaneDownTerminalAction, ScrollToPreviousCommandAction, ScrollToNextCommandAction, SelectToPreviousCommandAction, SelectToNextCommandAction, SelectToPreviousLineAction, SelectToNextLineAction, ToggleEscapeSequenceLoggingAction, SendSequenceTerminalCommand, ToggleRegexCommand, ToggleWholeWordCommand, ToggleCaseSensitiveCommand, FindNext, FindPrevious, DeleteToLineStartTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; -import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { TerminalService } from 'vs/workbench/parts/terminal/electron-browser/terminalService'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; -import { registerColors } from 'vs/workbench/parts/terminal/common/terminalColorRegistry'; -import { getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; -import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; -import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { TerminalPanel } from 'vs/workbench/parts/terminal/electron-browser/terminalPanel'; -import { TerminalPickerHandler } from 'vs/workbench/parts/terminal/browser/terminalQuickOpen'; -import { setupTerminalCommands, TERMINAL_COMMAND_ID } from 'vs/workbench/parts/terminal/common/terminalCommands'; -import { setupTerminalMenu } from 'vs/workbench/parts/terminal/common/terminalMenu'; -import { DEFAULT_COMMANDS_TO_SKIP_SHELL } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { Extensions as ActionBarExtensions, IActionBarRegistry, Scope } from 'vs/workbench/browser/actions'; +import * as panel from 'vs/workbench/browser/panel'; +import { getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; +import { Extensions as QuickOpenExtensions, IQuickOpenRegistry, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; +import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; +import { AllowWorkspaceShellTerminalCommand, ClearSelectionTerminalAction, ClearTerminalAction, CopyTerminalSelectionAction, CreateNewInActiveWorkspaceTerminalAction, CreateNewTerminalAction, DeleteToLineStartTerminalAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, DisallowWorkspaceShellTerminalCommand, FindNext, FindPrevious, FocusActiveTerminalAction, FocusNextPaneTerminalAction, FocusNextTerminalAction, FocusPreviousPaneTerminalAction, FocusPreviousTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, KillTerminalAction, MoveToLineEndTerminalAction, MoveToLineStartTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, RenameTerminalAction, ResizePaneDownTerminalAction, ResizePaneLeftTerminalAction, ResizePaneRightTerminalAction, ResizePaneUpTerminalAction, RunActiveFileInTerminalAction, RunSelectedTextInTerminalAction, ScrollDownPageTerminalAction, ScrollDownTerminalAction, ScrollToBottomTerminalAction, ScrollToNextCommandAction, ScrollToPreviousCommandAction, ScrollToTopTerminalAction, ScrollUpPageTerminalAction, ScrollUpTerminalAction, SelectAllTerminalAction, SelectDefaultShellWindowsTerminalAction, SelectToNextCommandAction, SelectToNextLineAction, SelectToPreviousCommandAction, SelectToPreviousLineAction, SendSequenceTerminalCommand, SplitInActiveWorkspaceTerminalAction, SplitTerminalAction, TerminalPasteAction, TERMINAL_PICKER_PREFIX, ToggleCaseSensitiveCommand, ToggleEscapeSequenceLoggingAction, ToggleRegexCommand, ToggleTerminalAction, ToggleWholeWordCommand } from 'vs/workbench/contrib/terminal/browser/terminalActions'; +import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel'; +import { TerminalPickerHandler } from 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; +import { KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, TERMINAL_PANEL_ID, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, TerminalCursorStyle } from 'vs/workbench/contrib/terminal/common/terminal'; +import { registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; +import { setupTerminalCommands, TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands'; +import { setupTerminalMenu } from 'vs/workbench/contrib/terminal/common/terminalMenu'; +import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; +import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; +import { DEFAULT_COMMANDS_TO_SKIP_SHELL } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; const quickOpenRegistry = (Registry.as(QuickOpenExtensions.Quickopen)); @@ -57,11 +54,6 @@ CommandsRegistry.registerCommand( { id: quickOpenNavigatePreviousInTerminalPickerId, handler: getQuickNavigateHandler(quickOpenNavigatePreviousInTerminalPickerId, false) }); -const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenTermAction, QuickOpenTermAction.ID, QuickOpenTermAction.LABEL), 'Terminal: Switch Active Terminal', nls.localize('terminal', "Terminal")); -const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); -actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionTermContributor); - const configurationRegistry = Registry.as(Extensions.Configuration); configurationRegistry.registerConfiguration({ id: 'terminal', @@ -69,11 +61,6 @@ configurationRegistry.registerConfiguration({ title: nls.localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), type: 'object', properties: { - 'terminal.integrated.shell.linux': { - markdownDescription: nls.localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), - type: 'string', - default: getDefaultShell(platform.Platform.Linux) - }, 'terminal.integrated.shellArgs.linux': { markdownDescription: nls.localize('terminal.integrated.shellArgs.linux', "The command line arguments to use when on the Linux terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), type: 'array', @@ -82,11 +69,6 @@ configurationRegistry.registerConfiguration({ }, default: [] }, - 'terminal.integrated.shell.osx': { - markdownDescription: nls.localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), - type: 'string', - default: getDefaultShell(platform.Platform.Mac) - }, 'terminal.integrated.shellArgs.osx': { markdownDescription: nls.localize('terminal.integrated.shellArgs.osx', "The command line arguments to use when on the macOS terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), type: 'array', @@ -98,11 +80,6 @@ configurationRegistry.registerConfiguration({ // the environment. See http://unix.stackexchange.com/a/119675/115410 default: ['-l'] }, - 'terminal.integrated.shell.windows': { - markdownDescription: nls.localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), - type: 'string', - default: getDefaultShell(platform.Platform.Windows) - }, 'terminal.integrated.shellArgs.windows': { markdownDescription: nls.localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), 'anyOf': [ @@ -290,7 +267,10 @@ configurationRegistry.registerConfiguration({ } }); -registerSingleton(ITerminalService, TerminalService, true); +const registry = Registry.as(ActionExtensions.WorkbenchActions); +registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenTermAction, QuickOpenTermAction.ID, QuickOpenTermAction.LABEL), 'Terminal: Switch Active Terminal', nls.localize('terminal', "Terminal")); +const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); +actionBarRegistry.registerActionBarContributor(Scope.VIEWER, QuickOpenActionTermContributor); (Registry.as(panel.Extensions.Panels)).registerPanel(new panel.PanelDescriptor( TerminalPanel, @@ -514,11 +494,26 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FindPrevious, Fi const sendSequenceTerminalCommand = new SendSequenceTerminalCommand({ id: SendSequenceTerminalCommand.ID, - precondition: null + precondition: null, + description: { + description: `Send Custom Sequence To Terminal`, + args: [{ + name: 'args', + schema: { + 'type': 'object', + 'required': ['text'], + 'properties': { + 'text': { + 'type': 'string' + } + }, + } + }] + } }); sendSequenceTerminalCommand.register(); setupTerminalCommands(); setupTerminalMenu(); -registerColors(); \ No newline at end of file +registerColors(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts new file mode 100644 index 0000000000..c6363c8fa6 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Terminal as XTermTerminal } from 'vscode-xterm'; +import { ITerminalInstance, IWindowsShellHelper, ITerminalProcessManager, ITerminalConfigHelper, ITerminalChildProcess, IShellLaunchConfig } from 'vs/workbench/contrib/terminal/common/terminal'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IProcessEnvironment } from 'vs/base/common/platform'; + +export const ITerminalInstanceService = createDecorator('terminalInstanceService'); + +export interface ITerminalInstanceService { + _serviceBrand: any; + + getXtermConstructor(): Promise; + createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper; + createTerminalProcessManager(id: number, configHelper: ITerminalConfigHelper): ITerminalProcessManager; + createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess; +} + +export interface IBrowserTerminalConfigHelper extends ITerminalConfigHelper { + panelContainer: HTMLElement; +} diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts similarity index 93% rename from src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts rename to src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 4f99e71014..92ad6accdb 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -4,56 +4,59 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as os from 'os'; import { Action, IAction } from 'vs/base/common/actions'; import { EndOfLinePreference } from 'vs/editor/common/model'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, Direction, ITerminalConfigHelper } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, Direction, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; import { SelectActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { TogglePanelAction } from 'vs/workbench/browser/panel'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IQuickInputService, IPickOptions, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { ActionBarContributor } from 'vs/workbench/browser/actions'; -import { TerminalEntry } from 'vs/workbench/parts/terminal/browser/terminalQuickOpen'; +import { TerminalEntry } from 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { TERMINAL_COMMAND_ID } from 'vs/workbench/parts/terminal/common/terminalCommands'; +import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands'; import { Command } from 'vs/editor/browser/editorExtensions'; import { timeout } from 'vs/base/common/async'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox'; +import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; +import { isWindows } from 'vs/base/common/platform'; export const TERMINAL_PICKER_PREFIX = 'term '; function getCwdForSplit(configHelper: ITerminalConfigHelper, instance: ITerminalInstance, folders?: IWorkspaceFolder[], commandService?: ICommandService): Promise { switch (configHelper.config.splitCwd) { case 'workspaceRoot': - let pathPromise: Promise; - if (folders.length === 0) { - pathPromise = Promise.resolve(''); - } else if (folders.length === 1) { - pathPromise = Promise.resolve(folders[0].uri); - } else if (folders.length > 1) { - // Only choose a path when there's more than 1 folder - const options: IPickOptions = { - placeHolder: nls.localize('workbench.action.terminal.newWorkspacePlaceholder', "Select current working directory for new terminal") - }; - pathPromise = commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, [options]).then(workspace => { - if (!workspace) { - // Don't split the instance if the workspace picker was canceled - return undefined; - } - return Promise.resolve(workspace.uri); - }); + let pathPromise: Promise = Promise.resolve(''); + if (folders !== undefined && commandService !== undefined) { + if (folders.length === 1) { + pathPromise = Promise.resolve(folders[0].uri); + } else if (folders.length > 1) { + // Only choose a path when there's more than 1 folder + const options: IPickOptions = { + placeHolder: nls.localize('workbench.action.terminal.newWorkspacePlaceholder', "Select current working directory for new terminal") + }; + pathPromise = commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, [options]).then(workspace => { + if (!workspace) { + // Don't split the instance if the workspace picker was canceled + return undefined; + } + return Promise.resolve(workspace.uri); + }); + } } return pathPromise; case 'initial': @@ -71,10 +74,10 @@ export class ToggleTerminalAction extends TogglePanelAction { constructor( id: string, label: string, @IPanelService panelService: IPanelService, - @IPartService partService: IPartService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, @ITerminalService private readonly terminalService: ITerminalService ) { - super(id, label, TERMINAL_PANEL_ID, panelService, partService); + super(id, label, TERMINAL_PANEL_ID, panelService, layoutService); } public run(event?: any): Promise { @@ -134,7 +137,7 @@ export class QuickKillTerminalAction extends Action { if (instance) { instance.dispose(true); } - return Promise.resolve(timeout(50)).then(result => this.quickOpenService.show(TERMINAL_PICKER_PREFIX, null)); + return Promise.resolve(timeout(50)).then(result => this.quickOpenService.show(TERMINAL_PICKER_PREFIX, undefined)); } } @@ -283,7 +286,14 @@ export class SendSequenceTerminalCommand extends Command { if (!terminalInstance) { return; } - terminalInstance.sendText(args.text, false); + + const configurationResolverService = accessor.get(IConfigurationResolverService); + const workspaceContextService = accessor.get(IWorkspaceContextService); + const historyService = accessor.get(IHistoryService); + const activeWorkspaceRootUri = historyService.getLastActiveWorkspaceRoot(Schemas.file); + const lastActiveWorkspaceRoot = activeWorkspaceRootUri ? workspaceContextService.getWorkspaceFolder(activeWorkspaceRootUri) || undefined : undefined; + const resolvedText = configurationResolverService.resolve(lastActiveWorkspaceRoot, args.text); + terminalInstance.sendText(resolvedText, false); } } @@ -314,7 +324,7 @@ export class CreateNewTerminalAction extends Action { } } - let instancePromise: Promise; + let instancePromise: Promise; if (folders.length <= 1) { // Allow terminal service to handle the path when there is only a // single root @@ -641,7 +651,7 @@ export class RunSelectedTextInTerminalAction extends Action { return Promise.resolve(undefined); } let editor = this.codeEditorService.getFocusedCodeEditor(); - if (!editor) { + if (!editor || !editor.hasModel()) { return Promise.resolve(undefined); } let selection = editor.getSelection(); @@ -649,7 +659,7 @@ export class RunSelectedTextInTerminalAction extends Action { if (selection.isEmpty()) { text = editor.getModel().getLineContent(selection.selectionStartLineNumber).trim(); } else { - const endOfLinePreference = os.EOL === '\n' ? EndOfLinePreference.LF : EndOfLinePreference.CRLF; + const endOfLinePreference = isWindows ? EndOfLinePreference.LF : EndOfLinePreference.CRLF; text = editor.getModel().getValueInRange(selection, endOfLinePreference); } instance.sendText(text, true); @@ -677,7 +687,7 @@ export class RunActiveFileInTerminalAction extends Action { return Promise.resolve(undefined); } const editor = this.codeEditorService.getActiveCodeEditor(); - if (!editor) { + if (!editor || !editor.hasModel()) { return Promise.resolve(undefined); } const uri = editor.getModel().uri; @@ -686,7 +696,7 @@ export class RunActiveFileInTerminalAction extends Action { return Promise.resolve(undefined); } - return instance.preparePathForTerminalAsync(uri.fsPath).then(path => { + return this.terminalService.preparePathForTerminalAsync(uri.fsPath, instance.shellLaunchConfig.executable, instance.title).then(path => { instance.sendText(path, true); return this.terminalService.showPanel(); }); @@ -1040,7 +1050,7 @@ export class QuickOpenTermAction extends Action { } public run(): Promise { - return this.quickOpenService.show(TERMINAL_PICKER_PREFIX, null); + return this.quickOpenService.show(TERMINAL_PICKER_PREFIX, undefined); } } @@ -1061,7 +1071,7 @@ export class RenameTerminalQuickOpenAction extends RenameTerminalAction { super.run(this.terminal) // This timeout is needed to make sure the previous quickOpen has time to close before we show the next one .then(() => timeout(50)) - .then(result => this.quickOpenService.show(TERMINAL_PICKER_PREFIX, null)); + .then(result => this.quickOpenService.show(TERMINAL_PICKER_PREFIX, undefined)); return Promise.resolve(null); } } diff --git a/src/vs/workbench/parts/terminal/node/terminalCommandTracker.ts b/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts similarity index 98% rename from src/vs/workbench/parts/terminal/node/terminalCommandTracker.ts rename to src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts index 037ad6be34..a6c3a619b8 100644 --- a/src/vs/workbench/parts/terminal/node/terminalCommandTracker.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalCommandTracker.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Terminal, IMarker } from 'vscode-xterm'; -import { ITerminalCommandTracker } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalCommandTracker } from 'vs/workbench/contrib/terminal/common/terminal'; import { IDisposable } from 'vs/base/common/lifecycle'; /** diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts similarity index 92% rename from src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts rename to src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index 3a7543938b..a4e23e4ae5 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -4,17 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import { EDITOR_FONT_DEFAULTS, IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { ITerminalConfiguration, ITerminalConfigHelper, ITerminalFont, IShellLaunchConfig, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, MINIMUM_LETTER_SPACING } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalConfiguration, ITerminalFont, IShellLaunchConfig, IS_WORKSPACE_SHELL_ALLOWED_STORAGE_KEY, TERMINAL_CONFIG_SECTION, DEFAULT_LETTER_SPACING, DEFAULT_LINE_HEIGHT, MINIMUM_LETTER_SPACING, LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; import Severity from 'vs/base/common/severity'; -import { isFedora, isUbuntu } from 'vs/workbench/parts/terminal/node/terminal'; import { Terminal as XTermTerminal } from 'vscode-xterm'; import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal'; const MINIMUM_FONT_SIZE = 6; const MAXIMUM_FONT_SIZE = 25; @@ -23,7 +22,7 @@ const MAXIMUM_FONT_SIZE = 25; * Encapsulates terminal configuration logic, the primary purpose of this file is so that platform * specific test cases can be written. */ -export class TerminalConfigHelper implements ITerminalConfigHelper { +export class TerminalConfigHelper implements IBrowserTerminalConfigHelper { public panelContainer: HTMLElement; private _charMeasureElement: HTMLElement; @@ -31,8 +30,9 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { public config: ITerminalConfiguration; public constructor( + private readonly _linuxDistro: LinuxDistro, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IWorkspaceConfigurationService private readonly _workspaceConfigurationService: IWorkspaceConfigurationService, + @IConfigurationService private readonly _workspaceConfigurationService: IConfigurationService, @INotificationService private readonly _notificationService: INotificationService, @IStorageService private readonly _storageService: IStorageService ) { @@ -118,10 +118,10 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { // Work around bad font on Fedora/Ubuntu if (!this.config.fontFamily) { - if (isFedora) { + if (this._linuxDistro === LinuxDistro.Fedora) { fontFamily = '\'DejaVu Sans Mono\', monospace'; } - if (isUbuntu) { + if (this._linuxDistro === LinuxDistro.Ubuntu) { fontFamily = '\'Ubuntu Mono\', monospace'; // Ubuntu mono is somehow smaller, so set fontSize a bit larger to get the same perceived size. @@ -239,7 +239,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { // Change Sysnative to System32 if the OS is Windows but NOT WoW64. It's // safe to assume that this was used by accident as Sysnative does not // exist and will break the terminal in non-WoW64 environments. - if (platform.isWindows && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') && process.env.windir) { + if ((platformOverride === platform.Platform.Windows) && !process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') && process.env.windir) { const sysnativePath = path.join(process.env.windir, 'Sysnative').toLowerCase(); if (shell.executable.toLowerCase().indexOf(sysnativePath) === 0) { shell.executable = path.join(process.env.windir, 'System32', shell.executable.substr(sysnativePath.length)); @@ -247,7 +247,7 @@ export class TerminalConfigHelper implements ITerminalConfigHelper { } // Convert / to \ on Windows for convenience - if (platform.isWindows) { + if (platformOverride === platform.Platform.Windows) { shell.executable = shell.executable.replace(/\//g, '\\'); } } diff --git a/src/vs/workbench/parts/terminal/browser/terminalFindWidget.ts b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts similarity index 97% rename from src/vs/workbench/parts/terminal/browser/terminalFindWidget.ts rename to src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts index e951d4a152..1db217c8fd 100644 --- a/src/vs/workbench/parts/terminal/browser/terminalFindWidget.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalFindWidget.ts @@ -3,10 +3,9 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./terminalFindWidget'; import { SimpleFindWidget } from 'vs/editor/contrib/find/simpleFindWidget'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_INPUT_FOCUSED, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_FOCUSED } from 'vs/workbench/contrib/terminal/common/terminal'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts similarity index 89% rename from src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts rename to src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 3472934cc0..8e6e37061c 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -3,17 +3,13 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { execFile } from 'child_process'; -import * as os from 'os'; -import * as path from 'path'; -import * as browser from 'vs/base/browser/browser'; +import * as path from 'vs/base/common/path'; import * as dom from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { debounce } from 'vs/base/common/decorators'; import { Emitter, Event } from 'vs/base/common/event'; import { KeyCode } from 'vs/base/common/keyCodes'; import * as lifecycle from 'vs/base/common/lifecycle'; -import * as paths from 'vs/base/common/paths'; import * as platform from 'vs/base/common/platform'; import { TabFocus } from 'vs/editor/common/config/commonEditorConfig'; import * as nls from 'vs/nls'; @@ -28,17 +24,17 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { activeContrastBorder, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground } from 'vs/platform/theme/common/colorRegistry'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; -import { TerminalWidgetManager } from 'vs/workbench/parts/terminal/browser/terminalWidgetManager'; -import { IShellLaunchConfig, ITerminalDimensions, ITerminalInstance, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_PANEL_ID } from 'vs/workbench/parts/terminal/common/terminal'; -import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/parts/terminal/common/terminalColorRegistry'; -import { TERMINAL_COMMAND_ID } from 'vs/workbench/parts/terminal/common/terminalCommands'; -import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; -import { TerminalLinkHandler } from 'vs/workbench/parts/terminal/electron-browser/terminalLinkHandler'; -import { TerminalProcessManager } from 'vs/workbench/parts/terminal/electron-browser/terminalProcessManager'; -import { TerminalCommandTracker } from 'vs/workbench/parts/terminal/node/terminalCommandTracker'; -import { WindowsShellHelper } from 'vs/workbench/parts/terminal/node/windowsShellHelper'; +import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager'; +import { IShellLaunchConfig, ITerminalDimensions, ITerminalInstance, ITerminalProcessManager, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, NEVER_MEASURE_RENDER_TIME_STORAGE_KEY, ProcessState, TERMINAL_PANEL_ID, IWindowsShellHelper } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; +import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands'; +import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; +import { TerminalLinkHandler } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; +import { TerminalCommandTracker } from 'vs/workbench/contrib/terminal/browser/terminalCommandTracker'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { ISearchOptions, Terminal as XTermTerminal } from 'vscode-xterm'; +import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; // How long in milliseconds should an average frame take to render for a notification to appear // which suggests the fallback DOM-based renderer @@ -152,8 +148,6 @@ export const DEFAULT_COMMANDS_TO_SKIP_SHELL: string[] = [ 'workbench.action.toggleMaximizedPanel' ]; -let Terminal: typeof XTermTerminal; - export class TerminalInstance implements ITerminalInstance { private static readonly EOL_REGEX = /\r?\n/g; @@ -161,6 +155,7 @@ export class TerminalInstance implements ITerminalInstance { private static _idCounter = 1; private _processManager: ITerminalProcessManager | undefined; + private _pressAnyKeyToCloseListener: lifecycle.IDisposable | undefined; private _id: number; private _isExiting: boolean; @@ -176,13 +171,13 @@ export class TerminalInstance implements ITerminalInstance { private _cols: number; private _rows: number; private _dimensionsOverride: ITerminalDimensions; - private _windowsShellHelper: WindowsShellHelper; + private _windowsShellHelper: IWindowsShellHelper | undefined; private _xtermReadyPromise: Promise; private _titleReadyPromise: Promise; private _titleReadyComplete: (title: string) => any; private _disposables: lifecycle.IDisposable[]; - private _messageTitleDisposable: lifecycle.IDisposable; + private _messageTitleDisposable: lifecycle.IDisposable | undefined; private _widgetManager: TerminalWidgetManager; private _linkHandler: TerminalLinkHandler; @@ -190,8 +185,18 @@ export class TerminalInstance implements ITerminalInstance { public disableLayout: boolean; public get id(): number { return this._id; } - public get cols(): number { return this._cols; } - public get rows(): number { return this._rows; } + public get cols(): number { + if (this._dimensionsOverride && this._dimensionsOverride.cols) { + return Math.min(Math.max(this._dimensionsOverride.cols, 2), this._cols); + } + return this._cols; + } + public get rows(): number { + if (this._dimensionsOverride && this._dimensionsOverride.rows) { + return Math.min(Math.max(this._dimensionsOverride.rows, 2), this._rows); + } + return this._rows; + } // TODO: Ideally processId would be merged into processReady public get processId(): number | undefined { return this._processManager ? this._processManager.shellProcessId : undefined; } // TODO: How does this work with detached processes? @@ -231,6 +236,7 @@ export class TerminalInstance implements ITerminalInstance { private readonly _configHelper: TerminalConfigHelper, private _container: HTMLElement, private _shellLaunchConfig: IShellLaunchConfig, + @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IKeybindingService private readonly _keybindingService: IKeybindingService, @INotificationService private readonly _notificationService: INotificationService, @@ -240,7 +246,8 @@ export class TerminalInstance implements ITerminalInstance { @IThemeService private readonly _themeService: IThemeService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ILogService private readonly _logService: ILogService, - @IStorageService private readonly _storageService: IStorageService + @IStorageService private readonly _storageService: IStorageService, + @IAccessibilityService private readonly _accessibilityService: IAccessibilityService ) { this._disposables = []; this._skipTerminalCommands = []; @@ -388,17 +395,7 @@ export class TerminalInstance implements ITerminalInstance { * Create xterm.js instance and attach data listeners. */ protected async _createXterm(): Promise { - if (!Terminal) { - Terminal = (await import('vscode-xterm')).Terminal; - // Enable xterm.js addons - Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/search/search')); - Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/webLinks/webLinks')); - Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/winptyCompat/winptyCompat')); - // Localize strings - Terminal.strings.blankLine = nls.localize('terminal.integrated.a11yBlankLine', 'Blank line'); - Terminal.strings.promptLabel = nls.localize('terminal.integrated.a11yPromptLabel', 'Terminal input'); - Terminal.strings.tooMuchOutput = nls.localize('terminal.integrated.a11yTooMuchOutput', 'Too much output to announce, navigate to rows manually to read'); - } + const Terminal = await this._terminalInstanceService.getXtermConstructor(); const font = this._configHelper.getFont(undefined, true); const config = this._configHelper.config; this._xterm = new Terminal({ @@ -433,7 +430,7 @@ export class TerminalInstance implements ITerminalInstance { this._processManager.onProcessData(data => this._onProcessData(data)); this._xterm.on('data', data => this._processManager!.write(data)); // TODO: How does the cwd work on detached processes? - this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, platform.platform); + this._linkHandler = this._instantiationService.createInstance(TerminalLinkHandler, this._xterm, platform.platform, this._processManager); this.processReady.then(async () => { this._linkHandler.processCwd = await this._processManager!.getInitialCwd(); }); @@ -450,7 +447,7 @@ export class TerminalInstance implements ITerminalInstance { } private _isScreenReaderOptimized(): boolean { - const detected = browser.getAccessibilitySupport() === platform.AccessibilitySupport.Enabled; + const detected = this._accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled; const config = this._configurationService.getValue('editor.accessibilitySupport'); return config === 'on' || (config === 'auto' && detected); } @@ -580,10 +577,6 @@ export class TerminalInstance implements ITerminalInstance { if (this._processManager) { this._widgetManager = new TerminalWidgetManager(this._wrapperElement); - // HACK: This can be removed once this is fixed upstream xtermjs/xterm.js#1908 - this._disposables.push(dom.addDisposableListener(this._xterm.element, 'mouseleave', () => { - this._widgetManager.closeMessage(); - })); this._linkHandler.setWidgetManager(this._widgetManager); } @@ -708,7 +701,8 @@ export class TerminalInstance implements ITerminalInstance { public dispose(immediate?: boolean): void { this._logService.trace(`terminalInstance#dispose (id: ${this.id})`); - this._windowsShellHelper = lifecycle.dispose(this._windowsShellHelper); + lifecycle.dispose(this._windowsShellHelper); + this._windowsShellHelper = undefined; this._linkHandler = lifecycle.dispose(this._linkHandler); this._commandTracker = lifecycle.dispose(this._commandTracker); this._widgetManager = lifecycle.dispose(this._widgetManager); @@ -729,9 +723,21 @@ export class TerminalInstance implements ITerminalInstance { this._sendLineData(buffer, buffer.ybase + buffer.y); this._xterm.dispose(); } + + if (this._pressAnyKeyToCloseListener) { + this._pressAnyKeyToCloseListener.dispose(); + this._pressAnyKeyToCloseListener = undefined; + } + if (this._processManager) { this._processManager.dispose(immediate); + } else { + // In cases where there is no associated process (for example executing an extension callback task) + // consumers still expect on onExit event to be fired. An example of this is terminating the extension callback + // task. + this._onExit.fire(0); } + if (!this._isDisposed) { this._isDisposed = true; this._onDisposed.fire(this); @@ -739,6 +745,16 @@ export class TerminalInstance implements ITerminalInstance { this._disposables = lifecycle.dispose(this._disposables); } + public rendererExit(exitCode: number): void { + // The use of this API is for cases where there is no backing process behind a terminal + // instance (eg. a custom execution task). + if (!this.shellLaunchConfig.isRendererOnly) { + throw new Error('rendererExit is only expected to be called on a renderer only terminal'); + } + + return this._onProcessExit(exitCode); + } + public forceRedraw(): void { this._xterm.refresh(0, this._xterm.rows - 1); } @@ -795,68 +811,6 @@ export class TerminalInstance implements ITerminalInstance { } } - public preparePathForTerminalAsync(originalPath: string): Promise { - return new Promise(c => { - const exe = this.shellLaunchConfig.executable; - if (!exe) { - c(originalPath); - return; - } - - const hasSpace = originalPath.indexOf(' ') !== -1; - - const pathBasename = path.basename(exe, '.exe'); - const isPowerShell = pathBasename === 'pwsh' || - this.title === 'pwsh' || - pathBasename === 'powershell' || - this.title === 'powershell'; - - if (isPowerShell && (hasSpace || originalPath.indexOf('\'') !== -1)) { - c(`& '${originalPath.replace(/'/g, '\'\'')}'`); - return; - } - - if (platform.isWindows) { - // 17063 is the build number where wsl path was introduced. - // Update Windows uriPath to be executed in WSL. - if (((exe.indexOf('wsl') !== -1) || ((exe.indexOf('bash.exe') !== -1) && (exe.indexOf('git') === -1))) && (TerminalInstance.getWindowsBuildNumber() >= 17063)) { - execFile('bash.exe', ['-c', 'echo $(wslpath ' + this._escapeNonWindowsPath(originalPath) + ')'], {}, (error, stdout, stderr) => { - c(this._escapeNonWindowsPath(stdout.trim())); - }); - return; - } else if (hasSpace) { - c('"' + originalPath + '"'); - } else { - c(originalPath); - } - return; - } - c(this._escapeNonWindowsPath(originalPath)); - }); - } - - private _escapeNonWindowsPath(path: string): string { - let newPath = path; - if (newPath.indexOf('\\') !== 0) { - newPath = newPath.replace(/\\/g, '\\\\'); - } - if (!newPath && (newPath.indexOf('"') !== -1)) { - newPath = '\'' + newPath + '\''; - } else if (newPath.indexOf(' ') !== -1) { - newPath = newPath.replace(/ /g, '\\ '); - } - return newPath; - } - - public static getWindowsBuildNumber(): number { - const osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release()); - let buildNumber: number = 0; - if (osVersion && osVersion.length === 4) { - buildNumber = parseInt(osVersion[3]); - } - return buildNumber; - } - public setVisible(visible: boolean): void { this._isVisible = visible; if (this._wrapperElement) { @@ -915,13 +869,13 @@ export class TerminalInstance implements ITerminalInstance { private _refreshSelectionContextKey() { const activePanel = this._panelService.getActivePanel(); - const isActive = activePanel && activePanel.getId() === TERMINAL_PANEL_ID; + const isActive = !!activePanel && activePanel.getId() === TERMINAL_PANEL_ID; this._terminalHasTextContextKey.set(isActive && this.hasSelection()); } protected _createProcess(): void { - this._processManager = this._instantiationService.createInstance(TerminalProcessManager, this._id, this._configHelper); + this._processManager = this._terminalInstanceService.createTerminalProcessManager(this._id, this._configHelper); this._processManager.onProcessReady(() => this._onProcessIdReady.fire(this)); this._processManager.onProcessExit(exitCode => this._onProcessExit(exitCode)); this._processManager.onProcessData(data => this._onData.fire(data)); @@ -938,7 +892,7 @@ export class TerminalInstance implements ITerminalInstance { this._processManager.ptyProcessReady.then(() => { this._xtermReadyPromise.then(() => { if (!this._isDisposed) { - this._windowsShellHelper = new WindowsShellHelper(this._processManager!.shellProcessId, this, this._xterm); + this._terminalInstanceService.createWindowsShellHelper(this._processManager!.shellProcessId, this, this._xterm); } }); }); @@ -960,7 +914,13 @@ export class TerminalInstance implements ITerminalInstance { } } - private _onProcessExit(exitCode: number): void { + /** + * Called when either a process tied to a terminal has exited or when a terminal renderer + * simulates a process exiting (eg. custom execution task). + * @param exitCode The exit code of the process, this is undefined when the terminal was exited + * through user action. + */ + private _onProcessExit(exitCode?: number): void { this._logService.debug(`Terminal process exit (id: ${this.id}) with code ${exitCode}`); // Prevent dispose functions being triggered multiple times @@ -975,11 +935,11 @@ export class TerminalInstance implements ITerminalInstance { exitCodeMessage = nls.localize('terminal.integrated.exitedWithCode', 'The terminal process terminated with exit code: {0}', exitCode); } - this._logService.debug(`Terminal process exit (id: ${this.id}) state ${this._processManager!.processState}`); + this._logService.debug(`Terminal process exit (id: ${this.id})${this._processManager ? ' state ' + this._processManager.processState : ''}`); // Only trigger wait on exit when the exit was *not* triggered by the // user (via the `workbench.action.terminal.kill` command). - if (this._shellLaunchConfig.waitOnExit && this._processManager!.processState !== ProcessState.KILLED_BY_USER) { + if (this._shellLaunchConfig.waitOnExit && (!this._processManager || this._processManager.processState !== ProcessState.KILLED_BY_USER)) { if (exitCode) { this._xterm.writeln(exitCodeMessage!); } @@ -997,7 +957,7 @@ export class TerminalInstance implements ITerminalInstance { } else { this.dispose(); if (exitCode) { - if (this._processManager!.processState === ProcessState.KILLED_DURING_LAUNCH) { + if (this._processManager && this._processManager.processState === ProcessState.KILLED_DURING_LAUNCH) { let args = ''; if (typeof this._shellLaunchConfig.args === 'string') { args = this._shellLaunchConfig.args; @@ -1024,19 +984,34 @@ export class TerminalInstance implements ITerminalInstance { } } - this._onExit.fire(exitCode); + this._onExit.fire(exitCode || 0); } private _attachPressAnyKeyToCloseListener() { - this._processManager!.addDisposable(dom.addDisposableListener(this._xterm.textarea, 'keypress', (event: KeyboardEvent) => { - this.dispose(); - event.preventDefault(); - })); + if (!this._pressAnyKeyToCloseListener) { + this._pressAnyKeyToCloseListener = dom.addDisposableListener(this._xterm.textarea, 'keypress', (event: KeyboardEvent) => { + if (this._pressAnyKeyToCloseListener) { + this._pressAnyKeyToCloseListener.dispose(); + this._pressAnyKeyToCloseListener = undefined; + this.dispose(); + event.preventDefault(); + } + }); + } } public reuseTerminal(shell: IShellLaunchConfig): void { + // Unsubscribe any key listener we may have. + if (this._pressAnyKeyToCloseListener) { + this._pressAnyKeyToCloseListener.dispose(); + this._pressAnyKeyToCloseListener = undefined; + } + // Kill and clear up the process, making the process manager ready for a new process - this._processManager!.dispose(); + if (this._processManager) { + this._processManager.dispose(); + this._processManager = undefined; + } // Ensure new processes' output starts at start of new line this._xterm.write('\n\x1b[G'); @@ -1055,12 +1030,24 @@ export class TerminalInstance implements ITerminalInstance { // Set the new shell launch config this._shellLaunchConfig = shell; // Must be done before calling _createProcess() - // Initialize new process - this._createProcess(); + + // Launch the process unless this is only a renderer. + // In the renderer only cases, we still need to set the title correctly. + if (!this._shellLaunchConfig.isRendererOnly) { + this._createProcess(); + } else if (this._shellLaunchConfig.name) { + this.setTitle(this._shellLaunchConfig.name, false); + } + if (oldTitle !== this._title) { this.setTitle(this._title, true); } - this._processManager!.onProcessData(data => this._onProcessData(data)); + + if (this._processManager) { + // The "!" operator is required here because _processManager is set to undefiend earlier + // and TS does not know that createProcess sets it. + this._processManager!.onProcessData(data => this._onProcessData(data)); + } } private _sendRendererInput(input: string): void { @@ -1174,6 +1161,7 @@ export class TerminalInstance implements ITerminalInstance { return; } + const terminalWidth = this._evaluateColsAndRows(dimension.width, dimension.height); if (!terminalWidth) { return; @@ -1188,12 +1176,8 @@ export class TerminalInstance implements ITerminalInstance { @debounce(50) private _resize(): void { - let cols = this._cols; - let rows = this._rows; - if (this._dimensionsOverride && this._dimensionsOverride.cols && this._dimensionsOverride.rows) { - cols = Math.min(Math.max(this._dimensionsOverride.cols, 2), cols); - rows = Math.min(Math.max(this._dimensionsOverride.rows, 2), rows); - } + let cols = this.cols; + let rows = this.rows; if (this._xterm) { // Only apply these settings when the terminal is visible so that @@ -1239,7 +1223,7 @@ export class TerminalInstance implements ITerminalInstance { return; } if (eventFromProcess) { - title = paths.basename(title); + title = path.basename(title); if (platform.isWindows) { // Remove the .exe extension title = title.split('.exe')[0]; @@ -1249,6 +1233,9 @@ export class TerminalInstance implements ITerminalInstance { // automatically updates the terminal name if (this._messageTitleDisposable) { lifecycle.dispose(this._messageTitleDisposable); + lifecycle.dispose(this._windowsShellHelper); + this._messageTitleDisposable = undefined; + this._windowsShellHelper = undefined; } } const didTitleChange = title !== this._title; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts similarity index 70% rename from src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts rename to src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts index ae48d374bc..5a56145600 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalLinkHandler.ts @@ -4,18 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as path from 'path'; import * as platform from 'vs/base/common/platform'; -import * as pfs from 'vs/base/node/pfs'; -import { URI as Uri } from 'vs/base/common/uri'; +import { URI } from 'vs/base/common/uri'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { TerminalWidgetManager } from 'vs/workbench/parts/terminal/browser/terminalWidgetManager'; +import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/terminalWidgetManager'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; import { ITextEditorSelection } from 'vs/platform/editor/common/editor'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IFileService } from 'vs/platform/files/common/files'; import { ILinkMatcherOptions } from 'vscode-xterm'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { posix, win32 } from 'vs/base/common/path'; const pathPrefix = '(\\.\\.?|\\~)'; const pathSeparatorClause = '\\/'; @@ -57,26 +58,47 @@ const LOCAL_LINK_PRIORITY = -2; export type XtermLinkMatcherHandler = (event: MouseEvent, uri: string) => boolean | void; export type XtermLinkMatcherValidationCallback = (uri: string, callback: (isValid: boolean) => void) => void; +interface IPath { + join(...paths: string[]): string; + normalize(path: string): string; +} + export class TerminalLinkHandler { private _hoverDisposables: IDisposable[] = []; private _mouseMoveDisposable: IDisposable; private _widgetManager: TerminalWidgetManager; private _processCwd: string; - private _localLinkPattern: RegExp; + private _gitDiffPreImagePattern: RegExp; + private _gitDiffPostImagePattern: RegExp; + private readonly _tooltipCallback: (event: MouseEvent, uri: string) => boolean | void; constructor( private _xterm: any, private _platform: platform.Platform, + private readonly _processManager: ITerminalProcessManager, @IOpenerService private readonly _openerService: IOpenerService, @IEditorService private readonly _editorService: IEditorService, @IConfigurationService private readonly _configurationService: IConfigurationService, @ITerminalService private readonly _terminalService: ITerminalService, + @IFileService private readonly _fileService: IFileService ) { - const baseLocalLinkClause = _platform === platform.Platform.Windows ? winLocalLinkClause : unixLocalLinkClause; - // Append line and column number regex - this._localLinkPattern = new RegExp(`${baseLocalLinkClause}(${lineAndColumnClause})`); + // Matches '--- a/src/file1', capturing 'src/file1' in group 1 + this._gitDiffPreImagePattern = /^--- a\/(\S*)/; + // Matches '+++ b/src/file1', capturing 'src/file1' in group 1 + this._gitDiffPostImagePattern = /^\+\+\+ b\/(\S*)/; + + this._tooltipCallback = (e: MouseEvent) => { + if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') { + const target = (e.target as HTMLElement); + this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString()); + } else { + this._widgetManager.showMessage(e.offsetX, e.offsetY, this._getLinkHoverString()); + } + }; + this.registerWebLinkHandler(); this.registerLocalLinkHandler(); + this.registerGitDiffLinkHandlers(); } public setWidgetManager(widgetManager: TerminalWidgetManager): void { @@ -90,14 +112,7 @@ export class TerminalLinkHandler { public registerCustomLinkHandler(regex: RegExp, handler: (uri: string) => void, matchIndex?: number, validationCallback?: XtermLinkMatcherValidationCallback): number { const options: ILinkMatcherOptions = { matchIndex, - tooltipCallback: (e: MouseEvent) => { - if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') { - const target = (e.target as HTMLElement); - this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString()); - } else { - this._widgetManager.showMessage(e.offsetX, e.offsetY, this._getLinkHoverString()); - } - }, + tooltipCallback: this._tooltipCallback, leaveCallback: () => this._widgetManager.closeMessage(), willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e), priority: CUSTOM_LINK_PRIORITY @@ -114,14 +129,7 @@ export class TerminalLinkHandler { }); this._xterm.webLinksInit(wrappedHandler, { validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateWebLink(uri, callback), - tooltipCallback: (e: MouseEvent) => { - if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') { - const target = (e.target as HTMLElement); - this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString()); - } else { - this._widgetManager.showMessage(e.offsetX, e.offsetY, this._getLinkHoverString()); - } - }, + tooltipCallback: this._tooltipCallback, leaveCallback: () => this._widgetManager.closeMessage(), willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e) }); @@ -133,20 +141,29 @@ export class TerminalLinkHandler { }); this._xterm.registerLinkMatcher(this._localLinkRegex, wrappedHandler, { validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback), - tooltipCallback: (e: MouseEvent) => { - if (this._terminalService && this._terminalService.configHelper.config.rendererType === 'dom') { - const target = (e.target as HTMLElement); - this._widgetManager.showMessage(target.offsetLeft, target.offsetTop, this._getLinkHoverString()); - } else { - this._widgetManager.showMessage(e.offsetX, e.offsetY, this._getLinkHoverString()); - } - }, + tooltipCallback: this._tooltipCallback, leaveCallback: () => this._widgetManager.closeMessage(), willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e), priority: LOCAL_LINK_PRIORITY }); } + public registerGitDiffLinkHandlers(): void { + const wrappedHandler = this._wrapLinkHandler(url => { + this._handleLocalLink(url); + }); + const options = { + matchIndex: 1, + validationCallback: (uri: string, callback: (isValid: boolean) => void) => this._validateLocalLink(uri, callback), + tooltipCallback: this._tooltipCallback, + leaveCallback: () => this._widgetManager.closeMessage(), + willLinkActivate: (e: MouseEvent) => this._isLinkActivationModifierDown(e), + priority: LOCAL_LINK_PRIORITY + }; + this._xterm.registerLinkMatcher(this._gitDiffPreImagePattern, wrappedHandler, options); + this._xterm.registerLinkMatcher(this._gitDiffPostImagePattern, wrappedHandler, options); + } + public dispose(): void { this._xterm = null; this._hoverDisposables = dispose(this._hoverDisposables); @@ -169,7 +186,17 @@ export class TerminalLinkHandler { } protected get _localLinkRegex(): RegExp { - return this._localLinkPattern; + const baseLocalLinkClause = this._processManager.os === platform.OperatingSystem.Windows ? winLocalLinkClause : unixLocalLinkClause; + // Append line and column number regex + return new RegExp(`${baseLocalLinkClause}(${lineAndColumnClause})`); + } + + protected get _gitDiffPreImageRegex(): RegExp { + return this._gitDiffPreImagePattern; + } + + protected get _gitDiffPostImageRegex(): RegExp { + return this._gitDiffPostImagePattern; } private _handleLocalLink(link: string): PromiseLike { @@ -177,19 +204,12 @@ export class TerminalLinkHandler { if (!resolvedLink) { return Promise.resolve(null); } - const normalizedPath = path.normalize(path.resolve(resolvedLink)); - const normalizedUrl = this.extractLinkUrl(normalizedPath); - if (!normalizedUrl) { - return Promise.resolve(null); - } - const resource = Uri.file(normalizedUrl); const lineColumnInfo: LineColumnInfo = this.extractLineColumnInfo(link); const selection: ITextEditorSelection = { startLineNumber: lineColumnInfo.lineNumber, startColumn: lineColumnInfo.columnNumber }; - - return this._editorService.openEditor({ resource, options: { pinned: true, selection } }); + return this._editorService.openEditor({ resource: resolvedLink, options: { pinned: true, selection } }); }); } @@ -202,7 +222,7 @@ export class TerminalLinkHandler { } private _handleHypertextLink(url: string): void { - const uri = Uri.parse(url); + const uri = URI.parse(url); this._openerService.open(uri); } @@ -225,37 +245,44 @@ export class TerminalLinkHandler { return nls.localize('terminalLinkHandler.followLinkCtrl', 'Ctrl + click to follow link'); } - protected _preprocessPath(link: string): string | null { - if (this._platform === platform.Platform.Windows) { - // Resolve ~ -> %HOMEDRIVE%\%HOMEPATH% - if (link.charAt(0) === '~') { - if (!process.env.HOMEDRIVE || !process.env.HOMEPATH) { - return null; - } - link = `${process.env.HOMEDRIVE}\\${process.env.HOMEPATH + link.substring(1)}`; - } + private get osPath(): IPath { + if (this._processManager.os === platform.OperatingSystem.Windows) { + return win32; + } + return posix; + } - // Resolve relative paths (.\a, ..\a, ~\a, a\b) - if (!link.match('^' + winDrivePrefix)) { + protected _preprocessPath(link: string): string | null { + if (link.charAt(0) === '~') { + // Resolve ~ -> userHome + if (!this._processManager.userHome) { + return null; + } + link = this.osPath.join(this._processManager.userHome, link.substring(1)); + } else if (link.charAt(0) !== '/' && link.charAt(0) !== '~') { + // Resolve workspace path . | .. | -> /. | /.. | / + if (this._processManager.os === platform.OperatingSystem.Windows) { + if (!link.match('^' + winDrivePrefix)) { + if (!this._processCwd) { + // Abort if no workspace is open + return null; + } + link = this.osPath.join(this._processCwd, link); + } + } else { if (!this._processCwd) { // Abort if no workspace is open return null; } - link = path.join(this._processCwd, link); + link = this.osPath.join(this._processCwd, link); } } - // Resolve workspace path . | .. | -> /. | /.. | / - else if (link.charAt(0) !== '/' && link.charAt(0) !== '~') { - if (!this._processCwd) { - // Abort if no workspace is open - return null; - } - link = path.join(this._processCwd, link); - } + link = this.osPath.normalize(link); + return link; } - private _resolvePath(link: string): PromiseLike { + private _resolvePath(link: string): PromiseLike { const preprocessedLink = this._preprocessPath(link); if (!preprocessedLink) { return Promise.resolve(null); @@ -266,13 +293,31 @@ export class TerminalLinkHandler { return Promise.resolve(null); } - // Ensure the file exists on disk, so an editor can be opened after clicking it - return pfs.fileExists(linkUrl).then(isFile => { - if (!isFile) { - return null; + try { + let uri: URI; + if (this._processManager.remoteAuthority) { + uri = URI.from({ + scheme: REMOTE_HOST_SCHEME, + authority: this._processManager.remoteAuthority, + path: linkUrl + }); + } else { + uri = URI.file(linkUrl); } - return preprocessedLink; - }); + + return this._fileService.resolveFile(uri).then(stat => { + if (stat.isDirectory) { + return null; + } + return uri; + }).catch(() => { + // Does not exist + return null; + }); + } catch { + // Errors in parsing the path + return Promise.resolve(null); + } } /** diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts similarity index 89% rename from src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts rename to src/vs/workbench/contrib/terminal/browser/terminalPanel.ts index 0e4224474b..8b2e6156f6 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalPanel.ts @@ -6,25 +6,23 @@ import * as dom from 'vs/base/browser/dom'; import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; -import * as terminalEnvironment from 'vs/workbench/parts/terminal/node/terminalEnvironment'; import { Action, IAction } from 'vs/base/common/actions'; import { IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { ITerminalService, TERMINAL_PANEL_ID } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService, TERMINAL_PANEL_ID } from 'vs/workbench/contrib/terminal/common/terminal'; import { IThemeService, ITheme, registerThemingParticipant, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { TerminalFindWidget } from 'vs/workbench/parts/terminal/browser/terminalFindWidget'; +import { TerminalFindWidget } from 'vs/workbench/contrib/terminal/browser/terminalFindWidget'; import { editorHoverBackground, editorHoverBorder, editorForeground } from 'vs/platform/theme/common/colorRegistry'; -import { KillTerminalAction, SwitchTerminalAction, SwitchTerminalActionItem, CopyTerminalSelectionAction, TerminalPasteAction, ClearTerminalAction, SelectAllTerminalAction, CreateNewTerminalAction, SplitTerminalAction } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; +import { KillTerminalAction, SwitchTerminalAction, SwitchTerminalActionItem, CopyTerminalSelectionAction, TerminalPasteAction, ClearTerminalAction, SelectAllTerminalAction, CreateNewTerminalAction, SplitTerminalAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { Panel } from 'vs/workbench/browser/panel'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { URI } from 'vs/base/common/uri'; -import { TERMINAL_BACKGROUND_COLOR, TERMINAL_BORDER_COLOR } from 'vs/workbench/parts/terminal/common/terminalColorRegistry'; +import { TERMINAL_BACKGROUND_COLOR, TERMINAL_BORDER_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { DataTransfers } from 'vs/base/browser/dnd'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; -import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; import { IStorageService } from 'vs/platform/storage/common/storage'; const FIND_FOCUS_CLASS = 'find-focused'; @@ -82,14 +80,12 @@ export class TerminalPanel extends Panel { if (e.affectsConfiguration('terminal.integrated.fontFamily') || e.affectsConfiguration('editor.fontFamily')) { const configHelper = this._terminalService.configHelper; - if (configHelper instanceof TerminalConfigHelper) { - if (!configHelper.configFontIsMonospace()) { - const choices: IPromptChoice[] = [{ - label: nls.localize('terminal.useMonospace', "Use 'monospace'"), - run: () => this._configurationService.updateValue('terminal.integrated.fontFamily', 'monospace'), - }]; - this._notificationService.prompt(Severity.Warning, nls.localize('terminal.monospaceOnly', "The terminal only supports monospace fonts."), choices); - } + if (!configHelper.configFontIsMonospace()) { + const choices: IPromptChoice[] = [{ + label: nls.localize('terminal.useMonospace', "Use 'monospace'"), + run: () => this._configurationService.updateValue('terminal.integrated.fontFamily', 'monospace'), + }]; + this._notificationService.prompt(Severity.Warning, nls.localize('terminal.monospaceOnly', "The terminal only supports monospace fonts."), choices); } } })); @@ -161,11 +157,11 @@ export class TerminalPanel extends Panel { }); } const activeInstance = this._terminalService.getActiveInstance(); - this._copyContextMenuAction.enabled = activeInstance && activeInstance.hasSelection(); + this._copyContextMenuAction.enabled = !!activeInstance && activeInstance.hasSelection(); return this._contextMenuActions; } - public getActionItem(action: Action): IActionItem { + public getActionItem(action: Action): IActionItem | null { if (action.id === SwitchTerminalAction.ID) { return this._instantiationService.createInstance(SwitchTerminalActionItem, action); } @@ -182,7 +178,7 @@ export class TerminalPanel extends Panel { public focusFindWidget() { const activeInstance = this._terminalService.getActiveInstance(); - if (activeInstance && activeInstance.hasSelection() && (activeInstance.selection.indexOf('\n') === -1)) { + if (activeInstance && activeInstance.hasSelection() && activeInstance.selection!.indexOf('\n') === -1) { this._findWidget.reveal(activeInstance.selection); } else { this._findWidget.reveal(); @@ -195,7 +191,7 @@ export class TerminalPanel extends Panel { public showFindWidget() { const activeInstance = this._terminalService.getActiveInstance(); - if (activeInstance && activeInstance.hasSelection() && (activeInstance.selection.indexOf('\n') === -1)) { + if (activeInstance && activeInstance.hasSelection() && activeInstance.selection!.indexOf('\n') === -1) { this._findWidget.show(activeInstance.selection); } else { this._findWidget.show(); @@ -215,10 +211,16 @@ export class TerminalPanel extends Panel { if (event.which === 2 && platform.isLinux) { // Drop selection and focus terminal on Linux to enable middle button paste when click // occurs on the selection itself. - this._terminalService.getActiveInstance().focus(); + const terminal = this._terminalService.getActiveInstance(); + if (terminal) { + terminal.focus(); + } } else if (event.which === 3) { if (this._terminalService.configHelper.config.rightClickBehavior === 'copyPaste') { const terminal = this._terminalService.getActiveInstance(); + if (!terminal) { + return; + } if (terminal.hasSelection()) { terminal.copySelection(); terminal.clearSelection(); @@ -246,7 +248,7 @@ export class TerminalPanel extends Panel { if (event.which === 1) { const terminal = this._terminalService.getActiveInstance(); - if (terminal.hasSelection()) { + if (terminal && terminal.hasSelection()) { terminal.copySelection(); } } @@ -278,14 +280,14 @@ export class TerminalPanel extends Panel { event.stopPropagation(); } })); - this._register(dom.addDisposableListener(this._parentDomElement, dom.EventType.DROP, (e: DragEvent) => { + this._register(dom.addDisposableListener(this._parentDomElement, dom.EventType.DROP, async (e: DragEvent) => { if (e.target === this._parentDomElement || dom.isAncestor(e.target as HTMLElement, this._parentDomElement)) { if (!e.dataTransfer) { return; } // Check if files were dragged from the tree explorer - let path: string; + let path: string | undefined; const resources = e.dataTransfer.getData(DataTransfers.RESOURCES); if (resources) { path = URI.parse(JSON.parse(resources)[0]).fsPath; @@ -299,7 +301,11 @@ export class TerminalPanel extends Panel { } const terminal = this._terminalService.getActiveInstance(); - terminal.sendText(terminalEnvironment.preparePathForTerminal(path), false); + if (terminal) { + return this._terminalService.preparePathForTerminalAsync(path, terminal.shellLaunchConfig.executable, terminal.title).then(preparedPath => { + terminal.sendText(preparedPath, false); + }); + } } })); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts similarity index 78% rename from src/vs/workbench/parts/terminal/electron-browser/terminalProcessManager.ts rename to src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index ec36e1c501..e106a92417 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -4,23 +4,25 @@ *--------------------------------------------------------------------------------------------*/ import * as platform from 'vs/base/common/platform'; -import * as terminalEnvironment from 'vs/workbench/parts/terminal/node/terminalEnvironment'; +import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper } from 'vs/workbench/parts/terminal/common/terminal'; +import { ProcessState, ITerminalProcessManager, IShellLaunchConfig, ITerminalConfigHelper, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal'; import { ILogService } from 'vs/platform/log/common/log'; import { Emitter, Event } from 'vs/base/common/event'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { ITerminalChildProcess } from 'vs/workbench/parts/terminal/node/terminal'; -import { TerminalProcessExtHostProxy } from 'vs/workbench/parts/terminal/node/terminalProcessExtHostProxy'; +import { TerminalProcessExtHostProxy } from 'vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { TerminalProcess } from 'vs/workbench/parts/terminal/node/terminalProcess'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { Schemas } from 'vs/base/common/network'; import { REMOTE_HOST_SCHEME, getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts'; -import { sanitizeProcessEnvironment } from 'vs/base/node/processes'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { sanitizeProcessEnvironment } from 'vs/base/common/processes'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IProductService } from 'vs/platform/product/common/product'; +import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IRemoteEnvironmentService } from 'vs/workbench/services/remote/common/remoteEnvironmentService'; /** The amount of time to consider terminal errors to be related to the launch */ const LAUNCHING_DURATION = 500; @@ -37,6 +39,9 @@ export class TerminalProcessManager implements ITerminalProcessManager { public processState: ProcessState = ProcessState.UNINITIALIZED; public ptyProcessReady: Promise; public shellProcessId: number; + public remoteAuthority: string | undefined; + public os: platform.OperatingSystem | undefined; + public userHome: string | undefined; private _process: ITerminalChildProcess | null = null; private _preLaunchInputQueue: string[] = []; @@ -60,7 +65,11 @@ export class TerminalProcessManager implements ITerminalProcessManager { @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, @IConfigurationResolverService private readonly _configurationResolverService: IConfigurationResolverService, @IWindowService private readonly _windowService: IWindowService, - @IWorkspaceConfigurationService private readonly _workspaceConfigurationService: IWorkspaceConfigurationService, + @IConfigurationService private readonly _workspaceConfigurationService: IConfigurationService, + @IEnvironmentService private readonly _environmentService: IEnvironmentService, + @IProductService private readonly _productService: IProductService, + @ITerminalInstanceService private readonly _terminalInstanceService: ITerminalInstanceService, + @IRemoteEnvironmentService private readonly _remoteEnvironmentService: IRemoteEnvironmentService ) { this.ptyProcessReady = new Promise(c => { this.onProcessReady(() => { @@ -92,18 +101,29 @@ export class TerminalProcessManager implements ITerminalProcessManager { cols: number, rows: number ): void { - let launchRemotely = false; const forceExtHostProcess = (this._configHelper.config as any).extHostProcess; - if (shellLaunchConfig.cwd && typeof shellLaunchConfig.cwd === 'object') { - launchRemotely = !!getRemoteAuthority(shellLaunchConfig.cwd); - shellLaunchConfig.cwd = shellLaunchConfig.cwd.fsPath; + this.remoteAuthority = getRemoteAuthority(shellLaunchConfig.cwd); } else { - launchRemotely = !!this._windowService.getConfiguration().remoteAuthority; + this.remoteAuthority = this._windowService.getConfiguration().remoteAuthority; } + const hasRemoteAuthority = !!this.remoteAuthority; + let launchRemotely = hasRemoteAuthority || forceExtHostProcess; - if (launchRemotely || forceExtHostProcess) { - const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(forceExtHostProcess ? undefined : REMOTE_HOST_SCHEME); + this.userHome = this._environmentService.userHome; + this.os = platform.OS; + if (launchRemotely) { + if (hasRemoteAuthority) { + this._remoteEnvironmentService.remoteEnvironment.then(env => { + if (!env) { + return; + } + this.userHome = env.userHome.path; + this.os = env.os; + }); + } + + const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(hasRemoteAuthority ? REMOTE_HOST_SCHEME : undefined); this._process = this._instantiationService.createInstance(TerminalProcessExtHostProxy, this._terminalId, shellLaunchConfig, activeWorkspaceRootUri, cols, rows); } else { if (!shellLaunchConfig.executable) { @@ -111,7 +131,7 @@ export class TerminalProcessManager implements ITerminalProcessManager { } const activeWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot(Schemas.file); - const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, activeWorkspaceRootUri, this._configHelper.config.cwd); + const initialCwd = terminalEnvironment.getCwd(shellLaunchConfig, this._environmentService.userHome, activeWorkspaceRootUri, this._configHelper.config.cwd); // Compel type system as process.env should not have any undefined entries let env: platform.IProcessEnvironment = {}; @@ -139,14 +159,14 @@ export class TerminalProcessManager implements ITerminalProcessManager { // Sanitize the environment, removing any undesirable VS Code and Electron environment // variables - sanitizeProcessEnvironment(env); + sanitizeProcessEnvironment(env, 'VSCODE_IPC_HOOK_CLI'); // Adding other env keys necessary to create the process - terminalEnvironment.addTerminalEnvironmentKeys(env, platform.locale, this._configHelper.config.setLocaleVariables); + terminalEnvironment.addTerminalEnvironmentKeys(env, this._productService.version, platform.locale, this._configHelper.config.setLocaleVariables); } this._logService.debug(`Terminal process launching`, shellLaunchConfig, initialCwd, cols, rows, env); - this._process = new TerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, this._configHelper.config.windowsEnableConpty); + this._process = this._terminalInstanceService.createTerminalProcess(shellLaunchConfig, initialCwd, cols, rows, env, this._configHelper.config.windowsEnableConpty); } this.processState = ProcessState.LAUNCHING; diff --git a/src/vs/workbench/parts/terminal/browser/terminalQuickOpen.ts b/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts similarity index 97% rename from src/vs/workbench/parts/terminal/browser/terminalQuickOpen.ts rename to src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts index fe1cb25f7d..20eba8bfde 100644 --- a/src/vs/workbench/parts/terminal/browser/terminalQuickOpen.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalQuickOpen.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen'; import { QuickOpenModel, QuickOpenEntry } from 'vs/base/parts/quickopen/browser/quickOpenModel'; import { QuickOpenHandler } from 'vs/workbench/browser/quickopen'; -import { ITerminalService, ITerminalInstance } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService, ITerminalInstance } from 'vs/workbench/contrib/terminal/common/terminal'; import { ContributableActionProvider } from 'vs/workbench/browser/actions'; import { stripWildcards } from 'vs/base/common/strings'; import { matchesFuzzy } from 'vs/base/common/filters'; @@ -95,7 +95,11 @@ export class TerminalPickerHandler extends QuickOpenHandler { return true; } - const highlights = matchesFuzzy(normalizedSearchValueLowercase, e.getLabel(), true); + const label = e.getLabel(); + if (!label) { + return false; + } + const highlights = matchesFuzzy(normalizedSearchValueLowercase, label, true); if (!highlights) { return false; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts new file mode 100644 index 0000000000..fcb8106525 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -0,0 +1,182 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as platform from 'vs/base/common/platform'; +import { ITerminalService, TERMINAL_PANEL_ID, ITerminalInstance, IShellLaunchConfig, NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TerminalService as CommonTerminalService } from 'vs/workbench/contrib/terminal/common/terminalService'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { TerminalPanel } from 'vs/workbench/contrib/terminal/browser/terminalPanel'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { TerminalTab } from 'vs/workbench/contrib/terminal/browser/terminalTab'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { TerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; +import { IBrowserTerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminal'; + +export abstract class TerminalService extends CommonTerminalService implements ITerminalService { + protected _configHelper: IBrowserTerminalConfigHelper; + + constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @IPanelService panelService: IPanelService, + @IWorkbenchLayoutService private _layoutService: IWorkbenchLayoutService, + @ILifecycleService lifecycleService: ILifecycleService, + @IStorageService storageService: IStorageService, + @INotificationService notificationService: INotificationService, + @IDialogService dialogService: IDialogService, + @IInstantiationService protected readonly _instantiationService: IInstantiationService, + @IWindowService private _windowService: IWindowService, + @IExtensionService extensionService: IExtensionService, + @IFileService fileService: IFileService, + ) { + super(contextKeyService, panelService, lifecycleService, storageService, notificationService, dialogService, extensionService, fileService); + } + + protected abstract _getDefaultShell(p: platform.Platform): string; + + public createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance { + const instance = this._instantiationService.createInstance(TerminalInstance, terminalFocusContextKey, configHelper, container, shellLaunchConfig); + this._onInstanceCreated.fire(instance); + return instance; + } + + public createTerminal(shell: IShellLaunchConfig = {}, wasNewTerminalAction?: boolean): ITerminalInstance { + const terminalTab = this._instantiationService.createInstance(TerminalTab, + this._terminalFocusContextKey, + this.configHelper, + this._terminalContainer, + shell); + this._terminalTabs.push(terminalTab); + const instance = terminalTab.terminalInstances[0]; + terminalTab.addDisposable(terminalTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed)); + terminalTab.addDisposable(terminalTab.onInstancesChanged(this._onInstancesChanged.fire, this._onInstancesChanged)); + this._initInstanceListeners(instance); + if (this.terminalInstances.length === 1) { + // It's the first instance so it should be made active automatically + this.setActiveInstanceByIndex(0); + } + this._onInstancesChanged.fire(); + this._suggestShellChange(wasNewTerminalAction); + return instance; + } + + private _suggestShellChange(wasNewTerminalAction?: boolean): void { + // Only suggest on Windows since $SHELL works great for macOS/Linux + if (!platform.isWindows) { + return; + } + + if (this._windowService.getConfiguration().remoteAuthority) { + // Don't suggest if the opened workspace is remote + return; + } + + // Only suggest when the terminal instance is being created by an explicit user action to + // launch a terminal, as opposed to something like tasks, debug, panel restore, etc. + if (!wasNewTerminalAction) { + return; + } + + if (this._windowService.getConfiguration().remoteAuthority) { + // Don't suggest if the opened workspace is remote + return; + } + + // Don't suggest if the user has explicitly opted out + const neverSuggest = this._storageService.getBoolean(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, StorageScope.GLOBAL, false); + if (neverSuggest) { + return; + } + + // Never suggest if the setting is non-default already (ie. they set the setting manually) + if (this.configHelper.config.shell.windows !== this._getDefaultShell(platform.Platform.Windows)) { + this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true, StorageScope.GLOBAL); + return; + } + + this._notificationService.prompt( + Severity.Info, + nls.localize('terminal.integrated.chooseWindowsShellInfo', "You can change the default terminal shell by selecting the customize button."), + [{ + label: nls.localize('customize', "Customize"), + run: () => { + this.selectDefaultWindowsShell().then(shell => { + if (!shell) { + return Promise.resolve(null); + } + // Launch a new instance with the newly selected shell + const instance = this.createTerminal({ + executable: shell, + args: this.configHelper.config.shellArgs.windows + }); + if (instance) { + this.setActiveInstance(instance); + } + return Promise.resolve(null); + }); + } + }, + { + label: nls.localize('never again', "Don't Show Again"), + isSecondary: true, + run: () => this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true, StorageScope.GLOBAL) + }] + ); + } + + public focusFindWidget(): Promise { + return this.showPanel(false).then(() => { + const panel = this._panelService.getActivePanel() as TerminalPanel; + panel.focusFindWidget(); + this._findWidgetVisible.set(true); + }); + } + + public hideFindWidget(): void { + const panel = this._panelService.getActivePanel() as TerminalPanel; + if (panel && panel.getId() === TERMINAL_PANEL_ID) { + panel.hideFindWidget(); + this._findWidgetVisible.reset(); + panel.focus(); + } + } + + public findNext(): void { + const panel = this._panelService.getActivePanel() as TerminalPanel; + if (panel && panel.getId() === TERMINAL_PANEL_ID) { + panel.showFindWidget(); + panel.getFindWidget().find(false); + } + } + + public findPrevious(): void { + const panel = this._panelService.getActivePanel() as TerminalPanel; + if (panel && panel.getId() === TERMINAL_PANEL_ID) { + panel.showFindWidget(); + panel.getFindWidget().find(true); + } + } + + public setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void { + this._configHelper.panelContainer = panelContainer; + this._terminalContainer = terminalContainer; + this._terminalTabs.forEach(tab => tab.attachToElement(this._terminalContainer)); + } + + public hidePanel(): void { + const panel = this._panelService.getActivePanel(); + if (panel && panel.getId() === TERMINAL_PANEL_ID) { + this._layoutService.setPanelHidden(true); + } + } +} diff --git a/src/vs/workbench/parts/terminal/browser/terminalTab.ts b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts similarity index 97% rename from src/vs/workbench/parts/terminal/browser/terminalTab.ts rename to src/vs/workbench/contrib/terminal/browser/terminalTab.ts index 26c89c9dfc..a0a064985c 100644 --- a/src/vs/workbench/parts/terminal/browser/terminalTab.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTab.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as aria from 'vs/base/browser/ui/aria/aria'; import * as nls from 'vs/nls'; -import { ITerminalInstance, IShellLaunchConfig, ITerminalTab, Direction, ITerminalService, ITerminalConfigHelper } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalInstance, IShellLaunchConfig, ITerminalTab, Direction, ITerminalService, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; import { IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Event, Emitter } from 'vs/base/common/event'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { SplitView, Orientation, IView, Sizing } from 'vs/base/browser/ui/splitview/splitview'; -import { IPartService, Position } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService, Position } from 'vs/workbench/services/layout/browser/layoutService'; const SPLIT_PANE_MIN_SIZE = 120; const TERMINAL_MIN_USEFUL_SIZE = 250; @@ -227,7 +227,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { private _container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, @ITerminalService private readonly _terminalService: ITerminalService, - @IPartService private readonly _partService: IPartService + @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService ) { super(); this._onDisposed = new Emitter(); @@ -341,7 +341,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { this._tabElement.classList.add('terminal-tab'); this._container.appendChild(this._tabElement); if (!this._splitPaneContainer) { - this._panelPosition = this._partService.getPanelPosition(); + this._panelPosition = this._layoutService.getPanelPosition(); const orientation = this._panelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; const newLocal = new SplitPaneContainer(this._tabElement, orientation); this._splitPaneContainer = newLocal; @@ -399,7 +399,7 @@ export class TerminalTab extends Disposable implements ITerminalTab { public layout(width: number, height: number): void { if (this._splitPaneContainer) { // Check if the panel position changed and rotate panes if so - const newPanelPosition = this._partService.getPanelPosition(); + const newPanelPosition = this._layoutService.getPanelPosition(); const panelPositionChanged = newPanelPosition !== this._panelPosition; if (panelPositionChanged) { const newOrientation = newPanelPosition === Position.BOTTOM ? Orientation.HORIZONTAL : Orientation.VERTICAL; diff --git a/src/vs/workbench/parts/terminal/browser/terminalWidgetManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts similarity index 100% rename from src/vs/workbench/parts/terminal/browser/terminalWidgetManager.ts rename to src/vs/workbench/contrib/terminal/browser/terminalWidgetManager.ts diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/contrib/terminal/common/terminal.ts similarity index 93% rename from src/vs/workbench/parts/terminal/common/terminal.ts rename to src/vs/workbench/contrib/terminal/common/terminal.ts index 6af3947594..d0369b096b 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/contrib/terminal/common/terminal.ts @@ -13,8 +13,6 @@ import { FindReplaceState } from 'vs/editor/contrib/find/findState'; export const TERMINAL_PANEL_ID = 'workbench.panel.terminal'; -export const TERMINAL_SERVICE_ID = 'terminalService'; - /** A context key that is set when there is at least one opened integrated terminal. */ export const KEYBINDING_CONTEXT_TERMINAL_IS_OPEN = new RawContextKey('terminalIsOpen', false); /** A context key that is set when the integrated terminal has focus. */ @@ -47,7 +45,7 @@ export const NEVER_MEASURE_RENDER_TIME_STORAGE_KEY = 'terminal.integrated.neverM // trying to create the corressponding object on the ext host. export const EXT_HOST_CREATION_DELAY = 100; -export const ITerminalService = createDecorator(TERMINAL_SERVICE_ID); +export const ITerminalService = createDecorator('terminalService'); export const TerminalCursorStyle = { BLOCK: 'block', @@ -107,6 +105,7 @@ export interface ITerminalConfiguration { export interface ITerminalConfigHelper { config: ITerminalConfiguration; + configFontIsMonospace(): boolean; getFont(): ITerminalFont; /** * Merges the default shell path and args into the provided launch configuration @@ -224,6 +223,7 @@ export interface ITerminalService { * @param name The name of the terminal. */ createTerminalRenderer(name: string): ITerminalInstance; + /** * Creates a raw terminal instance, this should not be used outside of the terminal part. */ @@ -251,9 +251,20 @@ export interface ITerminalService { findPrevious(): void; setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; - selectDefaultWindowsShell(): Promise; + selectDefaultWindowsShell(): Promise; setWorkspaceShellAllowed(isAllowed: boolean): void; + /** + * Takes a path and returns the properly escaped path to send to the terminal. + * On Windows, this included trying to prepare the path for WSL if needed. + * + * @param executable The executable off the shellLaunchConfig + * @param title The terminal's title + * @param path The path to be escaped and formatted. + * @returns An escaped version of the path to be execuded in the terminal. + */ + preparePathForTerminalAsync(path: string, executable: string | undefined, title: string): Promise; + requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number): void; } @@ -428,6 +439,14 @@ export interface ITerminalInstance { */ dispose(immediate?: boolean): void; + /** + * Indicates that a consumer of a renderer only terminal is finished with it. + * + * @param exitCode The exit code of the terminal. Zero indicates success, non-zero indicates + * failure. + */ + rendererExit(exitCode: number): void; + /** * Forces the terminal to redraw its viewport. */ @@ -524,15 +543,6 @@ export interface ITerminalInstance { */ sendText(text: string, addNewLine: boolean): void; - /** - * Takes a path and returns the properly escaped path to send to the terminal. - * On Windows, this included trying to prepare the path for WSL if needed. - * - * @param path The path to be escaped and formatted. - * @returns An escaped version of the path to be execuded in the terminal. - */ - preparePathForTerminalAsync(path: string): Promise; - /** * Write text directly to the terminal, skipping the process if it exists. * @param text The text to write. @@ -627,6 +637,9 @@ export interface ITerminalProcessManager extends IDisposable { readonly processState: ProcessState; readonly ptyProcessReady: Promise; readonly shellProcessId: number; + readonly remoteAuthority: string | undefined; + readonly os: platform.OperatingSystem | undefined; + readonly userHome: string | undefined; readonly onProcessReady: Event; readonly onProcessData: Event; @@ -686,4 +699,38 @@ export interface ITerminalProcessExtHostRequest { activeWorkspaceRootUri: URI; cols: number; rows: number; +} + +export enum LinuxDistro { + Fedora, + Ubuntu, + Unknown +} + +export interface IWindowsShellHelper extends IDisposable { + getShellName(): Promise; +} + +/** + * An interface representing a raw terminal child process, this contains a subset of the + * child_process.ChildProcess node.js interface. + */ +export interface ITerminalChildProcess { + onProcessData: Event; + onProcessExit: Event; + onProcessIdReady: Event; + onProcessTitleChanged: Event; + + /** + * Shutdown the terminal process. + * + * @param immediate When true the process will be killed immediately, otherwise the process will + * be given some time to make sure no additional data comes through. + */ + shutdown(immediate: boolean): void; + input(data: string): void; + resize(cols: number, rows: number): void; + + getInitialCwd(): Promise; + getCwd(): Promise; } \ No newline at end of file diff --git a/src/vs/workbench/parts/terminal/common/terminalColorRegistry.ts b/src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts similarity index 100% rename from src/vs/workbench/parts/terminal/common/terminalColorRegistry.ts rename to src/vs/workbench/contrib/terminal/common/terminalColorRegistry.ts diff --git a/src/vs/workbench/parts/terminal/common/terminalCommands.ts b/src/vs/workbench/contrib/terminal/common/terminalCommands.ts similarity index 98% rename from src/vs/workbench/parts/terminal/common/terminalCommands.ts rename to src/vs/workbench/contrib/terminal/common/terminalCommands.ts index b1a313c0bb..3cbd4749b5 100644 --- a/src/vs/workbench/parts/terminal/common/terminalCommands.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalCommands.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/common/terminal'; export const enum TERMINAL_COMMAND_ID { FIND_NEXT = 'workbench.action.terminal.findNext', diff --git a/src/vs/workbench/parts/terminal/node/terminalEnvironment.ts b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts similarity index 75% rename from src/vs/workbench/parts/terminal/node/terminalEnvironment.ts rename to src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts index 91551c1e55..74eba1e044 100644 --- a/src/vs/workbench/parts/terminal/node/terminalEnvironment.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalEnvironment.ts @@ -3,13 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as os from 'os'; -import * as paths from 'vs/base/common/paths'; +import * as path from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; -import pkg from 'vs/platform/node/package'; import { URI as Uri } from 'vs/base/common/uri'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IShellLaunchConfig, ITerminalEnvironment } from 'vs/workbench/parts/terminal/common/terminal'; +import { IShellLaunchConfig, ITerminalEnvironment } from 'vs/workbench/contrib/terminal/common/terminal'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; /** @@ -51,9 +49,9 @@ function _mergeEnvironmentValue(env: ITerminalEnvironment, key: string, value: s } } -export function addTerminalEnvironmentKeys(env: ITerminalEnvironment, locale: string | undefined, setLocaleVariables: boolean): void { +export function addTerminalEnvironmentKeys(env: ITerminalEnvironment, version: string | undefined, locale: string | undefined, setLocaleVariables: boolean): void { env['TERM_PROGRAM'] = 'vscode'; - env['TERM_PROGRAM_VERSION'] = pkg.version; + env['TERM_PROGRAM_VERSION'] = version ? version : null; if (setLocaleVariables) { env['LANG'] = _getLangEnvVariable(locale); } @@ -102,25 +100,25 @@ function _getLangEnvVariable(locale?: string) { return parts.join('_') + '.UTF-8'; } -export function getCwd(shell: IShellLaunchConfig, root?: Uri, customCwd?: string): string { +export function getCwd(shell: IShellLaunchConfig, userHome: string, root?: Uri, customCwd?: string): string { if (shell.cwd) { - return (typeof shell.cwd === 'object') ? shell.cwd.path : shell.cwd; + return (typeof shell.cwd === 'object') ? shell.cwd.fsPath : shell.cwd; } let cwd: string | undefined; // TODO: Handle non-existent customCwd if (!shell.ignoreConfigurationCwd && customCwd) { - if (paths.isAbsolute(customCwd)) { + if (path.isAbsolute(customCwd)) { cwd = customCwd; } else if (root) { - cwd = paths.normalize(paths.join(root.fsPath, customCwd)); + cwd = path.join(root.fsPath, customCwd); } } // If there was no custom cwd or it was relative with no workspace if (!cwd) { - cwd = root ? root.fsPath : os.homedir(); + cwd = root ? root.fsPath : userHome; } return _sanitizeCwd(cwd); @@ -134,26 +132,15 @@ function _sanitizeCwd(cwd: string): string { return cwd; } -/** - * Adds quotes to a path if it contains whitespaces - */ -export function preparePathForTerminal(path: string): string { - if (platform.isWindows) { - if (/\s+/.test(path)) { - return `"${path}"`; - } - return path; +export function escapeNonWindowsPath(path: string): string { + let newPath = path; + if (newPath.indexOf('\\') !== 0) { + newPath = newPath.replace(/\\/g, '\\\\'); } - path = path.replace(/(%5C|\\)/g, '\\\\'); - const charsToEscape = [ - ' ', '\'', '"', '?', ':', ';', '!', '*', '(', ')', '{', '}', '[', ']' - ]; - for (let i = 0; i < path.length; i++) { - const indexOfChar = charsToEscape.indexOf(path.charAt(i)); - if (indexOfChar >= 0) { - path = `${path.substring(0, i)}\\${path.charAt(i)}${path.substring(i + 1)}`; - i++; // Skip char due to escape char being added - } + if (!newPath && (newPath.indexOf('"') !== -1)) { + newPath = '\'' + newPath + '\''; + } else if (newPath.indexOf(' ') !== -1) { + newPath = newPath.replace(/ /g, '\\ '); } - return path; + return newPath; } diff --git a/src/vs/workbench/parts/terminal/common/terminalMenu.ts b/src/vs/workbench/contrib/terminal/common/terminalMenu.ts similarity index 95% rename from src/vs/workbench/parts/terminal/common/terminalMenu.ts rename to src/vs/workbench/contrib/terminal/common/terminalMenu.ts index 2d4c74f105..5e89a58075 100644 --- a/src/vs/workbench/parts/terminal/common/terminalMenu.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalMenu.ts @@ -5,7 +5,7 @@ import * as nls from 'vs/nls'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { TERMINAL_COMMAND_ID } from 'vs/workbench/parts/terminal/common/terminalCommands'; +import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; export function setupTerminalMenu() { diff --git a/src/vs/workbench/parts/terminal/node/terminalProcessExtHostProxy.ts b/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts similarity index 96% rename from src/vs/workbench/parts/terminal/node/terminalProcessExtHostProxy.ts rename to src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts index bc5f4cb9bb..89b3984168 100644 --- a/src/vs/workbench/parts/terminal/node/terminalProcessExtHostProxy.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalProcessExtHostProxy.ts @@ -3,9 +3,8 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ITerminalChildProcess } from 'vs/workbench/parts/terminal/node/terminal'; import { Event, Emitter } from 'vs/base/common/event'; -import { ITerminalService, ITerminalProcessExtHostProxy, IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService, ITerminalProcessExtHostProxy, IShellLaunchConfig, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal'; import { IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/contrib/terminal/common/terminalService.ts similarity index 74% rename from src/vs/workbench/parts/terminal/common/terminalService.ts rename to src/vs/workbench/contrib/terminal/common/terminalService.ts index e432787e75..6f9105113b 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalService.ts @@ -3,15 +3,22 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; -import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID, ITerminalTab, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID, ITerminalTab, ITerminalProcessExtHostProxy, ITerminalProcessExtHostRequest, KEYBINDING_CONTEXT_TERMINAL_IS_OPEN } from 'vs/workbench/contrib/terminal/common/terminal'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { URI } from 'vs/base/common/uri'; import { FindReplaceState } from 'vs/editor/contrib/find/findState'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; +import { isWindows } from 'vs/base/common/platform'; +import { basename } from 'vs/base/common/path'; export abstract class TerminalService implements ITerminalService { public _serviceBrand: any; @@ -20,8 +27,10 @@ export abstract class TerminalService implements ITerminalService { protected _terminalFocusContextKey: IContextKey; protected _findWidgetVisible: IContextKey; protected _terminalContainer: HTMLElement; - protected _terminalTabs: ITerminalTab[]; - protected abstract _terminalInstances: ITerminalInstance[]; + protected _terminalTabs: ITerminalTab[] = []; + protected get _terminalInstances(): ITerminalInstance[] { + return this._terminalTabs.reduce((p, c) => p.concat(c.terminalInstances), []); + } private _findState: FindReplaceState; private _activeTabIndex: number; @@ -56,9 +65,12 @@ export abstract class TerminalService implements ITerminalService { constructor( @IContextKeyService private readonly _contextKeyService: IContextKeyService, @IPanelService protected readonly _panelService: IPanelService, - @IPartService private readonly _partService: IPartService, @ILifecycleService lifecycleService: ILifecycleService, - @IStorageService protected readonly _storageService: IStorageService + @IStorageService protected readonly _storageService: IStorageService, + @INotificationService protected readonly _notificationService: INotificationService, + @IDialogService private readonly _dialogService: IDialogService, + @IExtensionService private readonly _extensionService: IExtensionService, + @IFileService private readonly _fileService: IFileService ) { this._activeTabIndex = 0; this._isShuttingDown = false; @@ -86,15 +98,32 @@ export abstract class TerminalService implements ITerminalService { this.onInstancesChanged(() => updateTerminalContextKeys()); } - protected abstract _showTerminalCloseConfirmation(): Promise; - protected abstract _showNotEnoughSpaceToast(): void; + protected abstract _getWslPath(path: string): Promise; + protected abstract _getWindowsBuildNumber(): number; + public abstract createTerminal(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance; - public abstract createTerminalRenderer(name: string): ITerminalInstance; public abstract createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance; - public abstract getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance; - public abstract selectDefaultWindowsShell(): Promise; + public abstract selectDefaultWindowsShell(): Promise; public abstract setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; - public abstract requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number): void; + + public createTerminalRenderer(name: string): ITerminalInstance { + return this.createTerminal({ name, isRendererOnly: true }); + } + + public getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance { + const activeInstance = this.getActiveInstance(); + return activeInstance ? activeInstance : this.createTerminal(undefined, wasNewTerminalAction); + } + + public requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number): void { + // Ensure extension host is ready before requesting a process + this._extensionService.whenInstalledExtensionsRegistered().then(() => { + // TODO: MainThreadTerminalService is not ready at this point, fix this + setTimeout(() => { + this._onInstanceRequestExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows }); + }, 500); + }); + } private _onBeforeShutdown(): boolean | Promise { if (this.terminalInstances.length === 0) { @@ -339,12 +368,7 @@ export abstract class TerminalService implements ITerminalService { }); } - public hidePanel(): void { - const panel = this._panelService.getActivePanel(); - if (panel && panel.getId() === TERMINAL_PANEL_ID) { - this._partService.setPanelHidden(true); - } - } + public abstract hidePanel(): void; public abstract focusFindWidget(): Promise; public abstract hideFindWidget(): void; @@ -368,4 +392,73 @@ export abstract class TerminalService implements ITerminalService { public setWorkspaceShellAllowed(isAllowed: boolean): void { this.configHelper.setWorkspaceShellAllowed(isAllowed); } + + protected _showTerminalCloseConfirmation(): Promise { + let message; + if (this.terminalInstances.length === 1) { + message = nls.localize('terminalService.terminalCloseConfirmationSingular', "There is an active terminal session, do you want to kill it?"); + } else { + message = nls.localize('terminalService.terminalCloseConfirmationPlural', "There are {0} active terminal sessions, do you want to kill them?", this.terminalInstances.length); + } + + return this._dialogService.confirm({ + message, + type: 'warning', + }).then(res => !res.confirmed); + } + + protected _showNotEnoughSpaceToast(): void { + this._notificationService.info(nls.localize('terminal.minWidth', "Not enough space to split terminal.")); + } + + protected _validateShellPaths(label: string, potentialPaths: string[]): Promise<[string, string] | null> { + if (potentialPaths.length === 0) { + return Promise.resolve(null); + } + const current = potentialPaths.shift(); + return this._fileService.existsFile(URI.file(current!)).then(exists => { + if (!exists) { + return this._validateShellPaths(label, potentialPaths); + } + return [label, current] as [string, string]; + }); + } + + public preparePathForTerminalAsync(originalPath: string, executable: string, title: string): Promise { + return new Promise(c => { + const exe = executable; + if (!exe) { + c(originalPath); + return; + } + + const hasSpace = originalPath.indexOf(' ') !== -1; + + const pathBasename = basename(exe, '.exe'); + const isPowerShell = pathBasename === 'pwsh' || + title === 'pwsh' || + pathBasename === 'powershell' || + title === 'powershell'; + + if (isPowerShell && (hasSpace || originalPath.indexOf('\'') !== -1)) { + c(`& '${originalPath.replace(/'/g, '\'\'')}'`); + return; + } + + if (isWindows) { + // 17063 is the build number where wsl path was introduced. + // Update Windows uriPath to be executed in WSL. + if (((exe.indexOf('wsl') !== -1) || ((exe.indexOf('bash.exe') !== -1) && (exe.indexOf('git') === -1))) && (this._getWindowsBuildNumber() >= 17063)) { + c(this._getWslPath(originalPath)); + return; + } else if (hasSpace) { + c('"' + originalPath + '"'); + } else { + c(originalPath); + } + return; + } + c(escapeNonWindowsPath(originalPath)); + }); + } } diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts new file mode 100644 index 0000000000..be2c22374d --- /dev/null +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminal.contribution.ts @@ -0,0 +1,43 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as platform from 'vs/base/common/platform'; +import * as nls from 'vs/nls'; +import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ITerminalService } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TerminalInstanceService } from 'vs/workbench/contrib/terminal/electron-browser/terminalInstanceService'; +import { TerminalService } from 'vs/workbench/contrib/terminal/electron-browser/terminalService'; +import { getDefaultShell } from 'vs/workbench/contrib/terminal/node/terminal'; + +const configurationRegistry = Registry.as(Extensions.Configuration); +configurationRegistry.registerConfiguration({ + id: 'terminal', + order: 100, + title: nls.localize('terminalIntegratedConfigurationTitle', "Integrated Terminal"), + type: 'object', + properties: { + 'terminal.integrated.shell.linux': { + markdownDescription: nls.localize('terminal.integrated.shell.linux', "The path of the shell that the terminal uses on Linux. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), + type: 'string', + default: getDefaultShell(platform.Platform.Linux) + }, + 'terminal.integrated.shell.osx': { + markdownDescription: nls.localize('terminal.integrated.shell.osx', "The path of the shell that the terminal uses on macOS. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), + type: 'string', + default: getDefaultShell(platform.Platform.Mac) + }, + 'terminal.integrated.shell.windows': { + markdownDescription: nls.localize('terminal.integrated.shell.windows', "The path of the shell that the terminal uses on Windows. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), + type: 'string', + default: getDefaultShell(platform.Platform.Windows) + } + } +}); + +registerSingleton(ITerminalService, TerminalService, true); +registerSingleton(ITerminalInstanceService, TerminalInstanceService, true); diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts new file mode 100644 index 0000000000..a090438b31 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalInstanceService.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { ITerminalInstanceService } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { Terminal as XTermTerminal } from 'vscode-xterm'; +import { ITerminalInstance, IWindowsShellHelper, ITerminalConfigHelper, ITerminalProcessManager, IShellLaunchConfig, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal'; +import { WindowsShellHelper } from 'vs/workbench/contrib/terminal/node/windowsShellHelper'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { TerminalProcessManager } from 'vs/workbench/contrib/terminal/browser/terminalProcessManager'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { TerminalProcess } from 'vs/workbench/contrib/terminal/node/terminalProcess'; + +let Terminal: typeof XTermTerminal; + +/** + * A service used by TerminalInstance (and components owned by it) that allows it to break its + * dependency on electron-browser and node layers, while at the same time avoiding a cyclic + * dependency on ITerminalService. + */ +export class TerminalInstanceService implements ITerminalInstanceService { + public _serviceBrand: any; + + constructor( + @IInstantiationService private readonly _instantiationService: IInstantiationService + ) { + } + + public async getXtermConstructor(): Promise { + if (!Terminal) { + Terminal = (await import('vscode-xterm')).Terminal; + // Enable xterm.js addons + Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/search/search')); + Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/webLinks/webLinks')); + Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/winptyCompat/winptyCompat')); + // Localize strings + Terminal.strings.blankLine = nls.localize('terminal.integrated.a11yBlankLine', 'Blank line'); + Terminal.strings.promptLabel = nls.localize('terminal.integrated.a11yPromptLabel', 'Terminal input'); + Terminal.strings.tooMuchOutput = nls.localize('terminal.integrated.a11yTooMuchOutput', 'Too much output to announce, navigate to rows manually to read'); + } + return Terminal; + } + + public createWindowsShellHelper(shellProcessId: number, instance: ITerminalInstance, xterm: XTermTerminal): IWindowsShellHelper { + return new WindowsShellHelper(shellProcessId, instance, xterm); + } + + public createTerminalProcessManager(id: number, configHelper: ITerminalConfigHelper): ITerminalProcessManager { + return this._instantiationService.createInstance(TerminalProcessManager, id, configHelper); + } + + public createTerminalProcess(shellLaunchConfig: IShellLaunchConfig, cwd: string, cols: number, rows: number, env: IProcessEnvironment, windowsEnableConpty: boolean): ITerminalChildProcess { + return new TerminalProcess(shellLaunchConfig, cwd, cols, rows, env, windowsEnableConpty); + } +} diff --git a/src/vs/workbench/contrib/terminal/electron-browser/terminalService.ts b/src/vs/workbench/contrib/terminal/electron-browser/terminalService.ts new file mode 100644 index 0000000000..498c180c88 --- /dev/null +++ b/src/vs/workbench/contrib/terminal/electron-browser/terminalService.ts @@ -0,0 +1,153 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as pfs from 'vs/base/node/pfs'; +import * as platform from 'vs/base/common/platform'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { ITerminalService, ITerminalConfigHelper } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TerminalService as BrowserTerminalService } from 'vs/workbench/contrib/terminal/browser/terminalService'; +import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { getDefaultShell, linuxDistro, getWindowsBuildNumber } from 'vs/workbench/contrib/terminal/node/terminal'; +import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { ipcRenderer as ipc } from 'electron'; +import { IOpenFileRequest, IWindowService } from 'vs/platform/windows/common/windows'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput'; +import { coalesce } from 'vs/base/common/arrays'; +import { IFileService } from 'vs/platform/files/common/files'; +import { escapeNonWindowsPath } from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; +import { execFile } from 'child_process'; + +export class TerminalService extends BrowserTerminalService implements ITerminalService { + public get configHelper(): ITerminalConfigHelper { return this._configHelper; } + + constructor( + @IContextKeyService contextKeyService: IContextKeyService, + @IPanelService panelService: IPanelService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IStorageService storageService: IStorageService, + @ILifecycleService lifecycleService: ILifecycleService, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IInstantiationService instantiationService: IInstantiationService, + @IQuickInputService private readonly _quickInputService: IQuickInputService, + @INotificationService notificationService: INotificationService, + @IDialogService dialogService: IDialogService, + @IExtensionService extensionService: IExtensionService, + @IWindowService windowService: IWindowService, + @IFileService fileService: IFileService + ) { + super(contextKeyService, panelService, layoutService, lifecycleService, storageService, notificationService, dialogService, instantiationService, windowService, extensionService, fileService); + + this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper, linuxDistro); + ipc.on('vscode:openFiles', (_event: any, request: IOpenFileRequest) => { + // if the request to open files is coming in from the integrated terminal (identified though + // the termProgram variable) and we are instructed to wait for editors close, wait for the + // marker file to get deleted and then focus back to the integrated terminal. + if (request.termProgram === 'vscode' && request.filesToWait) { + pfs.whenDeleted(request.filesToWait.waitMarkerFilePath).then(() => { + if (this.terminalInstances.length > 0) { + const terminal = this.getActiveInstance(); + if (terminal) { + terminal.focus(); + } + } + }); + } + }); + ipc.on('vscode:osResume', () => { + const activeTab = this.getActiveTab(); + if (!activeTab) { + return; + } + activeTab.terminalInstances.forEach(instance => instance.forceRedraw()); + }); + } + + protected _getDefaultShell(p: platform.Platform): string { + return getDefaultShell(p); + } + + public selectDefaultWindowsShell(): Promise { + return this._detectWindowsShells().then(shells => { + const options: IPickOptions = { + placeHolder: nls.localize('terminal.integrated.chooseWindowsShell', "Select your preferred terminal shell, you can change this later in your settings") + }; + return this._quickInputService.pick(shells, options).then(value => { + if (!value) { + return undefined; + } + const shell = value.description; + return this._configurationService.updateValue('terminal.integrated.shell.windows', shell, ConfigurationTarget.USER).then(() => shell); + }); + }); + } + + private _detectWindowsShells(): Promise { + // Determine the correct System32 path. We want to point to Sysnative + // when the 32-bit version of VS Code is running on a 64-bit machine. + // The reason for this is because PowerShell's important PSReadline + // module doesn't work if this is not the case. See #27915. + const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); + const system32Path = `${process.env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}`; + + let useWSLexe = false; + + if (getWindowsBuildNumber() >= 16299) { + useWSLexe = true; + } + + const expectedLocations = { + 'Command Prompt': [`${system32Path}\\cmd.exe`], + PowerShell: [`${system32Path}\\WindowsPowerShell\\v1.0\\powershell.exe`], + 'WSL Bash': [`${system32Path}\\${useWSLexe ? 'wsl.exe' : 'bash.exe'}`], + 'Git Bash': [ + `${process.env['ProgramW6432']}\\Git\\bin\\bash.exe`, + `${process.env['ProgramW6432']}\\Git\\usr\\bin\\bash.exe`, + `${process.env['ProgramFiles']}\\Git\\bin\\bash.exe`, + `${process.env['ProgramFiles']}\\Git\\usr\\bin\\bash.exe`, + `${process.env['LocalAppData']}\\Programs\\Git\\bin\\bash.exe`, + ] + }; + const promises: PromiseLike<[string, string]>[] = []; + Object.keys(expectedLocations).forEach(key => promises.push(this._validateShellPaths(key, expectedLocations[key]))); + return Promise.all(promises) + .then(coalesce) + .then(results => { + return results.map(result => { + return { + label: result[0], + description: result[1] + }; + }); + }); + } + + protected _getWindowsBuildNumber(): number { + return getWindowsBuildNumber(); + } + + /** + * Converts a path to a path on WSL using the wslpath utility. + * @param path The original path. + */ + protected _getWslPath(path: string): Promise { + if (getWindowsBuildNumber() < 17063) { + throw new Error('wslpath does not exist on Windows build < 17063'); + } + return new Promise(c => { + execFile('bash.exe', ['-c', 'echo $(wslpath ' + escapeNonWindowsPath(path) + ')'], {}, (error, stdout, stderr) => { + c(escapeNonWindowsPath(stdout.trim())); + }); + }); + } +} diff --git a/src/vs/workbench/parts/terminal/node/terminal.ts b/src/vs/workbench/contrib/terminal/node/terminal.ts similarity index 75% rename from src/vs/workbench/parts/terminal/node/terminal.ts rename to src/vs/workbench/contrib/terminal/node/terminal.ts index 7e4268fd00..e32c977108 100644 --- a/src/vs/workbench/parts/terminal/node/terminal.ts +++ b/src/vs/workbench/contrib/terminal/node/terminal.ts @@ -7,31 +7,7 @@ import * as os from 'os'; import * as platform from 'vs/base/common/platform'; import * as processes from 'vs/base/node/processes'; import { readFile, fileExists } from 'vs/base/node/pfs'; -import { Event } from 'vs/base/common/event'; - -/** - * An interface representing a raw terminal child process, this contains a subset of the - * child_process.ChildProcess node.js interface. - */ -export interface ITerminalChildProcess { - onProcessData: Event; - onProcessExit: Event; - onProcessIdReady: Event; - onProcessTitleChanged: Event; - - /** - * Shutdown the terminal process. - * - * @param immediate When true the process will be killed immediately, otherwise the process will - * be given some time to make sure no additional data comes through. - */ - shutdown(immediate: boolean): void; - input(data: string): void; - resize(cols: number, rows: number): void; - - getInitialCwd(): Promise; - getCwd(): Promise; -} +import { LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; export function getDefaultShell(p: platform.Platform): string { if (p === platform.Platform.Windows) { @@ -78,6 +54,7 @@ function getTerminalDefaultShellWindows(): string { return _TERMINAL_DEFAULT_SHELL_WINDOWS; } +let detectedDistro = LinuxDistro.Unknown; if (platform.isLinux) { const file = '/etc/os-release'; fileExists(file).then(exists => { @@ -87,13 +64,21 @@ if (platform.isLinux) { readFile(file).then(b => { const contents = b.toString(); if (/NAME="?Fedora"?/.test(contents)) { - isFedora = true; + detectedDistro = LinuxDistro.Fedora; } else if (/NAME="?Ubuntu"?/.test(contents)) { - isUbuntu = true; + detectedDistro = LinuxDistro.Ubuntu; } }); }); } -export let isFedora = false; -export let isUbuntu = false; \ No newline at end of file +export const linuxDistro = detectedDistro; + +export function getWindowsBuildNumber(): number { + const osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release()); + let buildNumber: number = 0; + if (osVersion && osVersion.length === 4) { + buildNumber = parseInt(osVersion[3]); + } + return buildNumber; +} diff --git a/src/vs/workbench/parts/terminal/node/terminalProcess.ts b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts similarity index 87% rename from src/vs/workbench/parts/terminal/node/terminalProcess.ts rename to src/vs/workbench/contrib/terminal/node/terminalProcess.ts index e490ea8fde..c3fe09d7d7 100644 --- a/src/vs/workbench/parts/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/contrib/terminal/node/terminalProcess.ts @@ -4,13 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as os from 'os'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import * as pty from 'node-pty'; +import * as fs from 'fs'; import { Event, Emitter } from 'vs/base/common/event'; -import { ITerminalChildProcess } from 'vs/workbench/parts/terminal/node/terminal'; +import { getWindowsBuildNumber } from 'vs/workbench/contrib/terminal/node/terminal'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { IShellLaunchConfig } from 'vs/workbench/parts/terminal/common/terminal'; +import { IShellLaunchConfig, ITerminalChildProcess } from 'vs/workbench/contrib/terminal/common/terminal'; import { exec } from 'child_process'; export class TerminalProcess implements ITerminalChildProcess, IDisposable { @@ -50,7 +51,7 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { } this._initialCwd = cwd; - const useConpty = windowsEnableConpty && process.platform === 'win32' && this._getWindowsBuildNumber() >= 18309; + const useConpty = windowsEnableConpty && process.platform === 'win32' && getWindowsBuildNumber() >= 18309; const options: pty.IPtyForkOptions = { name: shellName, cwd, @@ -105,15 +106,6 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { this._onProcessTitleChanged.dispose(); } - private _getWindowsBuildNumber(): number { - const osVersion = (/(\d+)\.(\d+)\.(\d+)/g).exec(os.release()); - let buildNumber: number = 0; - if (osVersion && osVersion.length === 4) { - buildNumber = parseInt(osVersion[3]); - } - return buildNumber; - } - private _setupTitlePolling() { // Send initial timeout async to give event listeners a chance to init setTimeout(() => { @@ -196,18 +188,29 @@ export class TerminalProcess implements ITerminalChildProcess, IDisposable { } public getCwd(): Promise { - if (platform.isWindows) { + if (platform.isMacintosh) { return new Promise(resolve => { - resolve(this._initialCwd); + exec('lsof -p ' + this._ptyProcess.pid + ' | grep cwd', (error, stdout, stderr) => { + if (stdout !== '') { + resolve(stdout.substring(stdout.indexOf('/'), stdout.length - 1)); + } + }); + }); + } + + if (platform.isLinux) { + return new Promise(resolve => { + fs.readlink('/proc/' + this._ptyProcess.pid + '/cwd', (err, linkedstr) => { + if (err) { + resolve(this._initialCwd); + } + resolve(linkedstr); + }); }); } return new Promise(resolve => { - exec('lsof -p ' + this._ptyProcess.pid + ' | grep cwd', (error, stdout, stderr) => { - if (stdout !== '') { - resolve(stdout.substring(stdout.indexOf('/'), stdout.length - 1)); - } - }); + resolve(this._initialCwd); }); } } diff --git a/src/vs/workbench/parts/terminal/node/windowsShellHelper.ts b/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts similarity index 95% rename from src/vs/workbench/parts/terminal/node/windowsShellHelper.ts rename to src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts index e68c529fa4..28d1193c6a 100644 --- a/src/vs/workbench/parts/terminal/node/windowsShellHelper.ts +++ b/src/vs/workbench/contrib/terminal/node/windowsShellHelper.ts @@ -5,7 +5,7 @@ import * as platform from 'vs/base/common/platform'; import { Emitter, Event } from 'vs/base/common/event'; -import { ITerminalInstance } from 'vs/workbench/parts/terminal/common/terminal'; +import { ITerminalInstance, IWindowsShellHelper } from 'vs/workbench/contrib/terminal/common/terminal'; import { Terminal as XTermTerminal } from 'vscode-xterm'; import WindowsProcessTreeType = require('windows-process-tree'); @@ -24,7 +24,7 @@ const SHELL_EXECUTABLES = [ let windowsProcessTree: typeof WindowsProcessTreeType; -export class WindowsShellHelper { +export class WindowsShellHelper implements IWindowsShellHelper { private _onCheckShell: Emitter | undefined>; private _isDisposed: boolean; private _currentRequest: Promise | null; diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalColorRegistry.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts similarity index 98% rename from src/vs/workbench/parts/terminal/test/electron-browser/terminalColorRegistry.test.ts rename to src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts index 28e77bdcfe..acc0950d84 100644 --- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalColorRegistry.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalColorRegistry.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { Extensions as ThemeingExtensions, IColorRegistry, ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ansiColorIdentifiers, registerColors } from 'vs/workbench/parts/terminal/common/terminalColorRegistry'; +import { ansiColorIdentifiers, registerColors } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { ITheme, ThemeType } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; diff --git a/src/vs/workbench/parts/terminal/test/node/terminalCommandTracker.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts similarity index 50% rename from src/vs/workbench/parts/terminal/test/node/terminalCommandTracker.test.ts rename to src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts index 9678348077..87d25074eb 100644 --- a/src/vs/workbench/parts/terminal/test/node/terminalCommandTracker.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalCommandTracker.test.ts @@ -4,6 +4,27 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; +import { Terminal, TerminalCore } from 'vscode-xterm'; +import { TerminalCommandTracker } from 'vs/workbench/contrib/terminal/browser/terminalCommandTracker'; +import { isWindows } from 'vs/base/common/platform'; + +interface TestTerminalCore extends TerminalCore { + writeBuffer: string[]; + _innerWrite(): void; +} + +interface TestTerminal extends Terminal { + _core: TestTerminalCore; +} + +function syncWrite(term: TestTerminal, data: string): void { + // Terminal.write is asynchronous + term._core.writeBuffer.push(data); + term._core._innerWrite(); +} + +const ROWS = 10; +const COLS = 10; suite('Workbench - TerminalCommandTracker', () => { suite('Command tracking', () => { diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalConfigHelper.test.ts similarity index 79% rename from src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts rename to src/vs/workbench/contrib/terminal/test/electron-browser/terminalConfigHelper.test.ts index 31dfe4b081..7d9061a004 100644 --- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalConfigHelper.test.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; +import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { EDITOR_FONT_DEFAULTS } from 'vs/editor/common/config/editorOptions'; -import { isFedora, isUbuntu } from 'vs/workbench/parts/terminal/node/terminal'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { LinuxDistro } from 'vs/workbench/contrib/terminal/common/terminal'; suite('Workbench - TerminalConfigHelper', () => { test('TerminalConfigHelper - getFont fontFamily', function () { // {{SQL CARBON EDIT}} - Remove tests }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts similarity index 67% rename from src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts rename to src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts index 3dc2516fde..cf3f5a002c 100644 --- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/contrib/terminal/test/electron-browser/terminalLinkHandler.test.ts @@ -4,16 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Platform } from 'vs/base/common/platform'; -import { TerminalLinkHandler, LineColumnInfo } from 'vs/workbench/parts/terminal/electron-browser/terminalLinkHandler'; +import { Platform, OperatingSystem } from 'vs/base/common/platform'; +import { TerminalLinkHandler, LineColumnInfo } from 'vs/workbench/contrib/terminal/browser/terminalLinkHandler'; import * as strings from 'vs/base/common/strings'; -import * as path from 'path'; -import * as sinon from 'sinon'; class TestTerminalLinkHandler extends TerminalLinkHandler { public get localLinkRegex(): RegExp { return this._localLinkRegex; } + public get gitDiffLinkPreImageRegex(): RegExp { + return this._gitDiffPreImageRegex; + } + public get gitDiffLinkPostImageRegex(): RegExp { + return this._gitDiffPostImageRegex; + } public preprocessPath(link: string): string | null { return this._preprocessPath(link); } @@ -33,7 +37,10 @@ interface LinkFormatInfo { suite('Workbench - TerminalLinkHandler', () => { suite('localLinkRegex', () => { test('Windows', () => { - const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, null!, null!, null!, null!); + const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, { + os: OperatingSystem.Windows, + userHome: '' + } as any, null!, null!, null!, null!, null!); function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) { assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl); assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl); @@ -105,7 +112,10 @@ suite('Workbench - TerminalLinkHandler', () => { }); test('Linux', () => { - const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, null!, null!, null!, null!); + const terminalLinkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, { + os: OperatingSystem.Linux, + userHome: '' + } as any, null!, null!, null!, null!, null!); function testLink(link: string, linkUrl: string, lineNo?: string, columnNo?: string) { assert.equal(terminalLinkHandler.extractLinkUrl(link), linkUrl); assert.equal(terminalLinkHandler.extractLinkUrl(`:${link}:`), linkUrl); @@ -169,52 +179,84 @@ suite('Workbench - TerminalLinkHandler', () => { suite('preprocessPath', () => { test('Windows', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, null!, null!, null!, null!); + const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, { + os: OperatingSystem.Windows, + userHome: 'C:\\Users\\Me' + } as any, null!, null!, null!, null!, null!); linkHandler.processCwd = 'C:\\base'; - let stub = sinon.stub(path, 'join', function (arg1: string, arg2: string) { - return arg1 + '\\' + arg2; - }); - assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base\\./src/file1'); + assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base\\src\\file1'); assert.equal(linkHandler.preprocessPath('src\\file2'), 'C:\\base\\src\\file2'); - assert.equal(linkHandler.preprocessPath('C:\\absolute\\path\\file3'), 'C:\\absolute\\path\\file3'); - - stub.restore(); + assert.equal(linkHandler.preprocessPath('~/src/file3'), 'C:\\Users\\Me\\src\\file3'); + assert.equal(linkHandler.preprocessPath('~\\src\\file4'), 'C:\\Users\\Me\\src\\file4'); + assert.equal(linkHandler.preprocessPath('C:\\absolute\\path\\file5'), 'C:\\absolute\\path\\file5'); }); test('Windows - spaces', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, null!, null!, null!, null!); + const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Windows, { + os: OperatingSystem.Windows, + userHome: 'C:\\Users\\M e' + } as any, null!, null!, null!, null!, null!); linkHandler.processCwd = 'C:\\base dir'; - let stub = sinon.stub(path, 'join', function (arg1: string, arg2: string) { - return arg1 + '\\' + arg2; - }); - assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base dir\\./src/file1'); + assert.equal(linkHandler.preprocessPath('./src/file1'), 'C:\\base dir\\src\\file1'); assert.equal(linkHandler.preprocessPath('src\\file2'), 'C:\\base dir\\src\\file2'); - assert.equal(linkHandler.preprocessPath('C:\\absolute\\path\\file3'), 'C:\\absolute\\path\\file3'); - - stub.restore(); + assert.equal(linkHandler.preprocessPath('~/src/file3'), 'C:\\Users\\M e\\src\\file3'); + assert.equal(linkHandler.preprocessPath('~\\src\\file4'), 'C:\\Users\\M e\\src\\file4'); + assert.equal(linkHandler.preprocessPath('C:\\abso lute\\path\\file5'), 'C:\\abso lute\\path\\file5'); }); test('Linux', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, null!, null!, null!, null!); + const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, { + os: OperatingSystem.Linux, + userHome: '/home/me' + } as any, null!, null!, null!, null!, null!); linkHandler.processCwd = '/base'; - let stub = sinon.stub(path, 'join', function (arg1: string, arg2: string) { - return arg1 + '/' + arg2; - }); - - assert.equal(linkHandler.preprocessPath('./src/file1'), '/base/./src/file1'); + assert.equal(linkHandler.preprocessPath('./src/file1'), '/base/src/file1'); assert.equal(linkHandler.preprocessPath('src/file2'), '/base/src/file2'); - assert.equal(linkHandler.preprocessPath('/absolute/path/file3'), '/absolute/path/file3'); - stub.restore(); + assert.equal(linkHandler.preprocessPath('~/src/file3'), '/home/me/src/file3'); + assert.equal(linkHandler.preprocessPath('/absolute/path/file4'), '/absolute/path/file4'); }); test('No Workspace', () => { - const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, null!, null!, null!, null!); + const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, { + os: OperatingSystem.Linux, + userHome: '/home/me' + } as any, null!, null!, null!, null!, null!); assert.equal(linkHandler.preprocessPath('./src/file1'), null); assert.equal(linkHandler.preprocessPath('src/file2'), null); - assert.equal(linkHandler.preprocessPath('/absolute/path/file3'), '/absolute/path/file3'); + assert.equal(linkHandler.preprocessPath('~/src/file3'), '/home/me/src/file3'); + assert.equal(linkHandler.preprocessPath('/absolute/path/file4'), '/absolute/path/file4'); }); }); + + test('gitDiffLinkRegex', () => { + // The platform is irrelevant because the links generated by Git are the same format regardless of platform + const linkHandler = new TestTerminalLinkHandler(new TestXterm(), Platform.Linux, { + os: OperatingSystem.Linux, + userHome: '' + } as any, null!, null!, null!, null!, null!); + + function assertAreGoodMatches(matches: RegExpMatchArray | null) { + if (matches) { + assert.equal(matches.length, 2); + assert.equal(matches[1], 'src/file1'); + } else { + assert.fail(); + } + } + + // Happy cases + assertAreGoodMatches('--- a/src/file1'.match(linkHandler.gitDiffLinkPreImageRegex)); + assertAreGoodMatches('--- a/src/file1 '.match(linkHandler.gitDiffLinkPreImageRegex)); + assertAreGoodMatches('+++ b/src/file1'.match(linkHandler.gitDiffLinkPostImageRegex)); + assertAreGoodMatches('+++ b/src/file1 '.match(linkHandler.gitDiffLinkPostImageRegex)); + + // Make sure /dev/null isn't a match + assert.equal(linkHandler.gitDiffLinkPreImageRegex.test('--- /dev/null'), false); + assert.equal(linkHandler.gitDiffLinkPreImageRegex.test('--- /dev/null '), false); + assert.equal(linkHandler.gitDiffLinkPostImageRegex.test('+++ /dev/null'), false); + assert.equal(linkHandler.gitDiffLinkPostImageRegex.test('+++ /dev/null '), false); + }); }); diff --git a/src/vs/workbench/parts/terminal/test/node/terminalEnvironment.test.ts b/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts similarity index 68% rename from src/vs/workbench/parts/terminal/test/node/terminalEnvironment.test.ts rename to src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts index 82cb587999..fc451a1754 100644 --- a/src/vs/workbench/parts/terminal/test/node/terminalEnvironment.test.ts +++ b/src/vs/workbench/contrib/terminal/test/node/terminalEnvironment.test.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as os from 'os'; import * as platform from 'vs/base/common/platform'; -import * as terminalEnvironment from 'vs/workbench/parts/terminal/node/terminalEnvironment'; +import * as terminalEnvironment from 'vs/workbench/contrib/terminal/common/terminalEnvironment'; import { URI as Uri } from 'vs/base/common/uri'; import { IStringDictionary } from 'vs/base/common/collections'; @@ -14,21 +13,21 @@ suite('Workbench - TerminalEnvironment', () => { test('addTerminalEnvironmentKeys', () => { const env = { FOO: 'bar' }; const locale = 'en-au'; - terminalEnvironment.addTerminalEnvironmentKeys(env, locale, true); + terminalEnvironment.addTerminalEnvironmentKeys(env, '1.2.3', locale, true); assert.equal(env['TERM_PROGRAM'], 'vscode'); - assert.equal(env['TERM_PROGRAM_VERSION'].search(/^\d+\.\d+\.\d+$/), 0); + assert.equal(env['TERM_PROGRAM_VERSION'], '1.2.3'); assert.equal(env['LANG'], 'en_AU.UTF-8', 'LANG is equal to the requested locale with UTF-8'); const env2 = { FOO: 'bar' }; - terminalEnvironment.addTerminalEnvironmentKeys(env2, undefined, true); + terminalEnvironment.addTerminalEnvironmentKeys(env2, '1.2.3', undefined, true); assert.equal(env2['LANG'], 'en_US.UTF-8', 'LANG is equal to en_US.UTF-8 as fallback.'); // More info on issue #14586 const env3 = { LANG: 'replace' }; - terminalEnvironment.addTerminalEnvironmentKeys(env3, undefined, true); + terminalEnvironment.addTerminalEnvironmentKeys(env3, '1.2.3', undefined, true); assert.equal(env3['LANG'], 'en_US.UTF-8', 'LANG is set to the fallback LANG'); const env4 = { LANG: 'en_US.UTF-8' }; - terminalEnvironment.addTerminalEnvironmentKeys(env3, undefined, true); + terminalEnvironment.addTerminalEnvironmentKeys(env3, '1.2.3', undefined, true); assert.equal(env4['LANG'], 'en_US.UTF-8', 'LANG is equal to the parent environment\'s LANG'); }); @@ -101,42 +100,32 @@ suite('Workbench - TerminalEnvironment', () => { assert.equal(Uri.file(a).fsPath, Uri.file(b).fsPath); } - test('should default to os.homedir() for an empty workspace', () => { - assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, undefined, undefined), os.homedir()); + test('should default to userHome for an empty workspace', () => { + assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, undefined), '/userHome/'); }); test('should use to the workspace if it exists', () => { - assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, Uri.file('/foo'), undefined), '/foo'); + assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', Uri.file('/foo'), undefined), '/foo'); }); test('should use an absolute custom cwd as is', () => { - assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, undefined, '/foo'), '/foo'); + assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, '/foo'), '/foo'); }); test('should normalize a relative custom cwd against the workspace path', () => { - assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, Uri.file('/bar'), 'foo'), '/bar/foo'); - assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, Uri.file('/bar'), './foo'), '/bar/foo'); - assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, Uri.file('/bar'), '../foo'), '/foo'); + assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', Uri.file('/bar'), 'foo'), '/bar/foo'); + assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', Uri.file('/bar'), './foo'), '/bar/foo'); + assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', Uri.file('/bar'), '../foo'), '/foo'); }); test('should fall back for relative a custom cwd that doesn\'t have a workspace', () => { - assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, undefined, 'foo'), os.homedir()); - assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, undefined, './foo'), os.homedir()); - assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, undefined, '../foo'), os.homedir()); + assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, 'foo'), '/userHome/'); + assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, './foo'), '/userHome/'); + assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [] }, '/userHome/', undefined, '../foo'), '/userHome/'); }); test('should ignore custom cwd when told to ignore', () => { - assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [], ignoreConfigurationCwd: true }, Uri.file('/bar'), '/foo'), '/bar'); + assertPathsMatch(terminalEnvironment.getCwd({ executable: undefined, args: [], ignoreConfigurationCwd: true }, '/userHome/', Uri.file('/bar'), '/foo'), '/bar'); }); }); - - test('preparePathForTerminal', () => { - if (platform.isWindows) { - assert.equal(terminalEnvironment.preparePathForTerminal('C:\\foo'), 'C:\\foo'); - assert.equal(terminalEnvironment.preparePathForTerminal('C:\\foo bar'), '"C:\\foo bar"'); - return; - } - assert.equal(terminalEnvironment.preparePathForTerminal('/a/\\foo bar"\'? ;\'?? :'), '/a/\\\\foo\\ bar\\"\\\'\\?\\ \\;\\\'\\?\\?\\ \\ \\:'); - assert.equal(terminalEnvironment.preparePathForTerminal('/\\\'"?:;!*(){}[]'), '/\\\\\\\'\\"\\?\\:\\;\\!\\*\\(\\)\\{\\}\\[\\]'); - }); }); diff --git a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts similarity index 95% rename from src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts rename to src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 9f862a5ce8..0ed27fc769 100644 --- a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -11,15 +11,14 @@ import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { IWorkbenchThemeService, COLOR_THEME_SETTING, ICON_THEME_SETTING, IColorTheme, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/parts/extensions/common/extensions'; +import { VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/contrib/extensions/common/extensions'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { Delayer } from 'vs/base/common/async'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IColorRegistry, Extensions as ColorRegistryExtensions } from 'vs/platform/theme/common/colorRegistry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Color } from 'vs/base/common/color'; -import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { LIGHT, DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; import { colorThemeSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -37,7 +36,7 @@ export class SelectColorThemeAction extends Action { @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IViewletService private readonly viewletService: IViewletService, - @IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, label); } @@ -99,7 +98,7 @@ class SelectIconThemeAction extends Action { @IWorkbenchThemeService private readonly themeService: IWorkbenchThemeService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IViewletService private readonly viewletService: IViewletService, - @IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, label); diff --git a/src/vs/workbench/parts/themes/test/electron-browser/fixtures/foo.js b/src/vs/workbench/contrib/themes/test/electron-browser/fixtures/foo.js similarity index 100% rename from src/vs/workbench/parts/themes/test/electron-browser/fixtures/foo.js rename to src/vs/workbench/contrib/themes/test/electron-browser/fixtures/foo.js diff --git a/src/vs/workbench/parts/themes/test/electron-browser/themes.test.contribution.ts b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts similarity index 86% rename from src/vs/workbench/parts/themes/test/electron-browser/themes.test.contribution.ts rename to src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts index eeade8e581..50af7229e3 100644 --- a/src/vs/workbench/parts/themes/test/electron-browser/themes.test.contribution.ts +++ b/src/vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution.ts @@ -3,25 +3,25 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as paths from 'vs/base/common/paths'; import { URI } from 'vs/base/common/uri'; import { IModeService } from 'vs/editor/common/services/modeService'; -import * as pfs from 'vs/base/node/pfs'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { toResource } from 'vs/workbench/common/editor'; -import { ITextMateService } from 'vs/workbench/services/textMate/electron-browser/textMateService'; +import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; import { IGrammar, StackElement } from 'vscode-textmate'; import { TokenizationRegistry, TokenMetadata } from 'vs/editor/common/modes'; -import { ThemeRule, findMatchingThemeRule } from 'vs/workbench/services/textMate/electron-browser/TMHelper'; +import { ThemeRule, findMatchingThemeRule } from 'vs/workbench/services/textMate/common/TMHelper'; import { Color } from 'vs/base/common/color'; +import { IFileService } from 'vs/platform/files/common/files'; +import { basename } from 'vs/base/common/resources'; interface IToken { c: string; t: string; - r: { [themeName: string]: string; }; + r: { [themeName: string]: string | undefined; }; } interface IThemedToken { @@ -48,7 +48,7 @@ class ThemeDocument { for (let i = 0, len = this._theme.tokenColors.length; i < len; i++) { let rule = this._theme.tokenColors[i]; if (!rule.scope) { - this._defaultColor = rule.settings.foreground; + this._defaultColor = rule.settings.foreground!; } } } @@ -69,7 +69,7 @@ class ThemeDocument { return this._generateExplanation('default', color); } - let expected = Color.fromHex(matchingRule.settings.foreground); + let expected = Color.fromHex(matchingRule.settings.foreground!); if (!color.equals(expected)) { throw new Error(`[${this._theme.label}]: Unexpected color ${Color.Format.CSS.formatHexA(color)} for ${scopes}. Expected ${Color.Format.CSS.formatHexA(expected)} coming in from ${matchingRule.rawSelector}`); } @@ -78,7 +78,7 @@ class ThemeDocument { private _findMatchingThemeRule(scopes: string): ThemeRule { if (!this._cache[scopes]) { - this._cache[scopes] = findMatchingThemeRule(this._theme, scopes.split(' ')); + this._cache[scopes] = findMatchingThemeRule(this._theme, scopes.split(' '))!; } return this._cache[scopes]; } @@ -112,7 +112,7 @@ class Snapper { result[resultLen++] = { text: tokenText, - color: colorMap[color] + color: colorMap![color] }; } @@ -124,7 +124,8 @@ class Snapper { private _tokenize(grammar: IGrammar, lines: string[]): IToken[] { let state: StackElement | null = null; - let result: IToken[] = [], resultLen = 0; + let result: IToken[] = []; + let resultLen = 0; for (let i = 0, len = lines.length; i < len; i++) { let line = lines[i]; @@ -144,11 +145,11 @@ class Snapper { c: tokenText, t: tokenScopes, r: { - dark_plus: null, - light_plus: null, - dark_vs: null, - light_vs: null, - hc_black: null, + dark_plus: undefined, + light_plus: undefined, + dark_vs: undefined, + light_vs: undefined, + hc_black: undefined, } }; } @@ -177,16 +178,16 @@ class Snapper { let defaultThemes = themeDatas.filter(themeData => !!getThemeName(themeData.id)); for (let defaultTheme of defaultThemes) { let themeId = defaultTheme.id; - let success = await this.themeService.setColorTheme(themeId, null); + let success = await this.themeService.setColorTheme(themeId, undefined); if (success) { let themeName = getThemeName(themeId); - result[themeName] = { + result[themeName!] = { document: new ThemeDocument(this.themeService.getColorTheme()), tokens: this._themedTokenize(grammar, lines) }; } } - await this.themeService.setColorTheme(currentTheme.id, null); + await this.themeService.setColorTheme(currentTheme.id, undefined); return result; } @@ -214,7 +215,7 @@ class Snapper { public captureSyntaxTokens(fileName: string, content: string): Promise { const modeId = this.modeService.getModeIdByFilepathOrFirstLine(fileName); - return this.textMateService.createGrammar(modeId).then((grammar) => { + return this.textMateService.createGrammar(modeId!).then((grammar) => { let lines = content.split(/\r\n|\r|\n/); let result = this._tokenize(grammar, lines); @@ -229,18 +230,18 @@ class Snapper { CommandsRegistry.registerCommand('_workbench.captureSyntaxTokens', function (accessor: ServicesAccessor, resource: URI) { let process = (resource: URI) => { - let filePath = resource.fsPath; - let fileName = paths.basename(filePath); + let fileService = accessor.get(IFileService); + let fileName = basename(resource); let snapper = accessor.get(IInstantiationService).createInstance(Snapper); - return pfs.readFile(filePath).then(content => { - return snapper.captureSyntaxTokens(fileName, content.toString()); + return fileService.resolveContent(resource).then(content => { + return snapper.captureSyntaxTokens(fileName, content.value); }); }; if (!resource) { - let editorService = accessor.get(IEditorService); - let file = toResource(editorService.activeEditor, { filter: 'file' }); + const editorService = accessor.get(IEditorService); + const file = editorService.activeEditor ? toResource(editorService.activeEditor, { filter: 'file' }) : null; if (file) { process(file).then(result => { console.log(result); diff --git a/src/vs/workbench/parts/update/electron-browser/media/code-icon.svg b/src/vs/workbench/contrib/update/electron-browser/media/code-icon.svg similarity index 100% rename from src/vs/workbench/parts/update/electron-browser/media/code-icon.svg rename to src/vs/workbench/contrib/update/electron-browser/media/code-icon.svg diff --git a/src/vs/workbench/parts/update/electron-browser/media/markdown.css b/src/vs/workbench/contrib/update/electron-browser/media/markdown.css similarity index 100% rename from src/vs/workbench/parts/update/electron-browser/media/markdown.css rename to src/vs/workbench/contrib/update/electron-browser/media/markdown.css diff --git a/src/vs/workbench/parts/update/electron-browser/media/update.contribution.css b/src/vs/workbench/contrib/update/electron-browser/media/update.contribution.css similarity index 100% rename from src/vs/workbench/parts/update/electron-browser/media/update.contribution.css rename to src/vs/workbench/contrib/update/electron-browser/media/update.contribution.css diff --git a/src/vs/workbench/parts/update/electron-browser/media/update.svg b/src/vs/workbench/contrib/update/electron-browser/media/update.svg similarity index 100% rename from src/vs/workbench/parts/update/electron-browser/media/update.svg rename to src/vs/workbench/contrib/update/electron-browser/media/update.svg diff --git a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts similarity index 86% rename from src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts rename to src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts index b2aefbdbb4..524242c617 100644 --- a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/contrib/update/electron-browser/releaseNotesEditor.ts @@ -20,12 +20,13 @@ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IRequestService } from 'vs/platform/request/node/request'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { addGAParameters } from 'vs/platform/telemetry/node/telemetryNodeUtils'; -import { IWebviewEditorService } from 'vs/workbench/parts/webview/electron-browser/webviewEditorService'; +import { IWebviewEditorService } from 'vs/workbench/contrib/webview/electron-browser/webviewEditorService'; import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { WebviewEditorInput } from 'vs/workbench/parts/webview/electron-browser/webviewEditorInput'; +import { WebviewEditorInput } from 'vs/workbench/contrib/webview/electron-browser/webviewEditorInput'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; function renderBody( body: string, @@ -60,6 +61,7 @@ export class ReleaseNotesManager { @IRequestService private readonly _requestService: IRequestService, @ITelemetryService private readonly _telemetryService: ITelemetryService, @IEditorService private readonly _editorService: IEditorService, + @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IWebviewEditorService private readonly _webviewEditorService: IWebviewEditorService, @IExtensionService private readonly _extensionService: IExtensionService ) { @@ -87,7 +89,7 @@ export class ReleaseNotesManager { if (this._currentReleaseNotes) { this._currentReleaseNotes.setName(title); this._currentReleaseNotes.html = html; - this._webviewEditorService.revealWebview(this._currentReleaseNotes, activeControl ? activeControl.group : undefined, false); + this._webviewEditorService.revealWebview(this._currentReleaseNotes, activeControl ? activeControl.group : this._editorGroupService.activeGroup, false); } else { this._currentReleaseNotes = this._webviewEditorService.createWebview( 'releaseNotes', @@ -129,7 +131,7 @@ export class ReleaseNotesManager { return unassigned; } - return keybinding.getLabel(); + return keybinding.getLabel() || unassigned; }; const kbstyle = (match: string, kb: string) => { @@ -145,7 +147,7 @@ export class ReleaseNotesManager { return unassigned; } - return resolvedKeybindings[0].getLabel(); + return resolvedKeybindings[0].getLabel() || unassigned; }; return text @@ -157,7 +159,7 @@ export class ReleaseNotesManager { this._releaseNotesCache[version] = this._requestService.request({ url }, CancellationToken.None) .then(asText) .then(text => { - if (!/^#\s/.test(text)) { // release notes always starts with `#` followed by whitespace + if (!text || !/^#\s/.test(text)) { // release notes always starts with `#` followed by whitespace return Promise.reject(new Error('Invalid release notes')); } @@ -178,7 +180,7 @@ export class ReleaseNotesManager { private async renderBody(text: string) { const content = await this.renderContent(text); const colorMap = TokenizationRegistry.getColorMap(); - const css = generateTokensCSSForColorMap(colorMap); + const css = colorMap ? generateTokensCSSForColorMap(colorMap) : ''; const body = renderBody(content, css); return body; } @@ -189,14 +191,16 @@ export class ReleaseNotesManager { } private async getRenderer(text: string): Promise { - let result: Promise[] = []; + let result: Promise[] = []; const renderer = new marked.Renderer(); renderer.code = (_code, lang) => { const modeId = this._modeService.getModeIdForLanguageName(lang); - result.push(this._extensionService.whenInstalledExtensionsRegistered().then(_ => { - this._modeService.triggerMode(modeId); - return TokenizationRegistry.getPromise(modeId); - })); + if (modeId) { + result.push(this._extensionService.whenInstalledExtensionsRegistered().then(() => { + this._modeService.triggerMode(modeId); + return TokenizationRegistry.getPromise(modeId); + })); + } return ''; }; @@ -205,7 +209,7 @@ export class ReleaseNotesManager { renderer.code = (code, lang) => { const modeId = this._modeService.getModeIdForLanguageName(lang); - return `${tokenizeToString(code, TokenizationRegistry.get(modeId))}`; + return `${tokenizeToString(code, modeId ? TokenizationRegistry.get(modeId) : undefined)}`; }; return renderer; } diff --git a/src/vs/workbench/parts/update/electron-browser/update.contribution.ts b/src/vs/workbench/contrib/update/electron-browser/update.contribution.ts similarity index 86% rename from src/vs/workbench/parts/update/electron-browser/update.contribution.ts rename to src/vs/workbench/contrib/update/electron-browser/update.contribution.ts index b91003dab8..61546d19bb 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.contribution.ts +++ b/src/vs/workbench/contrib/update/electron-browser/update.contribution.ts @@ -11,7 +11,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr import { IGlobalActivityRegistry, GlobalActivityExtensions } from 'vs/workbench/common/activity'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution, Win3264BitContribution } from './update'; +import { ShowCurrentReleaseNotesAction, ProductContribution, UpdateContribution, Win3264BitContribution, Linux32BitContribution } from './update'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; const workbench = Registry.as(WorkbenchExtensions.Workbench); @@ -24,6 +24,13 @@ if (platform.isWindows) { } } +// TODO@ben remove me after a while +if (platform.isLinux) { + if (process.arch === 'ia32') { + workbench.registerWorkbenchContribution(Linux32BitContribution, LifecyclePhase.Restored); + } +} + Registry.as(GlobalActivityExtensions) .registerActivity(UpdateContribution); diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/contrib/update/electron-browser/update.ts similarity index 90% rename from src/vs/workbench/parts/update/electron-browser/update.ts rename to src/vs/workbench/contrib/update/electron-browser/update.ts index f2fe1baad3..fc1aa979d3 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/contrib/update/electron-browser/update.ts @@ -8,8 +8,8 @@ import severity from 'vs/base/common/severity'; import { IAction, Action } from 'vs/base/common/actions'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import pkg from 'vs/platform/node/package'; -import product from 'vs/platform/node/product'; +import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/node/product'; import { URI } from 'vs/base/common/uri'; import { IActivityService, NumberBadge, IBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -35,7 +35,7 @@ function showReleaseNotes(instantiationService: IInstantiationService, version: releaseNotesManager = instantiationService.createInstance(ReleaseNotesManager); } - return instantiationService.invokeFunction(accessor => releaseNotesManager.show(accessor, version)); + return instantiationService.invokeFunction(accessor => releaseNotesManager!.show(accessor, version)); } export class OpenLatestReleaseNotesInBrowserAction extends Action { @@ -43,7 +43,7 @@ export class OpenLatestReleaseNotesInBrowserAction extends Action { constructor( @IOpenerService private readonly openerService: IOpenerService ) { - super('update.openLatestReleaseNotes', nls.localize('releaseNotes', "Release Notes"), null, true); + super('update.openLatestReleaseNotes', nls.localize('releaseNotes', "Release Notes"), undefined, true); } run(): Promise { @@ -63,7 +63,7 @@ export abstract class AbstractShowReleaseNotesAction extends Action { private version: string, @IInstantiationService private readonly instantiationService: IInstantiationService ) { - super(id, label, null, true); + super(id, label, undefined, true); } run(): Promise { @@ -216,6 +216,53 @@ export class Win3264BitContribution implements IWorkbenchContribution { } } +export class Linux32BitContribution implements IWorkbenchContribution { + + private static readonly KEY = 'update/linux32-64bits'; + private static readonly URL = 'https://code.visualstudio.com/updates/v1_32#_linux-32-bit-support-ends-soon'; + private static readonly INSIDER_URL = 'https://github.com/Microsoft/vscode-docs/blob/vnext/release-notes/v1_32.md#linux-32-bit-support-ends-soon'; + + constructor( + @IStorageService storageService: IStorageService, + @INotificationService notificationService: INotificationService, + @IEnvironmentService environmentService: IEnvironmentService + ) { + if (environmentService.disableUpdates) { + return; + } + + const neverShowAgain = new NeverShowAgain(Linux32BitContribution.KEY, storageService); + + if (!neverShowAgain.shouldShow()) { + return; + } + + const url = product.quality === 'insider' + ? Linux32BitContribution.INSIDER_URL + : Linux32BitContribution.URL; + + const handle = notificationService.prompt( + severity.Info, + nls.localize('linux64bits', "{0} for 32-bit Linux will soon be discontinued. Please update to the 64-bit version.", product.nameShort, url), + [{ + label: nls.localize('learnmore', "Learn More"), + run: () => { + window.open(url); + } + }, + { + label: nls.localize('neveragain', "Don't Show Again"), + isSecondary: true, + run: () => { + neverShowAgain.action.run(handle); + neverShowAgain.action.dispose(); + } + }], + { sticky: true } + ); + } +} + class CommandAction extends Action { constructor( @@ -467,11 +514,11 @@ export class UpdateContribution implements IGlobalActivity { // if version != stored version, save version and date if (currentVersion !== lastKnownVersion) { - this.storageService.store('update/lastKnownVersion', currentVersion, StorageScope.GLOBAL); + this.storageService.store('update/lastKnownVersion', currentVersion!, StorageScope.GLOBAL); this.storageService.store('update/updateNotificationTime', currentMillis, StorageScope.GLOBAL); } - const updateNotificationMillis = this.storageService.getInteger('update/updateNotificationTime', StorageScope.GLOBAL, currentMillis); + const updateNotificationMillis = this.storageService.getNumber('update/updateNotificationTime', StorageScope.GLOBAL, currentMillis); const diffDays = (currentMillis - updateNotificationMillis) / (1000 * 60 * 60 * 24); return diffDays > 5; @@ -516,7 +563,7 @@ export class UpdateContribution implements IGlobalActivity { return new Action('update.checking', nls.localize('checkingForUpdates', "Checking For Updates..."), undefined, false); case StateType.AvailableForDownload: - return new Action('update.downloadNow', nls.localize('download now', "Download Now"), null, true, () => + return new Action('update.downloadNow', nls.localize('download now', "Download Now"), undefined, true, () => this.updateService.downloadUpdate()); case StateType.Downloading: @@ -530,7 +577,7 @@ export class UpdateContribution implements IGlobalActivity { return new Action('update.updating', nls.localize('installingUpdate', "Installing Update..."), undefined, false); case StateType.Ready: - return new Action('update.restart', nls.localize('restartToUpdate', "Restart to Update..."), undefined, true, () => + return new Action('update.restart', nls.localize('restartToUpdate', "Restart to Update"), undefined, true, () => this.updateService.quitAndInstall()); } } diff --git a/src/vs/workbench/parts/url/electron-browser/url.contribution.ts b/src/vs/workbench/contrib/url/common/url.contribution.ts similarity index 100% rename from src/vs/workbench/parts/url/electron-browser/url.contribution.ts rename to src/vs/workbench/contrib/url/common/url.contribution.ts diff --git a/src/vs/workbench/parts/watermark/electron-browser/watermark.css b/src/vs/workbench/contrib/watermark/browser/watermark.css similarity index 100% rename from src/vs/workbench/parts/watermark/electron-browser/watermark.css rename to src/vs/workbench/contrib/watermark/browser/watermark.css diff --git a/src/vs/workbench/parts/watermark/electron-browser/watermark.ts b/src/vs/workbench/contrib/watermark/browser/watermark.ts similarity index 80% rename from src/vs/workbench/parts/watermark/electron-browser/watermark.ts rename to src/vs/workbench/contrib/watermark/browser/watermark.ts index 2af6b59e17..64a7364923 100644 --- a/src/vs/workbench/parts/watermark/electron-browser/watermark.ts +++ b/src/vs/workbench/contrib/watermark/browser/watermark.ts @@ -15,17 +15,19 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { OpenRecentAction } from 'vs/workbench/electron-browser/actions/windowActions'; -import { GlobalNewUntitledFileAction } from 'vs/workbench/parts/files/electron-browser/fileActions'; +import { GlobalNewUntitledFileAction } from 'vs/workbench/contrib/files/browser/fileActions'; import { OpenFolderAction, OpenFileFolderAction, OpenFileAction } from 'vs/workbench/browser/actions/workspaceActions'; -import { ShowAllCommandsAction } from 'vs/workbench/parts/quickopen/browser/commandsHandler'; -import { Parts, IPartService, IDimension } from 'vs/workbench/services/part/common/partService'; -import { StartAction } from 'vs/workbench/parts/debug/browser/debugActions'; -import { FindInFilesActionId } from 'vs/workbench/parts/search/common/constants'; +import { ShowAllCommandsAction } from 'vs/workbench/contrib/quickopen/browser/commandsHandler'; +import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; +import { StartAction } from 'vs/workbench/contrib/debug/browser/debugActions'; +import { FindInFilesActionId } from 'vs/workbench/contrib/search/common/constants'; import { QUICKOPEN_ACTION_ID } from 'vs/workbench/browser/parts/quickopen/quickopen'; -import { TERMINAL_COMMAND_ID } from 'vs/workbench/parts/terminal/common/terminalCommands'; +import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminalCommands'; import * as dom from 'vs/base/browser/dom'; import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IDimension } from 'vs/platform/layout/browser/layoutService'; const $ = dom.$; @@ -75,7 +77,7 @@ const openFileOrFolderMacOnly: WatermarkEntry = { }; const openRecent: WatermarkEntry = { text: nls.localize('watermark.openRecent', "Open Recent"), - id: OpenRecentAction.ID + id: 'workbench.action.openRecent' }; const newUntitledFile: WatermarkEntry = { text: nls.localize('watermark.newUntitledFile', "New Untitled File"), @@ -121,10 +123,11 @@ export class WatermarkContribution implements IWorkbenchContribution { constructor( @ILifecycleService lifecycleService: ILifecycleService, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IKeybindingService private readonly keybindingService: IKeybindingService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IConfigurationService private readonly configurationService: IConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService, + @IEditorGroupsService private readonly editorGroupsService: IEditorGroupsService ) { this.workbenchState = contextService.getWorkbenchState(); @@ -157,14 +160,15 @@ export class WatermarkContribution implements IWorkbenchContribution { } private create(): void { - const container = this.partService.getContainer(Parts.EDITOR_PART); + const container = this.layoutService.getContainer(Parts.EDITOR_PART); container.classList.add('has-watermark'); this.watermark = $('.watermark'); const box = dom.append(this.watermark, $('.watermark-box')); const folder = this.workbenchState !== WorkbenchState.EMPTY; const selected = folder ? folderEntries : noFolderEntries - .filter(entry => !('mac' in entry) || entry.mac === isMacintosh); + .filter(entry => !('mac' in entry) || entry.mac === isMacintosh) + .filter(entry => !!CommandsRegistry.getCommand(entry.id)); const update = () => { dom.clearNode(box); selected.map(entry => { @@ -180,15 +184,23 @@ export class WatermarkContribution implements IWorkbenchContribution { update(); dom.prepend(container.firstElementChild as HTMLElement, this.watermark); this.toDispose.push(this.keybindingService.onDidUpdateKeybindings(update)); - this.toDispose.push(this.partService.onEditorLayout(({ height }: IDimension) => { - container.classList[height <= 478 ? 'add' : 'remove']('max-height-478px'); - })); + this.toDispose.push(this.editorGroupsService.onDidLayout(dimension => this.handleEditorPartSize(container, dimension))); + this.handleEditorPartSize(container, this.editorGroupsService.dimension); + } + + private handleEditorPartSize(container: HTMLElement, dimension: IDimension): void { + if (dimension.height <= 478) { + dom.addClass(container, 'max-height-478px'); + } else { + dom.removeClass(container, 'max-height-478px'); + } } private destroy(): void { if (this.watermark) { this.watermark.remove(); - this.partService.getContainer(Parts.EDITOR_PART).classList.remove('has-watermark'); + const container = this.layoutService.getContainer(Parts.EDITOR_PART); + container.classList.remove('has-watermark'); this.dispose(); } } diff --git a/src/vs/workbench/parts/webview/electron-browser/webview-pre.js b/src/vs/workbench/contrib/webview/electron-browser/webview-pre.js similarity index 91% rename from src/vs/workbench/parts/webview/electron-browser/webview-pre.js rename to src/vs/workbench/contrib/webview/electron-browser/webview-pre.js index 799691d2ca..a704462b01 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webview-pre.js +++ b/src/vs/workbench/contrib/webview/electron-browser/webview-pre.js @@ -36,7 +36,7 @@ * @param {() => void} handlers.onFocus * @param {() => void} handlers.onBlur */ - const trackFocus = ({onFocus, onBlur}) => { + const trackFocus = ({ onFocus, onBlur }) => { const interval = 50; let isFocused = document.hasFocus(); setInterval(() => { @@ -57,7 +57,6 @@ let firstLoad = true; let loadTimeout; let pendingMessages = []; - let enableWrappedPostMessage = false; let isInDevelopmentMode = false; const initData = { @@ -65,14 +64,22 @@ }; /** + * @param {HTMLDocument} document * @param {HTMLElement} body */ - const styleBody = (body) => { + const applyStyles = (document, body) => { if (!body) { return; } + body.classList.remove('vscode-light', 'vscode-dark', 'vscode-high-contrast'); body.classList.add(initData.activeTheme); + + if (initData.styles) { + for (const variable of Object.keys(initData.styles)) { + document.documentElement.style.setProperty(`--${variable}`, initData.styles[variable]); + } + } }; const getActiveFrame = () => { @@ -169,11 +176,7 @@ return; } - styleBody(target.contentDocument.body); - - Object.keys(variables).forEach((variable) => { - target.contentDocument.documentElement.style.setProperty(`--${variable}`, variables[variable]); - }); + applyStyles(target.contentDocument, target.contentDocument.body); }); // propagate focus @@ -187,18 +190,15 @@ // update iframe-contents ipcRenderer.on('content', (_event, data) => { const options = data.options; - enableWrappedPostMessage = options && options.enableWrappedPostMessage; - if (enableWrappedPostMessage) { - registerVscodeResourceScheme(); - } + registerVscodeResourceScheme(); const text = data.contents; const newDocument = new DOMParser().parseFromString(text, 'text/html'); newDocument.querySelectorAll('a').forEach(a => { if (!a.title) { - a.title = a.href; + a.title = a.getAttribute('href'); } }); @@ -210,7 +210,7 @@ } // apply default script - if (enableWrappedPostMessage && options.allowScripts) { + if (options.allowScripts) { const defaultScript = newDocument.createElement('script'); defaultScript.textContent = ` const acquireVsCodeApi = (function() { @@ -244,16 +244,16 @@ delete window.frameElement; `; - newDocument.head.prepend(defaultScript, newDocument.head.firstChild); + newDocument.head.prepend(defaultScript); } // apply default styles const defaultStyles = newDocument.createElement('style'); defaultStyles.id = '_defaultStyles'; - defaultStyles.innerHTML = getDefaultCss(initData.styles); + defaultStyles.innerHTML = defaultCssRules; newDocument.head.prepend(defaultStyles); - styleBody(newDocument.body); + applyStyles(newDocument, newDocument.body); const frame = getActiveFrame(); const wasFirstLoad = firstLoad; @@ -321,6 +321,8 @@ if (oldActiveFrame) { document.body.removeChild(oldActiveFrame); } + // Styles may have changed since we created the element. Make sure we re-style + applyStyles(newFrame.contentDocument, newFrame.contentDocument.body); newFrame.setAttribute('id', 'active-frame'); newFrame.style.visibility = 'visible'; newFrame.contentWindow.focus(); @@ -395,19 +397,6 @@ ipcRenderer.sendToHost('webview-ready', process.pid); }); - /** - * @param {{ [variable: string]: string }} styles - */ - function getDefaultCss(styles) { - const vars = Object.keys(styles || {}).map(variable => { - return `--${variable}: ${styles[variable].replace(/[^\#\"\'\,\. a-z0-9\-\(\)]/gi, '')};`; - }); - return ` - :root { ${vars.join('\n')} } - ${defaultCssRules} - `; - } - const defaultCssRules = ` body { background-color: var(--vscode-editor-background); diff --git a/src/vs/workbench/parts/webview/electron-browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts similarity index 94% rename from src/vs/workbench/parts/webview/electron-browser/webview.contribution.ts rename to src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts index dfd67523b4..14e37ec9d3 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webview.contribution.ts @@ -14,13 +14,12 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { EditorDescriptor, Extensions as EditorExtensions, IEditorRegistry } from 'vs/workbench/browser/editor'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; -import { WebviewEditorInputFactory } from 'vs/workbench/parts/webview/electron-browser/webviewEditorInputFactory'; -import { KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE } from './baseWebviewEditor'; +import { WebviewEditorInputFactory } from 'vs/workbench/contrib/webview/electron-browser/webviewEditorInputFactory'; import { HideWebViewEditorFindCommand, OpenWebviewDeveloperToolsAction, ReloadWebviewAction, ShowWebViewEditorFindWidgetCommand, SelectAllWebviewEditorCommand, CopyWebviewEditorCommand, PasteWebviewEditorCommand, CutWebviewEditorCommand, UndoWebviewEditorCommand, RedoWebviewEditorCommand } from './webviewCommands'; -import { WebviewEditor } from './webviewEditor'; +import { WebviewEditor, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE } from './webviewEditor'; import { WebviewEditorInput } from './webviewEditorInput'; import { IWebviewEditorService, WebviewEditorService } from './webviewEditorService'; -import { InputFocusedContextKey } from 'vs/platform/workbench/common/contextkeys'; +import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys'; import { isMacintosh } from 'vs/base/common/platform'; (Registry.as(EditorExtensions.Editors)).registerEditor(new EditorDescriptor( diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewCommands.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts similarity index 92% rename from src/vs/workbench/parts/webview/electron-browser/webviewCommands.ts rename to src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts index bb7b5a7a63..c96c8969ba 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewCommands.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewCommands.ts @@ -8,7 +8,7 @@ import { Command } from 'vs/editor/browser/editorExtensions'; import * as nls from 'vs/nls'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { BaseWebviewEditor } from './baseWebviewEditor'; +import { WebviewEditor } from 'vs/workbench/contrib/webview/electron-browser/webviewEditor'; export class ShowWebViewEditorFindWidgetCommand extends Command { public static readonly ID = 'editor.action.webvieweditor.showFind'; @@ -143,13 +143,13 @@ export class ReloadWebviewAction extends Action { private getVisibleWebviews() { return this.editorService.visibleControls - .filter(control => control && (control as BaseWebviewEditor).isWebviewEditor) - .map(control => control as BaseWebviewEditor); + .filter(control => control && (control as WebviewEditor).isWebviewEditor) + .map(control => control as WebviewEditor); } } -function getActiveWebviewEditor(accessor: ServicesAccessor): BaseWebviewEditor | null { +function getActiveWebviewEditor(accessor: ServicesAccessor): WebviewEditor | null { const editorService = accessor.get(IEditorService); - const activeControl = editorService.activeControl as BaseWebviewEditor; + const activeControl = editorService.activeControl as WebviewEditor; return activeControl.isWebviewEditor ? activeControl : null; } \ No newline at end of file diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewEditor.ts similarity index 68% rename from src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts rename to src/vs/workbench/contrib/webview/electron-browser/webviewEditor.ts index 5d0ec47f47..38a1e6c889 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewEditor.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewEditor.ts @@ -6,29 +6,36 @@ import * as DOM from 'vs/base/browser/dom'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IStorageService } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { EditorOptions } from 'vs/workbench/common/editor'; -import { WebviewEditorInput } from 'vs/workbench/parts/webview/electron-browser/webviewEditorInput'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; -import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; -import { BaseWebviewEditor, KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE } from './baseWebviewEditor'; -import { WebviewElement } from './webviewElement'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { EditorOptions } from 'vs/workbench/common/editor'; +import { WebviewEditorInput } from 'vs/workbench/contrib/webview/electron-browser/webviewEditorInput'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; +import { WebviewElement } from './webviewElement'; -export class WebviewEditor extends BaseWebviewEditor { +/** A context key that is set when the find widget in a webview is visible. */ +export const KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE = new RawContextKey('webviewFindWidgetVisible', false); + + +export class WebviewEditor extends BaseEditor { + + protected _webview: WebviewElement | undefined; + protected findWidgetVisible: IContextKey; public static readonly ID = 'WebviewEditor'; private _editorFrame: HTMLElement; - private _content: HTMLElement; + private _content?: HTMLElement; private _webviewContent: HTMLElement | undefined; private _webviewFocusTrackerDisposables: IDisposable[] = []; @@ -43,14 +50,17 @@ export class WebviewEditor extends BaseWebviewEditor { @ITelemetryService telemetryService: ITelemetryService, @IThemeService themeService: IThemeService, @IContextKeyService private _contextKeyService: IContextKeyService, - @IPartService private readonly _partService: IPartService, + @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @IInstantiationService private readonly _instantiationService: IInstantiationService, @IEditorService private readonly _editorService: IEditorService, @IWindowService private readonly _windowService: IWindowService, @IStorageService storageService: IStorageService ) { - super(WebviewEditor.ID, telemetryService, themeService, _contextKeyService, storageService); + super(WebviewEditor.ID, telemetryService, themeService, storageService); + if (_contextKeyService) { + this.findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(_contextKeyService); + } } protected createEditor(parent: HTMLElement): void { @@ -73,27 +83,6 @@ export class WebviewEditor extends BaseWebviewEditor { } } - public layout(dimension: DOM.Dimension): void { - if (this._webview) { - this.doUpdateContainer(); - } - super.layout(dimension); - } - - public focus() { - super.focus(); - if (this._onFocusWindowHandler) { - return; - } - - // Make sure we restore focus when switching back to a VS Code window - this._onFocusWindowHandler = this._windowService.onDidChangeFocus(focused => { - if (focused && this._editorService.activeControl === this) { - this.focus(); - } - }); - } - public dispose(): void { this.pendingMessages = []; @@ -122,6 +111,78 @@ export class WebviewEditor extends BaseWebviewEditor { this.pendingMessages.push(data); } } + public showFind() { + if (this._webview) { + this._webview.showFind(); + this.findWidgetVisible.set(true); + } + } + + public hideFind() { + this.findWidgetVisible.reset(); + if (this._webview) { + this._webview.hideFind(); + } + } + + public get isWebviewEditor() { + return true; + } + + public reload() { + this.withWebviewElement(webview => webview.reload()); + } + + public layout(_dimension: DOM.Dimension): void { + this.withWebviewElement(webview => { + this.doUpdateContainer(); + webview.layout(); + }); + } + + public focus(): void { + super.focus(); + if (!this._onFocusWindowHandler) { + + // Make sure we restore focus when switching back to a VS Code window + this._onFocusWindowHandler = this._windowService.onDidChangeFocus(focused => { + if (focused && this._editorService.activeControl === this) { + this.focus(); + } + }); + } + this.withWebviewElement(webview => webview.focus()); + } + + public selectAll(): void { + this.withWebviewElement(webview => webview.selectAll()); + } + + public copy(): void { + this.withWebviewElement(webview => webview.copy()); + } + + public paste(): void { + this.withWebviewElement(webview => webview.paste()); + } + + public cut(): void { + this.withWebviewElement(webview => webview.cut()); + } + + public undo(): void { + this.withWebviewElement(webview => webview.undo()); + } + + public redo(): void { + this.withWebviewElement(webview => webview.redo()); + } + + private withWebviewElement(f: (element: WebviewElement) => void): void { + if (this._webview) { + f(this._webview); + } + } protected setEditorVisible(visible: boolean, group: IEditorGroup): void { if (this.input && this.input instanceof WebviewEditorInput) { @@ -171,8 +232,9 @@ export class WebviewEditor extends BaseWebviewEditor { if (token.isCancellationRequested) { return; } - - input.updateGroup(this.group.id); + if (this.group) { + input.updateGroup(this.group.id); + } this.updateWebview(input); }); } @@ -182,12 +244,8 @@ export class WebviewEditor extends BaseWebviewEditor { input.claimWebview(this); webview.update(input.html, { allowScripts: input.options.enableScripts, - allowSvgs: true, - enableWrappedPostMessage: true, - useSameOriginForRoot: false, localResourceRoots: input.options.localResourceRoots || this.getDefaultLocalResourceRoots(), - extensionLocation: input.extensionLocation - }, input.options.retainContextWhenHidden); + }, !!input.options.retainContextWhenHidden); if (this._webviewContent) { this._webviewContent.style.visibility = 'visible'; @@ -198,8 +256,9 @@ export class WebviewEditor extends BaseWebviewEditor { private getDefaultLocalResourceRoots(): URI[] { const rootPaths = this._contextService.getWorkspace().folders.map(x => x.uri); - if ((this.input as WebviewEditorInput).extensionLocation) { - rootPaths.push((this.input as WebviewEditorInput).extensionLocation); + const extensionLocation = (this.input as WebviewEditorInput).extensionLocation; + if (extensionLocation) { + rootPaths.push(extensionLocation); } return rootPaths; } @@ -220,12 +279,13 @@ export class WebviewEditor extends BaseWebviewEditor { } this._webview = this._instantiationService.createInstance(WebviewElement, - this._partService.getContainer(Parts.EDITOR_PART), + this._layoutService.getContainer(Parts.EDITOR_PART), { - enableWrappedPostMessage: true, - useSameOriginForRoot: false, - extensionLocation: input.extensionLocation - }); + allowSvgs: true, + extensionLocation: input.extensionLocation, + enableFindWidget: input.options.enableFindWidget + }, + {}); this._webview.mountTo(this._webviewContent); input.webview = this._webview; @@ -235,7 +295,7 @@ export class WebviewEditor extends BaseWebviewEditor { this._webview.state = input.webviewState; - this._content.setAttribute('aria-flowto', this._webviewContent.id); + this._content!.setAttribute('aria-flowto', this._webviewContent.id); this.doUpdateContainer(); } @@ -254,11 +314,11 @@ export class WebviewEditor extends BaseWebviewEditor { this._webviewFocusTrackerDisposables = dispose(this._webviewFocusTrackerDisposables); // Track focus in webview content - const webviewContentFocusTracker = DOM.trackFocus(this._webviewContent); + const webviewContentFocusTracker = DOM.trackFocus(this._webviewContent!); this._webviewFocusTrackerDisposables.push(webviewContentFocusTracker); this._webviewFocusTrackerDisposables.push(webviewContentFocusTracker.onDidFocus(() => this._onDidFocusWebview.fire())); // Track focus in webview element - this._webviewFocusTrackerDisposables.push(this._webview.onDidFocus(() => this._onDidFocusWebview.fire())); + this._webviewFocusTrackerDisposables.push(this._webview!.onDidFocus(() => this._onDidFocusWebview.fire())); } } diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewEditorInput.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInput.ts similarity index 86% rename from src/vs/workbench/parts/webview/electron-browser/webviewEditorInput.ts rename to src/vs/workbench/contrib/webview/electron-browser/webviewEditorInput.ts index 31cc33cc79..88af525f84 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewEditorInput.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInput.ts @@ -8,9 +8,9 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { EditorInput, EditorModel, GroupIdentifier, IEditorInput } from 'vs/workbench/common/editor'; -import { IPartService, Parts } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import * as vscode from 'vscode'; -import { WebviewEvents, WebviewInputOptions, WebviewReviver } from './webviewEditorService'; +import { WebviewEvents, WebviewInputOptions } from './webviewEditorService'; import { WebviewElement } from './webviewElement'; export class WebviewEditorInput extends EditorInput { @@ -56,7 +56,7 @@ export class WebviewEditorInput extends EditorInput { private _html: string = ''; private _currentWebviewHtml: string = ''; public _events: WebviewEvents | undefined; - private _container: HTMLElement; + private _container?: HTMLElement; private _webview: WebviewElement | undefined; private _webviewOwner: any; private _webviewDisposables: IDisposable[] = []; @@ -64,8 +64,6 @@ export class WebviewEditorInput extends EditorInput { private _scrollYPercentage: number = 0; private _state: any; - private _revived: boolean = false; - public readonly extensionLocation: URI | undefined; private readonly _id: number; @@ -77,8 +75,7 @@ export class WebviewEditorInput extends EditorInput { state: any, events: WebviewEvents, extensionLocation: URI | undefined, - public readonly reviver: WebviewReviver | undefined, - @IPartService private readonly _partService: IPartService, + @IWorkbenchLayoutService private readonly _layoutService: IWorkbenchLayoutService, ) { super(); @@ -139,7 +136,7 @@ export class WebviewEditorInput extends EditorInput { return this.getName(); } - public getDescription(): string { + public getDescription() { return null; } @@ -207,19 +204,12 @@ export class WebviewEditorInput extends EditorInput { if (this._webview) { this._webview.options = { allowScripts: this._options.enableScripts, - allowSvgs: true, - enableWrappedPostMessage: true, - useSameOriginForRoot: false, localResourceRoots: this._options.localResourceRoots }; } } public resolve(): Promise { - if (this.reviver && !this._revived) { - this._revived = true; - return this.reviver.reviveWebview(this).then(() => new EditorModel()); - } return Promise.resolve(new EditorModel()); } @@ -231,7 +221,8 @@ export class WebviewEditorInput extends EditorInput { if (!this._container) { this._container = document.createElement('div'); this._container.id = `webview-${this._id}`; - this._partService.getContainer(Parts.EDITOR_PART).appendChild(this._container); + const part = this._layoutService.getContainer(Parts.EDITOR_PART); + part.appendChild(this._container); } return this._container; } @@ -240,10 +231,13 @@ export class WebviewEditorInput extends EditorInput { return this._webview; } - public set webview(value: WebviewElement) { + public set webview(value: WebviewElement | undefined) { this._webviewDisposables = dispose(this._webviewDisposables); this._webview = value; + if (!this._webview) { + return; + } this._webview.onDidClickLink(link => { if (this._events && this._events.onDidClickLink) { @@ -307,3 +301,30 @@ export class WebviewEditorInput extends EditorInput { this._group = group; } } + + +export class RevivedWebviewEditorInput extends WebviewEditorInput { + private _revived: boolean = false; + + constructor( + viewType: string, + id: number | undefined, + name: string, + options: WebviewInputOptions, + state: any, + events: WebviewEvents, + extensionLocation: URI | undefined, + public readonly reviver: (input: WebviewEditorInput) => Promise, + @IWorkbenchLayoutService partService: IWorkbenchLayoutService, + ) { + super(viewType, id, name, options, state, events, extensionLocation, partService); + } + + public async resolve(): Promise { + if (!this._revived) { + this._revived = true; + await this.reviver(this); + } + return super.resolve(); + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewEditorInputFactory.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInputFactory.ts similarity index 90% rename from src/vs/workbench/parts/webview/electron-browser/webviewEditorInputFactory.ts rename to src/vs/workbench/contrib/webview/electron-browser/webviewEditorInputFactory.ts index d310fbc6e7..61e39dd74a 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewEditorInputFactory.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorInputFactory.ts @@ -19,9 +19,10 @@ interface SerializedWebview { readonly id: number; readonly title: string; readonly options: WebviewInputOptions; - readonly extensionLocation: string | UriComponents; + readonly extensionLocation: string | UriComponents | undefined; readonly state: any; readonly iconPath: SerializedIconPath | undefined; + readonly group?: number; } export class WebviewEditorInputFactory implements IEditorInputFactory { @@ -34,14 +35,8 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { public serialize( input: WebviewEditorInput - ): string { - // Has no state, don't revive - if (!input.state) { - return null; - } - - // Only attempt revival if we may have a reviver - if (!this._webviewService.canRevive(input) && !input.reviver) { + ): string | null { + if (!this._webviewService.shouldPersist(input)) { return null; } @@ -53,6 +48,7 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { extensionLocation: input.extensionLocation, state: input.state, iconPath: input.iconPath ? { light: input.iconPath.light, dark: input.iconPath.dark, } : undefined, + group: input.group }; try { @@ -69,7 +65,7 @@ export class WebviewEditorInputFactory implements IEditorInputFactory { const data: SerializedWebview = JSON.parse(serializedEditorInput); const extensionLocation = reviveUri(data.extensionLocation); const iconPath = reviveIconPath(data.iconPath); - return this._webviewService.reviveWebview(data.viewType, data.id, data.title, iconPath, data.state, data.options, extensionLocation); + return this._webviewService.reviveWebview(data.viewType, data.id, data.title, iconPath, data.state, data.options, extensionLocation, data.group); } } function reviveIconPath(data: SerializedIconPath | undefined) { diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewEditorService.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorService.ts similarity index 60% rename from src/vs/workbench/parts/webview/electron-browser/webviewEditorService.ts rename to src/vs/workbench/contrib/webview/electron-browser/webviewEditorService.ts index bb18b6731c..ff7e4464a1 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewEditorService.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewEditorService.ts @@ -3,15 +3,16 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorService, ACTIVE_GROUP_TYPE, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; -import * as vscode from 'vscode'; -import { WebviewEditorInput } from './webviewEditorInput'; -import { GroupIdentifier } from 'vs/workbench/common/editor'; import { equals } from 'vs/base/common/arrays'; +import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { values } from 'vs/base/common/map'; +import { URI } from 'vs/base/common/uri'; +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { GroupIdentifier } from 'vs/workbench/common/editor'; +import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ACTIVE_GROUP_TYPE, IEditorService, SIDE_GROUP_TYPE } from 'vs/workbench/services/editor/common/editorService'; +import * as vscode from 'vscode'; +import { RevivedWebviewEditorInput, WebviewEditorInput } from './webviewEditorInput'; export const IWebviewEditorService = createDecorator('webviewEditorService'); @@ -28,7 +29,7 @@ export interface IWebviewEditorService { title: string, showOptions: ICreateWebViewShowOptions, options: WebviewInputOptions, - extensionLocation: URI, + extensionLocation: URI | undefined, events: WebviewEvents ): WebviewEditorInput; @@ -39,7 +40,8 @@ export interface IWebviewEditorService { iconPath: { light: URI, dark: URI } | undefined, state: any, options: WebviewInputOptions, - extensionLocation: URI + extensionLocation: URI | undefined, + group: number | undefined ): WebviewEditorInput; revealWebview( @@ -49,11 +51,10 @@ export interface IWebviewEditorService { ): void; registerReviver( - viewType: string, reviver: WebviewReviver ): IDisposable; - canRevive( + shouldPersist( input: WebviewEditorInput ): boolean; } @@ -87,11 +88,35 @@ export function areWebviewInputOptionsEqual(a: WebviewInputOptions, b: WebviewIn && (a.localResourceRoots === b.localResourceRoots || (Array.isArray(a.localResourceRoots) && Array.isArray(b.localResourceRoots) && equals(a.localResourceRoots, b.localResourceRoots, (a, b) => a.toString() === b.toString()))); } +function canRevive(reviver: WebviewReviver, webview: WebviewEditorInput): boolean { + if (webview.isDisposed()) { + return false; + } + return reviver.canRevive(webview); +} + +class RevivalPool { + private _awaitingRevival: Array<{ input: WebviewEditorInput, resolve: () => void }> = []; + + public add(input: WebviewEditorInput, resolve: () => void) { + this._awaitingRevival.push({ input, resolve }); + } + + public reviveFor(reviver: WebviewReviver) { + const toRevive = this._awaitingRevival.filter(({ input }) => canRevive(reviver, input)); + this._awaitingRevival = this._awaitingRevival.filter(({ input }) => !canRevive(reviver, input)); + + for (const { input, resolve } of toRevive) { + reviver.reviveWebview(input).then(resolve); + } + } +} + export class WebviewEditorService implements IWebviewEditorService { _serviceBrand: any; - private readonly _revivers = new Map(); - private _awaitingRevival: { input: WebviewEditorInput, resolve: (x: any) => void }[] = []; + private readonly _revivers = new Set(); + private readonly _revivalPool = new RevivalPool(); constructor( @IEditorService private readonly _editorService: IEditorService, @@ -99,12 +124,12 @@ export class WebviewEditorService implements IWebviewEditorService { @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, ) { } - createWebview( + public createWebview( viewType: string, title: string, showOptions: ICreateWebViewShowOptions, options: vscode.WebviewOptions, - extensionLocation: URI, + extensionLocation: URI | undefined, events: WebviewEvents ): WebviewEditorInput { const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, viewType, undefined, title, options, {}, events, extensionLocation, undefined); @@ -112,7 +137,7 @@ export class WebviewEditorService implements IWebviewEditorService { return webviewInput; } - revealWebview( + public revealWebview( webview: WebviewEditorInput, group: IEditorGroup, preserveFocus: boolean @@ -120,82 +145,75 @@ export class WebviewEditorService implements IWebviewEditorService { if (webview.group === group.id) { this._editorService.openEditor(webview, { preserveFocus }, webview.group); } else { - this._editorGroupService.getGroup(webview.group).moveEditor(webview, group, { preserveFocus }); + const groupView = this._editorGroupService.getGroup(webview.group!); + if (groupView) { + groupView.moveEditor(webview, group, { preserveFocus }); + } } } - reviveWebview( + public reviveWebview( viewType: string, id: number, title: string, iconPath: { light: URI, dark: URI } | undefined, state: any, options: WebviewInputOptions, - extensionLocation: URI + extensionLocation: URI, + group: number | undefined, ): WebviewEditorInput { - const webviewInput = this._instantiationService.createInstance(WebviewEditorInput, viewType, id, title, options, state, {}, extensionLocation, { - canRevive: (_webview) => { - return true; - }, - reviveWebview: (webview: WebviewEditorInput): Promise => { - return this.tryRevive(webview).then(didRevive => { - if (didRevive) { - return Promise.resolve(undefined); - } - - // A reviver may not be registered yet. Put into queue and resolve promise when we can revive - let resolve: (value: void) => void; - const promise = new Promise(r => { resolve = r; }); - this._awaitingRevival.push({ input: webview, resolve }); - return promise; - }); + const webviewInput = this._instantiationService.createInstance(RevivedWebviewEditorInput, viewType, id, title, options, state, {}, extensionLocation, async (webview: WebviewEditorInput): Promise => { + const didRevive = await this.tryRevive(webview); + if (didRevive) { + return Promise.resolve(undefined); } + + // A reviver may not be registered yet. Put into pool and resolve promise when we can revive + let resolve: () => void; + const promise = new Promise(r => { resolve = r; }); + this._revivalPool.add(webview, resolve!); + return promise; }); webviewInput.iconPath = iconPath; + if (typeof group === 'number') { + webviewInput.updateGroup(group); + } return webviewInput; } - registerReviver( - viewType: string, + public registerReviver( reviver: WebviewReviver ): IDisposable { - if (this._revivers.has(viewType)) { - this._revivers.get(viewType).push(reviver); - } else { - this._revivers.set(viewType, [reviver]); - } - - - // Resolve any pending views - const toRevive = this._awaitingRevival.filter(x => x.input.viewType === viewType); - this._awaitingRevival = this._awaitingRevival.filter(x => x.input.viewType !== viewType); - - for (const input of toRevive) { - reviver.reviveWebview(input.input).then(() => input.resolve(undefined)); - } + this._revivers.add(reviver); + this._revivalPool.reviveFor(reviver); return toDisposable(() => { - this._revivers.delete(viewType); + this._revivers.delete(reviver); }); } - canRevive( + public shouldPersist( webview: WebviewEditorInput ): boolean { - const viewType = webview.viewType; - return this._revivers.has(viewType) && this._revivers.get(viewType).some(reviver => reviver.canRevive(webview)); + // Has no state, don't persist + if (!webview.state) { + return false; + } + + if (values(this._revivers).some(reviver => canRevive(reviver, webview))) { + return true; + } + + // Revived webviews may not have an actively registered reviver but we still want to presist them + // since a reviver should exist when it is actually needed. + return !(webview instanceof RevivedWebviewEditorInput); } private async tryRevive( webview: WebviewEditorInput ): Promise { - const revivers = this._revivers.get(webview.viewType); - if (!revivers) { - return false; - } - - for (const reviver of revivers) { - if (reviver.canRevive(webview)) { + for (const reviver of values(this._revivers)) { + if (canRevive(reviver, webview)) { await reviver.reviveWebview(webview); return true; } diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts similarity index 78% rename from src/vs/workbench/parts/webview/electron-browser/webviewElement.ts rename to src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index 4b85f9a022..94e20d647e 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -12,22 +12,22 @@ import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; import { DARK, ITheme, IThemeService, LIGHT } from 'vs/platform/theme/common/themeService'; -import { registerFileProtocol, WebviewProtocol } from 'vs/workbench/parts/webview/electron-browser/webviewProtocols'; +import { registerFileProtocol, WebviewProtocol } from 'vs/workbench/contrib/webview/electron-browser/webviewProtocols'; import { areWebviewInputOptionsEqual } from './webviewEditorService'; import { WebviewFindWidget } from './webviewFindWidget'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { endsWith } from 'vs/base/common/strings'; import { isMacintosh } from 'vs/base/common/platform'; export interface WebviewOptions { - readonly allowScripts?: boolean; readonly allowSvgs?: boolean; - readonly svgWhiteList?: string[]; - readonly enableWrappedPostMessage?: boolean; - readonly useSameOriginForRoot?: boolean; - readonly localResourceRoots?: ReadonlyArray; readonly extensionLocation?: URI; + readonly enableFindWidget?: boolean; +} + +export interface WebviewContentOptions { + readonly allowScripts?: boolean; + readonly svgWhiteList?: string[]; + readonly localResourceRoots?: ReadonlyArray; } interface IKeydownEvent { @@ -44,7 +44,7 @@ interface IKeydownEvent { class WebviewProtocolProvider extends Disposable { constructor( webview: Electron.WebviewTag, - private readonly _extensionLocation: URI, + private readonly _extensionLocation: URI | undefined, private readonly _getLocalResourceRoots: () => ReadonlyArray, private readonly _environmentService: IEnvironmentService, private readonly _fileService: IFileService, @@ -89,14 +89,10 @@ class SvgBlocker extends Disposable { constructor( webview: Electron.WebviewTag, - private readonly _getOptions: () => WebviewOptions, + private readonly _options: WebviewContentOptions, ) { super(); - if (this.options.allowSvgs) { - return; - } - let loaded = false; this._register(addDisposableListener(webview, 'did-start-loading', () => { if (loaded) { @@ -134,25 +130,20 @@ class SvgBlocker extends Disposable { })); } - private get options() { - return this._getOptions(); - } - private isAllowedSvg(uri: URI): boolean { - if (this.options.allowSvgs) { - return true; - } - if (this.options.svgWhiteList) { - return this.options.svgWhiteList.indexOf(uri.authority.toLowerCase()) >= 0; + if (this._options.svgWhiteList) { + return this._options.svgWhiteList.indexOf(uri.authority.toLowerCase()) >= 0; } return false; } } class WebviewKeyboardHandler extends Disposable { + + private _ignoreMenuShortcut = false; + constructor( - private readonly _webview: Electron.WebviewTag, - private readonly _keybindingService: IKeybindingService + private readonly _webview: Electron.WebviewTag ) { super(); @@ -161,8 +152,9 @@ class WebviewKeyboardHandler extends Disposable { const contents = this.getWebContents(); if (contents) { contents.on('before-input-event', (_event, input) => { - if (input.type === 'keyDown') { - this.setIgnoreMenuShortcuts(input.control || input.meta); + if (input.type === 'keyDown' && document.activeElement === this._webview) { + this._ignoreMenuShortcut = input.control || input.meta; + this.setIgnoreMenuShortcuts(this._ignoreMenuShortcut); } }); } @@ -178,6 +170,10 @@ class WebviewKeyboardHandler extends Disposable { this.handleKeydown(event.args[0]); return; + case 'did-focus': + this.setIgnoreMenuShortcuts(this._ignoreMenuShortcut); + break; + case 'did-blur': this.setIgnoreMenuShortcuts(false); return; @@ -208,23 +204,14 @@ class WebviewKeyboardHandler extends Disposable { } private handleKeydown(event: IKeydownEvent): void { - // return; // Create a fake KeyboardEvent from the data provided - const emulatedKeyboardEvent = new KeyboardEvent('keydown', { - code: event.code, - key: event.key, - keyCode: event.keyCode, - shiftKey: event.shiftKey, - altKey: event.altKey, - ctrlKey: event.ctrlKey, - metaKey: event.metaKey, - repeat: event.repeat - } as KeyboardEvent); - - // Dispatch through our keybinding service - // Note: we set the as target of the event so that scoped context key - // services function properly to enable commands like select all and find. - this._keybindingService.dispatchEvent(new StandardKeyboardEvent(emulatedKeyboardEvent), this._webview); + const emulatedKeyboardEvent = new KeyboardEvent('keydown', event); + // Force override the target + Object.defineProperty(emulatedKeyboardEvent, 'target', { + get: () => this._webview + }); + // And re-dispatch + window.dispatchEvent(emulatedKeyboardEvent); } } @@ -244,16 +231,16 @@ export class WebviewElement extends Disposable { constructor( private readonly _styleElement: Element, - private _options: WebviewOptions, + private readonly _options: WebviewOptions, + private _contentOptions: WebviewContentOptions, @IInstantiationService instantiationService: IInstantiationService, - @IThemeService private readonly _themeService: IThemeService, + @IThemeService themeService: IThemeService, @IEnvironmentService environmentService: IEnvironmentService, - @IFileService fileService: IFileService, - @IKeybindingService private readonly _keybindingService: IKeybindingService + @IFileService fileService: IFileService ) { super(); this._webview = document.createElement('webview'); - this._webview.setAttribute('partition', this._options.allowSvgs ? 'webview' : `webview${Date.now()}`); + this._webview.setAttribute('partition', `webview${Date.now()}`); this._webview.setAttribute('webpreferences', 'contextIsolation=yes'); @@ -263,7 +250,7 @@ export class WebviewElement extends Disposable { this._webview.style.outline = '0'; this._webview.preload = require.toUrl('./webview-pre.js'); - this._webview.src = this._options.useSameOriginForRoot ? require.toUrl('./webview.html') : 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E'; + this._webview.src = 'data:text/html;charset=utf-8,%3C%21DOCTYPE%20html%3E%0D%0A%3Chtml%20lang%3D%22en%22%20style%3D%22width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3Chead%3E%0D%0A%09%3Ctitle%3EVirtual%20Document%3C%2Ftitle%3E%0D%0A%3C%2Fhead%3E%0D%0A%3Cbody%20style%3D%22margin%3A%200%3B%20overflow%3A%20hidden%3B%20width%3A%20100%25%3B%20height%3A%20100%25%22%3E%0D%0A%3C%2Fbody%3E%0D%0A%3C%2Fhtml%3E'; this._ready = new Promise(resolve => { const subscription = this._register(addDisposableListener(this._webview, 'ipc-message', (event) => { @@ -281,14 +268,16 @@ export class WebviewElement extends Disposable { new WebviewProtocolProvider( this._webview, this._options.extensionLocation, - () => (this._options.localResourceRoots || []), + () => (this._contentOptions.localResourceRoots || []), environmentService, fileService)); - const svgBlocker = this._register(new SvgBlocker(this._webview, () => this._options)); - svgBlocker.onDidBlockSvg(() => this.onDidBlockSvg()); + if (!this._options.allowSvgs) { + const svgBlocker = this._register(new SvgBlocker(this._webview, this._contentOptions)); + svgBlocker.onDidBlockSvg(() => this.onDidBlockSvg()); + } - this._register(new WebviewKeyboardHandler(this._webview, this._keybindingService)); + this._register(new WebviewKeyboardHandler(this._webview)); this._register(addDisposableListener(this._webview, 'console-message', function (e: { level: number; message: string; line: number; sourceId: string; }) { console.log(`[Embedded Page] ${e.message}`); @@ -308,7 +297,7 @@ export class WebviewElement extends Disposable { this._register(addDisposableListener(this._webview, 'ipc-message', (event) => { switch (event.channel) { case 'onmessage': - if (this._options.enableWrappedPostMessage && event.args && event.args.length) { + if (event.args && event.args.length) { this._onMessage.fire(event.args[0]); } return; @@ -353,14 +342,18 @@ export class WebviewElement extends Disposable { this._send('devtools-opened'); })); - this._webviewFindWidget = this._register(instantiationService.createInstance(WebviewFindWidget, this)); + if (_options.enableFindWidget) { + this._webviewFindWidget = this._register(instantiationService.createInstance(WebviewFindWidget, this)); + } - this.style(this._themeService.getTheme()); - this._register(this._themeService.onThemeChange(this.style, this)); + this.style(themeService.getTheme()); + themeService.onThemeChange(this.style, this, this._toDispose); } public mountTo(parent: HTMLElement) { - parent.appendChild(this._webviewFindWidget.getDomNode()); + if (this._webviewFindWidget) { + parent.appendChild(this._webviewFindWidget.getDomNode()!); + } parent.appendChild(this._webview); } @@ -371,8 +364,8 @@ export class WebviewElement extends Disposable { } } - this._webview = undefined; - this._webviewFindWidget = undefined; + this._webview = undefined!; + this._webviewFindWidget = undefined!; super.dispose(); } @@ -402,15 +395,15 @@ export class WebviewElement extends Disposable { this._state = value; } - public set options(value: WebviewOptions) { - if (this._options && areWebviewInputOptionsEqual(value, this._options)) { + public set options(value: WebviewContentOptions) { + if (this._contentOptions && areWebviewInputOptionsEqual(value, this._contentOptions)) { return; } - this._options = value; + this._contentOptions = value; this._send('content', { contents: this._contents, - options: this._options, + options: this._contentOptions, state: this._state }); } @@ -419,20 +412,20 @@ export class WebviewElement extends Disposable { this._contents = value; this._send('content', { contents: value, - options: this._options, + options: this._contentOptions, state: this._state }); } - public update(value: string, options: WebviewOptions, retainContextWhenHidden: boolean) { - if (retainContextWhenHidden && value === this._contents && this._options && areWebviewInputOptionsEqual(options, this._options)) { + public update(value: string, options: WebviewContentOptions, retainContextWhenHidden: boolean) { + if (retainContextWhenHidden && value === this._contents && this._contentOptions && areWebviewInputOptionsEqual(options, this._contentOptions)) { return; } this._contents = value; - this._options = options; + this._contentOptions = options; this._send('content', { contents: this._contents, - options: this._options, + options: this._contentOptions, state: this._state }); } @@ -480,16 +473,6 @@ export class WebviewElement extends Disposable { const styles = { - // Old vars - 'font-family': fontFamily, - 'font-weight': fontWeight, - 'font-size': fontSize, - 'background-color': theme.getColor(colorRegistry.editorBackground).toString(), - 'color': theme.getColor(colorRegistry.editorForeground).toString(), - 'link-color': theme.getColor(colorRegistry.textLinkForeground).toString(), - 'link-active-color': theme.getColor(colorRegistry.textLinkActiveForeground).toString(), - - // Offical API 'vscode-editor-font-family': fontFamily, 'vscode-editor-font-weight': fontWeight, 'vscode-editor-font-size': fontSize, @@ -499,7 +482,9 @@ export class WebviewElement extends Disposable { const activeTheme = ApiThemeClassName.fromTheme(theme); this._send('styles', styles, activeTheme); - this._webviewFindWidget.updateTheme(theme); + if (this._webviewFindWidget) { + this._webviewFindWidget.updateTheme(theme); + } } public layout(): void { @@ -567,11 +552,15 @@ export class WebviewElement extends Disposable { } public showFind() { - this._webviewFindWidget.reveal(); + if (this._webviewFindWidget) { + this._webviewFindWidget.reveal(); + } } public hideFind() { - this._webviewFindWidget.hide(); + if (this._webviewFindWidget) { + this._webviewFindWidget.hide(); + } } public reload() { diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewFindWidget.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewFindWidget.ts similarity index 87% rename from src/vs/workbench/parts/webview/electron-browser/webviewFindWidget.ts rename to src/vs/workbench/contrib/webview/electron-browser/webviewFindWidget.ts index 9f88eed6ef..1c82a69131 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewFindWidget.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewFindWidget.ts @@ -11,7 +11,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export class WebviewFindWidget extends SimpleFindWidget { constructor( - private _webview: WebviewElement, + private _webview: WebviewElement | undefined, @IContextViewService contextViewService: IContextViewService, @IContextKeyService contextKeyService: IContextKeyService ) { @@ -24,6 +24,9 @@ export class WebviewFindWidget extends SimpleFindWidget { } public find(previous: boolean) { + if (!this._webview) { + return; + } const val = this.inputValue; if (val) { this._webview.find(val, { findNext: true, forward: !previous }); @@ -32,11 +35,16 @@ export class WebviewFindWidget extends SimpleFindWidget { public hide() { super.hide(); - this._webview.stopFind(true); - this._webview.focus(); + if (this._webview) { + this._webview.stopFind(true); + this._webview.focus(); + } } public onInputChanged() { + if (!this._webview) { + return; + } const val = this.inputValue; if (val) { this._webview.startFind(val); diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewProtocols.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts similarity index 95% rename from src/vs/workbench/parts/webview/electron-browser/webviewProtocols.ts rename to src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts index 56c05fd0e0..2996fa4edc 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewProtocols.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewProtocols.ts @@ -2,9 +2,8 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { extname } from 'path'; +import { extname, sep } from 'vs/base/common/path'; import { getMediaMime, MIME_UNKNOWN } from 'vs/base/common/mime'; -import { nativeSep } from 'vs/base/common/paths'; import { startsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; @@ -52,7 +51,7 @@ export function registerFileProtocol( const requestPath = URI.parse(request.url).path; const normalizedPath = URI.file(requestPath); for (const root of getRoots()) { - if (startsWith(normalizedPath.fsPath, root.fsPath + nativeSep)) { + if (startsWith(normalizedPath.fsPath, root.fsPath + sep)) { resolveContent(fileService, normalizedPath, getMimeType(normalizedPath), callback); return; } diff --git a/src/vs/workbench/parts/welcome/code-icon.svg b/src/vs/workbench/contrib/welcome/code-icon.svg similarity index 100% rename from src/vs/workbench/parts/welcome/code-icon.svg rename to src/vs/workbench/contrib/welcome/code-icon.svg diff --git a/src/vs/workbench/parts/welcome/gettingStarted/electron-browser/gettingStarted.contribution.ts b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution.ts similarity index 100% rename from src/vs/workbench/parts/welcome/gettingStarted/electron-browser/gettingStarted.contribution.ts rename to src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution.ts diff --git a/src/vs/workbench/parts/welcome/gettingStarted/electron-browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.ts similarity index 97% rename from src/vs/workbench/parts/welcome/gettingStarted/electron-browser/gettingStarted.ts rename to src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.ts index 69a0c983ef..956dd99c02 100644 --- a/src/vs/workbench/parts/welcome/gettingStarted/electron-browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.ts @@ -8,7 +8,7 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { ITelemetryService, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import * as platform from 'vs/base/common/platform'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; export class GettingStarted implements IWorkbenchContribution { diff --git a/src/vs/workbench/parts/welcome/gettingStarted/electron-browser/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts similarity index 96% rename from src/vs/workbench/parts/welcome/gettingStarted/electron-browser/telemetryOptOut.ts rename to src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts index 8985972e77..0c0ef0aa34 100644 --- a/src/vs/workbench/parts/welcome/gettingStarted/electron-browser/telemetryOptOut.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut.ts @@ -6,14 +6,14 @@ import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { onUnexpectedError } from 'vs/base/common/errors'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; -import { IExperimentService, ExperimentState } from 'vs/workbench/parts/experiments/node/experimentService'; +import { IExperimentService, ExperimentState } from 'vs/workbench/contrib/experiments/node/experimentService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { language, locale } from 'vs/base/common/platform'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; @@ -96,7 +96,7 @@ export class TelemetryOptOut implements IWorkbenchContribution { return this.galleryService.getCoreTranslation(extensionToFetchTranslationsFrom, locale!) .then(translation => { - const translationsFromPack = translation && translation.contents ? translation.contents['vs/workbench/parts/welcome/gettingStarted/electron-browser/telemetryOptOut'] : {}; + const translationsFromPack = translation && translation.contents ? translation.contents['vs/workbench/contrib/welcome/gettingStarted/electron-browser/telemetryOptOut'] : {}; if (!!translationsFromPack[promptMessageKey] && !!translationsFromPack[yesLabelKey] && !!translationsFromPack[noLabelKey]) { promptMessage = translationsFromPack[promptMessageKey].replace('{0}', this.privacyUrl) + ' (Please help Microsoft improve Visual Studio Code by allowing the collection of usage data.)'; yesLabel = translationsFromPack[yesLabelKey] + ' (Yes)'; diff --git a/src/vs/workbench/parts/welcome/gettingStarted/test/common/gettingStarted.test.ts b/src/vs/workbench/contrib/welcome/gettingStarted/test/common/gettingStarted.test.ts similarity index 100% rename from src/vs/workbench/parts/welcome/gettingStarted/test/common/gettingStarted.test.ts rename to src/vs/workbench/contrib/welcome/gettingStarted/test/common/gettingStarted.test.ts diff --git a/src/vs/workbench/parts/welcome/overlay/browser/media/commandpalette-dark.svg b/src/vs/workbench/contrib/welcome/overlay/browser/media/commandpalette-dark.svg similarity index 100% rename from src/vs/workbench/parts/welcome/overlay/browser/media/commandpalette-dark.svg rename to src/vs/workbench/contrib/welcome/overlay/browser/media/commandpalette-dark.svg diff --git a/src/vs/workbench/parts/welcome/overlay/browser/media/commandpalette.svg b/src/vs/workbench/contrib/welcome/overlay/browser/media/commandpalette.svg similarity index 100% rename from src/vs/workbench/parts/welcome/overlay/browser/media/commandpalette.svg rename to src/vs/workbench/contrib/welcome/overlay/browser/media/commandpalette.svg diff --git a/src/vs/workbench/parts/welcome/overlay/browser/welcomeOverlay.css b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.css similarity index 100% rename from src/vs/workbench/parts/welcome/overlay/browser/welcomeOverlay.css rename to src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.css diff --git a/src/vs/workbench/parts/welcome/overlay/browser/welcomeOverlay.ts b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts similarity index 94% rename from src/vs/workbench/parts/welcome/overlay/browser/welcomeOverlay.ts rename to src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts index e949815a90..ed9b2cd793 100644 --- a/src/vs/workbench/parts/welcome/overlay/browser/welcomeOverlay.ts +++ b/src/vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay.ts @@ -7,9 +7,9 @@ import 'vs/css!./welcomeOverlay'; import * as dom from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { ShowAllCommandsAction } from 'vs/workbench/parts/quickopen/browser/commandsHandler'; +import { ShowAllCommandsAction } from 'vs/workbench/contrib/quickopen/browser/commandsHandler'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { Parts, IPartService } from 'vs/workbench/services/part/common/partService'; +import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; @@ -157,7 +157,7 @@ class WelcomeOverlay { private _overlay: HTMLElement; constructor( - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IEditorService private readonly editorService: IEditorService, @ICommandService private readonly commandService: ICommandService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, @@ -168,10 +168,10 @@ class WelcomeOverlay { } private create(): void { - const container = this.partService.getContainer(Parts.EDITOR_PART); + const container = this.layoutService.getContainer(Parts.EDITOR_PART)!; - const offset = this.partService.getTitleBarOffset(); - this._overlay = dom.append(container.parentElement, $('.welcomeOverlay')); + const offset = this.layoutService.getTitleBarOffset(); + this._overlay = dom.append(container.parentElement!, $('.welcomeOverlay')); this._overlay.style.top = `${offset}px`; this._overlay.style.height = `calc(100% - ${offset}px)`; this._overlay.style.display = 'none'; diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/vs_code_welcome_page.ts b/src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts similarity index 100% rename from src/vs/workbench/parts/welcome/page/electron-browser/vs_code_welcome_page.ts rename to src/vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page.ts diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts similarity index 98% rename from src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution.ts rename to src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts index fefa4fbfc3..c43fd7efc3 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.contribution.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; -import { WelcomePageContribution, WelcomePageAction, WelcomeInputFactory } from 'vs/workbench/parts/welcome/page/electron-browser/welcomePage'; +import { WelcomePageContribution, WelcomePageAction, WelcomeInputFactory } from 'vs/workbench/contrib/welcome/page/browser/welcomePage'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.css b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.css similarity index 92% rename from src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.css rename to src/vs/workbench/contrib/welcome/page/browser/welcomePage.css index 55d6c1601b..c4c420ffb2 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.css +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.css @@ -228,21 +228,21 @@ .monaco-workbench .part.editor > .content .welcomePage .linux-only { display: none; } -.mac > .monaco-workbench .part.editor > .content .welcomePage .mac-only { +.monaco-workbench.mac .part.editor > .content .welcomePage .mac-only { display: initial; } -.windows > .monaco-workbench .part.editor > .content .welcomePage .windows-only { +.monaco-workbench.windows .part.editor > .content .welcomePage .windows-only { display: initial; } -.linux > .monaco-workbench .part.editor > .content .welcomePage .linux-only { +.monaco-workbench.linux .part.editor > .content .welcomePage .linux-only { display: initial; } -.mac > .monaco-workbench .part.editor > .content .welcomePage li.mac-only { +.monaco-workbench.mac .part.editor > .content .welcomePage li.mac-only { display: list-item; } -.windows > .monaco-workbench .part.editor > .content .welcomePage li.windows-only { +.monaco-workbench.windows .part.editor > .content .welcomePage li.windows-only { display: list-item; } -.linux > .monaco-workbench .part.editor > .content .welcomePage li.linux-only { +.monaco-workbench.linux .part.editor > .content .welcomePage li.linux-only { display: list-item; } diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts similarity index 91% rename from src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts rename to src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts index 9d5d291725..2fcebd50c4 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts +++ b/src/vs/workbench/contrib/welcome/page/browser/welcomePage.ts @@ -6,34 +6,31 @@ import 'vs/css!./welcomePage'; import { URI } from 'vs/base/common/uri'; import * as strings from 'vs/base/common/strings'; -import * as path from 'path'; import { ICommandService } from 'vs/platform/commands/common/commands'; import * as arrays from 'vs/base/common/arrays'; -import { WalkThroughInput } from 'vs/workbench/parts/welcome/walkThrough/node/walkThroughInput'; +import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { onUnexpectedError, isPromiseCanceledError } from 'vs/base/common/errors'; -import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IWindowService, URIType } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { localize } from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Schemas } from 'vs/base/common/network'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { getInstalledExtensions, IExtensionStatus, onExtensionChanged, isKeymapExtension } from 'vs/workbench/parts/extensions/electron-browser/extensionsUtils'; +import { getInstalledExtensions, IExtensionStatus, onExtensionChanged, isKeymapExtension } from 'vs/workbench/contrib/extensions/common/extensionsUtils'; import { IExtensionEnablementService, IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, EnablementState, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { used } from 'vs/workbench/parts/welcome/page/electron-browser/vs_code_welcome_page'; +import { used } from 'vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page'; import { ILifecycleService, StartupKind } from 'vs/platform/lifecycle/common/lifecycle'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { tildify, getBaseLabel } from 'vs/base/common/labels'; +import { splitName } from 'vs/base/common/labels'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { registerColor, focusBorder, textLinkForeground, textLinkActiveForeground, foreground, descriptionForeground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { getExtraColor } from 'vs/workbench/parts/welcome/walkThrough/node/walkThroughUtils'; -import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils'; +import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; import { IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { TimeoutTimer } from 'vs/base/common/async'; @@ -41,6 +38,8 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens import { ILabelService } from 'vs/platform/label/common/label'; import { IFileService } from 'vs/platform/files/common/files'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { joinPath } from 'vs/base/common/resources'; +import { IRecentlyOpened, isRecentWorkspace, IRecentWorkspace, IRecentFolder, isRecentFolder } from 'vs/platform/history/common/history'; used(); @@ -73,9 +72,7 @@ export class WelcomePageContribution implements IWorkbenchContribution { .then(files => { const file = arrays.find(files.sort(), file => strings.startsWith(file.toLowerCase(), 'readme')); if (file) { - return folderUri.with({ - path: path.posix.join(folderUri.path, file) - }); + return joinPath(folderUri, file); } return undefined; }, onUnexpectedError); @@ -259,7 +256,6 @@ class WelcomePage { @IWindowService private readonly windowService: IWindowService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, @ILabelService private readonly labelService: ILabelService, @INotificationService private readonly notificationService: INotificationService, @IExtensionEnablementService private readonly extensionEnablementService: IExtensionEnablementService, @@ -277,7 +273,7 @@ class WelcomePage { const resource = URI.parse(require.toUrl('./vs_code_welcome_page')) .with({ scheme: Schemas.walkThrough, - query: JSON.stringify({ moduleId: 'vs/workbench/parts/welcome/page/electron-browser/vs_code_welcome_page' }) + query: JSON.stringify({ moduleId: 'vs/workbench/contrib/welcome/page/browser/vs_code_welcome_page' }) }); this.editorInput = this.instantiationService.createInstance(WalkThroughInput, { typeId: welcomeInputTypeId, @@ -292,7 +288,7 @@ class WelcomePage { return this.editorService.openEditor(this.editorInput, { pinned: false }); } - private onReady(container: HTMLElement, recentlyOpened: Promise<{ files: URI[]; workspaces: Array; }>, installedExtensions: Promise): void { + private onReady(container: HTMLElement, recentlyOpened: Promise, installedExtensions: Promise): void { const enabled = isWelcomePageEnabled(this.configurationService, this.contextService); const showOnStartup = container.querySelector('#showOnStartup'); if (enabled) { @@ -304,7 +300,7 @@ class WelcomePage { recentlyOpened.then(({ workspaces }) => { // Filter out the current workspace - workspaces = workspaces.filter(workspace => !this.contextService.isCurrentWorkspace(workspace)); + workspaces = workspaces.filter(recent => !this.contextService.isCurrentWorkspace(isRecentWorkspace(recent) ? recent.workspace : recent.folderUri)); if (!workspaces.length) { const recent = container.querySelector('.welcomePage') as HTMLElement; recent.classList.add('emptyRecent'); @@ -342,43 +338,29 @@ class WelcomePage { })); } - private createListEntries(workspaces: (URI | IWorkspaceIdentifier)[]) { - return workspaces.map(workspace => { - let label: string; + private createListEntries(recents: (IRecentWorkspace | IRecentFolder)[]) { + return recents.map(recent => { + let fullPath: string; let resource: URI; - if (isSingleFolderWorkspaceIdentifier(workspace)) { - resource = workspace; - label = this.labelService.getWorkspaceLabel(workspace); - } else if (isWorkspaceIdentifier(workspace)) { - label = this.labelService.getWorkspaceLabel(workspace); - resource = URI.file(workspace.configPath); + let typeHint: URIType | undefined; + if (isRecentFolder(recent)) { + resource = recent.folderUri; + fullPath = recent.label || this.labelService.getWorkspaceLabel(recent.folderUri, { verbose: true }); + typeHint = 'folder'; } else { - label = getBaseLabel(workspace); - resource = URI.file(workspace); + fullPath = recent.label || this.labelService.getWorkspaceLabel(recent.workspace, { verbose: true }); + resource = recent.workspace.configPath; + typeHint = 'file'; } + const { name, parentPath } = splitName(fullPath); + const li = document.createElement('li'); - const a = document.createElement('a'); - let name = label; - let parentFolderPath: string | undefined; - - if (resource.scheme === Schemas.file) { - let parentFolder = path.dirname(resource.fsPath); - if (!name && parentFolder) { - const tmp = name; - name = parentFolder; - parentFolder = tmp; - } - parentFolderPath = tildify(parentFolder, this.environmentService.userHome); - } else { - parentFolderPath = this.labelService.getUriLabel(resource); - } - a.innerText = name; - a.title = label; - a.setAttribute('aria-label', localize('welcomePage.openFolderWithPath', "Open folder {0} with path {1}", name, parentFolderPath)); + a.title = fullPath; + a.setAttribute('aria-label', localize('welcomePage.openFolderWithPath', "Open folder {0} with path {1}", name, parentPath)); a.href = 'javascript:void(0)'; a.addEventListener('click', e => { /* __GDPR__ @@ -391,7 +373,7 @@ class WelcomePage { id: 'openRecentFolder', from: telemetryFrom }); - this.windowService.openWindow([resource], { forceNewWindow: e.ctrlKey || e.metaKey }); + this.windowService.openWindow([{ uri: resource, typeHint }], { forceNewWindow: e.ctrlKey || e.metaKey }); e.preventDefault(); e.stopPropagation(); }); @@ -400,8 +382,8 @@ class WelcomePage { const span = document.createElement('span'); span.classList.add('path'); span.classList.add('detail'); - span.innerText = parentFolderPath; - span.title = label; + span.innerText = parentPath; + span.title = fullPath; li.appendChild(span); return li; diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/editorWalkThrough.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts similarity index 97% rename from src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/editorWalkThrough.ts rename to src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts index 35563868e2..eb0af87319 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/editorWalkThrough.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough.ts @@ -8,7 +8,7 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { Action } from 'vs/base/common/actions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { URI } from 'vs/base/common/uri'; -import { WalkThroughInput, WalkThroughInputOptions } from 'vs/workbench/parts/welcome/walkThrough/node/walkThroughInput'; +import { WalkThroughInput, WalkThroughInputOptions } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput'; import { Schemas } from 'vs/base/common/network'; import { IEditorInputFactory, EditorInput } from 'vs/workbench/common/editor'; diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/vs_code_editor_walkthrough.md b/src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.md similarity index 100% rename from src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/vs_code_editor_walkthrough.md rename to src/vs/workbench/contrib/welcome/walkThrough/browser/editor/vs_code_editor_walkthrough.md diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts similarity index 85% rename from src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution.ts rename to src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts index 914238fa78..263bc199c6 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import { localize } from 'vs/nls'; -import { WalkThroughInput } from 'vs/workbench/parts/welcome/walkThrough/node/walkThroughInput'; -import { WalkThroughPart } from 'vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart'; -import { WalkThroughArrowUp, WalkThroughArrowDown, WalkThroughPageUp, WalkThroughPageDown } from 'vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughActions'; -import { WalkThroughContentProvider, WalkThroughSnippetContentProvider } from 'vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider'; -import { EditorWalkThroughAction, EditorWalkThroughInputFactory } from 'vs/workbench/parts/welcome/walkThrough/electron-browser/editor/editorWalkThrough'; +import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput'; +import { WalkThroughPart } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart'; +import { WalkThroughArrowUp, WalkThroughArrowDown, WalkThroughPageUp, WalkThroughPageDown } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughActions'; +import { WalkThroughContentProvider, WalkThroughSnippetContentProvider } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider'; +import { EditorWalkThroughAction, EditorWalkThroughInputFactory } from 'vs/workbench/contrib/welcome/walkThrough/browser/editor/editorWalkThrough'; import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughActions.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughActions.ts similarity index 97% rename from src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughActions.ts rename to src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughActions.ts index 81b2a5ee46..db8f51aeab 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughActions.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { WalkThroughPart, WALK_THROUGH_FOCUS } from 'vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart'; +import { WalkThroughPart, WALK_THROUGH_FOCUS } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart'; import { ICommandAndKeybindingRule, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.css b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.css similarity index 91% rename from src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.css rename to src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.css index 13ddd871bd..967e230cdd 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.css +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.css @@ -98,7 +98,7 @@ .monaco-workbench .part.editor > .content .walkThroughContent code, .monaco-workbench .part.editor > .content .walkThroughContent .shortcut { - font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; + font-family: var(--monaco-monospace-font); font-size: 14px; line-height: 19px; } @@ -123,13 +123,13 @@ .monaco-workbench .part.editor > .content .walkThroughContent .linux-only { display: none; } -.mac > .monaco-workbench .part.editor > .content .walkThroughContent .mac-only { +.monaco-workbench.mac .part.editor > .content .walkThroughContent .mac-only { display: initial; } -.windows > .monaco-workbench .part.editor > .content .walkThroughContent .windows-only { +.monaco-workbench.windows .part.editor > .content .walkThroughContent .windows-only { display: initial; } -.linux > .monaco-workbench .part.editor > .content .walkThroughContent .linux-only { +.monaco-workbench.linux .part.editor > .content .walkThroughContent .linux-only { display: initial; } diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts similarity index 98% rename from src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts rename to src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts index 2ce357ff8b..5febf0f2ea 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart.ts @@ -12,7 +12,7 @@ import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { EditorOptions, IEditorMemento } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { WalkThroughInput } from 'vs/workbench/parts/welcome/walkThrough/node/walkThroughInput'; +import { WalkThroughInput } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import * as marked from 'vs/base/common/marked/marked'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -29,13 +29,13 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { registerColor, focusBorder, textLinkForeground, textLinkActiveForeground, textPreformatForeground, contrastBorder, textBlockQuoteBackground, textBlockQuoteBorder } from 'vs/platform/theme/common/colorRegistry'; -import { getExtraColor } from 'vs/workbench/parts/welcome/walkThrough/node/walkThroughUtils'; +import { getExtraColor } from 'vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils'; import { UILabelProvider } from 'vs/base/common/keybindingLabels'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { deepClone } from 'vs/base/common/objects'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Dimension, size } from 'vs/base/browser/dom'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; export const WALK_THROUGH_FOCUS = new RawContextKey('interactivePlaygroundFocus', false); diff --git a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts similarity index 100% rename from src/vs/workbench/parts/welcome/walkThrough/node/walkThroughContentProvider.ts rename to src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughContentProvider.ts diff --git a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughInput.ts b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts similarity index 90% rename from src/vs/workbench/parts/welcome/walkThrough/node/walkThroughInput.ts rename to src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts index 4f4dd8a21f..1fde776d4f 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughInput.ts +++ b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughInput.ts @@ -7,11 +7,9 @@ import * as strings from 'vs/base/common/strings'; import { EditorInput, EditorModel, ITextEditorModel } from 'vs/workbench/common/editor'; import { URI } from 'vs/base/common/uri'; import { IReference, IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUtils'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import * as marked from 'vs/base/common/marked/marked'; import { Schemas } from 'vs/base/common/network'; -import { IHashService } from 'vs/workbench/services/hash/common/hashService'; export class WalkThroughModel extends EditorModel { @@ -57,8 +55,7 @@ export class WalkThroughInput extends EditorInput { constructor( private options: WalkThroughInputOptions, - @ITextModelService private readonly textModelResolverService: ITextModelService, - @IHashService private readonly hashService: IHashService + @ITextModelService private readonly textModelResolverService: ITextModelService ) { super(); } @@ -86,11 +83,9 @@ export class WalkThroughInput extends EditorInput { getTelemetryDescriptor(): object { const descriptor = super.getTelemetryDescriptor(); descriptor['target'] = this.getTelemetryFrom(); - descriptor['resource'] = telemetryURIDescriptor(this.options.resource, path => this.hashService.createSHA1(path)); /* __GDPR__FRAGMENT__ "EditorTelemetryDescriptor" : { - "target" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "resource": { "${inline}": [ "${URIDescriptor}" ] } + "target" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } } */ return descriptor; diff --git a/src/vs/workbench/parts/welcome/walkThrough/node/walkThroughUtils.ts b/src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils.ts similarity index 100% rename from src/vs/workbench/parts/welcome/walkThrough/node/walkThroughUtils.ts rename to src/vs/workbench/contrib/welcome/walkThrough/common/walkThroughUtils.ts diff --git a/src/vs/workbench/electron-browser/actions/developerActions.ts b/src/vs/workbench/electron-browser/actions/developerActions.ts index 15906ef27d..116d44142f 100644 --- a/src/vs/workbench/electron-browser/actions/developerActions.ts +++ b/src/vs/workbench/electron-browser/actions/developerActions.ts @@ -15,6 +15,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Context } from 'vs/platform/contextkey/browser/contextKeyService'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { timeout } from 'vs/base/common/async'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; export class ToggleDevToolsAction extends Action { @@ -115,7 +116,12 @@ export class ToggleScreencastModeAction extends Action { static disposable: IDisposable | undefined; - constructor(id: string, label: string, @IKeybindingService private readonly keybindingService: IKeybindingService) { + constructor( + id: string, + label: string, + @IKeybindingService private readonly keybindingService: IKeybindingService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService + ) { super(id, label); } @@ -126,7 +132,9 @@ export class ToggleScreencastModeAction extends Action { return; } - const mouseMarker = append(document.body, $('div')); + const container = this.layoutService.getWorkbenchElement(); + + const mouseMarker = append(container, $('div')); mouseMarker.style.position = 'absolute'; mouseMarker.style.border = '2px solid red'; mouseMarker.style.borderRadius = '20px'; @@ -139,9 +147,9 @@ export class ToggleScreencastModeAction extends Action { mouseMarker.style.pointerEvents = 'none'; mouseMarker.style.display = 'none'; - const onMouseDown = domEvent(document.body, 'mousedown', true); - const onMouseUp = domEvent(document.body, 'mouseup', true); - const onMouseMove = domEvent(document.body, 'mousemove', true); + const onMouseDown = domEvent(container, 'mousedown', true); + const onMouseUp = domEvent(container, 'mouseup', true); + const onMouseMove = domEvent(container, 'mousemove', true); const mouseListener = onMouseDown(e => { mouseMarker.style.top = `${e.clientY - 10}px`; @@ -159,7 +167,7 @@ export class ToggleScreencastModeAction extends Action { }); }); - const keyboardMarker = append(document.body, $('div')); + const keyboardMarker = append(container, $('div')); keyboardMarker.style.position = 'absolute'; keyboardMarker.style.backgroundColor = 'rgba(0, 0, 0 ,0.5)'; keyboardMarker.style.width = '100%'; @@ -174,7 +182,7 @@ export class ToggleScreencastModeAction extends Action { keyboardMarker.style.fontSize = '56px'; keyboardMarker.style.display = 'none'; - const onKeyDown = domEvent(document.body, 'keydown', true); + const onKeyDown = domEvent(container, 'keydown', true); let keyboardTimeout: IDisposable = Disposable.None; const keyboardListener = onKeyDown(e => { @@ -204,7 +212,8 @@ export class ToggleScreencastModeAction extends Action { ToggleScreencastModeAction.disposable = toDisposable(() => { mouseListener.dispose(); keyboardListener.dispose(); - document.body.removeChild(mouseMarker); + mouseMarker.remove(); + keyboardMarker.remove(); }); } } diff --git a/src/vs/workbench/electron-browser/actions/helpActions.ts b/src/vs/workbench/electron-browser/actions/helpActions.ts index 5ce1d89e9a..533545688a 100644 --- a/src/vs/workbench/electron-browser/actions/helpActions.ts +++ b/src/vs/workbench/electron-browser/actions/helpActions.ts @@ -5,61 +5,8 @@ import { Action } from 'vs/base/common/actions'; import * as nls from 'vs/nls'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { isMacintosh, isLinux, language } from 'vs/base/common/platform'; -import { IssueType } from 'vs/platform/issue/common/issue'; -import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; - -export class OpenIssueReporterAction extends Action { - static readonly ID = 'workbench.action.openIssueReporter'; - static readonly LABEL = nls.localize({ key: 'reportIssueInEnglish', comment: ['Translate this to "Report Issue in English" in all languages please!'] }, "Report Issue"); - - constructor( - id: string, - label: string, - @IWorkbenchIssueService private readonly issueService: IWorkbenchIssueService - ) { - super(id, label); - } - - run(): Promise { - return this.issueService.openReporter().then(() => true); - } -} - -export class OpenProcessExplorer extends Action { - static readonly ID = 'workbench.action.openProcessExplorer'; - static readonly LABEL = nls.localize('openProcessExplorer', "Open Process Explorer"); - - constructor( - id: string, - label: string, - @IWorkbenchIssueService private readonly issueService: IWorkbenchIssueService - ) { - super(id, label); - } - - run(): Promise { - return this.issueService.openProcessExplorer().then(() => true); - } -} - -export class ReportPerformanceIssueUsingReporterAction extends Action { - static readonly ID = 'workbench.action.reportPerformanceIssueUsingReporter'; - static readonly LABEL = nls.localize('reportPerformanceIssue', "Report Performance Issue"); - - constructor( - id: string, - label: string, - @IWorkbenchIssueService private readonly issueService: IWorkbenchIssueService - ) { - super(id, label); - } - - run(): Promise { - return this.issueService.openReporter({ issueType: IssueType.PerformanceIssue }).then(() => true); - } -} export class KeybindingsReferenceAction extends Action { @@ -161,7 +108,7 @@ export class OpenTwitterUrlAction extends Action { super(id, label); } - run(): Promise { + run(): Promise { if (product.twitterUrl) { window.open(product.twitterUrl); } @@ -182,7 +129,7 @@ export class OpenRequestFeatureUrlAction extends Action { super(id, label); } - run(): Promise { + run(): Promise { if (product.requestFeatureUrl) { window.open(product.requestFeatureUrl); } @@ -203,7 +150,7 @@ export class OpenLicenseUrlAction extends Action { super(id, label); } - run(): Promise { + run(): Promise { if (product.licenseUrl) { if (language) { const queryArgChar = product.licenseUrl.indexOf('?') > 0 ? '&' : '?'; @@ -229,7 +176,7 @@ export class OpenPrivacyStatementUrlAction extends Action { super(id, label); } - run(): Promise { + run(): Promise { if (product.privacyStatementUrl) { if (language) { const queryArgChar = product.privacyStatementUrl.indexOf('?') > 0 ? '&' : '?'; diff --git a/src/vs/workbench/electron-browser/actions/windowActions.ts b/src/vs/workbench/electron-browser/actions/windowActions.ts index 68c6f259b2..44fccd3185 100644 --- a/src/vs/workbench/electron-browser/actions/windowActions.ts +++ b/src/vs/workbench/electron-browser/actions/windowActions.ts @@ -10,21 +10,22 @@ import { Action } from 'vs/base/common/actions'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import * as nls from 'vs/nls'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { isMacintosh } from 'vs/base/common/platform'; import * as browser from 'vs/base/browser/browser'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { webFrame } from 'electron'; -import { getBaseLabel } from 'vs/base/common/labels'; -import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { FileKind } from 'vs/platform/files/common/files'; import { ILabelService } from 'vs/platform/label/common/label'; -import { dirname } from 'vs/base/common/resources'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IQuickInputService, IQuickPickItem, IQuickInputButton, IQuickPickSeparator, IKeyMods } from 'vs/platform/quickinput/common/quickInput'; import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; +import { ICommandHandler } from 'vs/platform/commands/common/commands'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IRecentFolder, IRecentFile, IRecentWorkspace, IRecent, isRecentFolder, isRecentWorkspace } from 'vs/platform/history/common/history'; +import { splitName } from 'vs/base/common/labels'; export class CloseCurrentWindowAction extends Action { @@ -80,7 +81,7 @@ export abstract class BaseZoomAction extends Action { constructor( id: string, label: string, - @IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService + @IConfigurationService private readonly configurationService: IConfigurationService ) { super(id, label); } @@ -109,7 +110,7 @@ export class ZoomInAction extends BaseZoomAction { constructor( id: string, label: string, - @IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService + @IConfigurationService configurationService: IConfigurationService ) { super(id, label, configurationService); } @@ -129,7 +130,7 @@ export class ZoomOutAction extends BaseZoomAction { constructor( id: string, label: string, - @IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService + @IConfigurationService configurationService: IConfigurationService ) { super(id, label, configurationService); } @@ -149,7 +150,7 @@ export class ZoomResetAction extends BaseZoomAction { constructor( id: string, label: string, - @IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService + @IConfigurationService configurationService: IConfigurationService ) { super(id, label, configurationService); } @@ -226,7 +227,7 @@ export abstract class BaseSwitchWindow extends Action { return this.windowsService.getWindows().then(windows => { const placeHolder = nls.localize('switchWindowPlaceHolder', "Select a window to switch to"); const picks = windows.map(win => { - const resource = win.filename ? URI.file(win.filename) : win.folderUri ? win.folderUri : win.workspace ? URI.file(win.workspace.configPath) : undefined; + const resource = win.filename ? URI.file(win.filename) : win.folderUri ? win.folderUri : win.workspace ? win.workspace.configPath : undefined; const fileKind = win.filename ? FileKind.FILE : win.workspace ? FileKind.ROOT_FOLDER : win.folderUri ? FileKind.FOLDER : FileKind.FILE; return { payload: win.id, @@ -335,47 +336,47 @@ export abstract class BaseOpenRecentAction extends Action { .then(({ workspaces, files }) => this.openRecent(workspaces, files)); } - private openRecent(recentWorkspaces: Array, recentFiles: URI[]): void { + private openRecent(recentWorkspaces: Array, recentFiles: IRecentFile[]): void { - const toPick = (workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI, fileKind: FileKind, labelService: ILabelService, buttons: IQuickInputButton[] | undefined) => { - let resource: URI; - let label: string; - let description: string; - if (isSingleFolderWorkspaceIdentifier(workspace) && fileKind !== FileKind.FILE) { - resource = workspace; - label = labelService.getWorkspaceLabel(workspace); - description = labelService.getUriLabel(dirname(resource)!); - } else if (isWorkspaceIdentifier(workspace)) { - resource = URI.file(workspace.configPath); - label = labelService.getWorkspaceLabel(workspace); - description = labelService.getUriLabel(dirname(resource)!); + const toPick = (recent: IRecent, labelService: ILabelService, buttons: IQuickInputButton[] | undefined) => { + let resource: URI | undefined; + let fullLabel: string | undefined; + let fileKind: FileKind | undefined; + if (isRecentFolder(recent)) { + resource = recent.folderUri; + fullLabel = recent.label || labelService.getWorkspaceLabel(recent.folderUri, { verbose: true }); + fileKind = FileKind.FOLDER; + } else if (isRecentWorkspace(recent)) { + resource = recent.workspace.configPath; + fullLabel = recent.label || labelService.getWorkspaceLabel(recent.workspace, { verbose: true }); + fileKind = FileKind.ROOT_FOLDER; } else { - resource = workspace; - label = getBaseLabel(workspace); - description = labelService.getUriLabel(dirname(resource)!); + resource = recent.fileUri; + fullLabel = recent.label || labelService.getUriLabel(recent.fileUri); + fileKind = FileKind.FILE; } - + const { name, parentPath } = splitName(fullLabel); return { iconClasses: getIconClasses(this.modelService, this.modeService, resource, fileKind), - label, - description, + label: name, + description: parentPath, buttons, - workspace, resource, fileKind, }; }; - const runPick = (resource: URI, isFile: boolean, keyMods: IKeyMods) => { + const runPick = (uri: URI, isFile: boolean, keyMods: IKeyMods) => { const forceNewWindow = keyMods.ctrlCmd; - return this.windowService.openWindow([resource], { forceNewWindow, forceOpenWorkspaceAsFile: isFile }); + return this.windowService.openWindow([{ uri, typeHint: isFile ? 'file' : 'folder' }], { forceNewWindow, forceOpenWorkspaceAsFile: isFile }); }; - const workspacePicks = recentWorkspaces.map(workspace => toPick(workspace, isSingleFolderWorkspaceIdentifier(workspace) ? FileKind.FOLDER : FileKind.ROOT_FOLDER, this.labelService, !this.isQuickNavigate() ? [this.removeFromRecentlyOpened] : undefined)); - const filePicks = recentFiles.map(p => toPick(p, FileKind.FILE, this.labelService, !this.isQuickNavigate() ? [this.removeFromRecentlyOpened] : undefined)); + const workspacePicks = recentWorkspaces.map(workspace => toPick(workspace, this.labelService, !this.isQuickNavigate() ? [this.removeFromRecentlyOpened] : undefined)); + const filePicks = recentFiles.map(p => toPick(p, this.labelService, !this.isQuickNavigate() ? [this.removeFromRecentlyOpened] : undefined)); // focus second entry if the first recent workspace is the current workspace - let autoFocusSecondEntry: boolean = recentWorkspaces[0] && this.contextService.isCurrentWorkspace(recentWorkspaces[0]); + const firstEntry = recentWorkspaces[0]; + let autoFocusSecondEntry: boolean = firstEntry && this.contextService.isCurrentWorkspace(isRecentWorkspace(firstEntry) ? firstEntry.workspace : firstEntry.folderUri); let keyMods: IKeyMods; const workspaceSeparator: IQuickPickSeparator = { type: 'separator', label: nls.localize('workspaces', "workspaces") }; @@ -389,14 +390,13 @@ export abstract class BaseOpenRecentAction extends Action { onKeyMods: mods => keyMods = mods, quickNavigate: this.isQuickNavigate() ? { keybindings: this.keybindingService.lookupKeybindings(this.id) } : undefined, onDidTriggerItemButton: context => { - this.windowsService.removeFromRecentlyOpened([context.item.workspace]).then(() => context.removeItem()); + this.windowsService.removeFromRecentlyOpened([context.item.resource]).then(() => context.removeItem()); } - }) - .then((pick): Promise | void => { - if (pick) { - return runPick(pick.resource, pick.fileKind === FileKind.FILE, keyMods); - } - }); + }).then((pick): Promise | void => { + if (pick) { + return runPick(pick.resource, pick.fileKind === FileKind.FILE, keyMods); + } + }); } } @@ -468,110 +468,26 @@ export class ShowAboutDialogAction extends Action { } } -export class NewWindowTab extends Action { +export const NewWindowTabHandler: ICommandHandler = function (accessor: ServicesAccessor) { + return accessor.get(IWindowsService).newWindowTab(); +}; - static readonly ID = 'workbench.action.newWindowTab'; - static readonly LABEL = nls.localize('newTab', "New Window Tab"); +export const ShowPreviousWindowTabHandler: ICommandHandler = function (accessor: ServicesAccessor) { + return accessor.get(IWindowsService).showPreviousWindowTab(); +}; - constructor( - _id: string, - _label: string, - @IWindowsService private readonly windowsService: IWindowsService - ) { - super(NewWindowTab.ID, NewWindowTab.LABEL); - } +export const ShowNextWindowTabHandler: ICommandHandler = function (accessor: ServicesAccessor) { + return accessor.get(IWindowsService).showNextWindowTab(); +}; - run(): Promise { - return this.windowsService.newWindowTab().then(() => true); - } -} +export const MoveWindowTabToNewWindowHandler: ICommandHandler = function (accessor: ServicesAccessor) { + return accessor.get(IWindowsService).moveWindowTabToNewWindow(); +}; -export class ShowPreviousWindowTab extends Action { +export const MergeWindowTabsHandlerHandler: ICommandHandler = function (accessor: ServicesAccessor) { + return accessor.get(IWindowsService).mergeAllWindowTabs(); +}; - static readonly ID = 'workbench.action.showPreviousWindowTab'; - static readonly LABEL = nls.localize('showPreviousTab', "Show Previous Window Tab"); - - constructor( - _id: string, - _label: string, - @IWindowsService private readonly windowsService: IWindowsService - ) { - super(ShowPreviousWindowTab.ID, ShowPreviousWindowTab.LABEL); - } - - run(): Promise { - return this.windowsService.showPreviousWindowTab().then(() => true); - } -} - -export class ShowNextWindowTab extends Action { - - static readonly ID = 'workbench.action.showNextWindowTab'; - static readonly LABEL = nls.localize('showNextWindowTab', "Show Next Window Tab"); - - constructor( - _id: string, - _label: string, - @IWindowsService private readonly windowsService: IWindowsService - ) { - super(ShowNextWindowTab.ID, ShowNextWindowTab.LABEL); - } - - run(): Promise { - return this.windowsService.showNextWindowTab().then(() => true); - } -} - -export class MoveWindowTabToNewWindow extends Action { - - static readonly ID = 'workbench.action.moveWindowTabToNewWindow'; - static readonly LABEL = nls.localize('moveWindowTabToNewWindow', "Move Window Tab to New Window"); - - constructor( - _id: string, - _label: string, - @IWindowsService private readonly windowsService: IWindowsService - ) { - super(MoveWindowTabToNewWindow.ID, MoveWindowTabToNewWindow.LABEL); - } - - run(): Promise { - return this.windowsService.moveWindowTabToNewWindow().then(() => true); - } -} - -export class MergeAllWindowTabs extends Action { - - static readonly ID = 'workbench.action.mergeAllWindowTabs'; - static readonly LABEL = nls.localize('mergeAllWindowTabs', "Merge All Windows"); - - constructor( - _id: string, - _label: string, - @IWindowsService private readonly windowsService: IWindowsService - ) { - super(MergeAllWindowTabs.ID, MergeAllWindowTabs.LABEL); - } - - run(): Promise { - return this.windowsService.mergeAllWindowTabs().then(() => true); - } -} - -export class ToggleWindowTabsBar extends Action { - - static readonly ID = 'workbench.action.toggleWindowTabsBar'; - static readonly LABEL = nls.localize('toggleWindowTabsBar', "Toggle Window Tabs Bar"); - - constructor( - _id: string, - _label: string, - @IWindowsService private readonly windowsService: IWindowsService - ) { - super(ToggleWindowTabsBar.ID, ToggleWindowTabsBar.LABEL); - } - - run(): Promise { - return this.windowsService.toggleWindowTabsBar().then(() => true); - } -} +export const ToggleWindowTabsBarHandler: ICommandHandler = function (accessor: ServicesAccessor) { + return accessor.get(IWindowsService).toggleWindowTabsBar(); +}; diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts new file mode 100644 index 0000000000..99e62dee19 --- /dev/null +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -0,0 +1,705 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Registry } from 'vs/platform/registry/common/platform'; +import * as nls from 'vs/nls'; +import * as os from 'os'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; +import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; +import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; +import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; +import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction } from 'vs/workbench/electron-browser/actions/helpActions'; +import { ToggleSharedProcessAction, InspectContextKeysAction, ToggleScreencastModeAction, ToggleDevToolsAction } from 'vs/workbench/electron-browser/actions/developerActions'; +import { ShowAboutDialogAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, OpenRecentAction, ReloadWindowWithExtensionsDisabledAction, NewWindowTabHandler, ReloadWindowAction, ShowPreviousWindowTabHandler, ShowNextWindowTabHandler, MoveWindowTabToNewWindowHandler, MergeWindowTabsHandlerHandler, ToggleWindowTabsBarHandler } from 'vs/workbench/electron-browser/actions/windowActions'; +import { AddRootFolderAction, GlobalRemoveRootFolderAction, OpenWorkspaceAction, SaveWorkspaceAsAction, OpenWorkspaceConfigFileAction, DuplicateWorkspaceInNewWindowAction, OpenFileFolderAction, OpenFileAction, OpenFolderAction, CloseWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { inQuickOpenContext, getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ADD_ROOT_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; +import { SupportsWorkspacesContext, IsMacContext, HasMacNativeTabsContext, IsDevelopmentContext, WorkbenchStateContext, WorkspaceFolderCountContext } from 'vs/workbench/common/contextkeys'; +import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; +import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { LogStorageAction } from 'vs/platform/storage/node/storageService'; + +// {{SQL CARBON EDIT}} +import { InstallVSIXAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; +// {{SQL CARBON EDIT}} - End + +// Actions +(function registerActions(): void { + const registry = Registry.as(Extensions.WorkbenchActions); + + // Actions: File + (function registerFileActions(): void { + const fileCategory = nls.localize('file', "File"); + + if (isMacintosh) { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); + } else { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', fileCategory); + } + + registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRecentAction, OpenRecentAction.ID, OpenRecentAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent...', fileCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'File: Close Workspace', fileCategory); + + const recentFilesPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(inRecentFilesPickerContextKey)); + + const quickOpenNavigateNextInRecentFilesPickerId = 'workbench.action.quickOpenNavigateNextInRecentFilesPicker'; + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: quickOpenNavigateNextInRecentFilesPickerId, + weight: KeybindingWeight.WorkbenchContrib + 50, + handler: getQuickNavigateHandler(quickOpenNavigateNextInRecentFilesPickerId, true), + when: recentFilesPickerContext, + primary: KeyMod.CtrlCmd | KeyCode.KEY_R, + mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } + }); + + const quickOpenNavigatePreviousInRecentFilesPicker = 'workbench.action.quickOpenNavigatePreviousInRecentFilesPicker'; + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: quickOpenNavigatePreviousInRecentFilesPicker, + weight: KeybindingWeight.WorkbenchContrib + 50, + handler: getQuickNavigateHandler(quickOpenNavigatePreviousInRecentFilesPicker, false), + when: recentFilesPickerContext, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R, + mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_R } + }); + })(); + + // Actions: View + (function registerViewActions(): void { + const viewCategory = nls.localize('view', "View"); + + registry.registerWorkbenchAction(new SyncActionDescriptor(ZoomInAction, ZoomInAction.ID, ZoomInAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_EQUAL, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_EQUAL, KeyMod.CtrlCmd | KeyCode.NUMPAD_ADD] }), 'View: Zoom In', viewCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(ZoomOutAction, ZoomOutAction.ID, ZoomOutAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS, KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT], linux: { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT] } }), 'View: Zoom Out', viewCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(ZoomResetAction, ZoomResetAction.ID, ZoomResetAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.NUMPAD_0 }), 'View: Reset Zoom', viewCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleFullScreenAction, ToggleFullScreenAction.ID, ToggleFullScreenAction.LABEL, { primary: KeyCode.F11, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_F } }), 'View: Toggle Full Screen', viewCategory); + })(); + + // Actions: Window + (function registerWindowActions(): void { + registry.registerWorkbenchAction(new SyncActionDescriptor(NewWindowAction, NewWindowAction.ID, NewWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_N }), 'New Window'); + registry.registerWorkbenchAction(new SyncActionDescriptor(CloseCurrentWindowAction, CloseCurrentWindowAction.ID, CloseCurrentWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W }), 'Close Window'); + registry.registerWorkbenchAction(new SyncActionDescriptor(SwitchWindow, SwitchWindow.ID, SwitchWindow.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_W } }), 'Switch Window...'); + registry.registerWorkbenchAction(new SyncActionDescriptor(QuickSwitchWindow, QuickSwitchWindow.ID, QuickSwitchWindow.LABEL), 'Quick Switch Window...'); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.closeWindow', // close the window when the last editor is closed by reusing the same keybinding + weight: KeybindingWeight.WorkbenchContrib, + when: ContextKeyExpr.and(NoEditorsVisibleContext, SingleEditorGroupsContext), + primary: KeyMod.CtrlCmd | KeyCode.KEY_W, + handler: accessor => { + const windowService = accessor.get(IWindowService); + windowService.closeWindow(); + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'workbench.action.quit', + weight: KeybindingWeight.WorkbenchContrib, + handler(accessor: ServicesAccessor) { + const windowsService = accessor.get(IWindowsService); + windowsService.quit(); + }, + when: undefined, + mac: { primary: KeyMod.CtrlCmd | KeyCode.KEY_Q }, + linux: { primary: KeyMod.CtrlCmd | KeyCode.KEY_Q } + }); + })(); + + // Actions: Workspaces + (function registerWorkspaceActions(): void { + const workspacesCategory = nls.localize('workspaces', "Workspaces"); + + registry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory, SupportsWorkspacesContext); + registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory, SupportsWorkspacesContext); + registry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory, SupportsWorkspacesContext); + registry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory); + + CommandsRegistry.registerCommand(OpenWorkspaceConfigFileAction.ID, serviceAccessor => { + serviceAccessor.get(IInstantiationService).createInstance(OpenWorkspaceConfigFileAction, OpenWorkspaceConfigFileAction.ID, OpenWorkspaceConfigFileAction.LABEL).run(); + }); + + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: OpenWorkspaceConfigFileAction.ID, + title: { value: `${workspacesCategory}: ${OpenWorkspaceConfigFileAction.LABEL}`, original: 'Workspaces: Open Workspace Configuration File' }, + }, + when: WorkbenchStateContext.isEqualTo('workspace') + }); + })(); + + // Actions: macOS Native Tabs + (function registerMacOSNativeTabsActions(): void { + if (isMacintosh) { + [ + { handler: NewWindowTabHandler, id: 'workbench.action.newWindowTab', title: { value: nls.localize('newTab', "New Window Tab"), original: 'New Window Tab' } }, + { handler: ShowPreviousWindowTabHandler, id: 'workbench.action.showPreviousWindowTab', title: { value: nls.localize('showPreviousTab', "Show Previous Window Tab"), original: 'Show Previous Window Tab' } }, + { handler: ShowNextWindowTabHandler, id: 'workbench.action.showNextWindowTab', title: { value: nls.localize('showNextWindowTab', "Show Next Window Tab"), original: 'Show Next Window Tab' } }, + { handler: MoveWindowTabToNewWindowHandler, id: 'workbench.action.moveWindowTabToNewWindow', title: { value: nls.localize('moveWindowTabToNewWindow', "Move Window Tab to New Window"), original: 'Move Window Tab to New Window' } }, + { handler: MergeWindowTabsHandlerHandler, id: 'workbench.action.mergeAllWindowTabs', title: { value: nls.localize('mergeAllWindowTabs', "Merge All Windows"), original: 'Merge All Windows' } }, + { handler: ToggleWindowTabsBarHandler, id: 'workbench.action.toggleWindowTabsBar', title: { value: nls.localize('toggleWindowTabsBar', "Toggle Window Tabs Bar"), original: 'Toggle Window Tabs Bar' } } + ].forEach(command => { + CommandsRegistry.registerCommand(command.id, command.handler); + + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command, + when: HasMacNativeTabsContext + }); + }); + } + })(); + + // Actions: Developer + (function registerDeveloperActions(): void { + const developerCategory = nls.localize('developer', "Developer"); + registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSharedProcessAction, ToggleSharedProcessAction.ID, ToggleSharedProcessAction.LABEL), 'Developer: Toggle Shared Process', developerCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(InspectContextKeysAction, InspectContextKeysAction.ID, InspectContextKeysAction.LABEL), 'Developer: Inspect Context Keys', developerCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleScreencastModeAction, ToggleScreencastModeAction.ID, ToggleScreencastModeAction.LABEL), 'Developer: Toggle Mouse Clicks', developerCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowWithExtensionsDisabledAction, ReloadWindowWithExtensionsDisabledAction.ID, ReloadWindowWithExtensionsDisabledAction.LABEL), 'Developer: Reload Window Without Extensions', developerCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(LogStorageAction, LogStorageAction.ID, LogStorageAction.LABEL), 'Developer: Log Storage', developerCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL), 'Developer: Reload Window', developerCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleDevToolsAction, ToggleDevToolsAction.ID, ToggleDevToolsAction.LABEL), 'Developer: Toggle Developer Tools', developerCategory); + + KeybindingsRegistry.registerKeybindingRule({ + id: ReloadWindowAction.ID, + weight: KeybindingWeight.WorkbenchContrib + 50, + when: IsDevelopmentContext, + primary: KeyMod.CtrlCmd | KeyCode.KEY_R + }); + + KeybindingsRegistry.registerKeybindingRule({ + id: ToggleDevToolsAction.ID, + weight: KeybindingWeight.WorkbenchContrib + 50, + when: IsDevelopmentContext, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_I, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_I } + }); + })(); + + // Actions: help + (function registerHelpActions(): void { + const helpCategory = nls.localize('help', "Help"); + + if (KeybindingsReferenceAction.AVAILABLE) { + registry.registerWorkbenchAction(new SyncActionDescriptor(KeybindingsReferenceAction, KeybindingsReferenceAction.ID, KeybindingsReferenceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_R) }), 'Help: Keyboard Shortcuts Reference', helpCategory); + } + + if (OpenDocumentationUrlAction.AVAILABLE) { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDocumentationUrlAction, OpenDocumentationUrlAction.ID, OpenDocumentationUrlAction.LABEL), 'Help: Documentation', helpCategory); + } + + if (OpenIntroductoryVideosUrlAction.AVAILABLE) { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenIntroductoryVideosUrlAction, OpenIntroductoryVideosUrlAction.ID, OpenIntroductoryVideosUrlAction.LABEL), 'Help: Introductory Videos', helpCategory); + } + + if (OpenTipsAndTricksUrlAction.AVAILABLE) { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenTipsAndTricksUrlAction, OpenTipsAndTricksUrlAction.ID, OpenTipsAndTricksUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory); + } + + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenTwitterUrlAction, OpenTwitterUrlAction.ID, OpenTwitterUrlAction.LABEL), 'Help: Join Us on Twitter', helpCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRequestFeatureUrlAction, OpenRequestFeatureUrlAction.ID, OpenRequestFeatureUrlAction.LABEL), 'Help: Search Feature Requests', helpCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenLicenseUrlAction, OpenLicenseUrlAction.ID, OpenLicenseUrlAction.LABEL), 'Help: View License', helpCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenPrivacyStatementUrlAction, OpenPrivacyStatementUrlAction.ID, OpenPrivacyStatementUrlAction.LABEL), 'Help: Privacy Statement', helpCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAboutDialogAction, ShowAboutDialogAction.ID, ShowAboutDialogAction.LABEL), 'Help: About', helpCategory); + })(); +})(); + +// Menu +(function registerMenu(): void { + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '1_new', + command: { + id: NewWindowAction.ID, + title: nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window") + }, + order: 2 + }); + + if (isMacintosh) { + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '2_open', + command: { + id: OpenFileFolderAction.ID, + title: nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...") + }, + order: 1 + }); + } else { + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '2_open', + command: { + id: OpenFileAction.ID, + title: nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...") + }, + order: 1 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '2_open', + command: { + id: OpenFolderAction.ID, + title: nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...") + }, + order: 2 + }); + } + + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '2_open', + command: { + id: OpenWorkspaceAction.ID, + title: nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...") + }, + order: 3, + when: SupportsWorkspacesContext + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + title: nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent"), + submenu: MenuId.MenubarRecentMenu, + group: '2_open', + order: 4 + }); + + // {{SQL CARBON EDIT}} - Add install VSIX menu item + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '5.1_installExtension', + command: { + id: InstallVSIXAction.ID, + title: nls.localize({ key: 'miinstallVsix', comment: ['&& denotes a mnemonic'] }, "Install Extension from VSIX Package") + } + }); + // {{SQL CARBON EDIT}} - End + + // More + MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { + group: 'y_more', + command: { + id: OpenRecentAction.ID, + title: nls.localize({ key: 'miMore', comment: ['&& denotes a mnemonic'] }, "&&More...") + }, + order: 1 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '3_workspace', + command: { + id: ADD_ROOT_FOLDER_COMMAND_ID, + title: nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...") + }, + order: 1, + when: SupportsWorkspacesContext + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '3_workspace', + command: { + id: SaveWorkspaceAsAction.ID, + title: nls.localize('miSaveWorkspaceAs', "Save Workspace As...") + }, + order: 2, + when: SupportsWorkspacesContext + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + title: nls.localize({ key: 'miPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences"), + submenu: MenuId.MenubarPreferencesMenu, + group: '5_autosave', + order: 2, + when: IsMacContext.toNegated() + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '6_close', + command: { + id: CloseWorkspaceAction.ID, + title: nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), + precondition: WorkspaceFolderCountContext.notEqualsTo('0') + }, + order: 3, + when: WorkbenchStateContext.notEqualsTo('workspace') + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '6_close', + command: { + id: CloseWorkspaceAction.ID, + title: nls.localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace") + }, + order: 3, + when: WorkbenchStateContext.isEqualTo('workspace') + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '6_close', + command: { + id: CloseCurrentWindowAction.ID, + title: nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Clos&&e Window") + }, + order: 4 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: 'z_Exit', + command: { + id: 'workbench.action.quit', + title: nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit") + }, + order: 1, + when: IsMacContext.toNegated() + }); + + // Appereance menu + MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { + group: '2_appearance', + title: nls.localize({ key: 'miAppearance', comment: ['&& denotes a mnemonic'] }, "&&Appearance"), + submenu: MenuId.MenubarAppearanceMenu, + order: 1 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '1_toggle_view', + command: { + id: ToggleFullScreenAction.ID, + title: nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen") + }, + order: 1 + }); + + // Zoom + + MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '3_zoom', + command: { + id: ZoomInAction.ID, + title: nls.localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom In") + }, + order: 1 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '3_zoom', + command: { + id: ZoomOutAction.ID, + title: nls.localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "&&Zoom Out") + }, + order: 2 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + group: '3_zoom', + command: { + id: ZoomResetAction.ID, + title: nls.localize({ key: 'miZoomReset', comment: ['&& denotes a mnemonic'] }, "&&Reset Zoom") + }, + order: 3 + }); + + // Help + + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '1_welcome', + command: { + id: 'workbench.action.openDocumentationUrl', + title: nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation") + }, + order: 3 + }); + + /* // {{SQL CARBON EDIT}} - Disable unused menu item + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '1_welcome', + command: { + id: 'update.showCurrentReleaseNotes', + title: nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes") + }, + order: 4 + }); + + // Reference + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '2_reference', + command: { + id: 'workbench.action.keybindingsReference', + title: nls.localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference") + }, + order: 1 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '2_reference', + command: { + id: 'workbench.action.openIntroductoryVideosUrl', + title: nls.localize({ key: 'miIntroductoryVideos', comment: ['&& denotes a mnemonic'] }, "Introductory &&Videos") + }, + order: 2 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '2_reference', + command: { + id: 'workbench.action.openTipsAndTricksUrl', + title: nls.localize({ key: 'miTipsAndTricks', comment: ['&& denotes a mnemonic'] }, "Tips and Tri&&cks") + }, + order: 3 + }); + + // Feedback + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '3_feedback', + command: { + id: 'workbench.action.openTwitterUrl', + title: nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join Us on Twitter") + }, + order: 1 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '3_feedback', + command: { + id: 'workbench.action.openRequestFeatureUrl', + title: nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests") + }, + order: 2 + }); + */ + + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '3_feedback', + command: { + id: 'workbench.action.openIssueReporter', + title: nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue") + }, + order: 3 + }); + + // Legal + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '4_legal', + command: { + id: 'workbench.action.openLicenseUrl', + title: nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License") + }, + order: 1 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '4_legal', + command: { + id: 'workbench.action.openPrivacyStatementUrl', + title: nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "Privac&&y Statement") + }, + order: 2 + }); + + // Tools + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '5_tools', + command: { + id: 'workbench.action.toggleDevTools', + title: nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools") + }, + order: 1 + }); + + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '5_tools', + command: { + id: 'workbench.action.openProcessExplorer', + title: nls.localize({ key: 'miOpenProcessExplorerer', comment: ['&& denotes a mnemonic'] }, "Open &&Process Explorer") + }, + order: 2 + }); + + // About + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: 'z_about', + command: { + id: 'workbench.action.showAboutDialog', + title: nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About") + }, + order: 1, + when: IsMacContext.toNegated() + }); +})(); + +// Configuration +(function registerConfiguration(): void { + const registry = Registry.as(ConfigurationExtensions.Configuration); + + // Window + registry.registerConfiguration({ + 'id': 'window', + 'order': 8, + 'title': nls.localize('windowConfigurationTitle', "Window"), + 'type': 'object', + 'properties': { + 'window.openFilesInNewWindow': { + 'type': 'string', + 'enum': ['on', 'off', 'default'], + 'enumDescriptions': [ + nls.localize('window.openFilesInNewWindow.on', "Files will open in a new window."), + nls.localize('window.openFilesInNewWindow.off', "Files will open in the window with the files' folder open or the last active window."), + isMacintosh ? + nls.localize('window.openFilesInNewWindow.defaultMac', "Files will open in the window with the files' folder open or the last active window unless opened via the Dock or from Finder.") : + nls.localize('window.openFilesInNewWindow.default', "Files will open in a new window unless picked from within the application (e.g. via the File menu).") + ], + 'default': 'off', + 'scope': ConfigurationScope.APPLICATION, + 'markdownDescription': + isMacintosh ? + nls.localize('openFilesInNewWindowMac', "Controls whether files should open in a new window. \nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") : + nls.localize('openFilesInNewWindow', "Controls whether files should open in a new window.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") + }, + 'window.openFoldersInNewWindow': { + 'type': 'string', + 'enum': ['on', 'off', 'default'], + 'enumDescriptions': [ + nls.localize('window.openFoldersInNewWindow.on', "Folders will open in a new window."), + nls.localize('window.openFoldersInNewWindow.off', "Folders will replace the last active window."), + nls.localize('window.openFoldersInNewWindow.default', "Folders will open in a new window unless a folder is picked from within the application (e.g. via the File menu).") + ], + 'default': 'default', + 'scope': ConfigurationScope.APPLICATION, + 'markdownDescription': nls.localize('openFoldersInNewWindow', "Controls whether folders should open in a new window or replace the last active window.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") + }, + 'window.openWithoutArgumentsInNewWindow': { + 'type': 'string', + 'enum': ['on', 'off'], + 'enumDescriptions': [ + nls.localize('window.openWithoutArgumentsInNewWindow.on', "Open a new empty window."), + nls.localize('window.openWithoutArgumentsInNewWindow.off', "Focus the last active running instance.") + ], + 'default': isMacintosh ? 'off' : 'on', + 'scope': ConfigurationScope.APPLICATION, + 'markdownDescription': nls.localize('openWithoutArgumentsInNewWindow', "Controls whether a new empty window should open when starting a second instance without arguments or if the last running instance should get focus.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") + }, + 'window.restoreWindows': { + 'type': 'string', + 'enum': ['all', 'folders', 'one', 'none'], + 'enumDescriptions': [ + nls.localize('window.reopenFolders.all', "Reopen all windows."), + nls.localize('window.reopenFolders.folders', "Reopen all folders. Empty workspaces will not be restored."), + nls.localize('window.reopenFolders.one', "Reopen the last active window."), + nls.localize('window.reopenFolders.none', "Never reopen a window. Always start with an empty one.") + ], + 'default': 'one', + 'scope': ConfigurationScope.APPLICATION, + 'description': nls.localize('restoreWindows', "Controls how windows are being reopened after a restart.") + }, + 'window.restoreFullscreen': { + 'type': 'boolean', + 'default': false, + 'scope': ConfigurationScope.APPLICATION, + 'description': nls.localize('restoreFullscreen', "Controls whether a window should restore to full screen mode if it was exited in full screen mode.") + }, + 'window.zoomLevel': { + 'type': 'number', + 'default': 0, + 'description': nls.localize('zoomLevel', "Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity.") + }, + 'window.newWindowDimensions': { + 'type': 'string', + 'enum': ['default', 'inherit', 'maximized', 'fullscreen'], + 'enumDescriptions': [ + nls.localize('window.newWindowDimensions.default', "Open new windows in the center of the screen."), + nls.localize('window.newWindowDimensions.inherit', "Open new windows with same dimension as last active one."), + nls.localize('window.newWindowDimensions.maximized', "Open new windows maximized."), + nls.localize('window.newWindowDimensions.fullscreen', "Open new windows in full screen mode.") + ], + 'default': 'default', + 'scope': ConfigurationScope.APPLICATION, + 'description': nls.localize('newWindowDimensions', "Controls the dimensions of opening a new window when at least one window is already opened. Note that this setting does not have an impact on the first window that is opened. The first window will always restore the size and location as you left it before closing.") + }, + 'window.closeWhenEmpty': { + 'type': 'boolean', + 'default': false, + 'description': nls.localize('closeWhenEmpty', "Controls whether closing the last editor should also close the window. This setting only applies for windows that do not show folders.") + }, + 'window.menuBarVisibility': { + 'type': 'string', + 'enum': ['default', 'visible', 'toggle', 'hidden'], + 'enumDescriptions': [ + nls.localize('window.menuBarVisibility.default', "Menu is only hidden in full screen mode."), + nls.localize('window.menuBarVisibility.visible', "Menu is always visible even in full screen mode."), + nls.localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed via Alt key."), + nls.localize('window.menuBarVisibility.hidden', "Menu is always hidden.") + ], + 'default': 'default', + 'scope': ConfigurationScope.APPLICATION, + 'description': nls.localize('menuBarVisibility', "Control the visibility of the menu bar. A setting of 'toggle' means that the menu bar is hidden and a single press of the Alt key will show it. By default, the menu bar will be visible, unless the window is full screen."), + 'included': isWindows || isLinux + }, + 'window.enableMenuBarMnemonics': { + 'type': 'boolean', + 'default': true, + 'scope': ConfigurationScope.APPLICATION, + 'description': nls.localize('enableMenuBarMnemonics', "If enabled, the main menus can be opened via Alt-key shortcuts. Disabling mnemonics allows to bind these Alt-key shortcuts to editor commands instead."), + 'included': isWindows || isLinux + }, + 'window.autoDetectHighContrast': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('autoDetectHighContrast', "If enabled, will automatically change to high contrast theme if Windows is using a high contrast theme, and to dark theme when switching away from a Windows high contrast theme."), + 'included': isWindows + }, + 'window.doubleClickIconToClose': { + 'type': 'boolean', + 'default': false, + 'scope': ConfigurationScope.APPLICATION, + 'markdownDescription': nls.localize('window.doubleClickIconToClose', "If enabled, double clicking the application icon in the title bar will close the window and the window cannot be dragged by the icon. This setting only has an effect when `#window.titleBarStyle#` is set to `custom`.") + }, + 'window.titleBarStyle': { + 'type': 'string', + 'enum': ['native', 'custom'], + 'default': isLinux ? 'native' : 'custom', + 'scope': ConfigurationScope.APPLICATION, + 'description': nls.localize('titleBarStyle', "Adjust the appearance of the window title bar. On Linux and Windows, this setting also affects the application and context menu appearances. Changes require a full restart to apply.") + }, + 'window.nativeTabs': { + 'type': 'boolean', + 'default': false, + 'scope': ConfigurationScope.APPLICATION, + 'description': nls.localize('window.nativeTabs', "Enables macOS Sierra window tabs. Note that changes require a full restart to apply and that native tabs will disable a custom title bar style if configured."), + 'included': isMacintosh && parseFloat(os.release()) >= 16 // Minimum: macOS Sierra (10.12.x = darwin 16.x) + }, + 'window.nativeFullScreen': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('window.nativeFullScreen', "Controls if native full-screen should be used on macOS. Disable this option to prevent macOS from creating a new space when going full-screen."), + 'included': isMacintosh + }, + 'window.clickThroughInactive': { + 'type': 'boolean', + 'default': true, + 'scope': ConfigurationScope.APPLICATION, + 'description': nls.localize('window.clickThroughInactive', "If enabled, clicking on an inactive window will both activate the window and trigger the element under the mouse if it is clickable. If disabled, clicking anywhere on an inactive window will activate it only and a second click is required on the element."), + 'included': isMacintosh + } + } + }); + + // Telemetry + registry.registerConfiguration({ + 'id': 'telemetry', + 'order': 110, + title: nls.localize('telemetryConfigurationTitle', "Telemetry"), + 'type': 'object', + 'properties': { + 'telemetry.enableCrashReporter': { + 'type': 'boolean', + 'description': nls.localize('telemetry.enableCrashReporting', "Enable crash reports to be sent to a Microsoft online service. \nThis option requires restart to take effect."), + 'default': true, + 'tags': ['usesOnlineServices'] + } + } + }); +})(); diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 3fca22f9eb..1ab0af6979 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -3,286 +3,294 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as nls from 'vs/nls'; -import * as perf from 'vs/base/common/performance'; -import { Shell } from 'vs/workbench/electron-browser/shell'; -import * as browser from 'vs/base/browser/browser'; -import { domContentLoaded } from 'vs/base/browser/dom'; +import * as fs from 'fs'; +import * as gracefulFs from 'graceful-fs'; +import { createHash } from 'crypto'; +import { importEntries, mark } from 'vs/base/common/performance'; +import { Workbench } from 'vs/workbench/browser/workbench'; +import { ElectronWindow } from 'vs/workbench/electron-browser/window'; +import { setZoomLevel, setZoomFactor, setFullscreen } from 'vs/base/browser/browser'; +import { domContentLoaded, addDisposableListener, EventType, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { onUnexpectedError } from 'vs/base/common/errors'; -import * as comparer from 'vs/base/common/comparers'; -import * as platform from 'vs/base/common/platform'; +import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { URI as uri } from 'vs/base/common/uri'; -import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; +import { WorkspaceService, DefaultConfigurationExportHelper } from 'vs/workbench/services/configuration/node/configurationService'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { stat } from 'vs/base/node/pfs'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; -import * as gracefulFs from 'graceful-fs'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/keybindingService'; -import { IWindowConfiguration, IWindowsService } from 'vs/platform/windows/common/windows'; -import { WindowsChannelClient } from 'vs/platform/windows/node/windowsIpc'; +import { IWindowConfiguration, IWindowService } from 'vs/platform/windows/common/windows'; +import { WindowService } from 'vs/platform/windows/electron-browser/windowService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { Client as ElectronIPCClient } from 'vs/base/parts/ipc/electron-browser/ipc.electron-browser'; import { webFrame } from 'electron'; -import { UpdateChannelClient } from 'vs/platform/update/node/updateIpc'; -import { IUpdateService } from 'vs/platform/update/common/update'; -import { URLHandlerChannel, URLServiceChannelClient } from 'vs/platform/url/node/urlIpc'; -import { IURLService } from 'vs/platform/url/common/url'; -import { WorkspacesChannelClient } from 'vs/platform/workspaces/node/workspacesIpc'; -import { IWorkspacesService, ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, IMultiFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; +import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, IMultiFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { createSpdLogService } from 'vs/platform/log/node/spdlogService'; -import * as fs from 'fs'; import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log'; import { StorageService } from 'vs/platform/storage/node/storageService'; -import { IssueChannelClient } from 'vs/platform/issue/node/issueIpc'; -import { IIssueService } from 'vs/platform/issue/common/issue'; import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/node/logIpc'; -import { RelayURLService } from 'vs/platform/url/common/urlService'; -import { MenubarChannelClient } from 'vs/platform/menubar/node/menubarIpc'; -import { IMenubarService } from 'vs/platform/menubar/common/menubar'; import { Schemas } from 'vs/base/common/network'; import { sanitizeFilePath } from 'vs/base/node/extfs'; -import { basename } from 'path'; -import { createHash } from 'crypto'; -import { IdleValue } from 'vs/base/common/async'; -import { setGlobalLeakWarningThreshold } from 'vs/base/common/event'; +import { basename } from 'vs/base/common/path'; import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver'; +import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; -gracefulFs.gracefulify(fs); // enable gracefulFs +class CodeRendererMain extends Disposable { -export function startup(configuration: IWindowConfiguration): Promise { + private workbench: Workbench; - // Massage configuration file URIs - revive(configuration); + constructor(private readonly configuration: IWindowConfiguration) { + super(); - // Setup perf - perf.importEntries(configuration.perfEntries); - - // Configure emitter leak warning threshold - setGlobalLeakWarningThreshold(175); - - // Browser config - browser.setZoomFactor(webFrame.getZoomFactor()); // Ensure others can listen to zoom level changes - browser.setZoomLevel(webFrame.getZoomLevel(), true /* isTrusted */); // Can be trusted because we are not setting it ourselves (https://github.com/Microsoft/vscode/issues/26151) - browser.setFullscreen(!!configuration.fullscreen); - browser.setAccessibilitySupport(configuration.accessibilitySupport ? platform.AccessibilitySupport.Enabled : platform.AccessibilitySupport.Disabled); - - // Keyboard support - KeyboardMapperFactory.INSTANCE._onKeyboardLayoutChanged(); - - // Setup Intl for comparers - comparer.setFileNameComparer(new IdleValue(() => { - const collator = new Intl.Collator(undefined, { numeric: true, sensitivity: 'base' }); - return { - collator: collator, - collatorIsNumeric: collator.resolvedOptions().numeric - }; - })); - - // Open workbench - return openWorkbench(configuration); -} - -function revive(workbench: IWindowConfiguration) { - if (workbench.folderUri) { - workbench.folderUri = uri.revive(workbench.folderUri); + this.init(); } - const filesToWaitPaths = workbench.filesToWait && workbench.filesToWait.paths; - [filesToWaitPaths, workbench.filesToOpen, workbench.filesToCreate, workbench.filesToDiff].forEach(paths => { - if (Array.isArray(paths)) { - paths.forEach(path => { - if (path.fileUri) { - path.fileUri = uri.revive(path.fileUri); - } - }); + private init(): void { + + // Enable gracefulFs + gracefulFs.gracefulify(fs); + + // Massage configuration file URIs + this.reviveUris(); + + // Setup perf + importEntries(this.configuration.perfEntries); + + // Browser config + setZoomFactor(webFrame.getZoomFactor()); // Ensure others can listen to zoom level changes + setZoomLevel(webFrame.getZoomLevel(), true /* isTrusted */); // Can be trusted because we are not setting it ourselves (https://github.com/Microsoft/vscode/issues/26151) + setFullscreen(!!this.configuration.fullscreen); + + // Keyboard support + KeyboardMapperFactory.INSTANCE._onKeyboardLayoutChanged(); + } + + private reviveUris() { + if (this.configuration.folderUri) { + this.configuration.folderUri = uri.revive(this.configuration.folderUri); + } + if (this.configuration.workspace) { + this.configuration.workspace = reviveWorkspaceIdentifier(this.configuration.workspace); } - }); -} -function openWorkbench(configuration: IWindowConfiguration): Promise { - const mainProcessClient = new ElectronIPCClient(`window:${configuration.windowId}`); - const mainServices = createMainProcessServices(mainProcessClient); - - const environmentService = new EnvironmentService(configuration, configuration.execPath); - - const logService = createLogService(mainProcessClient, configuration, environmentService); - logService.trace('openWorkbench configuration', JSON.stringify(configuration)); - - // Resolve a workspace payload that we can get the workspace ID from - return createWorkspaceInitializationPayload(configuration, environmentService).then(payload => { - - return Promise.all([ - - // Create and initialize workspace/configuration service - createWorkspaceService(payload, environmentService, logService), - - // Create and initialize storage service - createStorageService(payload, environmentService, logService, mainProcessClient) - ]).then(services => { - const workspaceService = services[0]; - const storageService = services[1]; - - return domContentLoaded().then(() => { - perf.mark('willStartWorkbench'); - - // Create Shell - const shell = new Shell(document.body, { - contextService: workspaceService, - configurationService: workspaceService, - environmentService, - logService, - storageService - }, mainServices, mainProcessClient, configuration); - - // Gracefully Shutdown Storage - shell.onWillShutdown(event => { - event.join(storageService.close()); - }); - - // Open Shell - shell.open(); - - // Inform user about loading issues from the loader - (self).require.config({ - onError: err => { - if (err.errorCode === 'load') { - shell.onUnexpectedError(new Error(nls.localize('loaderErrorNative', "Failed to load a required file. Please restart the application to try again. Details: {0}", JSON.stringify(err)))); - } + const filesToWaitPaths = this.configuration.filesToWait && this.configuration.filesToWait.paths; + [filesToWaitPaths, this.configuration.filesToOpen, this.configuration.filesToCreate, this.configuration.filesToDiff].forEach(paths => { + if (Array.isArray(paths)) { + paths.forEach(path => { + if (path.fileUri) { + path.fileUri = uri.revive(path.fileUri); } }); + } + }); + } + + open(): Promise { + return this.initServices().then(services => { + + return domContentLoaded().then(() => { + mark('willStartWorkbench'); + + // Create Workbench + this.workbench = new Workbench(document.body, services.serviceCollection, services.logService); + + // Layout + this._register(addDisposableListener(window, EventType.RESIZE, e => this.onWindowResize(e, true))); + + // Workbench Lifecycle + this._register(this.workbench.onShutdown(() => this.dispose())); + this._register(this.workbench.onWillShutdown(event => event.join(services.storageService.close()))); + + // Startup + const instantiationService = this.workbench.startup(); + + // Window + this._register(instantiationService.createInstance(ElectronWindow)); + + // Driver + if (this.configuration.driver) { + instantiationService.invokeFunction(accessor => registerWindowDriver(accessor).then(disposable => this._register(disposable))); + } + + // Config Exporter + if (this.configuration['export-default-configuration']) { + instantiationService.createInstance(DefaultConfigurationExportHelper); + } + + // Logging + services.logService.trace('workbench configuration', JSON.stringify(this.configuration)); }); }); - }); -} - -function createWorkspaceInitializationPayload(configuration: IWindowConfiguration, environmentService: EnvironmentService): Promise { - - // Multi-root workspace - if (configuration.workspace) { - return Promise.resolve(configuration.workspace as IMultiFolderWorkspaceInitializationPayload); } - // Single-folder workspace - let workspaceInitializationPayload: Promise = Promise.resolve(); - if (configuration.folderUri) { - workspaceInitializationPayload = resolveSingleFolderWorkspaceInitializationPayload(configuration.folderUri); - } - - return workspaceInitializationPayload.then(payload => { - - // Fallback to empty workspace if we have no payload yet. - if (!payload) { - let id: string; - if (configuration.backupPath) { - id = basename(configuration.backupPath); // we know the backupPath must be a unique path so we leverage its name as workspace ID - } else if (environmentService.isExtensionDevelopment) { - id = 'ext-dev'; // extension development window never stores backups and is a singleton - } else { - return Promise.reject(new Error('Unexpected window configuration without backupPath')); + private onWindowResize(e: Event, retry: boolean): void { + if (e.target === window) { + if (window.document && window.document.body && window.document.body.clientWidth === 0) { + // TODO@Ben this is an electron issue on macOS when simple fullscreen is enabled + // where for some reason the window clientWidth is reported as 0 when switching + // between simple fullscreen and normal screen. In that case we schedule the layout + // call at the next animation frame once, in the hope that the dimensions are + // proper then. + if (retry) { + scheduleAtNextAnimationFrame(() => this.onWindowResize(e, false)); + } + return; } - payload = { id } as IEmptyWorkspaceInitializationPayload; + this.workbench.layout(); } - - return payload; - }); -} - -function resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier): Promise { - - // Return early the folder is not local - if (folderUri.scheme !== Schemas.file) { - return Promise.resolve({ id: createHash('md5').update(folderUri.toString()).digest('hex'), folder: folderUri }); } - function computeLocalDiskFolderId(folder: uri, stat: fs.Stats): string { - let ctime: number; - if (platform.isLinux) { - ctime = stat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead! - } else if (platform.isMacintosh) { - ctime = stat.birthtime.getTime(); // macOS: birthtime is fine to use as is - } else if (platform.isWindows) { - if (typeof stat.birthtimeMs === 'number') { - ctime = Math.floor(stat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897) - } else { - ctime = stat.birthtime.getTime(); + private initServices(): Promise<{ serviceCollection: ServiceCollection, logService: ILogService, storageService: StorageService }> { + const serviceCollection = new ServiceCollection(); + + // Main Process + const mainProcessService = this._register(new MainProcessService(this.configuration.windowId)); + serviceCollection.set(IMainProcessService, mainProcessService); + + // Window + serviceCollection.set(IWindowService, new SyncDescriptor(WindowService, [this.configuration])); + + // Environment + const environmentService = new EnvironmentService(this.configuration, this.configuration.execPath); + serviceCollection.set(IEnvironmentService, environmentService); + + // Log + const logService = this._register(this.createLogService(mainProcessService, environmentService)); + serviceCollection.set(ILogService, logService); + + return this.resolveWorkspaceInitializationPayload(environmentService).then(payload => Promise.all([ + this.createWorkspaceService(payload, environmentService, logService).then(service => { + + // Workspace + serviceCollection.set(IWorkspaceContextService, service); + + // Configuration + serviceCollection.set(IConfigurationService, service); + + return service; + }), + + this.createStorageService(payload, environmentService, logService, mainProcessService).then(service => { + + // Storage + serviceCollection.set(IStorageService, service); + + return service; + }) + ]).then(services => ({ serviceCollection, logService, storageService: services[1] }))); + } + + private resolveWorkspaceInitializationPayload(environmentService: EnvironmentService): Promise { + + // Multi-root workspace + if (this.configuration.workspace) { + return Promise.resolve(this.configuration.workspace as IMultiFolderWorkspaceInitializationPayload); + } + + // Single-folder workspace + let workspaceInitializationPayload: Promise = Promise.resolve(undefined); + if (this.configuration.folderUri) { + workspaceInitializationPayload = this.resolveSingleFolderWorkspaceInitializationPayload(this.configuration.folderUri); + } + + return workspaceInitializationPayload.then(payload => { + + // Fallback to empty workspace if we have no payload yet. + if (!payload) { + let id: string; + if (this.configuration.backupPath) { + id = basename(this.configuration.backupPath); // we know the backupPath must be a unique path so we leverage its name as workspace ID + } else if (environmentService.isExtensionDevelopment) { + id = 'ext-dev'; // extension development window never stores backups and is a singleton + } else { + return Promise.reject(new Error('Unexpected window configuration without backupPath')); + } + + payload = { id } as IEmptyWorkspaceInitializationPayload; } - } - // we use the ctime as extra salt to the ID so that we catch the case of a folder getting - // deleted and recreated. in that case we do not want to carry over previous state - return createHash('md5').update(folder.fsPath).update(ctime ? String(ctime) : '').digest('hex'); + return payload; + }); } - // For local: ensure path is absolute and exists - const sanitizedFolderPath = sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd()); - return stat(sanitizedFolderPath).then(stat => { - const sanitizedFolderUri = uri.file(sanitizedFolderPath); - return { - id: computeLocalDiskFolderId(sanitizedFolderUri, stat), - folder: sanitizedFolderUri - } as ISingleFolderWorkspaceInitializationPayload; - }, error => onUnexpectedError(error)); + private resolveSingleFolderWorkspaceInitializationPayload(folderUri: ISingleFolderWorkspaceIdentifier): Promise { + + // Return early the folder is not local + if (folderUri.scheme !== Schemas.file) { + return Promise.resolve({ id: createHash('md5').update(folderUri.toString()).digest('hex'), folder: folderUri }); + } + + function computeLocalDiskFolderId(folder: uri, stat: fs.Stats): string { + let ctime: number | undefined; + if (isLinux) { + ctime = stat.ino; // Linux: birthtime is ctime, so we cannot use it! We use the ino instead! + } else if (isMacintosh) { + ctime = stat.birthtime.getTime(); // macOS: birthtime is fine to use as is + } else if (isWindows) { + if (typeof stat.birthtimeMs === 'number') { + ctime = Math.floor(stat.birthtimeMs); // Windows: fix precision issue in node.js 8.x to get 7.x results (see https://github.com/nodejs/node/issues/19897) + } else { + ctime = stat.birthtime.getTime(); + } + } + + // we use the ctime as extra salt to the ID so that we catch the case of a folder getting + // deleted and recreated. in that case we do not want to carry over previous state + return createHash('md5').update(folder.fsPath).update(ctime ? String(ctime) : '').digest('hex'); + } + + // For local: ensure path is absolute and exists + const sanitizedFolderPath = sanitizeFilePath(folderUri.fsPath, process.env['VSCODE_CWD'] || process.cwd()); + return stat(sanitizedFolderPath).then(stat => { + const sanitizedFolderUri = uri.file(sanitizedFolderPath); + return { + id: computeLocalDiskFolderId(sanitizedFolderUri, stat), + folder: sanitizedFolderUri + } as ISingleFolderWorkspaceInitializationPayload; + }, error => onUnexpectedError(error)); + } + + private createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService, logService: ILogService): Promise { + const workspaceService = new WorkspaceService(environmentService); + + return workspaceService.initialize(payload).then(() => workspaceService, error => { + onUnexpectedError(error); + logService.error(error); + + return workspaceService; + }); + } + + private createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService, logService: ILogService, mainProcessService: IMainProcessService): Promise { + const globalStorageDatabase = new GlobalStorageDatabaseChannelClient(mainProcessService.getChannel('storage')); + const storageService = new StorageService(globalStorageDatabase, logService, environmentService); + + return storageService.initialize(payload).then(() => storageService, error => { + onUnexpectedError(error); + logService.error(error); + + return storageService; + }); + } + + private createLogService(mainProcessService: IMainProcessService, environmentService: IEnvironmentService): ILogService { + const spdlogService = createSpdLogService(`renderer${this.configuration.windowId}`, this.configuration.logLevel, environmentService.logsPath); + const consoleLogService = new ConsoleLogService(this.configuration.logLevel); + const logService = new MultiplexLogService([consoleLogService, spdlogService]); + const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel')); + + return new FollowerLogService(logLevelClient, logService); + } } -function createWorkspaceService(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService, logService: ILogService): Promise { - const workspaceService = new WorkspaceService(environmentService); +export function main(configuration: IWindowConfiguration): Promise { + const renderer = new CodeRendererMain(configuration); - return workspaceService.initialize(payload).then(() => workspaceService, error => { - onUnexpectedError(error); - logService.error(error); - - return workspaceService; - }); -} - -function createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IEnvironmentService, logService: ILogService, mainProcessClient: ElectronIPCClient): Promise { - const globalStorageDatabase = new GlobalStorageDatabaseChannelClient(mainProcessClient.getChannel('storage')); - const storageService = new StorageService(globalStorageDatabase, logService, environmentService); - - return storageService.initialize(payload).then(() => storageService, error => { - onUnexpectedError(error); - logService.error(error); - - return storageService; - }); -} - -function createLogService(mainProcessClient: ElectronIPCClient, configuration: IWindowConfiguration, environmentService: IEnvironmentService): ILogService { - const spdlogService = createSpdLogService(`renderer${configuration.windowId}`, configuration.logLevel, environmentService.logsPath); - const consoleLogService = new ConsoleLogService(configuration.logLevel); - const logService = new MultiplexLogService([consoleLogService, spdlogService]); - const logLevelClient = new LogLevelSetterChannelClient(mainProcessClient.getChannel('loglevel')); - - return new FollowerLogService(logLevelClient, logService); -} - -function createMainProcessServices(mainProcessClient: ElectronIPCClient): ServiceCollection { - const serviceCollection = new ServiceCollection(); - - const windowsChannel = mainProcessClient.getChannel('windows'); - serviceCollection.set(IWindowsService, new WindowsChannelClient(windowsChannel)); - - const updateChannel = mainProcessClient.getChannel('update'); - serviceCollection.set(IUpdateService, new SyncDescriptor(UpdateChannelClient, [updateChannel])); - - const urlChannel = mainProcessClient.getChannel('url'); - const mainUrlService = new URLServiceChannelClient(urlChannel); - const urlService = new RelayURLService(mainUrlService); - serviceCollection.set(IURLService, urlService); - - const urlHandlerChannel = new URLHandlerChannel(urlService); - mainProcessClient.registerChannel('urlHandler', urlHandlerChannel); - - const issueChannel = mainProcessClient.getChannel('issue'); - serviceCollection.set(IIssueService, new SyncDescriptor(IssueChannelClient, [issueChannel])); - - const menubarChannel = mainProcessClient.getChannel('menubar'); - serviceCollection.set(IMenubarService, new SyncDescriptor(MenubarChannelClient, [menubarChannel])); - - const workspacesChannel = mainProcessClient.getChannel('workspaces'); - serviceCollection.set(IWorkspacesService, new WorkspacesChannelClient(workspacesChannel)); - - return serviceCollection; + return renderer.open(); } diff --git a/src/vs/workbench/electron-browser/media/shell.css b/src/vs/workbench/electron-browser/media/shell.css deleted file mode 100644 index 4013d7a868..0000000000 --- a/src/vs/workbench/electron-browser/media/shell.css +++ /dev/null @@ -1,210 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-shell { - height: 100%; - width: 100%; - margin: 0; - padding: 0; - overflow: hidden; - font-size: 11px; - user-select: none; -} - -/* Font Families (with CJK support) */ - -/* mac */ -.monaco-shell.mac, -.monaco-shell.mac .monaco-menu-container .monaco-menu { font-family: -apple-system, BlinkMacSystemFont, sans-serif; } -.monaco-shell.mac:lang(zh-Hans), -.monaco-shell.mac:lang(zh-Hans) .monaco-menu-container .monaco-menu { font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB", sans-serif; } -.monaco-shell.mac:lang(zh-Hant), -.monaco-shell.mac:lang(zh-Hant) .monaco-menu-container .monaco-menu { font-family: -apple-system, BlinkMacSystemFont, "PingFang TC", sans-serif; } -.monaco-shell.mac:lang(ja), -.monaco-shell.mac:lang(ja) .monaco-menu-container .monaco-menu { font-family: -apple-system, BlinkMacSystemFont, "Hiragino Kaku Gothic Pro", sans-serif; } -.monaco-shell.mac:lang(ko), -.monaco-shell.mac:lang(ko) .monaco-menu-container .monaco-menu { font-family: -apple-system, BlinkMacSystemFont, "Nanum Gothic", "Apple SD Gothic Neo", "AppleGothic", sans-serif; } -/* windows */ -.monaco-shell.windows, -.monaco-shell.windows .monaco-menu-container .monaco-menu { font-family: "Segoe WPC", "Segoe UI", sans-serif; } -.monaco-shell.windows:lang(zh-Hans), -.monaco-shell.windows:lang(zh-Hans) .monaco-menu-container .monaco-menu { font-family: "Segoe WPC", "Segoe UI", "Microsoft YaHei", sans-serif; } -.monaco-shell.windows:lang(zh-Hant), -.monaco-shell.windows:lang(zh-Hant) .monaco-menu-container .monaco-menu { font-family: "Segoe WPC", "Segoe UI", "Microsoft Jhenghei", sans-serif; } -.monaco-shell.windows:lang(ja), -.monaco-shell.windows:lang(ja) .monaco-menu-container .monaco-menu { font-family: "Segoe WPC", "Segoe UI", "Meiryo", sans-serif; } -.monaco-shell.windows:lang(ko), -.monaco-shell.windows:lang(ko) .monaco-menu-container .monaco-menu { font-family: "Segoe WPC", "Segoe UI", "Malgun Gothic", "Dotom", sans-serif; } -/* linux */ -.monaco-shell.linux, -.monaco-shell.linux .monaco-menu-container .monaco-menu { font-family: "Ubuntu", "Droid Sans", sans-serif; } -.monaco-shell.linux:lang(zh-Hans), -.monaco-shell.linux:lang(zh-Hans) .monaco-menu-container .monaco-menu { font-family: "Ubuntu", "Droid Sans", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; } -.monaco-shell.linux:lang(zh-Hant), -.monaco-shell.linux:lang(zh-Hant) .monaco-menu-container .monaco-menu { font-family: "Ubuntu", "Droid Sans", "Source Han Sans TC", "Source Han Sans TW", "Source Han Sans", sans-serif; } -.monaco-shell.linux:lang(ja), -.monaco-shell.linux:lang(ja) .monaco-menu-container .monaco-menu { font-family: "Ubuntu", "Droid Sans", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", sans-serif; } -.monaco-shell.linux:lang(ko), -.monaco-shell.linux:lang(ko) .monaco-menu-container .monaco-menu { font-family: "Ubuntu", "Droid Sans", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } - -@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } - -.monaco-shell img { - border: 0; -} - -.monaco-shell label { - cursor: pointer; -} - -.monaco-shell a { - text-decoration: none; -} - -.monaco-shell a:active { - color: inherit; - background-color: inherit; -} - -.monaco-shell a.plain { - color: inherit; - text-decoration: none; -} - -.monaco-shell a.plain:hover, -.monaco-shell a.plain.hover { - color: inherit; - text-decoration: none; -} - -.monaco-shell input { - color: inherit; - font-family: inherit; - font-size: 100%; -} - -.monaco-shell select { - font-family: inherit; -} - -.monaco-shell .pointer { - cursor: pointer; -} - -.monaco-shell .context-view { - -webkit-app-region: no-drag; -} - -.monaco-shell .monaco-menu .monaco-action-bar.vertical { - padding: .5em 0; -} - -.monaco-shell .monaco-menu .monaco-action-bar.vertical .action-menu-item { - height: 1.8em; -} - -.monaco-shell .monaco-menu .monaco-action-bar.vertical .action-label:not(.separator), -.monaco-shell .monaco-menu .monaco-action-bar.vertical .keybinding { - font-size: inherit; - padding: 0 2em; -} - -.monaco-shell .monaco-menu .monaco-action-bar.vertical .menu-item-check { - font-size: inherit; - width: 2em; -} - -.monaco-shell .monaco-menu .monaco-action-bar.vertical .action-label.separator { - font-size: inherit; - padding: 0.2em 0 0 0; - margin-bottom: 0.2em; -} - -.monaco-shell.linux .monaco-menu .monaco-action-bar.vertical .action-label.separator { - margin-left: 0; - margin-right: 0; -} - -.monaco-shell .monaco-menu .monaco-action-bar.vertical .submenu-indicator { - font-size: 60%; - padding: 0 1.8em; -} - -.monaco-shell.linux .monaco-menu .monaco-action-bar.vertical .submenu-indicator { - height: 100%; - -webkit-mask-size: 10px 10px; - mask-size: 10px 10px; -} - -.monaco-shell .monaco-menu .action-item { - cursor: default; -} - -/* START Keyboard Focus Indication Styles */ - -.monaco-shell [tabindex="0"]:focus, -.monaco-shell .synthetic-focus, -.monaco-shell select:focus, -.monaco-shell input[type="button"]:focus, -.monaco-shell input[type="text"]:focus, -.monaco-shell textarea:focus, -.monaco-shell input[type="checkbox"]:focus { - outline-width: 1px; - outline-style: solid; - outline-offset: -1px; - opacity: 1 !important; -} - -.monaco-shell [tabindex="0"]:active, -.monaco-shell select:active, -.monaco-shell input[type="button"]:active, -.monaco-shell input[type="checkbox"]:active, -.monaco-shell .monaco-tree .monaco-tree-row -.monaco-shell .monaco-tree.focused.no-focused-item:active:before { - outline: 0 !important; /* fixes some flashing outlines from showing up when clicking */ -} - -.monaco-shell .mac select:focus { - border-color: transparent; /* outline is a square, but border has a radius, so we avoid this glitch when focused (https://github.com/Microsoft/vscode/issues/26045) */ -} - -.monaco-shell .monaco-tree.focused .monaco-tree-row.focused [tabindex="0"]:focus { - outline-width: 1px; /* higher contrast color for focusable elements in a row that shows focus feedback */ - outline-style: solid; -} - -.monaco-shell .monaco-tree.focused.no-focused-item:focus:before, -.monaco-shell .monaco-list:not(.element-focused):focus:before { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - z-index: 5; /* make sure we are on top of the tree items */ - content: ""; - pointer-events: none; /* enable click through */ - outline: 1px solid; /* we still need to handle the empty tree or no focus item case */ - outline-width: 1px; - outline-style: solid; - outline-offset: -1px; -} - -.monaco-shell .synthetic-focus :focus { - outline: 0 !important; /* elements within widgets that draw synthetic-focus should never show focus */ -} - -.monaco-shell .monaco-inputbox.info.synthetic-focus, -.monaco-shell .monaco-inputbox.warning.synthetic-focus, -.monaco-shell .monaco-inputbox.error.synthetic-focus, -.monaco-shell .monaco-inputbox.info input[type="text"]:focus, -.monaco-shell .monaco-inputbox.warning input[type="text"]:focus, -.monaco-shell .monaco-inputbox.error input[type="text"]:focus { - outline: 0 !important; /* outline is not going well with decoration */ -} - -.monaco-shell .monaco-tree.focused:focus, -.monaco-shell .monaco-list:focus { - outline: 0 !important; /* tree indicates focus not via outline but through the focused item */ -} diff --git a/src/vs/workbench/electron-browser/media/workbench.css b/src/vs/workbench/electron-browser/media/workbench.css deleted file mode 100644 index 93e295ee73..0000000000 --- a/src/vs/workbench/electron-browser/media/workbench.css +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-workbench { - font-size: 13px; - line-height: 1.4em; - position: relative; - z-index: 1; - overflow: hidden; -} - -.monaco-workbench .part { - position: absolute; - box-sizing: border-box; -} - -.monaco-font-aliasing-antialiased { - -webkit-font-smoothing: antialiased; -} - -.monaco-font-aliasing-none { - -webkit-font-smoothing: none; -} - -@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { - .monaco-font-aliasing-auto { - -webkit-font-smoothing: antialiased; - } -} diff --git a/src/vs/workbench/electron-browser/resources.ts b/src/vs/workbench/electron-browser/resources.ts deleted file mode 100644 index 747a5f4247..0000000000 --- a/src/vs/workbench/electron-browser/resources.ts +++ /dev/null @@ -1,117 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from 'vs/base/common/uri'; -import * as objects from 'vs/base/common/objects'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { Event, Emitter } from 'vs/base/common/event'; -import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob'; -import { relative } from 'path'; -import { normalize } from 'vs/base/common/paths'; - -export class ResourceGlobMatcher extends Disposable { - - private static readonly NO_ROOT: string | null = null; - - private readonly _onExpressionChange: Emitter = this._register(new Emitter()); - get onExpressionChange(): Event { return this._onExpressionChange.event; } - - private mapRootToParsedExpression: Map; - private mapRootToExpressionConfig: Map; - - constructor( - private globFn: (root?: URI) => IExpression, - private shouldUpdate: (event: IConfigurationChangeEvent) => boolean, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IConfigurationService private readonly configurationService: IConfigurationService - ) { - super(); - - this.mapRootToParsedExpression = new Map(); - this.mapRootToExpressionConfig = new Map(); - - this.updateExcludes(false); - - this.registerListeners(); - } - - private registerListeners(): void { - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (this.shouldUpdate(e)) { - this.updateExcludes(true); - } - })); - - this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.updateExcludes(true))); - } - - private updateExcludes(fromEvent: boolean): void { - let changed = false; - - // Add excludes per workspaces that got added - this.contextService.getWorkspace().folders.forEach(folder => { - const rootExcludes = this.globFn(folder.uri); - if (!this.mapRootToExpressionConfig.has(folder.uri.toString()) || !objects.equals(this.mapRootToExpressionConfig.get(folder.uri.toString()), rootExcludes)) { - changed = true; - - this.mapRootToParsedExpression.set(folder.uri.toString(), parse(rootExcludes)); - this.mapRootToExpressionConfig.set(folder.uri.toString(), objects.deepClone(rootExcludes)); - } - }); - - // Remove excludes per workspace no longer present - this.mapRootToExpressionConfig.forEach((value, root) => { - if (root === ResourceGlobMatcher.NO_ROOT) { - return; // always keep this one - } - - if (root && !this.contextService.getWorkspaceFolder(URI.parse(root))) { - this.mapRootToParsedExpression.delete(root); - this.mapRootToExpressionConfig.delete(root); - - changed = true; - } - }); - - // Always set for resources outside root as well - const globalExcludes = this.globFn(); - if (!this.mapRootToExpressionConfig.has(ResourceGlobMatcher.NO_ROOT) || !objects.equals(this.mapRootToExpressionConfig.get(ResourceGlobMatcher.NO_ROOT), globalExcludes)) { - changed = true; - - this.mapRootToParsedExpression.set(ResourceGlobMatcher.NO_ROOT, parse(globalExcludes)); - this.mapRootToExpressionConfig.set(ResourceGlobMatcher.NO_ROOT, objects.deepClone(globalExcludes)); - } - - if (fromEvent && changed) { - this._onExpressionChange.fire(); - } - } - - matches(resource: URI): boolean { - const folder = this.contextService.getWorkspaceFolder(resource); - - let expressionForRoot: ParsedExpression; - if (folder && this.mapRootToParsedExpression.has(folder.uri.toString())) { - expressionForRoot = this.mapRootToParsedExpression.get(folder.uri.toString())!; - } else { - expressionForRoot = this.mapRootToParsedExpression.get(ResourceGlobMatcher.NO_ROOT)!; - } - - // If the resource if from a workspace, convert its absolute path to a relative - // path so that glob patterns have a higher probability to match. For example - // a glob pattern of "src/**" will not match on an absolute path "/folder/src/file.txt" - // but can match on "src/file.txt" - let resourcePathToMatch: string; - if (folder) { - resourcePathToMatch = normalize(relative(folder.uri.fsPath, resource.fsPath)); - } else { - resourcePathToMatch = resource.fsPath; - } - - return !!expressionForRoot(resourcePathToMatch); - } -} \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/shell.contribution.ts b/src/vs/workbench/electron-browser/shell.contribution.ts deleted file mode 100644 index d7c66f0bdf..0000000000 --- a/src/vs/workbench/electron-browser/shell.contribution.ts +++ /dev/null @@ -1,650 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Registry } from 'vs/platform/registry/common/platform'; -import * as nls from 'vs/nls'; -import product from 'vs/platform/node/product'; -import * as os from 'os'; -import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; -import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenIssueReporterAction, ReportPerformanceIssueUsingReporterAction, OpenProcessExplorer, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction } from 'vs/workbench/electron-browser/actions/helpActions'; -import { ToggleSharedProcessAction, InspectContextKeysAction, ToggleScreencastModeAction } from 'vs/workbench/electron-browser/actions/developerActions'; -import { ShowAboutDialogAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, OpenRecentAction } from 'vs/workbench/electron-browser/actions/windowActions'; -import { AddRootFolderAction, GlobalRemoveRootFolderAction, OpenWorkspaceAction, SaveWorkspaceAsAction, OpenWorkspaceConfigFileAction, DuplicateWorkspaceInNewWindowAction, OpenFileFolderAction, OpenFileAction, OpenFolderAction, CloseWorkspaceAction } from 'vs/workbench/browser/actions/workspaceActions'; -import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { inQuickOpenContext, getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen'; -import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ADD_ROOT_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; -import { IsMacContext } from 'vs/platform/workbench/common/contextkeys'; -import { NoEditorsVisibleContext, SingleEditorGroupsContext } from 'vs/workbench/common/editor'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; -// {{SQL CARBON EDIT}} -import { InstallVSIXAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions'; -// {{SQL CARBON EDIT}} - End - -// Contribute Commands -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'workbench.action.closeWindow', // close the window when the last editor is closed by reusing the same keybinding - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(NoEditorsVisibleContext, SingleEditorGroupsContext), - primary: KeyMod.CtrlCmd | KeyCode.KEY_W, - handler: accessor => { - const windowService = accessor.get(IWindowService); - windowService.closeWindow(); - } -}); - -const QUIT_ID = 'workbench.action.quit'; -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: QUIT_ID, - weight: KeybindingWeight.WorkbenchContrib, - handler(accessor: ServicesAccessor) { - const windowsService = accessor.get(IWindowsService); - windowsService.quit(); - }, - when: undefined, - primary: KeyMod.CtrlCmd | KeyCode.KEY_Q, - win: { primary: undefined } -}); - -// Contribute Global Actions -const viewCategory = nls.localize('view', "View"); -const helpCategory = nls.localize('help', "Help"); -const fileCategory = nls.localize('file', "File"); -const workbenchActionsRegistry = Registry.as(Extensions.WorkbenchActions); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(NewWindowAction, NewWindowAction.ID, NewWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_N }), 'New Window'); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloseCurrentWindowAction, CloseCurrentWindowAction.ID, CloseCurrentWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W }), 'Close Window'); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SwitchWindow, SwitchWindow.ID, SwitchWindow.LABEL, { primary: 0, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_W } }), 'Switch Window...'); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(QuickSwitchWindow, QuickSwitchWindow.ID, QuickSwitchWindow.LABEL), 'Quick Switch Window...'); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); - -if (isMacintosh) { - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); -} else { - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', fileCategory); -} - -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloseWorkspaceAction, CloseWorkspaceAction.ID, CloseWorkspaceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'File: Close Workspace', fileCategory); -if (!!product.reportIssueUrl) { - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenIssueReporterAction, OpenIssueReporterAction.ID, OpenIssueReporterAction.LABEL), 'Help: Open Issue Reporter', helpCategory); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ReportPerformanceIssueUsingReporterAction, ReportPerformanceIssueUsingReporterAction.ID, ReportPerformanceIssueUsingReporterAction.LABEL), 'Help: Report Performance Issue', helpCategory); -} - -if (KeybindingsReferenceAction.AVAILABLE) { - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(KeybindingsReferenceAction, KeybindingsReferenceAction.ID, KeybindingsReferenceAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_R) }), 'Help: Keyboard Shortcuts Reference', helpCategory); -} - -if (OpenDocumentationUrlAction.AVAILABLE) { - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenDocumentationUrlAction, OpenDocumentationUrlAction.ID, OpenDocumentationUrlAction.LABEL), 'Help: Documentation', helpCategory); -} - -if (OpenIntroductoryVideosUrlAction.AVAILABLE) { - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenIntroductoryVideosUrlAction, OpenIntroductoryVideosUrlAction.ID, OpenIntroductoryVideosUrlAction.LABEL), 'Help: Introductory Videos', helpCategory); -} - -if (OpenTipsAndTricksUrlAction.AVAILABLE) { - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenTipsAndTricksUrlAction, OpenTipsAndTricksUrlAction.ID, OpenTipsAndTricksUrlAction.LABEL), 'Help: Tips and Tricks', helpCategory); -} - -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenTwitterUrlAction, OpenTwitterUrlAction.ID, OpenTwitterUrlAction.LABEL), 'Help: Join Us on Twitter', helpCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenRequestFeatureUrlAction, OpenRequestFeatureUrlAction.ID, OpenRequestFeatureUrlAction.LABEL), 'Help: Search Feature Requests', helpCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLicenseUrlAction, OpenLicenseUrlAction.ID, OpenLicenseUrlAction.LABEL), 'Help: View License', helpCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenPrivacyStatementUrlAction, OpenPrivacyStatementUrlAction.ID, OpenPrivacyStatementUrlAction.LABEL), 'Help: Privacy Statement', helpCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowAboutDialogAction, ShowAboutDialogAction.ID, ShowAboutDialogAction.LABEL), 'Help: About', helpCategory); - -workbenchActionsRegistry.registerWorkbenchAction( - new SyncActionDescriptor(ZoomInAction, ZoomInAction.ID, ZoomInAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.US_EQUAL, - secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_EQUAL, KeyMod.CtrlCmd | KeyCode.NUMPAD_ADD] - }), 'View: Zoom In', viewCategory); - -workbenchActionsRegistry.registerWorkbenchAction( - new SyncActionDescriptor(ZoomOutAction, ZoomOutAction.ID, ZoomOutAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, - secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS, KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT], - linux: { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyCode.NUMPAD_SUBTRACT] } - }), 'View: Zoom Out', viewCategory -); - -workbenchActionsRegistry.registerWorkbenchAction( - new SyncActionDescriptor(ZoomResetAction, ZoomResetAction.ID, ZoomResetAction.LABEL, { - primary: KeyMod.CtrlCmd | KeyCode.NUMPAD_0 - }), 'View: Reset Zoom', viewCategory -); - -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleFullScreenAction, ToggleFullScreenAction.ID, ToggleFullScreenAction.LABEL, { primary: KeyCode.F11, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_F } }), 'View: Toggle Full Screen', viewCategory); - -const workspacesCategory = nls.localize('workspaces', "Workspaces"); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL), 'Workspaces: Add Folder to Workspace...', workspacesCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRemoveRootFolderAction, GlobalRemoveRootFolderAction.ID, GlobalRemoveRootFolderAction.LABEL), 'Workspaces: Remove Folder from Workspace...', workspacesCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SaveWorkspaceAsAction, SaveWorkspaceAsAction.ID, SaveWorkspaceAsAction.LABEL), 'Workspaces: Save Workspace As...', workspacesCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(DuplicateWorkspaceInNewWindowAction, DuplicateWorkspaceInNewWindowAction.ID, DuplicateWorkspaceInNewWindowAction.LABEL), 'Workspaces: Duplicate Workspace in New Window', workspacesCategory); - -CommandsRegistry.registerCommand(OpenWorkspaceConfigFileAction.ID, serviceAccessor => { - serviceAccessor.get(IInstantiationService).createInstance(OpenWorkspaceConfigFileAction, OpenWorkspaceConfigFileAction.ID, OpenWorkspaceConfigFileAction.LABEL).run(); -}); -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: OpenWorkspaceConfigFileAction.ID, - title: { value: `${workspacesCategory}: ${OpenWorkspaceConfigFileAction.LABEL}`, original: 'Workspaces: Open Workspace Configuration File' }, - }, - when: new RawContextKey('workbenchState', '').isEqualTo('workspace') -}); - -// Developer related actions -const developerCategory = nls.localize('developer', "Developer"); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleSharedProcessAction, ToggleSharedProcessAction.ID, ToggleSharedProcessAction.LABEL), 'Developer: Toggle Shared Process', developerCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(InspectContextKeysAction, InspectContextKeysAction.ID, InspectContextKeysAction.LABEL), 'Developer: Inspect Context Keys', developerCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleScreencastModeAction, ToggleScreencastModeAction.ID, ToggleScreencastModeAction.LABEL), 'Developer: Toggle Mouse Clicks', developerCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenProcessExplorer, OpenProcessExplorer.ID, OpenProcessExplorer.LABEL), 'Developer: Open Process Explorer', developerCategory); - -const recentFilesPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(inRecentFilesPickerContextKey)); - -const quickOpenNavigateNextInRecentFilesPickerId = 'workbench.action.quickOpenNavigateNextInRecentFilesPicker'; -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigateNextInRecentFilesPickerId, - weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigateNextInRecentFilesPickerId, true), - when: recentFilesPickerContext, - primary: KeyMod.CtrlCmd | KeyCode.KEY_R, - mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } -}); - -const quickOpenNavigatePreviousInRecentFilesPicker = 'workbench.action.quickOpenNavigatePreviousInRecentFilesPicker'; -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: quickOpenNavigatePreviousInRecentFilesPicker, - weight: KeybindingWeight.WorkbenchContrib + 50, - handler: getQuickNavigateHandler(quickOpenNavigatePreviousInRecentFilesPicker, false), - when: recentFilesPickerContext, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_R, - mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.KEY_R } -}); - -// Menu registration - file menu - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '1_new', - command: { - id: NewWindowAction.ID, - title: nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window") - }, - order: 2 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '2_open', - command: { - id: OpenFileAction.ID, - title: nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...") - }, - order: 1, - when: IsMacContext.toNegated() -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '2_open', - command: { - id: OpenFolderAction.ID, - title: nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...") - }, - order: 2, - when: IsMacContext.toNegated() -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '2_open', - command: { - id: OpenFileFolderAction.ID, - title: nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...") - }, - order: 1, - when: IsMacContext -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '2_open', - command: { - id: OpenWorkspaceAction.ID, - title: nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...") - }, - order: 3 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - title: nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent"), - submenu: MenuId.MenubarRecentMenu, - group: '2_open', - order: 4 -}); - -// {{SQL CARBON EDIT}} - Add install VSIX menu item -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '5.1_installExtension', - command: { - id: InstallVSIXAction.ID, - title: nls.localize({ key: 'miinstallVsix', comment: ['&& denotes a mnemonic'] }, "Install Extension from VSIX Package") - } -}); -// {{SQL CARBON EDIT}} - End - -// More -MenuRegistry.appendMenuItem(MenuId.MenubarRecentMenu, { - group: 'y_more', - command: { - id: OpenRecentAction.ID, - title: nls.localize({ key: 'miMore', comment: ['&& denotes a mnemonic'] }, "&&More...") - }, - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '3_workspace', - command: { - id: ADD_ROOT_FOLDER_COMMAND_ID, - title: nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace...") - }, - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '3_workspace', - command: { - id: SaveWorkspaceAsAction.ID, - title: nls.localize('miSaveWorkspaceAs', "Save Workspace As...") - }, - order: 2 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - title: nls.localize({ key: 'miPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences"), - submenu: MenuId.MenubarPreferencesMenu, - group: '5_autosave', - order: 2, - when: IsMacContext.toNegated() -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '6_close', - command: { - id: CloseWorkspaceAction.ID, - title: nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), - precondition: new RawContextKey('workspaceFolderCount', 0).notEqualsTo('0') - }, - order: 3, - when: new RawContextKey('workbenchState', '').notEqualsTo('workspace') -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '6_close', - command: { - id: CloseWorkspaceAction.ID, - title: nls.localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace") - }, - order: 3, - when: new RawContextKey('workbenchState', '').isEqualTo('workspace') -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: '6_close', - command: { - id: CloseCurrentWindowAction.ID, - title: nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Clos&&e Window") - }, - order: 4 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { - group: 'z_Exit', - command: { - id: QUIT_ID, - title: nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit") - }, - order: 1, - when: IsMacContext.toNegated() -}); - -// Appereance menu -MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { - group: '2_appearance', - title: nls.localize({ key: 'miAppearance', comment: ['&& denotes a mnemonic'] }, "&&Appearance"), - submenu: MenuId.MenubarAppearanceMenu, - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '1_toggle_view', - command: { - id: ToggleFullScreenAction.ID, - title: nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen") - }, - order: 1 -}); - -// Zoom - -MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '3_zoom', - command: { - id: ZoomInAction.ID, - title: nls.localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom In") - }, - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '3_zoom', - command: { - id: ZoomOutAction.ID, - title: nls.localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "&&Zoom Out") - }, - order: 2 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { - group: '3_zoom', - command: { - id: ZoomResetAction.ID, - title: nls.localize({ key: 'miZoomReset', comment: ['&& denotes a mnemonic'] }, "&&Reset Zoom") - }, - order: 3 -}); - -// Help - -MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '1_welcome', - command: { - id: 'workbench.action.openDocumentationUrl', - title: nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation") - }, - order: 3 -}); - -// {{SQL CARBON EDIT}} - Disable unused menu item -// MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { -// group: '1_welcome', -// command: { -// id: 'update.showCurrentReleaseNotes', -// title: nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes") -// }, -// order: 4 -// }); -// {{SQL CARBON EDIT}} - End - -// Reference -// {{SQL CARBON EDIT}} - Disable unused menu items -// MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { -// group: '2_reference', -// command: { -// id: 'workbench.action.keybindingsReference', -// title: nls.localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference") -// }, -// order: 1 -// }); - -// MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { -// group: '2_reference', -// command: { -// id: 'workbench.action.openIntroductoryVideosUrl', -// title: nls.localize({ key: 'miIntroductoryVideos', comment: ['&& denotes a mnemonic'] }, "Introductory &&Videos") -// }, -// order: 2 -// }); - -// MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { -// group: '2_reference', -// command: { -// id: 'workbench.action.openTipsAndTricksUrl', -// title: nls.localize({ key: 'miTipsAndTricks', comment: ['&& denotes a mnemonic'] }, "Tips and Tri&&cks") -// }, -// order: 3 -// }); -// {{SQL CARBON EDIT}} - End - -// Feedback -// {{SQL CARBON EDIT}} - Disable unused menu items -// MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { -// group: '3_feedback', -// command: { -// id: 'workbench.action.openTwitterUrl', -// title: nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join Us on Twitter") -// }, -// order: 1 -// }); - -// MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { -// group: '3_feedback', -// command: { -// id: 'workbench.action.openRequestFeatureUrl', -// title: nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests") -// }, -// order: 2 -// }); -// {{SQL CARBON EDIT}} - End - -MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '3_feedback', - command: { - id: 'workbench.action.openIssueReporter', - title: nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue") - }, - order: 3 -}); - -// Legal -MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '4_legal', - command: { - id: 'workbench.action.openLicenseUrl', - title: nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License") - }, - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '4_legal', - command: { - id: 'workbench.action.openPrivacyStatementUrl', - title: nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "Privac&&y Statement") - }, - order: 2 -}); - -// Tools -MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '5_tools', - command: { - id: 'workbench.action.toggleDevTools', - title: nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools") - }, - order: 1 -}); - -MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '5_tools', - command: { - id: 'workbench.action.openProcessExplorer', - title: nls.localize({ key: 'miOpenProcessExplorerer', comment: ['&& denotes a mnemonic'] }, "Open &&Process Explorer") - }, - order: 2 -}); - -// About -MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: 'z_about', - command: { - id: 'workbench.action.showAboutDialog', - title: nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About") - }, - order: 1, - when: IsMacContext.toNegated() -}); - -// Configuration: Window -const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); -configurationRegistry.registerConfiguration({ - 'id': 'window', - 'order': 8, - 'title': nls.localize('windowConfigurationTitle', "Window"), - 'type': 'object', - 'properties': { - 'window.openFilesInNewWindow': { - 'type': 'string', - 'enum': ['on', 'off', 'default'], - 'enumDescriptions': [ - nls.localize('window.openFilesInNewWindow.on', "Files will open in a new window."), - nls.localize('window.openFilesInNewWindow.off', "Files will open in the window with the files' folder open or the last active window."), - isMacintosh ? - nls.localize('window.openFilesInNewWindow.defaultMac', "Files will open in the window with the files' folder open or the last active window unless opened via the Dock or from Finder.") : - nls.localize('window.openFilesInNewWindow.default', "Files will open in a new window unless picked from within the application (e.g. via the File menu).") - ], - 'default': 'off', - 'scope': ConfigurationScope.APPLICATION, - 'markdownDescription': - isMacintosh ? - nls.localize('openFilesInNewWindowMac', "Controls whether files should open in a new window. \nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") : - nls.localize('openFilesInNewWindow', "Controls whether files should open in a new window.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") - }, - 'window.openFoldersInNewWindow': { - 'type': 'string', - 'enum': ['on', 'off', 'default'], - 'enumDescriptions': [ - nls.localize('window.openFoldersInNewWindow.on', "Folders will open in a new window."), - nls.localize('window.openFoldersInNewWindow.off', "Folders will replace the last active window."), - nls.localize('window.openFoldersInNewWindow.default', "Folders will open in a new window unless a folder is picked from within the application (e.g. via the File menu).") - ], - 'default': 'default', - 'scope': ConfigurationScope.APPLICATION, - 'markdownDescription': nls.localize('openFoldersInNewWindow', "Controls whether folders should open in a new window or replace the last active window.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") - }, - 'window.openWithoutArgumentsInNewWindow': { - 'type': 'string', - 'enum': ['on', 'off'], - 'enumDescriptions': [ - nls.localize('window.openWithoutArgumentsInNewWindow.on', "Open a new empty window."), - nls.localize('window.openWithoutArgumentsInNewWindow.off', "Focus the last active running instance.") - ], - 'default': isMacintosh ? 'off' : 'on', - 'scope': ConfigurationScope.APPLICATION, - 'markdownDescription': nls.localize('openWithoutArgumentsInNewWindow', "Controls whether a new empty window should open when starting a second instance without arguments or if the last running instance should get focus.\nNote that there can still be cases where this setting is ignored (e.g. when using the `--new-window` or `--reuse-window` command line option).") - }, - 'window.restoreWindows': { - 'type': 'string', - 'enum': ['all', 'folders', 'one', 'none'], - 'enumDescriptions': [ - nls.localize('window.reopenFolders.all', "Reopen all windows."), - nls.localize('window.reopenFolders.folders', "Reopen all folders. Empty workspaces will not be restored."), - nls.localize('window.reopenFolders.one', "Reopen the last active window."), - nls.localize('window.reopenFolders.none', "Never reopen a window. Always start with an empty one.") - ], - 'default': 'one', - 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('restoreWindows', "Controls how windows are being reopened after a restart.") - }, - 'window.restoreFullscreen': { - 'type': 'boolean', - 'default': false, - 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('restoreFullscreen', "Controls whether a window should restore to full screen mode if it was exited in full screen mode.") - }, - 'window.zoomLevel': { - 'type': 'number', - 'default': 0, - 'description': nls.localize('zoomLevel', "Adjust the zoom level of the window. The original size is 0 and each increment above (e.g. 1) or below (e.g. -1) represents zooming 20% larger or smaller. You can also enter decimals to adjust the zoom level with a finer granularity.") - }, - 'window.title': { - 'type': 'string', - 'default': isMacintosh ? '${activeEditorShort}${separator}${rootName}' : '${dirty}${activeEditorShort}${separator}${rootName}${separator}${appName}', - 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by parenthesis are not to be translated.'], key: 'title' }, - "Controls the window title based on the active editor. Variables are substituted based on the context:\n- `\${activeEditorShort}`: the file name (e.g. myFile.txt).\n- `\${activeEditorMedium}`: the path of the file relative to the workspace folder (e.g. myFolder/myFileFolder/myFile.txt).\n- `\${activeEditorLong}`: the full path of the file (e.g. /Users/Development/myFolder/myFileFolder/myFile.txt).\n- `\${activeFolderShort}`: the name of the folder the file is contained in (e.g. myFileFolder).\n- `\${activeFolderMedium}`: the path of the folder the file is contained in, relative to the workspace folder (e.g. myFolder/myFileFolder).\n- `\${activeFolderLong}`: the full path of the folder the file is contained in (e.g. /Users/Development/myFolder/myFileFolder).\n- `\${folderName}`: name of the workspace folder the file is contained in (e.g. myFolder).\n- `\${folderPath}`: file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder).\n- `\${rootName}`: name of the workspace (e.g. myFolder or myWorkspace).\n- `\${rootPath}`: file path of the workspace (e.g. /Users/Development/myWorkspace).\n- `\${appName}`: e.g. VS Code.\n- `\${dirty}`: a dirty indicator if the active editor is dirty.\n- `\${separator}`: a conditional separator (\" - \") that only shows when surrounded by variables with values or static text.") - }, - 'window.newWindowDimensions': { - 'type': 'string', - 'enum': ['default', 'inherit', 'maximized', 'fullscreen'], - 'enumDescriptions': [ - nls.localize('window.newWindowDimensions.default', "Open new windows in the center of the screen."), - nls.localize('window.newWindowDimensions.inherit', "Open new windows with same dimension as last active one."), - nls.localize('window.newWindowDimensions.maximized', "Open new windows maximized."), - nls.localize('window.newWindowDimensions.fullscreen', "Open new windows in full screen mode.") - ], - 'default': 'default', - 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('newWindowDimensions', "Controls the dimensions of opening a new window when at least one window is already opened. Note that this setting does not have an impact on the first window that is opened. The first window will always restore the size and location as you left it before closing.") - }, - 'window.closeWhenEmpty': { - 'type': 'boolean', - 'default': false, - 'description': nls.localize('closeWhenEmpty', "Controls whether closing the last editor should also close the window. This setting only applies for windows that do not show folders.") - }, - 'window.menuBarVisibility': { - 'type': 'string', - 'enum': ['default', 'visible', 'toggle', 'hidden'], - 'enumDescriptions': [ - nls.localize('window.menuBarVisibility.default', "Menu is only hidden in full screen mode."), - nls.localize('window.menuBarVisibility.visible', "Menu is always visible even in full screen mode."), - nls.localize('window.menuBarVisibility.toggle', "Menu is hidden but can be displayed via Alt key."), - nls.localize('window.menuBarVisibility.hidden', "Menu is always hidden.") - ], - 'default': 'default', - 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('menuBarVisibility', "Control the visibility of the menu bar. A setting of 'toggle' means that the menu bar is hidden and a single press of the Alt key will show it. By default, the menu bar will be visible, unless the window is full screen."), - 'included': isWindows || isLinux - }, - 'window.enableMenuBarMnemonics': { - 'type': 'boolean', - 'default': true, - 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('enableMenuBarMnemonics', "If enabled, the main menus can be opened via Alt-key shortcuts. Disabling mnemonics allows to bind these Alt-key shortcuts to editor commands instead."), - 'included': isWindows || isLinux - }, - 'window.autoDetectHighContrast': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('autoDetectHighContrast', "If enabled, will automatically change to high contrast theme if Windows is using a high contrast theme, and to dark theme when switching away from a Windows high contrast theme."), - 'included': isWindows - }, - 'window.doubleClickIconToClose': { - 'type': 'boolean', - 'default': false, - 'scope': ConfigurationScope.APPLICATION, - 'markdownDescription': nls.localize('window.doubleClickIconToClose', "If enabled, double clicking the application icon in the title bar will close the window and the window cannot be dragged by the icon. This setting only has an effect when `#window.titleBarStyle#` is set to `custom`.") - }, - 'window.titleBarStyle': { - 'type': 'string', - 'enum': ['native', 'custom'], - 'default': 'custom', - 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('titleBarStyle', "Adjust the appearance of the window title bar. Changes require a full restart to apply.") - }, - 'window.nativeTabs': { - 'type': 'boolean', - 'default': false, - 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('window.nativeTabs', "Enables macOS Sierra window tabs. Note that changes require a full restart to apply and that native tabs will disable a custom title bar style if configured."), - 'included': isMacintosh && parseFloat(os.release()) >= 16 // Minimum: macOS Sierra (10.12.x = darwin 16.x) - }, - 'window.nativeFullScreen': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('window.nativeFullScreen', "Controls if native full-screen should be used on macOS. Disable this option to prevent macOS from creating a new space when going full-screen."), - 'included': isMacintosh - }, - 'window.clickThroughInactive': { - 'type': 'boolean', - 'default': true, - 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('window.clickThroughInactive', "If enabled, clicking on an inactive window will both activate the window and trigger the element under the mouse if it is clickable. If disabled, clicking anywhere on an inactive window will activate it only and a second click is required on the element."), - 'included': isMacintosh - } - } -}); - diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts deleted file mode 100644 index 97a0783c6c..0000000000 --- a/src/vs/workbench/electron-browser/shell.ts +++ /dev/null @@ -1,793 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./media/shell'; - -import * as platform from 'vs/base/common/platform'; -import * as perf from 'vs/base/common/performance'; -import * as aria from 'vs/base/browser/ui/aria/aria'; -import { Disposable } from 'vs/base/common/lifecycle'; -import * as errors from 'vs/base/common/errors'; -import { toErrorMessage } from 'vs/base/common/errorMessage'; -import product from 'vs/platform/node/product'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import pkg from 'vs/platform/node/package'; -import { Workbench, IWorkbenchStartedInfo } from 'vs/workbench/electron-browser/workbench'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { NullTelemetryService, configurationTelemetry, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils'; -import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc'; -import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; -import ErrorTelemetry from 'vs/platform/telemetry/browser/errorTelemetry'; -import { ElectronWindow } from 'vs/workbench/electron-browser/window'; -import { resolveWorkbenchCommonProperties } from 'vs/platform/telemetry/node/workbenchCommonProperties'; -import { IWindowsService, IWindowService, IWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { WindowService } from 'vs/platform/windows/electron-browser/windowService'; -import { IRequestService } from 'vs/platform/request/node/request'; -import { RequestService } from 'vs/platform/request/electron-browser/requestService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { SearchService } from 'vs/workbench/services/search/node/searchService'; -import { LifecycleService } from 'vs/platform/lifecycle/electron-browser/lifecycleService'; -import { MarkerService } from 'vs/platform/markers/common/markerService'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { CodeEditorService } from 'vs/workbench/services/codeEditor/browser/codeEditorService'; -import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { IntegrityServiceImpl } from 'vs/platform/integrity/node/integrityServiceImpl'; -import { IIntegrityService } from 'vs/platform/integrity/common/integrity'; -import { EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl'; -import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; -import { ExtensionService } from 'vs/workbench/services/extensions/electron-browser/extensionService'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { InstantiationService } from 'vs/platform/instantiation/node/instantiationService'; -import { ILifecycleService, LifecyclePhase, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; -import { IMarkerService } from 'vs/platform/markers/common/markers'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ISearchService, ISearchHistoryService } from 'vs/platform/search/common/search'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { CommandService } from 'vs/workbench/services/commands/common/commandService'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { WorkbenchModeServiceImpl } from 'vs/workbench/services/mode/common/workbenchModeService'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { ICrashReporterService, NullCrashReporterService, CrashReporterService } from 'vs/workbench/services/crashReporter/electron-browser/crashReporterService'; -import { getDelayedChannel, IPCClient } from 'vs/base/parts/ipc/node/ipc'; -import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net'; -import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/node/extensionManagementIpc'; -import { IExtensionManagementService, IExtensionEnablementService, IExtensionManagementServerService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; -import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; -import { restoreFontInfo, readFontInfo, saveFontInfo } from 'vs/editor/browser/config/configuration'; -import * as browser from 'vs/base/browser/browser'; -import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { WorkbenchThemeService } from 'vs/workbench/services/themes/electron-browser/workbenchThemeService'; -import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; -import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; -import { registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; -import { foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; -import { TextMateService } from 'vs/workbench/services/textMate/electron-browser/TMSyntax'; -import { ITextMateService } from 'vs/workbench/services/textMate/electron-browser/textMateService'; -import { IBroadcastService, BroadcastService } from 'vs/platform/broadcast/electron-browser/broadcastService'; -import { HashService } from 'vs/workbench/services/hash/node/hashService'; -import { IHashService } from 'vs/workbench/services/hash/common/hashService'; -import { ILogService } from 'vs/platform/log/common/log'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { StorageService } from 'vs/platform/storage/node/storageService'; -import { Event, Emitter } from 'vs/base/common/event'; -import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; -import { LocalizationsChannelClient } from 'vs/platform/localizations/node/localizationsIpc'; -import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; -import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue'; -import { WorkbenchIssueService } from 'vs/workbench/services/issue/electron-browser/workbenchIssueService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { NotificationService } from 'vs/workbench/services/notification/common/notificationService'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { DialogService } from 'vs/workbench/services/dialogs/electron-browser/dialogService'; -import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc'; -import { EventType, addDisposableListener, scheduleAtNextAnimationFrame, addClasses } from 'vs/base/browser/dom'; -import { IRemoteAgentService } from 'vs/workbench/services/remote/node/remoteAgentService'; -import { RemoteAgentService } from 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { OpenerService } from 'vs/editor/browser/services/openerService'; -import { SearchHistoryService } from 'vs/workbench/services/search/node/searchHistoryService'; -import { ExtensionManagementServerService } from 'vs/workbench/services/extensions/node/extensionManagementServerService'; -import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; -import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc'; -import { ILabelService } from 'vs/platform/label/common/label'; -import { IDownloadService } from 'vs/platform/download/common/download'; -import { DownloadService } from 'vs/platform/download/node/downloadService'; -import { DownloadServiceChannel } from 'vs/platform/download/node/downloadIpc'; -import { TextResourcePropertiesService } from 'vs/workbench/services/textfile/electron-browser/textResourcePropertiesService'; -import { MultiExtensionManagementService } from 'vs/platform/extensionManagement/node/multiExtensionManagement'; -import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-browser/remoteAuthorityResolverService'; -import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; -import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; -import { LabelService } from 'vs/workbench/services/label/common/labelService'; - -// {{SQL CARBON EDIT}} -import { FileTelemetryService } from 'sql/workbench/services/telemetry/node/fileTelemetryService'; - -/** - * Services that we require for the Shell - */ -export interface ICoreServices { - contextService: IWorkspaceContextService; - configurationService: IConfigurationService; - environmentService: IEnvironmentService; - logService: ILogService; - storageService: StorageService; -} - -/** - * The workbench shell contains the workbench with a rich header containing navigation and the activity bar. - * With the Shell being the top level element in the page, it is also responsible for driving the layouting. - */ -export class Shell extends Disposable { - - private readonly _onWillShutdown = this._register(new Emitter()); - get onWillShutdown(): Event { return this._onWillShutdown.event; } - - private storageService: StorageService; - private environmentService: IEnvironmentService; - private logService: ILogService; - private configurationService: IConfigurationService; - private contextService: IWorkspaceContextService; - private telemetryService: ITelemetryService; - private broadcastService: IBroadcastService; - private themeService: WorkbenchThemeService; - private lifecycleService: LifecycleService; - private mainProcessServices: ServiceCollection; - private notificationService: INotificationService; - - private container: HTMLElement; - private previousErrorValue: string; - private previousErrorTime: number; - - private configuration: IWindowConfiguration; - private workbench: Workbench; - - constructor(container: HTMLElement, coreServices: ICoreServices, mainProcessServices: ServiceCollection, private mainProcessClient: IPCClient, configuration: IWindowConfiguration) { - super(); - - this.container = container; - - this.configuration = configuration; - - this.contextService = coreServices.contextService; - this.configurationService = coreServices.configurationService; - this.environmentService = coreServices.environmentService; - this.logService = coreServices.logService; - this.storageService = coreServices.storageService; - - this.mainProcessServices = mainProcessServices; - - this.previousErrorTime = 0; - } - - private renderContents(): void { - - // ARIA - aria.setARIAContainer(document.body); - - // Instantiation service with services - const [instantiationService, serviceCollection] = this.initServiceCollection(this.container); - - // Warm up font cache information before building up too many dom elements - restoreFontInfo(this.storageService); - readFontInfo(BareFontInfo.createFromRawSettings(this.configurationService.getValue('editor'), browser.getZoomLevel())); - this._register(this.storageService.onWillSaveState(() => { - saveFontInfo(this.storageService); // Keep font info for next startup around - })); - - // Workbench - this.workbench = this.createWorkbench(instantiationService, serviceCollection, this.container); - - // Window - this.workbench.getInstantiationService().createInstance(ElectronWindow); - - // Handle case where workbench is not starting up properly - const timeoutHandle = setTimeout(() => { - this.logService.warn('Workbench did not finish loading in 10 seconds, that might be a problem that should be reported.'); - }, 10000); - - this.lifecycleService.when(LifecyclePhase.Restored).then(() => { - clearTimeout(timeoutHandle); - }); - } - - private createWorkbench(instantiationService: IInstantiationService, serviceCollection: ServiceCollection, container: HTMLElement): Workbench { - - function handleStartupError(logService: ILogService, error: Error): void { - - // Log it - logService.error(toErrorMessage(error, true)); - - // Rethrow - throw error; - } - - try { - const workbench = instantiationService.createInstance(Workbench, container, this.configuration, serviceCollection, this.lifecycleService, this.mainProcessClient); - - // Startup Workbench - workbench.startup().then(startupInfos => { - - // Startup Telemetry - this.logStartupTelemetry(startupInfos); - - // Storage Telemetry (TODO@Ben remove me later, including storage errors) - if (!this.environmentService.extensionTestsPath) { - this.logStorageTelemetry(); - } - }, error => handleStartupError(this.logService, error)); - - return workbench; - } catch (error) { - handleStartupError(this.logService, error); - - return undefined; - } - } - - private logStartupTelemetry(info: IWorkbenchStartedInfo): void { - const { filesToOpen, filesToCreate, filesToDiff } = this.configuration; - /* __GDPR__ - "workspaceLoad" : { - "userAgent" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "windowSize.innerHeight": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "windowSize.innerWidth": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "windowSize.outerHeight": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "windowSize.outerWidth": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "emptyWorkbench": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workbench.filesToOpen": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workbench.filesToCreate": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workbench.filesToDiff": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "customKeybindingsCount": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "theme": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "language": { "classification": "SystemMetaData", "purpose": "BusinessInsight" }, - "pinnedViewlets": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "restoredViewlet": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "restoredEditors": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "pinnedViewlets": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "startupKind": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - this.telemetryService.publicLog('workspaceLoad', { - userAgent: navigator.userAgent, - windowSize: { innerHeight: window.innerHeight, innerWidth: window.innerWidth, outerHeight: window.outerHeight, outerWidth: window.outerWidth }, - emptyWorkbench: this.contextService.getWorkbenchState() === WorkbenchState.EMPTY, - 'workbench.filesToOpen': filesToOpen && filesToOpen.length || 0, - 'workbench.filesToCreate': filesToCreate && filesToCreate.length || 0, - 'workbench.filesToDiff': filesToDiff && filesToDiff.length || 0, - customKeybindingsCount: info.customKeybindingsCount, - theme: this.themeService.getColorTheme().id, - language: platform.language, - pinnedViewlets: info.pinnedViewlets, - restoredViewlet: info.restoredViewlet, - restoredEditors: info.restoredEditorsCount, - startupKind: this.lifecycleService.startupKind - }); - - // Telemetry: startup metrics - perf.mark('didStartWorkbench'); - } - - // {{SQL CARBON EDIT}} - private sendUsageEvents(): void { - const dailyLastUseDate = Date.parse(this.storageService.get('telemetry.dailyLastUseDate', StorageScope.GLOBAL, '0')); - const weeklyLastUseDate = Date.parse(this.storageService.get('telemetry.weeklyLastUseDate', StorageScope.GLOBAL, '0')); - const monthlyLastUseDate = Date.parse(this.storageService.get('telemetry.monthlyLastUseDate', StorageScope.GLOBAL, '0')); - - let today = new Date().toUTCString(); - - // daily user event - if (this.diffInDays(Date.parse(today), dailyLastUseDate) >= 1) { - // daily first use - this.telemetryService.publicLog('telemetry.dailyFirstUse', { dailyFirstUse: true }); - this.storageService.store('telemetry.dailyLastUseDate', today, StorageScope.GLOBAL); - } - - // weekly user event - if (this.diffInDays(Date.parse(today), weeklyLastUseDate) >= 7) { - // weekly first use - this.telemetryService.publicLog('telemetry.weeklyFirstUse', { weeklyFirstUse: true }); - this.storageService.store('telemetry.weeklyLastUseDate', today, StorageScope.GLOBAL); - } - - // monthly user events - if (this.diffInDays(Date.parse(today), monthlyLastUseDate) >= 30) { - this.telemetryService.publicLog('telemetry.monthlyUse', { monthlyFirstUse: true }); - this.storageService.store('telemetry.monthlyLastUseDate', today, StorageScope.GLOBAL); - } - } - - private logStorageTelemetry(): void { - const initialStartup = !!this.configuration.isInitialStartup; - - const appReadyDuration = initialStartup ? perf.getDuration('main:started', 'main:appReady') : 0; - const workbenchReadyDuration = perf.getDuration(initialStartup ? 'main:started' : 'main:loadWindow', 'didStartWorkbench'); - const workspaceStorageRequireDuration = perf.getDuration('willRequireSQLite', 'didRequireSQLite'); - const workspaceStorageSchemaDuration = perf.getDuration('willSetupSQLiteSchema', 'didSetupSQLiteSchema'); - const globalStorageInitDurationMain = perf.getDuration('main:willInitGlobalStorage', 'main:didInitGlobalStorage'); - const globalStorageInitDuratioRenderer = perf.getDuration('willInitGlobalStorage', 'didInitGlobalStorage'); - const workspaceStorageInitDuration = perf.getDuration('willInitWorkspaceStorage', 'didInitWorkspaceStorage'); - const workbenchLoadDuration = perf.getDuration('willLoadWorkbenchMain', 'didLoadWorkbenchMain'); - - // Handle errors (avoid duplicates to reduce spam) - const loggedStorageErrors = new Set(); - this._register(this.storageService.onWorkspaceStorageError(error => { - const errorStr = `${error}`; - - if (!loggedStorageErrors.has(errorStr)) { - loggedStorageErrors.add(errorStr); - - /* __GDPR__ - "sqliteStorageError5" : { - "appReadyTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workbenchReadyTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceSchemaTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "globalReadTimeMain" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "globalReadTimeRenderer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceReadTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workbenchRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceKeys" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "startupKind": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "storageError": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - - /* __GDPR__ - "sqliteStorageError" : { - "appReadyTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workbenchReadyTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceSchemaTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "globalReadTimeMain" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "globalReadTimeRenderer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceReadTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workbenchRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceKeys" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "startupKind": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "storageError": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('sqliteStorageError5', { - 'appReadyTime': appReadyDuration, - 'workbenchReadyTime': workbenchReadyDuration, - 'workspaceRequireTime': workspaceStorageRequireDuration, - 'workspaceSchemaTime': workspaceStorageSchemaDuration, - 'globalReadTimeMain': globalStorageInitDurationMain, - 'globalReadTimeRenderer': globalStorageInitDuratioRenderer, - 'workspaceReadTime': workspaceStorageInitDuration, - 'workbenchRequireTime': workbenchLoadDuration, - 'workspaceKeys': this.storageService.getSize(StorageScope.WORKSPACE), - 'startupKind': this.lifecycleService.startupKind, - 'storageError': errorStr - }); - } - })); - - - if (this.storageService.hasErrors) { - return; // do not log performance numbers when errors occured - } - - if (this.environmentService.verbose) { - return; // do not log when running in verbose mode - } - - /* __GDPR__ - "sqliteStorageTimers5" : { - "appReadyTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workbenchReadyTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceSchemaTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "globalReadTimeMain" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "globalReadTimeRenderer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceReadTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workbenchRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceKeys" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "startupKind": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - - /* __GDPR__ - "sqliteStorageTimers" : { - "appReadyTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workbenchReadyTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceSchemaTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "globalReadTimeMain" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "globalReadTimeRenderer" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceReadTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workbenchRequireTime" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "workspaceKeys" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "startupKind": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } - } - */ - this.telemetryService.publicLog('sqliteStorageTimers5', { - 'appReadyTime': appReadyDuration, - 'workbenchReadyTime': workbenchReadyDuration, - 'workspaceRequireTime': workspaceStorageRequireDuration, - 'workspaceSchemaTime': workspaceStorageSchemaDuration, - 'globalReadTimeMain': globalStorageInitDurationMain, - 'globalReadTimeRenderer': globalStorageInitDuratioRenderer, - 'workspaceReadTime': workspaceStorageInitDuration, - 'workbenchRequireTime': workbenchLoadDuration, - 'workspaceKeys': this.storageService.getSize(StorageScope.WORKSPACE), - 'startupKind': this.lifecycleService.startupKind - }); - } - - private initServiceCollection(container: HTMLElement): [IInstantiationService, ServiceCollection] { - const serviceCollection = new ServiceCollection(); - serviceCollection.set(IWorkspaceContextService, this.contextService); - serviceCollection.set(IConfigurationService, this.configurationService); - serviceCollection.set(IEnvironmentService, this.environmentService); - serviceCollection.set(ILabelService, new SyncDescriptor(LabelService, undefined, true)); - serviceCollection.set(ILogService, this._register(this.logService)); - serviceCollection.set(IStorageService, this.storageService); - - this.mainProcessServices.forEach((serviceIdentifier, serviceInstance) => { - serviceCollection.set(serviceIdentifier, serviceInstance); - }); - - const instantiationService: IInstantiationService = new InstantiationService(serviceCollection, true); - - this.notificationService = new NotificationService(); - serviceCollection.set(INotificationService, this.notificationService); - - this.broadcastService = instantiationService.createInstance(BroadcastService, this.configuration.windowId); - serviceCollection.set(IBroadcastService, this.broadcastService); - - serviceCollection.set(IWindowService, new SyncDescriptor(WindowService, [this.configuration.windowId, this.configuration])); - - const sharedProcess = (serviceCollection.get(IWindowsService)).whenSharedProcessReady() - .then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${this.configuration.windowId}`)) - .then(client => { - client.registerChannel('dialog', instantiationService.createInstance(DialogChannel)); - - return client; - }); - - // Hash - serviceCollection.set(IHashService, new SyncDescriptor(HashService, undefined, true)); - - // {{SQL CARBON EDIT}} - if (this.environmentService.args['perf-test']) { - let telemetryOutput = this.environmentService.args['telemetry-output']; - this.telemetryService = new FileTelemetryService(telemetryOutput); - // Telemetry - } else if (!this.environmentService.isExtensionDevelopment && !this.environmentService.args['disable-telemetry'] && !!product.enableTelemetry) { - const channel = getDelayedChannel(sharedProcess.then(c => c.getChannel('telemetryAppender'))); - const config: ITelemetryServiceConfig = { - appender: combinedAppender(new TelemetryAppenderClient(channel), new LogAppender(this.logService)), - commonProperties: resolveWorkbenchCommonProperties(this.storageService, product.commit, pkg.version, this.configuration.machineId, this.environmentService.installSourcePath), - piiPaths: [this.environmentService.appRoot, this.environmentService.extensionsPath] - }; - - this.telemetryService = this._register(instantiationService.createInstance(TelemetryService, config)); - this._register(new ErrorTelemetry(this.telemetryService)); - - // {{SQL CARBON EDIT}} - this.sendUsageEvents(); - } else { - this.telemetryService = NullTelemetryService; - } - - serviceCollection.set(ITelemetryService, this.telemetryService); - this._register(configurationTelemetry(this.telemetryService, this.configurationService)); - - let crashReporterService = NullCrashReporterService; - if (!this.environmentService.disableCrashReporter && product.crashReporter && product.hockeyApp) { - crashReporterService = instantiationService.createInstance(CrashReporterService); - } - serviceCollection.set(ICrashReporterService, crashReporterService); - - serviceCollection.set(IDialogService, instantiationService.createInstance(DialogService)); - - const lifecycleService = instantiationService.createInstance(LifecycleService); - this._register(lifecycleService.onWillShutdown(event => this._onWillShutdown.fire(event))); - this._register(lifecycleService.onShutdown(() => this.dispose())); - serviceCollection.set(ILifecycleService, lifecycleService); - this.lifecycleService = lifecycleService; - - serviceCollection.set(IRequestService, new SyncDescriptor(RequestService, undefined, true)); - serviceCollection.set(IDownloadService, new SyncDescriptor(DownloadService, undefined, true)); - serviceCollection.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService, undefined, true)); - - const remoteAuthorityResolverService = new RemoteAuthorityResolverService(); - serviceCollection.set(IRemoteAuthorityResolverService, remoteAuthorityResolverService); - - const remoteAgentService = new RemoteAgentService(this.configuration, this.notificationService, this.environmentService, remoteAuthorityResolverService); - serviceCollection.set(IRemoteAgentService, remoteAgentService); - - const remoteAgentConnection = remoteAgentService.getConnection(); - if (remoteAgentConnection) { - remoteAgentConnection.registerChannel('dialog', instantiationService.createInstance(DialogChannel)); - remoteAgentConnection.registerChannel('download', new DownloadServiceChannel()); - remoteAgentConnection.registerChannel('loglevel', new LogLevelSetterChannel(this.logService)); - } - - const extensionManagementChannel = getDelayedChannel(sharedProcess.then(c => c.getChannel('extensions'))); - const extensionManagementChannelClient = new ExtensionManagementChannelClient(extensionManagementChannel); - serviceCollection.set(IExtensionManagementServerService, new SyncDescriptor(ExtensionManagementServerService, [extensionManagementChannelClient])); - serviceCollection.set(IExtensionManagementService, new SyncDescriptor(MultiExtensionManagementService)); - - const extensionEnablementService = this._register(instantiationService.createInstance(ExtensionEnablementService)); - serviceCollection.set(IExtensionEnablementService, extensionEnablementService); - - serviceCollection.set(IExtensionService, instantiationService.createInstance(ExtensionService)); - - this.themeService = instantiationService.createInstance(WorkbenchThemeService, document.body); - serviceCollection.set(IWorkbenchThemeService, this.themeService); - - serviceCollection.set(ICommandService, new SyncDescriptor(CommandService, undefined, true)); - - serviceCollection.set(IMarkerService, new SyncDescriptor(MarkerService, undefined, true)); - - - serviceCollection.set(IModeService, new SyncDescriptor(WorkbenchModeServiceImpl)); - - serviceCollection.set(ITextResourceConfigurationService, new SyncDescriptor(TextResourceConfigurationService)); - - serviceCollection.set(ITextResourcePropertiesService, new SyncDescriptor(TextResourcePropertiesService)); - - serviceCollection.set(IModelService, new SyncDescriptor(ModelServiceImpl, undefined, true)); - - serviceCollection.set(IMarkerDecorationsService, new SyncDescriptor(MarkerDecorationsService)); - - serviceCollection.set(IEditorWorkerService, new SyncDescriptor(EditorWorkerServiceImpl)); - - serviceCollection.set(IUntitledEditorService, new SyncDescriptor(UntitledEditorService, undefined, true)); - - serviceCollection.set(ITextMateService, new SyncDescriptor(TextMateService)); - - serviceCollection.set(ISearchService, new SyncDescriptor(SearchService)); - - serviceCollection.set(ISearchHistoryService, new SyncDescriptor(SearchHistoryService)); - - serviceCollection.set(IWorkbenchIssueService, new SyncDescriptor(WorkbenchIssueService)); - - serviceCollection.set(ICodeEditorService, new SyncDescriptor(CodeEditorService)); - - serviceCollection.set(IOpenerService, new SyncDescriptor(OpenerService, undefined, true)); - - serviceCollection.set(IIntegrityService, new SyncDescriptor(IntegrityServiceImpl)); - - const localizationsChannel = getDelayedChannel(sharedProcess.then(c => c.getChannel('localizations'))); - serviceCollection.set(ILocalizationsService, new SyncDescriptor(LocalizationsChannelClient, [localizationsChannel])); - - return [instantiationService, serviceCollection]; - } - - open(): void { - - // Listen on unhandled rejection events - window.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => { - - // See https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent - errors.onUnexpectedError(event.reason); - - // Prevent the printing of this event to the console - event.preventDefault(); - }); - - // Listen on unexpected errors - errors.setUnexpectedErrorHandler((error: any) => { - this.onUnexpectedError(error); - }); - - // Shell Class for CSS Scoping - addClasses(this.container, 'monaco-shell', platform.isWindows ? 'windows' : platform.isLinux ? 'linux' : 'mac'); - - // Create Contents - this.renderContents(); - - // Layout - this.layout(); - - // Listeners - this.registerListeners(); - - // Set lifecycle phase to `Ready` - this.lifecycleService.phase = LifecyclePhase.Ready; - } - - private registerListeners(): void { - this._register(addDisposableListener(window, EventType.RESIZE, e => this.onWindowResize(e, true))); - } - - private onWindowResize(e: any, retry: boolean): void { - if (e.target === window) { - if (window.document && window.document.body && window.document.body.clientWidth === 0) { - // TODO@Ben this is an electron issue on macOS when simple fullscreen is enabled - // where for some reason the window clientWidth is reported as 0 when switching - // between simple fullscreen and normal screen. In that case we schedule the layout - // call at the next animation frame once, in the hope that the dimensions are - // proper then. - if (retry) { - scheduleAtNextAnimationFrame(() => this.onWindowResize(e, false)); - } - return; - } - - this.layout(); - } - } - - // {{SQL CARBON EDIT}} - private diffInDays(nowDate: number, lastUseDate: number): number { - return (nowDate - lastUseDate) / (24 * 3600 * 1000); - } - - onUnexpectedError(error: any): void { - const errorMsg = toErrorMessage(error, true); - if (!errorMsg) { - return; - } - - const now = Date.now(); - if (errorMsg === this.previousErrorValue && now - this.previousErrorTime <= 1000) { - return; // Return if error message identical to previous and shorter than 1 second - } - - this.previousErrorTime = now; - this.previousErrorValue = errorMsg; - - // Log it - this.logService.error(errorMsg); - - // Show to user if friendly message provided - if (error && error.friendlyMessage && this.notificationService) { - this.notificationService.error(error.friendlyMessage); - } - } - - private layout(): void { - this.workbench.layout(); - } - - dispose(): void { - super.dispose(); - - // Dispose Workbench - if (this.workbench) { - this.workbench.dispose(); - } - - this.mainProcessClient.dispose(); - } -} - -registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { - - // Foreground - const windowForeground = theme.getColor(foreground); - if (windowForeground) { - collector.addRule(`.monaco-shell { color: ${windowForeground}; }`); - } - - // Selection - const windowSelectionBackground = theme.getColor(selectionBackground); - if (windowSelectionBackground) { - collector.addRule(`.monaco-shell ::selection { background-color: ${windowSelectionBackground}; }`); - } - - // Input placeholder - const placeholderForeground = theme.getColor(inputPlaceholderForeground); - if (placeholderForeground) { - collector.addRule(`.monaco-shell input::-webkit-input-placeholder { color: ${placeholderForeground}; }`); - collector.addRule(`.monaco-shell textarea::-webkit-input-placeholder { color: ${placeholderForeground}; }`); - } - - // List highlight - const listHighlightForegroundColor = theme.getColor(listHighlightForeground); - if (listHighlightForegroundColor) { - collector.addRule(` - .monaco-shell .monaco-tree .monaco-tree-row .monaco-highlighted-label .highlight, - .monaco-shell .monaco-list .monaco-list-row .monaco-highlighted-label .highlight { - color: ${listHighlightForegroundColor}; - } - `); - } - - // We need to set the workbench background color so that on Windows we get subpixel-antialiasing. - const workbenchBackground = WORKBENCH_BACKGROUND(theme); - collector.addRule(`.monaco-workbench { background-color: ${workbenchBackground}; }`); - - // Scrollbars - const scrollbarShadowColor = theme.getColor(scrollbarShadow); - if (scrollbarShadowColor) { - collector.addRule(` - .monaco-shell .monaco-scrollable-element > .shadow.top { - box-shadow: ${scrollbarShadowColor} 0 6px 6px -6px inset; - } - - .monaco-shell .monaco-scrollable-element > .shadow.left { - box-shadow: ${scrollbarShadowColor} 6px 0 6px -6px inset; - } - - .monaco-shell .monaco-scrollable-element > .shadow.top.left { - box-shadow: ${scrollbarShadowColor} 6px 6px 6px -6px inset; - } - `); - } - - const scrollbarSliderBackgroundColor = theme.getColor(scrollbarSliderBackground); - if (scrollbarSliderBackgroundColor) { - collector.addRule(` - .monaco-shell .monaco-scrollable-element > .scrollbar > .slider { - background: ${scrollbarSliderBackgroundColor}; - } - `); - } - - const scrollbarSliderHoverBackgroundColor = theme.getColor(scrollbarSliderHoverBackground); - if (scrollbarSliderHoverBackgroundColor) { - collector.addRule(` - .monaco-shell .monaco-scrollable-element > .scrollbar > .slider:hover { - background: ${scrollbarSliderHoverBackgroundColor}; - } - `); - } - - const scrollbarSliderActiveBackgroundColor = theme.getColor(scrollbarSliderActiveBackground); - if (scrollbarSliderActiveBackgroundColor) { - collector.addRule(` - .monaco-shell .monaco-scrollable-element > .scrollbar > .slider.active { - background: ${scrollbarSliderActiveBackgroundColor}; - } - `); - } - - // Focus outline - const focusOutline = theme.getColor(focusBorder); - if (focusOutline) { - collector.addRule(` - .monaco-shell [tabindex="0"]:focus, - .monaco-shell .synthetic-focus, - .monaco-shell select:focus, - .monaco-shell .monaco-tree.focused.no-focused-item:focus:before, - .monaco-shell .monaco-list:not(.element-focused):focus:before, - .monaco-shell input[type="button"]:focus, - .monaco-shell input[type="text"]:focus, - .monaco-shell button:focus, - .monaco-shell textarea:focus, - .monaco-shell input[type="search"]:focus, - .monaco-shell input[type="checkbox"]:focus { - outline-color: ${focusOutline}; - } - `); - } - - // High Contrast theme overwrites for outline - if (theme.type === HIGH_CONTRAST) { - collector.addRule(` - .monaco-shell.hc-black [tabindex="0"]:focus, - .monaco-shell.hc-black .synthetic-focus, - .monaco-shell.hc-black select:focus, - .monaco-shell.hc-black input[type="button"]:focus, - .monaco-shell.hc-black input[type="text"]:focus, - .monaco-shell.hc-black textarea:focus, - .monaco-shell.hc-black input[type="checkbox"]:focus { - outline-style: solid; - outline-width: 1px; - } - - .monaco-shell.hc-black .monaco-tree.focused.no-focused-item:focus:before { - outline-width: 1px; - outline-offset: -2px; - } - - .monaco-shell.hc-black .synthetic-focus input { - background: transparent; /* Search input focus fix when in high contrast */ - } - `); - } -}); diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 16661986a6..c1a14a1e25 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -6,7 +6,7 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import * as errors from 'vs/base/common/errors'; -import * as objects from 'vs/base/common/objects'; +import { equals, deepClone, assign } from 'vs/base/common/objects'; import * as DOM from 'vs/base/browser/dom'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction, Action } from 'vs/base/common/actions'; @@ -14,7 +14,6 @@ import { IFileService } from 'vs/platform/files/common/files'; import { toResource, IUntitledResourceInput } from 'vs/workbench/common/editor'; import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IWindowsService, IWindowService, IWindowSettings, IOpenFileRequest, IWindowsConfiguration, IAddFoldersRequest, IRunActionInWindowRequest, IPathData, IRunKeybindingInWindowRequest } from 'vs/platform/windows/common/windows'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { ITitleService } from 'vs/workbench/services/title/common/titleService'; @@ -23,22 +22,27 @@ import * as browser from 'vs/base/browser/browser'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/keybindingService'; -import { Themable } from 'vs/workbench/common/theme'; -import { ipcRenderer as ipc, webFrame } from 'electron'; +import { ipcRenderer as ipc, webFrame, crashReporter, Event } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { fillInActionBarActions } from 'vs/platform/actions/browser/menuItemActionItem'; import { RunOnceScheduler } from 'vs/base/common/async'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { LifecyclePhase, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; -import { IIntegrityService } from 'vs/platform/integrity/common/integrity'; -import { AccessibilitySupport, isRootUser, isWindows, isMacintosh } from 'vs/base/common/platform'; -import product from 'vs/platform/node/product'; +import { IIntegrityService } from 'vs/workbench/services/integrity/common/integrity'; +import { isRootUser, isWindows, isMacintosh, isLinux } from 'vs/base/common/platform'; +import product from 'vs/platform/product/node/product'; +import pkg from 'vs/platform/product/node/package'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; +import { WorkbenchState, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { coalesce } from 'vs/base/common/arrays'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const TextInputActions: IAction[] = [ new Action('undo', nls.localize('undo', "Undo"), undefined, true, () => Promise.resolve(document.execCommand('undo'))), @@ -51,7 +55,7 @@ const TextInputActions: IAction[] = [ new Action('editor.action.selectAll', nls.localize('selectAll', "Select All"), undefined, true, () => Promise.resolve(document.execCommand('selectAll'))) ]; -export class ElectronWindow extends Themable { +export class ElectronWindow extends Disposable { private touchBarMenu?: IMenu; private touchBarUpdater: RunOnceScheduler; @@ -63,11 +67,13 @@ export class ElectronWindow extends Themable { private addFoldersScheduler: RunOnceScheduler; private pendingFoldersToAdd: URI[]; + private closeEmptyWindowScheduler: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.onAllEditorsClosed(), 50)); + constructor( @IEditorService private readonly editorService: EditorServiceImpl, @IWindowsService private readonly windowsService: IWindowsService, @IWindowService private readonly windowService: IWindowService, - @IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService, + @IConfigurationService private readonly configurationService: IConfigurationService, @ITitleService private readonly titleService: ITitleService, @IWorkbenchThemeService protected themeService: IWorkbenchThemeService, @INotificationService private readonly notificationService: INotificationService, @@ -79,9 +85,12 @@ export class ElectronWindow extends Themable { @IFileService private readonly fileService: IFileService, @IMenuService private readonly menuService: IMenuService, @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IIntegrityService private readonly integrityService: IIntegrityService + @IIntegrityService private readonly integrityService: IIntegrityService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IAccessibilityService private readonly accessibilityService: IAccessibilityService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService ) { - super(themeService); + super(); this.touchBarDisposables = []; @@ -105,7 +114,7 @@ export class ElectronWindow extends Themable { }); // Support runAction event - ipc.on('vscode:runAction', (event: any, request: IRunActionInWindowRequest) => { + ipc.on('vscode:runAction', (event: Event, request: IRunActionInWindowRequest) => { const args: any[] = request.args || []; // If we run an action from the touchbar, we fill in the currently active resource @@ -136,29 +145,27 @@ export class ElectronWindow extends Themable { }); // Support runKeybinding event - ipc.on('vscode:runKeybinding', (event: any, request: IRunKeybindingInWindowRequest) => { + ipc.on('vscode:runKeybinding', (event: Event, request: IRunKeybindingInWindowRequest) => { if (document.activeElement) { this.keybindingService.dispatchByUserSettingsLabel(request.userSettingsLabel, document.activeElement); } }); // Error reporting from main - ipc.on('vscode:reportError', (event: any, error: string) => { + ipc.on('vscode:reportError', (event: Event, error: string) => { if (error) { - const errorParsed = JSON.parse(error); - errorParsed.mainProcess = true; - errors.onUnexpectedError(errorParsed); + errors.onUnexpectedError(JSON.parse(error)); } }); // Support openFiles event for existing and new files - ipc.on('vscode:openFiles', (event: any, request: IOpenFileRequest) => this.onOpenFiles(request)); + ipc.on('vscode:openFiles', (event: Event, request: IOpenFileRequest) => this.onOpenFiles(request)); // Support addFolders event if we have a workspace opened - ipc.on('vscode:addFolders', (event: any, request: IAddFoldersRequest) => this.onAddFoldersRequest(request)); + ipc.on('vscode:addFolders', (event: Event, request: IAddFoldersRequest) => this.onAddFoldersRequest(request)); // Message support - ipc.on('vscode:showInfoMessage', (event: any, message: string) => { + ipc.on('vscode:showInfoMessage', (event: Event, message: string) => { this.notificationService.info(message); }); @@ -200,8 +207,8 @@ export class ElectronWindow extends Themable { }); // keyboard layout changed event - ipc.on('vscode:accessibilitySupportChanged', (event: any, accessibilitySupportEnabled: boolean) => { - browser.setAccessibilitySupport(accessibilitySupportEnabled ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled); + ipc.on('vscode:accessibilitySupportChanged', (event: Event, accessibilitySupportEnabled: boolean) => { + this.accessibilityService.setAccessibilitySupport(accessibilitySupportEnabled ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled); }); // Zoom level changes @@ -214,6 +221,51 @@ export class ElectronWindow extends Themable { // Context menu support in input/textarea window.document.addEventListener('contextmenu', e => this.onContextMenu(e)); + + // Listen to visible editor changes + this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange())); + + // Listen to editor closing (if we run with --wait) + const filesToWait = this.windowService.getConfiguration().filesToWait; + if (filesToWait) { + const resourcesToWaitFor = coalesce(filesToWait.paths.map(p => p.fileUri)); + const waitMarkerFile = URI.file(filesToWait.waitMarkerFilePath); + const listenerDispose = this.editorService.onDidCloseEditor(() => this.onEditorClosed(listenerDispose, resourcesToWaitFor, waitMarkerFile)); + + this._register(listenerDispose); + } + } + + private onDidVisibleEditorsChange(): void { + + // Close when empty: check if we should close the window based on the setting + // Overruled by: window has a workspace opened or this window is for extension development + // or setting is disabled. Also enabled when running with --wait from the command line. + const visibleEditors = this.editorService.visibleControls; + if (visibleEditors.length === 0 && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && !this.environmentService.isExtensionDevelopment) { + const closeWhenEmpty = this.configurationService.getValue('window.closeWhenEmpty'); + if (closeWhenEmpty || this.environmentService.args.wait) { + this.closeEmptyWindowScheduler.schedule(); + } + } + } + + private onAllEditorsClosed(): void { + const visibleEditors = this.editorService.visibleControls.length; + if (visibleEditors === 0) { + this.windowService.closeWindow(); + } + } + + private onEditorClosed(listenerDispose: IDisposable, resourcesToWaitFor: URI[], waitMarkerFile: URI): void { + + // In wait mode, listen to changes to the editors and wait until the files + // are closed that the user wants to wait for. When this happens we delete + // the wait marker file to signal to the outside that editing is done. + if (resourcesToWaitFor.every(resource => !this.editorService.isOpen({ resource }))) { + listenerDispose.dispose(); + this.fileService.del(waitMarkerFile); + } } private onContextMenu(e: MouseEvent): void { @@ -260,7 +312,7 @@ export class ElectronWindow extends Themable { // Handle window.open() calls const $this = this; - (window).open = function (url: string, target: string, features: string, replace: boolean): any { + window.open = function (url: string, target: string, features: string, replace: boolean): Window | null { $this.windowsService.openExternal(url); return null; @@ -297,6 +349,11 @@ export class ElectronWindow extends Themable { // Touchbar menu (if enabled) this.updateTouchbarMenu(); + + // Crash reporter (if enabled) + if (!this.environmentService.disableCrashReporter && product.crashReporter && product.hockeyApp && this.configurationService.getValue('telemetry.enableCrashReporter')) { + this.setupCrashReporter(); + } } private updateTouchbarMenu(): void { @@ -354,12 +411,40 @@ export class ElectronWindow extends Themable { } // Only update if the actions have changed - if (!objects.equals(this.lastInstalledTouchedBar, items)) { + if (!equals(this.lastInstalledTouchedBar, items)) { this.lastInstalledTouchedBar = items; this.windowService.updateTouchBar(items); } } + private setupCrashReporter(): void { + + // base options with product info + const options = { + companyName: product.crashReporter.companyName, + productName: product.crashReporter.productName, + submitURL: isWindows ? product.hockeyApp[`win32-${process.arch}`] : isLinux ? product.hockeyApp[`linux-${process.arch}`] : product.hockeyApp.darwin, + extra: { + vscode_version: pkg.version, + vscode_commit: product.commit + } + }; + + // mixin telemetry info + this.telemetryService.getTelemetryInfo() + .then(info => { + assign(options.extra, { + vscode_sessionId: info.sessionId + }); + + // start crash reporter right here + crashReporter.start(deepClone(options)); + + // start crash reporter in the main process + return this.windowsService.startCrashReporter(options); + }); + } + private onAddFoldersRequest(request: IAddFoldersRequest): void { // Buffer all pending requests diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts deleted file mode 100644 index fa0bb41ffa..0000000000 --- a/src/vs/workbench/electron-browser/workbench.ts +++ /dev/null @@ -1,1933 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import 'vs/css!./media/workbench'; - -import { localize } from 'vs/nls'; -// {{SQL CARBON EDIT}} - Import toDisposable -import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { Event, Emitter } from 'vs/base/common/event'; -import * as DOM from 'vs/base/browser/dom'; -import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; -import * as browser from 'vs/base/browser/browser'; -import * as perf from 'vs/base/common/performance'; -import * as errors from 'vs/base/common/errors'; -import { BackupFileService, InMemoryBackupFileService } from 'vs/workbench/services/backup/node/backupFileService'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; -import { IResourceInput } from 'vs/platform/editor/common/editor'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { IEditorInputFactoryRegistry, Extensions as EditorExtensions, TextCompareEditorVisibleContext, TEXT_DIFF_EDITOR_ID, EditorsVisibleContext, InEditorZenModeContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, IUntitledResourceInput, IResourceDiffInput, SplitEditorsVertically, TextCompareEditorActiveContext, ActiveEditorContext } from 'vs/workbench/common/editor'; -import { HistoryService } from 'vs/workbench/services/history/electron-browser/history'; -import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; -import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; -import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; -import { StatusbarPart } from 'vs/workbench/browser/parts/statusbar/statusbarPart'; -import { TitlebarPart } from 'vs/workbench/browser/parts/titlebar/titlebarPart'; -import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; -import { IActionBarRegistry, Extensions as ActionBarExtensions } from 'vs/workbench/browser/actions'; -import { PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; -import { QuickOpenController } from 'vs/workbench/browser/parts/quickopen/quickOpenController'; -import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { QuickInputService } from 'vs/workbench/browser/parts/quickinput/quickInput'; -import { getServices } from 'vs/platform/instantiation/common/extensions'; -import { Position, Parts, IPartService, IDimension, PositionToString, ILayoutOptions } from 'vs/workbench/services/part/common/partService'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IStorageService, StorageScope, IWillSaveStateEvent, WillSaveStateReason } from 'vs/platform/storage/common/storage'; -import { ContextMenuService as NativeContextMenuService } from 'vs/workbench/services/contextview/electron-browser/contextmenuService'; -import { ContextMenuService as HTMLContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; -import { WorkbenchKeybindingService } from 'vs/workbench/services/keybinding/electron-browser/keybindingService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { WorkspaceService, DefaultConfigurationExportHelper } from 'vs/workbench/services/configuration/node/configurationService'; -import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService'; -import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { IKeybindingEditingService, KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; -import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IActivityService } from 'vs/workbench/services/activity/common/activity'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { RemoteFileService } from 'vs/workbench/services/files/electron-browser/remoteFileService'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/electron-browser/configurationResolverService'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ITitleService } from 'vs/workbench/services/title/common/titleService'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; -import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -import { ClipboardService } from 'vs/platform/clipboard/electron-browser/clipboardService'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { TextFileService } from 'vs/workbench/services/textfile/electron-browser/textFileService'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { ISCMService } from 'vs/workbench/services/scm/common/scm'; -import { SCMService } from 'vs/workbench/services/scm/common/scmService'; -import { IProgressService2 } from 'vs/platform/progress/common/progress'; -import { ProgressService2 } from 'vs/workbench/services/progress/browser/progressService2'; -import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { LifecyclePhase, StartupKind } from 'vs/platform/lifecycle/common/lifecycle'; -import { LifecycleService } from 'vs/platform/lifecycle/electron-browser/lifecycleService'; -import { IWindowService, IWindowConfiguration, IPath, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; -import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; -import { IMenuService, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { MenuService } from 'vs/platform/actions/common/menuService'; -import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { ShowPreviousWindowTab, MoveWindowTabToNewWindow, MergeAllWindowTabs, ShowNextWindowTab, ToggleWindowTabsBar, NewWindowTab, OpenRecentAction, ReloadWindowAction, ReloadWindowWithExtensionsDisabledAction } from 'vs/workbench/electron-browser/actions/windowActions'; -import { ToggleDevToolsAction } from 'vs/workbench/electron-browser/actions/developerActions'; -import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; -import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { WorkspaceEditingService } from 'vs/workbench/services/workspace/node/workspaceEditingService'; -import { FileDecorationsService } from 'vs/workbench/services/decorations/browser/decorationsService'; -import { IDecorationsService } from 'vs/workbench/services/decorations/browser/decorations'; -import { ActivityService } from 'vs/workbench/services/activity/browser/activityService'; -import { URI } from 'vs/base/common/uri'; -import { IListService, ListService } from 'vs/platform/list/browser/listService'; -import { InputFocusedContext, IsMacContext, IsLinuxContext, IsWindowsContext } from 'vs/platform/workbench/common/contextkeys'; -import { IViewsService } from 'vs/workbench/common/views'; -import { ViewsService } from 'vs/workbench/browser/parts/views/views'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { NotificationService } from 'vs/workbench/services/notification/common/notificationService'; -import { NotificationsCenter } from 'vs/workbench/browser/parts/notifications/notificationsCenter'; -import { NotificationsAlerts } from 'vs/workbench/browser/parts/notifications/notificationsAlerts'; -import { NotificationsStatus } from 'vs/workbench/browser/parts/notifications/notificationsStatus'; -import { registerNotificationCommands } from 'vs/workbench/browser/parts/notifications/notificationsCommands'; -import { NotificationsToasts } from 'vs/workbench/browser/parts/notifications/notificationsToasts'; -import { IPCClient } from 'vs/base/parts/ipc/node/ipc'; -import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver'; -import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -import { PreferencesService } from 'vs/workbench/services/preferences/browser/preferencesService'; -import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService, GroupDirection, preferredSideBySideGroupDirection } from 'vs/workbench/services/group/common/editorGroupsService'; -import { EditorService } from 'vs/workbench/services/editor/browser/editorService'; - -// {{SQL CARBON EDIT}} -import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement'; -import { ConnectionManagementService } from 'sql/platform/connection/common/connectionManagementService'; -import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService'; -import { ConnectionDialogService } from 'sql/workbench/services/connection/browser/connectionDialogService'; -import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService'; -import { ErrorMessageService } from 'sql/workbench/services/errorMessage/browser/errorMessageService'; -import { ServerGroupController } from 'sql/workbench/services/serverGroup/browser/serverGroupController'; -import { IServerGroupController } from 'sql/platform/serverGroup/common/serverGroupController'; -import { IAngularEventingService } from 'sql/platform/angularEventing/common/angularEventingService'; -import { AngularEventingService } from 'sql/platform/angularEventing/node/angularEventingService'; -import { ICapabilitiesService, CapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService'; -import { ICredentialsService, CredentialsService } from 'sql/platform/credentials/common/credentialsService'; -import { ISerializationService, SerializationService } from 'sql/platform/serialization/common/serializationService'; -import { IMetadataService, MetadataService } from 'sql/platform/metadata/common/metadataService'; -import { IObjectExplorerService, ObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService'; -import { ITaskService, TaskService } from 'sql/platform/taskHistory/common/taskService'; -import { IQueryModelService } from 'sql/platform/query/common/queryModel'; -import { QueryModelService } from 'sql/platform/query/common/queryModelService'; -import { IQueryEditorService } from 'sql/workbench/services/queryEditor/common/queryEditorService'; -import { QueryEditorService } from 'sql/workbench/services/queryEditor/browser/queryEditorService'; -import { IQueryManagementService, QueryManagementService } from 'sql/platform/query/common/queryManagement'; -import { IEditorDescriptorService, EditorDescriptorService } from 'sql/workbench/services/queryEditor/common/editorDescriptorService'; -import { IScriptingService, ScriptingService } from 'sql/platform/scripting/common/scriptingService'; -import { IAdminService, AdminService } from 'sql/workbench/services/admin/common/adminService'; -import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces'; -import { JobManagementService } from 'sql/platform/jobManagement/common/jobManagementService'; -import { IDacFxService, DacFxService } from 'sql/platform/dacfx/common/dacFxService'; -import { IBackupService } from 'sql/platform/backup/common/backupService'; -import { BackupService } from 'sql/platform/backup/common/backupServiceImp'; -import { IBackupUiService } from 'sql/workbench/services/backup/common/backupUiService'; -import { BackupUiService } from 'sql/workbench/services/backup/browser/backupUiService'; -import { IRestoreDialogController, IRestoreService } from 'sql/platform/restore/common/restoreService'; -import { RestoreService, RestoreDialogController } from 'sql/platform/restore/common/restoreServiceImpl'; -import { INewDashboardTabDialogService } from 'sql/workbench/services/dashboard/common/newDashboardTabDialog'; -import { NewDashboardTabDialogService } from 'sql/workbench/services/dashboard/browser/newDashboardTabDialogService'; -import { IFileBrowserService } from 'sql/platform/fileBrowser/common/interfaces'; -import { FileBrowserService } from 'sql/platform/fileBrowser/common/fileBrowserService'; -import { IFileBrowserDialogController } from 'sql/workbench/services/fileBrowser/common/fileBrowserDialogController'; -import { FileBrowserDialogController } from 'sql/workbench/services/fileBrowser/browser/fileBrowserDialogController'; -import { IInsightsDialogService } from 'sql/workbench/services/insights/common/insightsDialogService'; -import { InsightsDialogService } from 'sql/workbench/services/insights/browser/insightsDialogService'; -import { IAccountManagementService } from 'sql/platform/accountManagement/common/interfaces'; -import { AccountManagementService } from 'sql/workbench/services/accountManagement/browser/accountManagementService'; -import { IProfilerService } from 'sql/workbench/services/profiler/common/interfaces'; -import { ProfilerService } from 'sql/workbench/services/profiler/common/profilerService'; -import { ISqlOAuthService } from 'sql/platform/oAuth/common/sqlOAuthService'; -import { SqlOAuthService } from 'sql/platform/oAuth/electron-browser/sqlOAuthServiceImpl'; -import { IClipboardService as sqlIClipboardService } from 'sql/platform/clipboard/common/clipboardService'; -import { ClipboardService as sqlClipboardService } from 'sql/platform/clipboard/electron-browser/clipboardService'; -import { AccountPickerService } from 'sql/platform/accountManagement/browser/accountPickerService'; -import { IAccountPickerService } from 'sql/platform/accountManagement/common/accountPicker'; -import { IResourceProviderService } from 'sql/workbench/services/resourceProvider/common/resourceProviderService'; -import { ResourceProviderService } from 'sql/workbench/services/resourceProvider/browser/resourceProviderService'; -import { IDashboardViewService } from 'sql/platform/dashboard/common/dashboardViewService'; -import { DashboardViewService } from 'sql/platform/dashboard/common/dashboardViewServiceImpl'; -import { IModelViewService } from 'sql/platform/modelComponents/common/modelViewService'; -import { ModelViewService } from 'sql/platform/modelComponents/common/modelViewServiceImpl'; -import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService'; -import { DashboardService } from 'sql/platform/dashboard/browser/dashboardServiceImpl'; -import { NotebookService } from 'sql/workbench/services/notebook/common/notebookServiceImpl'; -import { INotebookService } from 'sql/workbench/services/notebook/common/notebookService'; -import { ICommandLineProcessing } from 'sql/workbench/services/commandLine/common/commandLine'; -import { CommandLineService } from 'sql/workbench/services/commandLine/common/commandLineService'; -import { OEShimService, IOEShimService } from 'sql/parts/objectExplorer/common/objectExplorerViewTreeShim'; -// {{SQL CARBON EDIT}} - End -import { IExtensionUrlHandler, ExtensionUrlHandler } from 'vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler'; -import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; -import { WorkbenchThemeService } from 'vs/workbench/services/themes/electron-browser/workbenchThemeService'; -import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { FileDialogService } from 'vs/workbench/services/dialogs/electron-browser/dialogService'; -import { LogStorageAction } from 'vs/platform/storage/node/storageService'; -import { Sizing, Direction, Grid, View } from 'vs/base/browser/ui/grid/grid'; -import { IEditor } from 'vs/editor/common/editorCommon'; -import { WorkbenchLayout } from 'vs/workbench/browser/layout'; - -interface WorkbenchParams { - configuration: IWindowConfiguration; - serviceCollection: ServiceCollection; -} - -interface IZenModeSettings { - fullScreen: boolean; - centerLayout: boolean; - hideTabs: boolean; - hideActivityBar: boolean; - hideStatusBar: boolean; - hideLineNumbers: boolean; - restore: boolean; -} - -export interface IWorkbenchStartedInfo { - customKeybindingsCount: number; - pinnedViewlets: string[]; - restoredViewlet: string; - restoredEditorsCount: number; -} - -type FontAliasingOption = 'default' | 'antialiased' | 'none' | 'auto'; - -const fontAliasingValues: FontAliasingOption[] = ['antialiased', 'none', 'auto']; - -const Identifiers = { - WORKBENCH_CONTAINER: 'workbench.main.container', - TITLEBAR_PART: 'workbench.parts.titlebar', - ACTIVITYBAR_PART: 'workbench.parts.activitybar', - SIDEBAR_PART: 'workbench.parts.sidebar', - PANEL_PART: 'workbench.parts.panel', - EDITOR_PART: 'workbench.parts.editor', - STATUSBAR_PART: 'workbench.parts.statusbar' -}; - -function getWorkbenchStateString(state: WorkbenchState): string { - switch (state) { - case WorkbenchState.EMPTY: return 'empty'; - case WorkbenchState.FOLDER: return 'folder'; - case WorkbenchState.WORKSPACE: return 'workspace'; - } -} - -interface IZenMode { - active: boolean; - transitionedToFullScreen: boolean; - transitionedToCenteredEditorLayout: boolean; - transitionDisposeables: IDisposable[]; - wasSideBarVisible: boolean; - wasPanelVisible: boolean; -} - -interface IWorkbenchUIState { - lastPanelHeight?: number; - lastPanelWidth?: number; - lastSidebarDimension?: number; -} - -export class Workbench extends Disposable implements IPartService { - - private static readonly sidebarHiddenStorageKey = 'workbench.sidebar.hidden'; - private static readonly menubarVisibilityConfigurationKey = 'window.menuBarVisibility'; - private static readonly panelHiddenStorageKey = 'workbench.panel.hidden'; - private static readonly zenModeActiveStorageKey = 'workbench.zenmode.active'; - private static readonly centeredEditorLayoutActiveStorageKey = 'workbench.centerededitorlayout.active'; - private static readonly panelPositionStorageKey = 'workbench.panel.location'; - private static readonly defaultPanelPositionStorageKey = 'workbench.panel.defaultLocation'; - private static readonly sidebarPositionConfigurationKey = 'workbench.sideBar.location'; - private static readonly statusbarVisibleConfigurationKey = 'workbench.statusBar.visible'; - private static readonly activityBarVisibleConfigurationKey = 'workbench.activityBar.visible'; - private static readonly closeWhenEmptyConfigurationKey = 'window.closeWhenEmpty'; - private static readonly fontAliasingConfigurationKey = 'workbench.fontAliasing'; - - _serviceBrand: any; - - private workbenchParams: WorkbenchParams; - private workbench: HTMLElement; - private workbenchStarted: boolean; - private workbenchRestored: boolean; - private workbenchShutdown: boolean; - - private editorService: EditorService; - private editorGroupService: IEditorGroupsService; - private contextViewService: ContextViewService; - private contextKeyService: IContextKeyService; - private keybindingService: IKeybindingService; - private backupFileService: IBackupFileService; - private fileService: IFileService; - private quickInput: QuickInputService; - - private workbenchGrid: Grid | WorkbenchLayout; - - private titlebarPart: TitlebarPart; - private activitybarPart: ActivitybarPart; - private sidebarPart: SidebarPart; - private panelPart: PanelPart; - private editorPart: EditorPart; - private statusbarPart: StatusbarPart; - - private titlebarPartView: View; - private activitybarPartView: View; - private sidebarPartView: View; - private panelPartView: View; - private editorPartView: View; - private statusbarPartView: View; - - private quickOpen: QuickOpenController; - private notificationsCenter: NotificationsCenter; - private notificationsToasts: NotificationsToasts; - - private editorHidden: boolean; - private sideBarHidden: boolean; - private statusBarHidden: boolean; - private activityBarHidden: boolean; - private menubarToggled: boolean; - private sideBarPosition: Position; - private panelPosition: Position; - private panelHidden: boolean; - private menubarVisibility: MenuBarVisibility; - private zenMode: IZenMode; - private fontAliasing: FontAliasingOption; - private hasInitialFilesToOpen: boolean; - private shouldCenterLayout = false; - private uiState: IWorkbenchUIState = { - lastPanelHeight: 350, - lastPanelWidth: 350, - lastSidebarDimension: 300, - }; - - private inZenMode: IContextKey; - private sideBarVisibleContext: IContextKey; - - private closeEmptyWindowScheduler: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.onAllEditorsClosed(), 50)); - - constructor( - private container: HTMLElement, - private configuration: IWindowConfiguration, - serviceCollection: ServiceCollection, - private lifecycleService: LifecycleService, - private mainProcessClient: IPCClient, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IStorageService private readonly storageService: IStorageService, - @IConfigurationService private readonly configurationService: WorkspaceService, - @IWorkbenchThemeService private readonly themeService: WorkbenchThemeService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IWindowService private readonly windowService: IWindowService, - @INotificationService private readonly notificationService: NotificationService - ) { - super(); - - this.workbenchParams = { configuration, serviceCollection }; - - this.hasInitialFilesToOpen = - (configuration.filesToCreate && configuration.filesToCreate.length > 0) || - (configuration.filesToOpen && configuration.filesToOpen.length > 0) || - (configuration.filesToDiff && configuration.filesToDiff.length > 0); - } - - startup(): Promise { - this.workbenchStarted = true; - - // Create Workbench Container - this.createWorkbench(); - - // Install some global actions - this.createGlobalActions(); - - // Services - this.initServices(); - - // Context Keys - this.handleContextKeys(); - - // Register Listeners - this.registerListeners(); - - // Settings - this.initSettings(); - - // Create Workbench and Parts - this.renderWorkbench(); - - // Workbench Layout - this.createWorkbenchLayout(); - - // Driver - if (this.environmentService.driverHandle) { - registerWindowDriver(this.mainProcessClient, this.configuration.windowId, this.instantiationService).then(disposable => this._register(disposable)); - } - - // Restore Parts - return this.restoreParts(); - } - - private createWorkbench(): void { - this.workbench = document.createElement('div'); - this.workbench.id = Identifiers.WORKBENCH_CONTAINER; - DOM.addClass(this.workbench, 'monaco-workbench'); - - this._register(DOM.addDisposableListener(this.workbench, DOM.EventType.SCROLL, () => { - this.workbench.scrollTop = 0; // Prevent workbench from scrolling #55456 - })); - } - - private createGlobalActions(): void { - const isDeveloping = !this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment; - - // Actions registered here to adjust for developing vs built workbench - const registry = Registry.as(Extensions.WorkbenchActions); - registry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL, isDeveloping ? { primary: KeyMod.CtrlCmd | KeyCode.KEY_R } : undefined), 'Reload Window'); - registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleDevToolsAction, ToggleDevToolsAction.ID, ToggleDevToolsAction.LABEL, isDeveloping ? { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_I, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_I } } : undefined), 'Developer: Toggle Developer Tools', localize('developer', "Developer")); - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRecentAction, OpenRecentAction.ID, OpenRecentAction.LABEL, { primary: isDeveloping ? null : KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent...', localize('file', "File")); - registry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowWithExtensionsDisabledAction, ReloadWindowWithExtensionsDisabledAction.ID, ReloadWindowWithExtensionsDisabledAction.LABEL), 'Reload Window Without Extensions'); - registry.registerWorkbenchAction(new SyncActionDescriptor(LogStorageAction, LogStorageAction.ID, LogStorageAction.LABEL), 'Developer: Log Storage', localize('developer', "Developer")); - - // Actions for macOS native tabs management (only when enabled) - const windowConfig = this.configurationService.getValue(); - if (windowConfig && windowConfig.window && windowConfig.window.nativeTabs) { - registry.registerWorkbenchAction(new SyncActionDescriptor(NewWindowTab, NewWindowTab.ID, NewWindowTab.LABEL), 'New Window Tab'); - registry.registerWorkbenchAction(new SyncActionDescriptor(ShowPreviousWindowTab, ShowPreviousWindowTab.ID, ShowPreviousWindowTab.LABEL), 'Show Previous Window Tab'); - registry.registerWorkbenchAction(new SyncActionDescriptor(ShowNextWindowTab, ShowNextWindowTab.ID, ShowNextWindowTab.LABEL), 'Show Next Window Tab'); - registry.registerWorkbenchAction(new SyncActionDescriptor(MoveWindowTabToNewWindow, MoveWindowTabToNewWindow.ID, MoveWindowTabToNewWindow.LABEL), 'Move Window Tab to New Window'); - registry.registerWorkbenchAction(new SyncActionDescriptor(MergeAllWindowTabs, MergeAllWindowTabs.ID, MergeAllWindowTabs.LABEL), 'Merge All Windows'); - registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleWindowTabsBar, ToggleWindowTabsBar.ID, ToggleWindowTabsBar.LABEL), 'Toggle Window Tabs Bar'); - } - } - - private initServices(): void { - const { serviceCollection } = this.workbenchParams; - - // Services we contribute - serviceCollection.set(IPartService, this); - - // Clipboard - serviceCollection.set(IClipboardService, new SyncDescriptor(ClipboardService)); - - // Status bar - this.statusbarPart = this.instantiationService.createInstance(StatusbarPart, Identifiers.STATUSBAR_PART); - serviceCollection.set(IStatusbarService, this.statusbarPart); - - // Progress 2 - serviceCollection.set(IProgressService2, new SyncDescriptor(ProgressService2)); - - // Keybindings - this.contextKeyService = this.instantiationService.createInstance(ContextKeyService); - serviceCollection.set(IContextKeyService, this.contextKeyService); - - this.keybindingService = this.instantiationService.createInstance(WorkbenchKeybindingService, window); - serviceCollection.set(IKeybindingService, this.keybindingService); - - // List - serviceCollection.set(IListService, this.instantiationService.createInstance(ListService)); - - // Context view service - this.contextViewService = this.instantiationService.createInstance(ContextViewService, this.workbench); - serviceCollection.set(IContextViewService, this.contextViewService); - - // Use themable context menus when custom titlebar is enabled to match custom menubar - if (!isMacintosh && this.useCustomTitleBarStyle()) { - serviceCollection.set(IContextMenuService, new SyncDescriptor(HTMLContextMenuService, [null])); - } else { - serviceCollection.set(IContextMenuService, new SyncDescriptor(NativeContextMenuService)); - } - - // Menus/Actions - serviceCollection.set(IMenuService, new SyncDescriptor(MenuService)); - - // Sidebar part - this.sidebarPart = this.instantiationService.createInstance(SidebarPart, Identifiers.SIDEBAR_PART); - - // Viewlet service - serviceCollection.set(IViewletService, this.sidebarPart); - - // Panel service (panel part) - this.panelPart = this.instantiationService.createInstance(PanelPart, Identifiers.PANEL_PART); - serviceCollection.set(IPanelService, this.panelPart); - - // views service - const viewsService = this.instantiationService.createInstance(ViewsService); - serviceCollection.set(IViewsService, viewsService); - - // Activity service (activitybar part) - this.activitybarPart = this.instantiationService.createInstance(ActivitybarPart, Identifiers.ACTIVITYBAR_PART); - const activityService = this.instantiationService.createInstance(ActivityService, this.activitybarPart, this.panelPart); - serviceCollection.set(IActivityService, activityService); - - // File Service - this.fileService = this.instantiationService.createInstance(RemoteFileService); - serviceCollection.set(IFileService, this.fileService); - this.configurationService.acquireFileService(this.fileService); - this.themeService.acquireFileService(this.fileService); - - // Editor and Group services - const restorePreviousEditorState = !this.hasInitialFilesToOpen; - this.editorPart = this.instantiationService.createInstance(EditorPart, Identifiers.EDITOR_PART, restorePreviousEditorState); - this.editorGroupService = this.editorPart; - serviceCollection.set(IEditorGroupsService, this.editorPart); - this.editorService = this.instantiationService.createInstance(EditorService); - serviceCollection.set(IEditorService, this.editorService); - - // Title bar - this.titlebarPart = this.instantiationService.createInstance(TitlebarPart, Identifiers.TITLEBAR_PART); - serviceCollection.set(ITitleService, this.titlebarPart); - - // History - serviceCollection.set(IHistoryService, new SyncDescriptor(HistoryService)); - - // File Dialogs - serviceCollection.set(IFileDialogService, new SyncDescriptor(FileDialogService)); - - // Backup File Service - if (this.workbenchParams.configuration.backupPath) { - this.backupFileService = this.instantiationService.createInstance(BackupFileService, this.workbenchParams.configuration.backupPath); - } else { - this.backupFileService = new InMemoryBackupFileService(); - } - serviceCollection.set(IBackupFileService, this.backupFileService); - - // Text File Service - serviceCollection.set(ITextFileService, new SyncDescriptor(TextFileService)); - - // File Decorations - serviceCollection.set(IDecorationsService, new SyncDescriptor(FileDecorationsService)); - - // SCM Service - serviceCollection.set(ISCMService, new SyncDescriptor(SCMService)); - - // Inactive extension URL handler - serviceCollection.set(IExtensionUrlHandler, new SyncDescriptor(ExtensionUrlHandler)); - - // Text Model Resolver Service - serviceCollection.set(ITextModelService, new SyncDescriptor(TextModelResolverService)); - - // JSON Editing - const jsonEditingService = this.instantiationService.createInstance(JSONEditingService); - serviceCollection.set(IJSONEditingService, jsonEditingService); - - // Workspace Editing - serviceCollection.set(IWorkspaceEditingService, new SyncDescriptor(WorkspaceEditingService)); - - // Keybinding Editing - serviceCollection.set(IKeybindingEditingService, this.instantiationService.createInstance(KeybindingsEditingService)); - - // Configuration Resolver - serviceCollection.set(IConfigurationResolverService, new SyncDescriptor(ConfigurationResolverService, [process.env])); - - // Quick open service (quick open controller) - this.quickOpen = this.instantiationService.createInstance(QuickOpenController); - serviceCollection.set(IQuickOpenService, this.quickOpen); - - // Quick input service - this.quickInput = this.instantiationService.createInstance(QuickInputService); - serviceCollection.set(IQuickInputService, this.quickInput); - - // PreferencesService - serviceCollection.set(IPreferencesService, this.instantiationService.createInstance(PreferencesService)); - - // Contributed services - const contributedServices = getServices(); - for (let contributedService of contributedServices) { - serviceCollection.set(contributedService.id, contributedService.descriptor); - } - - // Set the some services to registries that have been created eagerly - Registry.as(ActionBarExtensions.Actionbar).setInstantiationService(this.instantiationService); - Registry.as(WorkbenchExtensions.Workbench).start(this.instantiationService, this.lifecycleService); - Registry.as(EditorExtensions.EditorInputFactories).setInstantiationService(this.instantiationService); - - this.instantiationService.createInstance(DefaultConfigurationExportHelper); - - this.configurationService.acquireInstantiationService(this.getInstantiationService()); - - // {{SQL CARBON EDIT}} - // SQL Tools services - serviceCollection.set(IDashboardService, this.instantiationService.createInstance(DashboardService)); - serviceCollection.set(IDashboardViewService, this.instantiationService.createInstance(DashboardViewService)); - serviceCollection.set(IModelViewService, this.instantiationService.createInstance(ModelViewService)); - serviceCollection.set(IAngularEventingService, this.instantiationService.createInstance(AngularEventingService)); - serviceCollection.set(INewDashboardTabDialogService, this.instantiationService.createInstance(NewDashboardTabDialogService)); - serviceCollection.set(ISqlOAuthService, this.instantiationService.createInstance(SqlOAuthService)); - serviceCollection.set(sqlIClipboardService, this.instantiationService.createInstance(sqlClipboardService)); - let capabilitiesService = this.instantiationService.createInstance(CapabilitiesService); - serviceCollection.set(ICapabilitiesService, capabilitiesService); - serviceCollection.set(IErrorMessageService, this.instantiationService.createInstance(ErrorMessageService)); - serviceCollection.set(IConnectionDialogService, this.instantiationService.createInstance(ConnectionDialogService)); - serviceCollection.set(IServerGroupController, this.instantiationService.createInstance(ServerGroupController)); - serviceCollection.set(ICredentialsService, this.instantiationService.createInstance(CredentialsService)); - serviceCollection.set(IResourceProviderService, this.instantiationService.createInstance(ResourceProviderService)); - let accountManagementService = this.instantiationService.createInstance(AccountManagementService, undefined); - serviceCollection.set(IAccountManagementService, accountManagementService); - let connectionManagementService = this.instantiationService.createInstance(ConnectionManagementService, undefined, undefined); - serviceCollection.set(IConnectionManagementService, connectionManagementService); - serviceCollection.set(ISerializationService, this.instantiationService.createInstance(SerializationService)); - serviceCollection.set(IQueryManagementService, this.instantiationService.createInstance(QueryManagementService)); - serviceCollection.set(IQueryModelService, this.instantiationService.createInstance(QueryModelService)); - serviceCollection.set(IQueryEditorService, this.instantiationService.createInstance(QueryEditorService)); - serviceCollection.set(IEditorDescriptorService, this.instantiationService.createInstance(EditorDescriptorService)); - serviceCollection.set(ITaskService, this.instantiationService.createInstance(TaskService)); - serviceCollection.set(IMetadataService, this.instantiationService.createInstance(MetadataService)); - serviceCollection.set(IObjectExplorerService, this.instantiationService.createInstance(ObjectExplorerService)); - serviceCollection.set(IOEShimService, this.instantiationService.createInstance(OEShimService)); - serviceCollection.set(IScriptingService, this.instantiationService.createInstance(ScriptingService)); - serviceCollection.set(IAdminService, this.instantiationService.createInstance(AdminService)); - serviceCollection.set(IJobManagementService, this.instantiationService.createInstance(JobManagementService)); - serviceCollection.set(IBackupService, this.instantiationService.createInstance(BackupService)); - serviceCollection.set(IBackupUiService, this.instantiationService.createInstance(BackupUiService)); - serviceCollection.set(IRestoreService, this.instantiationService.createInstance(RestoreService)); - serviceCollection.set(IRestoreDialogController, this.instantiationService.createInstance(RestoreDialogController)); - serviceCollection.set(IFileBrowserService, this.instantiationService.createInstance(FileBrowserService)); - serviceCollection.set(IFileBrowserDialogController, this.instantiationService.createInstance(FileBrowserDialogController)); - serviceCollection.set(IInsightsDialogService, this.instantiationService.createInstance(InsightsDialogService)); - let notebookService = this.instantiationService.createInstance(NotebookService); - serviceCollection.set(INotebookService, notebookService); - serviceCollection.set(IAccountPickerService, this.instantiationService.createInstance(AccountPickerService)); - serviceCollection.set(IProfilerService, this.instantiationService.createInstance(ProfilerService)); - serviceCollection.set(ICommandLineProcessing, this.instantiationService.createInstance(CommandLineService)); - serviceCollection.set(IDacFxService, this.instantiationService.createInstance(DacFxService)); - - // {{SQL CARBON EDIT}} - End - } - - //#region event handling - - private registerListeners(): void { - - // Storage - this._register(this.storageService.onWillSaveState(e => this.saveState(e))); - - // Listen to visible editor changes - this._register(this.editorService.onDidVisibleEditorsChange(() => this.onDidVisibleEditorsChange())); - - // Listen to editor group activations when editor is hidden - this._register(this.editorPart.onDidActivateGroup(() => { if (this.editorHidden) { this.setEditorHidden(false); } })); - - // Listen to editor closing (if we run with --wait) - const filesToWait = this.workbenchParams.configuration.filesToWait; - if (filesToWait) { - const resourcesToWaitFor = filesToWait.paths.map(p => p.fileUri); - const waitMarkerFile = URI.file(filesToWait.waitMarkerFilePath); - const listenerDispose = this.editorService.onDidCloseEditor(() => this.onEditorClosed(listenerDispose, resourcesToWaitFor, waitMarkerFile)); - - this._register(listenerDispose); - } - - // Configuration changes - this._register(this.configurationService.onDidChangeConfiguration(() => this.onDidUpdateConfiguration())); - - // Fullscreen changes - this._register(browser.onDidChangeFullscreen(() => this.onFullscreenChanged())); - - // Group changes - this._register(this.editorGroupService.onDidAddGroup(() => this.centerEditorLayout(this.shouldCenterLayout))); - this._register(this.editorGroupService.onDidRemoveGroup(() => this.centerEditorLayout(this.shouldCenterLayout))); - } - - private onFullscreenChanged(): void { - - // Apply as CSS class - const isFullscreen = browser.isFullscreen(); - if (isFullscreen) { - DOM.addClass(this.workbench, 'fullscreen'); - } else { - DOM.removeClass(this.workbench, 'fullscreen'); - - if (this.zenMode.transitionedToFullScreen && this.zenMode.active) { - this.toggleZenMode(); - } - } - - // Changing fullscreen state of the window has an impact on custom title bar visibility, so we need to update - if (this.useCustomTitleBarStyle()) { - this._onTitleBarVisibilityChange.fire(); - this.layout(); // handle title bar when fullscreen changes - } - } - - private onMenubarToggled(visible: boolean) { - if (visible !== this.menubarToggled) { - this.menubarToggled = visible; - - if (browser.isFullscreen() && (this.menubarVisibility === 'toggle' || this.menubarVisibility === 'default')) { - this._onTitleBarVisibilityChange.fire(); - this.layout(); - } - } - } - - private onEditorClosed(listenerDispose: IDisposable, resourcesToWaitFor: URI[], waitMarkerFile: URI): void { - - // In wait mode, listen to changes to the editors and wait until the files - // are closed that the user wants to wait for. When this happens we delete - // the wait marker file to signal to the outside that editing is done. - if (resourcesToWaitFor.every(resource => !this.editorService.isOpen({ resource }))) { - listenerDispose.dispose(); - this.fileService.del(waitMarkerFile); - } - } - - private onDidVisibleEditorsChange(): void { - const visibleEditors = this.editorService.visibleControls; - - // Close when empty: check if we should close the window based on the setting - // Overruled by: window has a workspace opened or this window is for extension development - // or setting is disabled. Also enabled when running with --wait from the command line. - if (visibleEditors.length === 0 && this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && !this.environmentService.isExtensionDevelopment) { - const closeWhenEmpty = this.configurationService.getValue(Workbench.closeWhenEmptyConfigurationKey); - if (closeWhenEmpty || this.environmentService.args.wait) { - this.closeEmptyWindowScheduler.schedule(); - } - } - - if (this.editorHidden) { - this.setEditorHidden(false); - } - } - - private onAllEditorsClosed(): void { - const visibleEditors = this.editorService.visibleControls.length; - if (visibleEditors === 0) { - this.windowService.closeWindow(); - } - } - - private onDidUpdateConfiguration(skipLayout?: boolean): void { - const newSidebarPositionValue = this.configurationService.getValue(Workbench.sidebarPositionConfigurationKey); - const newSidebarPosition = (newSidebarPositionValue === 'right') ? Position.RIGHT : Position.LEFT; - if (newSidebarPosition !== this.getSideBarPosition()) { - this.setSideBarPosition(newSidebarPosition); - } - - this.setPanelPositionFromStorageOrConfig(); - - const fontAliasing = this.configurationService.getValue(Workbench.fontAliasingConfigurationKey); - if (fontAliasing !== this.fontAliasing) { - this.setFontAliasing(fontAliasing); - } - - if (!this.zenMode.active) { - const newStatusbarHiddenValue = !this.configurationService.getValue(Workbench.statusbarVisibleConfigurationKey); - if (newStatusbarHiddenValue !== this.statusBarHidden) { - this.setStatusBarHidden(newStatusbarHiddenValue, skipLayout); - } - - const newActivityBarHiddenValue = !this.configurationService.getValue(Workbench.activityBarVisibleConfigurationKey); - if (newActivityBarHiddenValue !== this.activityBarHidden) { - this.setActivityBarHidden(newActivityBarHiddenValue, skipLayout); - } - } - - const newMenubarVisibility = this.configurationService.getValue(Workbench.menubarVisibilityConfigurationKey); - this.setMenubarVisibility(newMenubarVisibility, skipLayout); - } - - //#endregion - - private handleContextKeys(): void { - this.inZenMode = InEditorZenModeContext.bindTo(this.contextKeyService); - - IsMacContext.bindTo(this.contextKeyService); - IsLinuxContext.bindTo(this.contextKeyService); - IsWindowsContext.bindTo(this.contextKeyService); - - const sidebarVisibleContextRaw = new RawContextKey('sidebarVisible', false); - this.sideBarVisibleContext = sidebarVisibleContextRaw.bindTo(this.contextKeyService); - - const activeEditorContext = ActiveEditorContext.bindTo(this.contextKeyService); - const editorsVisibleContext = EditorsVisibleContext.bindTo(this.contextKeyService); - const textCompareEditorVisible = TextCompareEditorVisibleContext.bindTo(this.contextKeyService); - const textCompareEditorActive = TextCompareEditorActiveContext.bindTo(this.contextKeyService); - const activeEditorGroupEmpty = ActiveEditorGroupEmptyContext.bindTo(this.contextKeyService); - const multipleEditorGroups = MultipleEditorGroupsContext.bindTo(this.contextKeyService); - - const updateEditorContextKeys = () => { - const activeControl = this.editorService.activeControl; - const visibleEditors = this.editorService.visibleControls; - - textCompareEditorActive.set(activeControl && activeControl.getId() === TEXT_DIFF_EDITOR_ID); - textCompareEditorVisible.set(visibleEditors.some(control => control.getId() === TEXT_DIFF_EDITOR_ID)); - - if (visibleEditors.length > 0) { - editorsVisibleContext.set(true); - } else { - editorsVisibleContext.reset(); - } - - if (!this.editorService.activeEditor) { - activeEditorGroupEmpty.set(true); - } else { - activeEditorGroupEmpty.reset(); - } - - if (this.editorGroupService.count > 1) { - multipleEditorGroups.set(true); - } else { - multipleEditorGroups.reset(); - } - - if (activeControl) { - activeEditorContext.set(activeControl.getId()); - } else { - activeEditorContext.reset(); - } - }; - - this.editorPart.whenRestored.then(() => updateEditorContextKeys()); - this._register(this.editorService.onDidActiveEditorChange(() => updateEditorContextKeys())); - this._register(this.editorService.onDidVisibleEditorsChange(() => updateEditorContextKeys())); - this._register(this.editorGroupService.onDidAddGroup(() => updateEditorContextKeys())); - this._register(this.editorGroupService.onDidRemoveGroup(() => updateEditorContextKeys())); - - const inputFocused = InputFocusedContext.bindTo(this.contextKeyService); - - function activeElementIsInput(): boolean { - return document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA'); - } - - function trackInputFocus(): void { - const isInputFocused = activeElementIsInput(); - inputFocused.set(isInputFocused); - - if (isInputFocused) { - const tracker = DOM.trackFocus(document.activeElement as HTMLElement); - Event.once(tracker.onDidBlur)(() => { - inputFocused.set(activeElementIsInput()); - - tracker.dispose(); - }); - } - } - - this._register(DOM.addDisposableListener(window, 'focusin', () => trackInputFocus(), true)); - - const workbenchStateRawContext = new RawContextKey('workbenchState', getWorkbenchStateString(this.configurationService.getWorkbenchState())); - const workbenchStateContext = workbenchStateRawContext.bindTo(this.contextKeyService); - this._register(this.configurationService.onDidChangeWorkbenchState(() => { - workbenchStateContext.set(getWorkbenchStateString(this.configurationService.getWorkbenchState())); - })); - - const workspaceFolderCountRawContext = new RawContextKey('workspaceFolderCount', this.configurationService.getWorkspace().folders.length); - const workspaceFolderCountContext = workspaceFolderCountRawContext.bindTo(this.contextKeyService); - this._register(this.configurationService.onDidChangeWorkspaceFolders(() => { - workspaceFolderCountContext.set(this.configurationService.getWorkspace().folders.length); - })); - - const splitEditorsVerticallyContext = SplitEditorsVertically.bindTo(this.contextKeyService); - - const updateSplitEditorsVerticallyContext = () => { - const direction = preferredSideBySideGroupDirection(this.configurationService); - splitEditorsVerticallyContext.set(direction === GroupDirection.DOWN); - }; - - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('workbench.editor.openSideBySideDirection')) { - updateSplitEditorsVerticallyContext(); - } - })); - - updateSplitEditorsVerticallyContext(); - } - - private restoreParts(): Promise { - const restorePromises: Promise[] = []; - - // Restore Editorpart - perf.mark('willRestoreEditors'); - restorePromises.push(this.editorPart.whenRestored.then(() => { - - function openEditors(editors: IResourceEditor[], editorService: IEditorService) { - if (editors.length) { - return editorService.openEditors(editors); - } - - return Promise.resolve(); - } - - const editorsToOpen = this.resolveEditorsToOpen(); - - if (Array.isArray(editorsToOpen)) { - return openEditors(editorsToOpen, this.editorService); - } - - return editorsToOpen.then(editors => openEditors(editors, this.editorService)); - }).then(() => perf.mark('didRestoreEditors'))); - - // Restore Sidebar - let viewletIdToRestore: string; - if (!this.sideBarHidden) { - this.sideBarVisibleContext.set(true); - - if (this.shouldRestoreLastOpenedViewlet()) { - viewletIdToRestore = this.storageService.get(SidebarPart.activeViewletSettingsKey, StorageScope.WORKSPACE); - } - - if (!viewletIdToRestore) { - viewletIdToRestore = this.sidebarPart.getDefaultViewletId(); - } - - perf.mark('willRestoreViewlet'); - restorePromises.push(this.sidebarPart.openViewlet(viewletIdToRestore) - .then(viewlet => viewlet || this.sidebarPart.openViewlet(this.sidebarPart.getDefaultViewletId())) - .then(() => perf.mark('didRestoreViewlet'))); - } - - // Restore Panel - const panelRegistry = Registry.as(PanelExtensions.Panels); - const panelId = this.storageService.get(PanelPart.activePanelSettingsKey, StorageScope.WORKSPACE, panelRegistry.getDefaultPanelId()); - if (!this.panelHidden && !!panelId) { - perf.mark('willRestorePanel'); - const isPanelToRestoreEnabled = !!this.panelPart.getPanels().filter(p => p.id === panelId).length; - const panelIdToRestore = isPanelToRestoreEnabled ? panelId : panelRegistry.getDefaultPanelId(); - this.panelPart.openPanel(panelIdToRestore, false); - perf.mark('didRestorePanel'); - } - - // Restore Zen Mode if active and supported for restore on startup - const zenConfig = this.configurationService.getValue('zenMode'); - const wasZenActive = this.storageService.getBoolean(Workbench.zenModeActiveStorageKey, StorageScope.WORKSPACE, false); - if (wasZenActive && zenConfig.restore) { - this.toggleZenMode(true, true); - } - - // Restore Forced Editor Center Mode - if (this.storageService.getBoolean(Workbench.centeredEditorLayoutActiveStorageKey, StorageScope.WORKSPACE, false)) { - this.centerEditorLayout(true); - } - - const onRestored = (error?: Error): IWorkbenchStartedInfo => { - this.workbenchRestored = true; - - // Set lifecycle phase to `Restored` - this.lifecycleService.phase = LifecyclePhase.Restored; - - // Set lifecycle phase to `Eventually` after a short delay and when - // idle (min 2.5sec, max 5sec) - setTimeout(() => { - this._register(runWhenIdle(() => { - this.lifecycleService.phase = LifecyclePhase.Eventually; - }, 2500)); - }, 2500); - - if (error) { - errors.onUnexpectedError(error); - } - - return { - customKeybindingsCount: this.keybindingService.customKeybindingsCount(), - pinnedViewlets: this.activitybarPart.getPinnedViewletIds(), - restoredViewlet: viewletIdToRestore, - restoredEditorsCount: this.editorService.visibleEditors.length - }; - }; - - return Promise.all(restorePromises).then(() => onRestored(), error => onRestored(error)); - } - - private shouldRestoreLastOpenedViewlet(): boolean { - if (!this.environmentService.isBuilt) { - return true; // always restore sidebar when we are in development mode - } - - // always restore sidebar when the window was reloaded - return this.lifecycleService.startupKind === StartupKind.ReloadedWindow; - } - - private resolveEditorsToOpen(): Promise | IResourceEditor[] { - const config = this.workbenchParams.configuration; - - // Files to open, diff or create - if (this.hasInitialFilesToOpen) { - - // Files to diff is exclusive - const filesToDiff = this.toInputs(config.filesToDiff, false); - if (filesToDiff && filesToDiff.length === 2) { - return [{ - leftResource: filesToDiff[0].resource, - rightResource: filesToDiff[1].resource, - options: { pinned: true }, - forceFile: true - }]; - } - - const filesToCreate = this.toInputs(config.filesToCreate, true); - const filesToOpen = this.toInputs(config.filesToOpen, false); - - // Otherwise: Open/Create files - return [...filesToOpen, ...filesToCreate]; - } - - // Empty workbench - else if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY && this.openUntitledFile()) { - const isEmpty = this.editorGroupService.count === 1 && this.editorGroupService.activeGroup.count === 0; - if (!isEmpty) { - return []; // do not open any empty untitled file if we restored editors from previous session - } - - return this.backupFileService.hasBackups().then(hasBackups => { - if (hasBackups) { - return []; // do not open any empty untitled file if we have backups to restore - } - - return [{}]; - }); - } - - return []; - } - - private toInputs(paths: IPath[], isNew: boolean): Array { - if (!paths || !paths.length) { - return []; - } - - return paths.map(p => { - const resource = p.fileUri; - let input: IResourceInput | IUntitledResourceInput; - if (isNew) { - input = { filePath: resource.fsPath, options: { pinned: true } } as IUntitledResourceInput; - } else { - input = { resource, options: { pinned: true }, forceFile: true } as IResourceInput; - } - - if (!isNew && p.lineNumber) { - input.options.selection = { - startLineNumber: p.lineNumber, - startColumn: p.columnNumber - }; - } - - return input; - }); - } - - private openUntitledFile() { - const startupEditor = this.configurationService.inspect('workbench.startupEditor'); - - // Fallback to previous workbench.welcome.enabled setting in case startupEditor is not defined - if (!startupEditor.user && !startupEditor.workspace) { - const welcomeEnabledValue = this.configurationService.getValue('workbench.welcome.enabled'); - if (typeof welcomeEnabledValue === 'boolean') { - return !welcomeEnabledValue; - } - } - - return startupEditor.value === 'newUntitledFile'; - } - - private initSettings(): void { - - // Editor visiblity - this.editorHidden = false; - - // Sidebar visibility - this.sideBarHidden = this.storageService.getBoolean(Workbench.sidebarHiddenStorageKey, StorageScope.WORKSPACE, this.contextService.getWorkbenchState() === WorkbenchState.EMPTY); - - // Panel part visibility - const panelRegistry = Registry.as(PanelExtensions.Panels); - this.panelHidden = this.storageService.getBoolean(Workbench.panelHiddenStorageKey, StorageScope.WORKSPACE, true); - if (!panelRegistry.getDefaultPanelId()) { - this.panelHidden = true; // we hide panel part if there is no default panel - } - - // Sidebar position - const sideBarPosition = this.configurationService.getValue(Workbench.sidebarPositionConfigurationKey); - this.sideBarPosition = (sideBarPosition === 'right') ? Position.RIGHT : Position.LEFT; - - // Panel position - this.setPanelPositionFromStorageOrConfig(); - - // Menubar visibility - const menuBarVisibility = this.configurationService.getValue(Workbench.menubarVisibilityConfigurationKey); - this.setMenubarVisibility(menuBarVisibility, true); - - // Statusbar visibility - const statusBarVisible = this.configurationService.getValue(Workbench.statusbarVisibleConfigurationKey); - this.statusBarHidden = !statusBarVisible; - - // Activity bar visibility - const activityBarVisible = this.configurationService.getValue(Workbench.activityBarVisibleConfigurationKey); - this.activityBarHidden = !activityBarVisible; - - // Font aliasing - this.fontAliasing = this.configurationService.getValue(Workbench.fontAliasingConfigurationKey); - - // Zen mode - this.zenMode = { - active: false, - transitionedToFullScreen: false, - transitionedToCenteredEditorLayout: false, - wasSideBarVisible: false, - wasPanelVisible: false, - transitionDisposeables: [] - }; - } - - private setPanelPositionFromStorageOrConfig() { - const defaultPanelPosition = this.configurationService.getValue(Workbench.defaultPanelPositionStorageKey); - const panelPosition = this.storageService.get(Workbench.panelPositionStorageKey, StorageScope.WORKSPACE, defaultPanelPosition); - - this.panelPosition = (panelPosition === 'right') ? Position.RIGHT : Position.BOTTOM; - } - - private useCustomTitleBarStyle(): boolean { - return getTitleBarStyle(this.configurationService, this.environmentService) === 'custom'; - } - - private saveLastPanelDimension(): void { - if (!(this.workbenchGrid instanceof Grid)) { - return; - } - - if (this.panelPosition === Position.BOTTOM) { - this.uiState.lastPanelHeight = this.workbenchGrid.getViewSize(this.panelPartView); - } else { - this.uiState.lastPanelWidth = this.workbenchGrid.getViewSize(this.panelPartView); - } - } - - private getLastPanelDimension(position: Position): number | undefined { - return position === Position.BOTTOM ? this.uiState.lastPanelHeight : this.uiState.lastPanelWidth; - } - - private setStatusBarHidden(hidden: boolean, skipLayout?: boolean): void { - this.statusBarHidden = hidden; - - // Adjust CSS - if (hidden) { - DOM.addClass(this.workbench, 'nostatusbar'); - } else { - DOM.removeClass(this.workbench, 'nostatusbar'); - } - - // Layout - if (!skipLayout) { - if (this.workbenchGrid instanceof Grid) { - this.layout(); - } else { - this.workbenchGrid.layout(); - } - } - } - - private setFontAliasing(aliasing: FontAliasingOption) { - this.fontAliasing = aliasing; - - // Remove all - document.body.classList.remove(...fontAliasingValues.map(value => `monaco-font-aliasing-${value}`)); - - // Add specific - if (fontAliasingValues.some(option => option === aliasing)) { - document.body.classList.add(`monaco-font-aliasing-${aliasing}`); - } - } - - private createWorkbenchLayout(): void { - if (this.configurationService.getValue('workbench.useExperimentalGridLayout')) { - // Create view wrappers for all parts - this.titlebarPartView = new View(this.titlebarPart); - this.sidebarPartView = new View(this.sidebarPart); - this.activitybarPartView = new View(this.activitybarPart); - this.editorPartView = new View(this.editorPart); - this.panelPartView = new View(this.panelPart); - this.statusbarPartView = new View(this.statusbarPart); - - this.workbenchGrid = new Grid(this.editorPartView, { proportionalLayout: false }); - - this.workbench.prepend(this.workbenchGrid.element); - this.layout(); - } else { - this.workbenchGrid = this.instantiationService.createInstance( - WorkbenchLayout, - this.container, - this.workbench, - { - titlebar: this.titlebarPart, - activitybar: this.activitybarPart, - editor: this.editorPart, - sidebar: this.sidebarPart, - panel: this.panelPart, - statusbar: this.statusbarPart, - }, - this.quickOpen, - this.quickInput, - this.notificationsCenter, - this.notificationsToasts - ); - } - } - - private renderWorkbench(): void { - - // Apply sidebar state as CSS class - if (this.sideBarHidden) { - DOM.addClass(this.workbench, 'nosidebar'); - } - - if (this.panelHidden) { - DOM.addClass(this.workbench, 'nopanel'); - } - - if (this.statusBarHidden) { - DOM.addClass(this.workbench, 'nostatusbar'); - } - - // Apply font aliasing - this.setFontAliasing(this.fontAliasing); - - // Apply fullscreen state - if (browser.isFullscreen()) { - DOM.addClass(this.workbench, 'fullscreen'); - } - - // Create Parts - this.createTitlebarPart(); - this.createActivityBarPart(); - this.createSidebarPart(); - this.createEditorPart(); - this.createPanelPart(); - this.createStatusbarPart(); - - // Notification Handlers - this.createNotificationsHandlers(); - - - // Menubar visibility changes - if ((isWindows || isLinux) && this.useCustomTitleBarStyle()) { - this.titlebarPart.onMenubarVisibilityChange()(e => this.onMenubarToggled(e)); - } - - // Add Workbench to DOM - this.container.appendChild(this.workbench); - } - - private createTitlebarPart(): void { - const titlebarContainer = this.createPart(Identifiers.TITLEBAR_PART, ['part', 'titlebar'], 'contentinfo'); - - this.titlebarPart.create(titlebarContainer); - } - - private createActivityBarPart(): void { - const activitybarPartContainer = this.createPart(Identifiers.ACTIVITYBAR_PART, ['part', 'activitybar', this.sideBarPosition === Position.LEFT ? 'left' : 'right'], 'navigation'); - - this.activitybarPart.create(activitybarPartContainer); - } - - private createSidebarPart(): void { - const sidebarPartContainer = this.createPart(Identifiers.SIDEBAR_PART, ['part', 'sidebar', this.sideBarPosition === Position.LEFT ? 'left' : 'right'], 'complementary'); - - this.sidebarPart.create(sidebarPartContainer); - } - - private createPanelPart(): void { - const panelPartContainer = this.createPart(Identifiers.PANEL_PART, ['part', 'panel', this.panelPosition === Position.BOTTOM ? 'bottom' : 'right'], 'complementary'); - - this.panelPart.create(panelPartContainer); - } - - private createEditorPart(): void { - const editorContainer = this.createPart(Identifiers.EDITOR_PART, ['part', 'editor'], 'main'); - - this.editorPart.create(editorContainer); - } - - private createStatusbarPart(): void { - const statusbarContainer = this.createPart(Identifiers.STATUSBAR_PART, ['part', 'statusbar'], 'contentinfo'); - - this.statusbarPart.create(statusbarContainer); - } - - private createPart(id: string, classes: string[], role: string): HTMLElement { - const part = document.createElement('div'); - classes.forEach(clazz => DOM.addClass(part, clazz)); - part.id = id; - part.setAttribute('role', role); - - if (!this.configurationService.getValue('workbench.useExperimentalGridLayout')) { - // Insert all workbench parts at the beginning. Issue #52531 - // This is primarily for the title bar to allow overriding -webkit-app-region - this.workbench.insertBefore(part, this.workbench.lastChild); - } - - return part; - } - - private createNotificationsHandlers(): void { - - // Notifications Center - this.notificationsCenter = this._register(this.instantiationService.createInstance(NotificationsCenter, this.workbench, this.notificationService.model)); - - // Notifications Toasts - this.notificationsToasts = this._register(this.instantiationService.createInstance(NotificationsToasts, this.workbench, this.notificationService.model)); - - // Notifications Alerts - this._register(this.instantiationService.createInstance(NotificationsAlerts, this.notificationService.model)); - - // Notifications Status - const notificationsStatus = this.instantiationService.createInstance(NotificationsStatus, this.notificationService.model); - - // Eventing - this._register(this.notificationsCenter.onDidChangeVisibility(() => { - - // Update status - notificationsStatus.update(this.notificationsCenter.isVisible); - - // Update toasts - this.notificationsToasts.update(this.notificationsCenter.isVisible); - })); - - // Register Commands - registerNotificationCommands(this.notificationsCenter, this.notificationsToasts); - } - - getInstantiationService(): IInstantiationService { - return this.instantiationService; - } - - private saveState(e: IWillSaveStateEvent): void { - if (this.zenMode.active) { - this.storageService.store(Workbench.zenModeActiveStorageKey, true, StorageScope.WORKSPACE); - } else { - this.storageService.remove(Workbench.zenModeActiveStorageKey, StorageScope.WORKSPACE); - } - - if (e.reason === WillSaveStateReason.SHUTDOWN && this.zenMode.active) { - const zenConfig = this.configurationService.getValue('zenMode'); - if (!zenConfig.restore) { - // We will not restore zen mode, need to clear all zen mode state changes - this.toggleZenMode(true); - } - } - } - - dispose(): void { - super.dispose(); - - this.workbenchShutdown = true; - } - - //#region IPartService - - private _onTitleBarVisibilityChange: Emitter = this._register(new Emitter()); - get onTitleBarVisibilityChange(): Event { return this._onTitleBarVisibilityChange.event; } - - get onEditorLayout(): Event { return this.editorPart.onDidLayout; } - - isRestored(): boolean { - return !!(this.workbenchRestored && this.workbenchStarted); - } - - hasFocus(part: Parts): boolean { - const activeElement = document.activeElement; - if (!activeElement) { - return false; - } - - const container = this.getContainer(part); - return DOM.isAncestor(activeElement, container); - } - - getContainer(part: Parts): HTMLElement { - let container: HTMLElement | null = null; - switch (part) { - case Parts.TITLEBAR_PART: - container = this.titlebarPart.getContainer(); - break; - case Parts.ACTIVITYBAR_PART: - container = this.activitybarPart.getContainer(); - break; - case Parts.SIDEBAR_PART: - container = this.sidebarPart.getContainer(); - break; - case Parts.PANEL_PART: - container = this.panelPart.getContainer(); - break; - case Parts.EDITOR_PART: - container = this.editorPart.getContainer(); - break; - case Parts.STATUSBAR_PART: - container = this.statusbarPart.getContainer(); - break; - } - - return container; - } - - isVisible(part: Parts): boolean { - switch (part) { - case Parts.TITLEBAR_PART: - if (!this.useCustomTitleBarStyle()) { - return false; - } else if (!browser.isFullscreen()) { - return true; - } else if (isMacintosh) { - return false; - } else if (this.menubarVisibility === 'visible') { - return true; - } else if (this.menubarVisibility === 'toggle' || this.menubarVisibility === 'default') { - return this.menubarToggled; - } - - return false; - case Parts.SIDEBAR_PART: - return !this.sideBarHidden; - case Parts.PANEL_PART: - return !this.panelHidden; - case Parts.STATUSBAR_PART: - return !this.statusBarHidden; - case Parts.ACTIVITYBAR_PART: - return !this.activityBarHidden; - case Parts.EDITOR_PART: - return this.workbenchGrid instanceof Grid ? !this.editorHidden : true; - } - - return true; // any other part cannot be hidden - } - - getTitleBarOffset(): number { - let offset = 0; - if (this.isVisible(Parts.TITLEBAR_PART)) { - if (this.workbenchGrid instanceof Grid) { - offset = this.gridHasView(this.titlebarPartView) ? this.workbenchGrid.getViewSize2(this.titlebarPartView).height : 0; - } else { - offset = this.workbenchGrid.partLayoutInfo.titlebar.height; - } - - if (isMacintosh || this.menubarVisibility === 'hidden') { - offset /= browser.getZoomFactor(); - } - } - - return offset; - } - - getWorkbenchElement(): HTMLElement { - return this.workbench; - } - - toggleZenMode(skipLayout?: boolean, restoring = false): void { - this.zenMode.active = !this.zenMode.active; - this.zenMode.transitionDisposeables = dispose(this.zenMode.transitionDisposeables); - - // Check if zen mode transitioned to full screen and if now we are out of zen mode - // -> we need to go out of full screen (same goes for the centered editor layout) - let toggleFullScreen = false; - const setLineNumbers = (lineNumbers: any) => { - this.editorService.visibleControls.forEach(editor => { - const control = editor.getControl(); - if (control) { - control.updateOptions({ lineNumbers }); - } - }); - }; - - // Zen Mode Active - if (this.zenMode.active) { - const config = this.configurationService.getValue('zenMode'); - - toggleFullScreen = !browser.isFullscreen() && config.fullScreen; - this.zenMode.transitionedToFullScreen = restoring ? config.fullScreen : toggleFullScreen; - this.zenMode.transitionedToCenteredEditorLayout = !this.isEditorLayoutCentered() && config.centerLayout; - this.zenMode.wasSideBarVisible = this.isVisible(Parts.SIDEBAR_PART); - this.zenMode.wasPanelVisible = this.isVisible(Parts.PANEL_PART); - - this.setPanelHidden(true, true); - this.setSideBarHidden(true, true); - - if (config.hideActivityBar) { - this.setActivityBarHidden(true, true); - } - - if (config.hideStatusBar) { - this.setStatusBarHidden(true, true); - } - - if (config.hideLineNumbers) { - setLineNumbers('off'); - this.zenMode.transitionDisposeables.push(this.editorService.onDidVisibleEditorsChange(() => setLineNumbers('off'))); - } - - if (config.hideTabs && this.editorPart.partOptions.showTabs) { - this.zenMode.transitionDisposeables.push(this.editorPart.enforcePartOptions({ showTabs: false })); - } - - if (config.centerLayout) { - this.centerEditorLayout(true, true); - } - } - - // Zen Mode Inactive - else { - if (this.zenMode.wasPanelVisible) { - this.setPanelHidden(false, true); - } - - if (this.zenMode.wasSideBarVisible) { - this.setSideBarHidden(false, true); - } - - if (this.zenMode.transitionedToCenteredEditorLayout) { - this.centerEditorLayout(false, true); - } - setLineNumbers(this.configurationService.getValue('editor.lineNumbers')); - - // Status bar and activity bar visibility come from settings -> update their visibility. - this.onDidUpdateConfiguration(true); - - this.editorGroupService.activeGroup.focus(); - - toggleFullScreen = this.zenMode.transitionedToFullScreen && browser.isFullscreen(); - } - - this.inZenMode.set(this.zenMode.active); - - if (!skipLayout) { - this.layout(); - } - - if (toggleFullScreen) { - this.windowService.toggleFullScreen(); - } - } - - private gridHasView(view: View): boolean { - if (!(this.workbenchGrid instanceof Grid)) { - return false; - } - - try { - this.workbenchGrid.getViewSize(view); - return true; - } catch { - return false; - } - } - - private updateGrid(): void { - if (!(this.workbenchGrid instanceof Grid)) { - return; - } - - let panelInGrid = this.gridHasView(this.panelPartView); - let sidebarInGrid = this.gridHasView(this.sidebarPartView); - let activityBarInGrid = this.gridHasView(this.activitybarPartView); - let statusBarInGrid = this.gridHasView(this.statusbarPartView); - let titlebarInGrid = this.gridHasView(this.titlebarPartView); - - // Add parts to grid - if (!statusBarInGrid) { - this.workbenchGrid.addView(this.statusbarPartView, Sizing.Split, this.editorPartView, Direction.Down); - statusBarInGrid = true; - } - - if (!titlebarInGrid && this.useCustomTitleBarStyle()) { - this.workbenchGrid.addView(this.titlebarPartView, Sizing.Split, this.editorPartView, Direction.Up); - titlebarInGrid = true; - } - - if (!activityBarInGrid) { - this.workbenchGrid.addView(this.activitybarPartView, Sizing.Split, panelInGrid && this.sideBarPosition === this.panelPosition ? this.panelPartView : this.editorPartView, this.sideBarPosition === Position.RIGHT ? Direction.Right : Direction.Left); - activityBarInGrid = true; - } - - if (!sidebarInGrid) { - this.workbenchGrid.addView(this.sidebarPartView, this.uiState.lastSidebarDimension !== undefined ? this.uiState.lastSidebarDimension : Sizing.Split, this.activitybarPartView, this.sideBarPosition === Position.LEFT ? Direction.Right : Direction.Left); - sidebarInGrid = true; - } - - if (!panelInGrid) { - this.workbenchGrid.addView(this.panelPartView, this.getLastPanelDimension(this.panelPosition) !== undefined ? this.getLastPanelDimension(this.panelPosition) : Sizing.Split, this.editorPartView, this.panelPosition === Position.BOTTOM ? Direction.Down : Direction.Right); - panelInGrid = true; - } - - // Hide parts - if (this.panelHidden) { - this.panelPartView.hide(); - } - - if (this.statusBarHidden) { - this.statusbarPartView.hide(); - } - - if (!this.isVisible(Parts.TITLEBAR_PART)) { - this.titlebarPartView.hide(); - } - - if (this.activityBarHidden) { - this.activitybarPartView.hide(); - } - - if (this.sideBarHidden) { - this.sidebarPartView.hide(); - } - - if (this.editorHidden) { - this.editorPartView.hide(); - } - - // Show visible parts - if (!this.editorHidden) { - this.editorPartView.show(); - } - - if (!this.statusBarHidden) { - this.statusbarPartView.show(); - } - - if (this.isVisible(Parts.TITLEBAR_PART)) { - this.titlebarPartView.show(); - } - - if (!this.activityBarHidden) { - this.activitybarPartView.show(); - } - - if (!this.sideBarHidden) { - this.sidebarPartView.show(); - } - - if (!this.panelHidden) { - this.panelPartView.show(); - } - } - - layout(options?: ILayoutOptions): void { - this.contextViewService.layout(); - - if (this.workbenchStarted && !this.workbenchShutdown) { - if (this.workbenchGrid instanceof Grid) { - const dimensions = DOM.getClientArea(this.container); - DOM.position(this.workbench, 0, 0, 0, 0, 'relative'); - DOM.size(this.workbench, dimensions.width, dimensions.height); - - // Layout the grid - this.workbenchGrid.layout(dimensions.width, dimensions.height); - - // Layout non-view ui components - this.quickInput.layout(dimensions); - this.quickOpen.layout(dimensions); - this.notificationsCenter.layout(dimensions); - this.notificationsToasts.layout(dimensions); - - // Update grid view membership - this.updateGrid(); - } else { - this.workbenchGrid.layout(options); - } - } - } - - isEditorLayoutCentered(): boolean { - return this.shouldCenterLayout; - } - - centerEditorLayout(active: boolean, skipLayout?: boolean): void { - this.storageService.store(Workbench.centeredEditorLayoutActiveStorageKey, active, StorageScope.WORKSPACE); - this.shouldCenterLayout = active; - let smartActive = active; - if (this.editorPart.groups.length > 1 && this.configurationService.getValue('workbench.editor.centeredLayoutAutoResize')) { - smartActive = false; // Respect the auto resize setting - do not go into centered layout if there is more than 1 group. - } - - // Enter Centered Editor Layout - if (this.editorPart.isLayoutCentered() !== smartActive) { - this.editorPart.centerLayout(smartActive); - - if (!skipLayout) { - this.layout(); - } - } - } - - resizePart(part: Parts, sizeChange: number): void { - let view: View; - switch (part) { - case Parts.SIDEBAR_PART: - view = this.sidebarPartView; - case Parts.PANEL_PART: - view = this.panelPartView; - case Parts.EDITOR_PART: - view = this.editorPartView; - if (this.workbenchGrid instanceof Grid) { - this.workbenchGrid.resizeView(view, this.workbenchGrid.getViewSize(view) + sizeChange); - } else { - this.workbenchGrid.resizePart(part, sizeChange); - } - break; - default: - return; // Cannot resize other parts - } - } - - setActivityBarHidden(hidden: boolean, skipLayout?: boolean): void { - this.activityBarHidden = hidden; - - // Layout - if (!skipLayout) { - if (this.workbenchGrid instanceof Grid) { - this.layout(); - } else { - this.workbenchGrid.layout(); - } - } - } - - setEditorHidden(hidden: boolean, skipLayout?: boolean): void { - if (!(this.workbenchGrid instanceof Grid)) { - return; - } - - this.editorHidden = hidden; - - // The editor and the panel cannot be hidden at the same time - if (this.editorHidden && this.panelHidden) { - this.setPanelHidden(false, true); - } - - if (!skipLayout) { - this.layout(); - } - } - - setSideBarHidden(hidden: boolean, skipLayout?: boolean): void { - this.sideBarHidden = hidden; - this.sideBarVisibleContext.set(!hidden); - - // Adjust CSS - if (hidden) { - DOM.addClass(this.workbench, 'nosidebar'); - } else { - DOM.removeClass(this.workbench, 'nosidebar'); - } - - // If sidebar becomes hidden, also hide the current active Viewlet if any - if (hidden && this.sidebarPart.getActiveViewlet()) { - this.sidebarPart.hideActiveViewlet(); - const activePanel = this.panelPart.getActivePanel(); - - // Pass Focus to Editor or Panel if Sidebar is now hidden - if (this.hasFocus(Parts.PANEL_PART) && activePanel) { - activePanel.focus(); - } else { - this.editorGroupService.activeGroup.focus(); - } - } - - // If sidebar becomes visible, show last active Viewlet or default viewlet - else if (!hidden && !this.sidebarPart.getActiveViewlet()) { - const viewletToOpen = this.sidebarPart.getLastActiveViewletId(); - if (viewletToOpen) { - const viewlet = this.sidebarPart.openViewlet(viewletToOpen, true); - if (!viewlet) { - this.sidebarPart.openViewlet(this.sidebarPart.getDefaultViewletId(), true); - } - } - } - - - // Remember in settings - const defaultHidden = this.contextService.getWorkbenchState() === WorkbenchState.EMPTY; - if (hidden !== defaultHidden) { - this.storageService.store(Workbench.sidebarHiddenStorageKey, hidden ? 'true' : 'false', StorageScope.WORKSPACE); - } else { - this.storageService.remove(Workbench.sidebarHiddenStorageKey, StorageScope.WORKSPACE); - } - - // Layout - if (!skipLayout) { - if (this.workbenchGrid instanceof Grid) { - this.layout(); - } else { - this.workbenchGrid.layout(); - } - } - } - - setPanelHidden(hidden: boolean, skipLayout?: boolean): void { - this.panelHidden = hidden; - - // Adjust CSS - if (hidden) { - DOM.addClass(this.workbench, 'nopanel'); - } else { - DOM.removeClass(this.workbench, 'nopanel'); - } - - // If panel part becomes hidden, also hide the current active panel if any - if (hidden && this.panelPart.getActivePanel()) { - this.panelPart.hideActivePanel(); - this.editorGroupService.activeGroup.focus(); // Pass focus to editor group if panel part is now hidden - } - - // If panel part becomes visible, show last active panel or default panel - else if (!hidden && !this.panelPart.getActivePanel()) { - const panelToOpen = this.panelPart.getLastActivePanelId(); - if (panelToOpen) { - this.panelPart.openPanel(panelToOpen, true); - } - } - - - // Remember in settings - if (!hidden) { - this.storageService.store(Workbench.panelHiddenStorageKey, 'false', StorageScope.WORKSPACE); - } else { - this.storageService.remove(Workbench.panelHiddenStorageKey, StorageScope.WORKSPACE); - } - - // The editor and panel cannot be hiddne at the same time - if (hidden && this.editorHidden) { - this.setEditorHidden(false, true); - } - - // Layout - if (!skipLayout) { - if (this.workbenchGrid instanceof Grid) { - this.layout(); - } else { - this.workbenchGrid.layout(); - } - } - } - - toggleMaximizedPanel(): void { - if (this.workbenchGrid instanceof Grid) { - this.workbenchGrid.maximizeViewSize(this.panelPartView); - } else { - this.workbenchGrid.layout({ toggleMaximizedPanel: true, source: Parts.PANEL_PART }); - } - } - - isPanelMaximized(): boolean { - if (this.workbenchGrid instanceof Grid) { - try { - return this.workbenchGrid.getViewSize2(this.panelPartView).height === this.panelPart.maximumHeight; - } catch (e) { - return false; - } - } else { - return this.workbenchGrid.isPanelMaximized(); - } - } - - getSideBarPosition(): Position { - return this.sideBarPosition; - } - - setSideBarPosition(position: Position): void { - const wasHidden = this.sideBarHidden; - - if (this.sideBarHidden) { - this.setSideBarHidden(false, true /* Skip Layout */); - } - - const newPositionValue = (position === Position.LEFT) ? 'left' : 'right'; - const oldPositionValue = (this.sideBarPosition === Position.LEFT) ? 'left' : 'right'; - this.sideBarPosition = position; - - // Adjust CSS - DOM.removeClass(this.activitybarPart.getContainer(), oldPositionValue); - DOM.removeClass(this.sidebarPart.getContainer(), oldPositionValue); - DOM.addClass(this.activitybarPart.getContainer(), newPositionValue); - DOM.addClass(this.sidebarPart.getContainer(), newPositionValue); - - // Update Styles - this.activitybarPart.updateStyles(); - this.sidebarPart.updateStyles(); - - // Layout - if (this.workbenchGrid instanceof Grid) { - - if (!wasHidden) { - this.uiState.lastSidebarDimension = this.workbenchGrid.getViewSize(this.sidebarPartView); - } - - this.workbenchGrid.removeView(this.sidebarPartView); - this.workbenchGrid.removeView(this.activitybarPartView); - - if (!this.panelHidden && this.panelPosition === Position.BOTTOM) { - this.workbenchGrid.removeView(this.panelPartView); - } - - this.layout(); - } else { - this.workbenchGrid.layout(); - } - } - - setMenubarVisibility(visibility: MenuBarVisibility, skipLayout: boolean): void { - if (this.menubarVisibility !== visibility) { - this.menubarVisibility = visibility; - - // Layout - if (!skipLayout) { - if (this.workbenchGrid instanceof Grid) { - const dimensions = DOM.getClientArea(this.container); - this.workbenchGrid.layout(dimensions.width, dimensions.height); - } else { - this.workbenchGrid.layout(); - } - } - } - } - - getMenubarVisibility(): MenuBarVisibility { - return this.menubarVisibility; - } - - getPanelPosition(): Position { - return this.panelPosition; - } - - setPanelPosition(position: Position): void { - const wasHidden = this.panelHidden; - - if (this.panelHidden) { - this.setPanelHidden(false, true /* Skip Layout */); - } else { - this.saveLastPanelDimension(); - } - - const newPositionValue = (position === Position.BOTTOM) ? 'bottom' : 'right'; - const oldPositionValue = (this.panelPosition === Position.BOTTOM) ? 'bottom' : 'right'; - this.panelPosition = position; - this.storageService.store(Workbench.panelPositionStorageKey, PositionToString(this.panelPosition).toLowerCase(), StorageScope.WORKSPACE); - - // Adjust CSS - DOM.removeClass(this.panelPart.getContainer(), oldPositionValue); - DOM.addClass(this.panelPart.getContainer(), newPositionValue); - - // Update Styles - this.panelPart.updateStyles(); - - // Layout - if (this.workbenchGrid instanceof Grid) { - if (!wasHidden) { - this.saveLastPanelDimension(); - } - - this.workbenchGrid.removeView(this.panelPartView); - this.layout(); - } else { - this.workbenchGrid.layout(); - } - } -} \ No newline at end of file diff --git a/src/vs/workbench/parts/codeEditor/browser/simpleEditorOptions.ts b/src/vs/workbench/parts/codeEditor/browser/simpleEditorOptions.ts deleted file mode 100644 index 6041d5e177..0000000000 --- a/src/vs/workbench/parts/codeEditor/browser/simpleEditorOptions.ts +++ /dev/null @@ -1,31 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; - -export function getSimpleEditorOptions(): IEditorOptions { - return { - wordWrap: 'on', - overviewRulerLanes: 0, - glyphMargin: false, - lineNumbers: 'off', - folding: false, - selectOnLineNumbers: false, - hideCursorInOverviewRuler: true, - selectionHighlight: false, - scrollbar: { - horizontal: 'hidden' - }, - lineDecorationsWidth: 0, - overviewRulerBorder: false, - scrollBeyondLastLine: false, - renderLineHighlight: 'none', - fixedOverflowWidgets: true, - acceptSuggestionOnEnter: 'smart', - minimap: { - enabled: false - } - }; -} diff --git a/src/vs/workbench/parts/comments/electron-browser/commentNode.ts b/src/vs/workbench/parts/comments/electron-browser/commentNode.ts deleted file mode 100644 index 5bfbbfb871..0000000000 --- a/src/vs/workbench/parts/comments/electron-browser/commentNode.ts +++ /dev/null @@ -1,401 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import * as dom from 'vs/base/browser/dom'; -import * as modes from 'vs/editor/common/modes'; -import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { ActionsOrientation, ActionItem, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; -import { Button } from 'vs/base/browser/ui/button/button'; -import { Action, IActionRunner } from 'vs/base/common/actions'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { URI } from 'vs/base/common/uri'; -import { ITextModel } from 'vs/editor/common/model'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { inputValidationErrorBorder } from 'vs/platform/theme/common/colorRegistry'; -import { attachButtonStyler } from 'vs/platform/theme/common/styler'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ICommentService } from 'vs/workbench/parts/comments/electron-browser/commentService'; -import { SimpleCommentEditor } from 'vs/workbench/parts/comments/electron-browser/simpleCommentEditor'; -import { KeyCode } from 'vs/base/common/keyCodes'; -import { isMacintosh } from 'vs/base/common/platform'; -import { Selection } from 'vs/editor/common/core/selection'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { Emitter, Event } from 'vs/base/common/event'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { assign } from 'vs/base/common/objects'; -import { MarkdownString } from 'vs/base/common/htmlContent'; -import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; - -const UPDATE_COMMENT_LABEL = nls.localize('label.updateComment', "Update comment"); -const UPDATE_IN_PROGRESS_LABEL = nls.localize('label.updatingComment', "Updating comment..."); - -export class CommentNode extends Disposable { - private _domNode: HTMLElement; - private _body: HTMLElement; - private _md: HTMLElement; - private _clearTimeout: any; - - private _editAction: Action; - private _commentEditContainer: HTMLElement; - private _commentDetailsContainer: HTMLElement; - private _reactionsActionBar?: ActionBar; - private _actionsContainer?: HTMLElement; - private _commentEditor: SimpleCommentEditor; - private _commentEditorModel: ITextModel; - private _updateCommentButton: Button; - private _errorEditingContainer: HTMLElement; - private _isPendingLabel: HTMLElement; - - private _deleteAction: Action; - protected actionRunner?: IActionRunner; - protected toolbar: ToolBar; - - private _onDidDelete = new Emitter(); - - public get domNode(): HTMLElement { - return this._domNode; - } - - constructor( - public comment: modes.Comment, - private owner: string, - private resource: URI, - private markdownRenderer: MarkdownRenderer, - private themeService: IThemeService, - private instantiationService: IInstantiationService, - private commentService: ICommentService, - private modelService: IModelService, - private modeService: IModeService, - private dialogService: IDialogService, - private notificationService: INotificationService, - private contextMenuService: IContextMenuService - ) { - super(); - - this._domNode = dom.$('div.review-comment'); - this._domNode.tabIndex = 0; - const avatar = dom.append(this._domNode, dom.$('div.avatar-container')); - if (comment.userIconPath) { - const img = dom.append(avatar, dom.$('img.avatar')); - img.src = comment.userIconPath.toString(); - img.onerror = _ => img.remove(); - } - this._commentDetailsContainer = dom.append(this._domNode, dom.$('.review-comment-contents')); - - this.createHeader(this._commentDetailsContainer); - - this._body = dom.append(this._commentDetailsContainer, dom.$('div.comment-body')); - this._md = this.markdownRenderer.render(comment.body).element; - this._body.appendChild(this._md); - - if (this.comment.commentReactions && this.comment.commentReactions.length) { - this.createReactions(this._commentDetailsContainer); - } - - this._domNode.setAttribute('aria-label', `${comment.userName}, ${comment.body.value}`); - this._domNode.setAttribute('role', 'treeitem'); - this._clearTimeout = null; - } - - public get onDidDelete(): Event { - return this._onDidDelete.event; - } - - private createHeader(commentDetailsContainer: HTMLElement): void { - const header = dom.append(commentDetailsContainer, dom.$('div.comment-title')); - const author = dom.append(header, dom.$('strong.author')); - author.innerText = this.comment.userName; - - this._isPendingLabel = dom.append(header, dom.$('span.isPending')); - - if (this.comment.isDraft) { - this._isPendingLabel.innerText = 'Pending'; - } - - const actions: Action[] = []; - if (this.comment.canEdit) { - this._editAction = this.createEditAction(commentDetailsContainer); - actions.push(this._editAction); - } - - if (this.comment.canDelete) { - this._deleteAction = this.createDeleteAction(); - actions.push(this._deleteAction); - } - - if (actions.length) { - const actionsContainer = dom.append(header, dom.$('.comment-actions.hidden')); - - this.toolbar = new ToolBar(actionsContainer, this.contextMenuService, { - actionItemProvider: action => this.actionItemProvider(action as Action), - orientation: ActionsOrientation.HORIZONTAL - }); - - this.registerActionBarListeners(actionsContainer); - - let reactionActions = []; - let reactionGroup = this.commentService.getReactionGroup(this.owner); - if (reactionGroup && reactionGroup.length) { - reactionActions = reactionGroup.map((reaction) => { - return new Action(`reaction.command.${reaction.label}`, `${reaction.label}`, '', true, async () => { - try { - await this.commentService.addReaction(this.owner, this.resource, this.comment, reaction); - } catch (e) { - const error = e.message - ? nls.localize('commentAddReactionError', "Deleting the comment reaction failed: {0}.", e.message) - : nls.localize('commentAddReactionDefaultError', "Deleting the comment reaction failed"); - this.notificationService.error(error); - } - }); - }); - } - - this.toolbar.setActions(actions, reactionActions)(); - this._toDispose.push(this.toolbar); - } - } - - actionItemProvider(action: Action) { - let options = {}; - if (action.id === 'comment.delete' || action.id === 'comment.edit') { - options = { label: false, icon: true }; - } else { - options = { label: true, icon: true }; - } - - let item = new ActionItem({}, action, options); - return item; - } - - private createReactions(commentDetailsContainer: HTMLElement): void { - this._actionsContainer = dom.append(commentDetailsContainer, dom.$('div.comment-reactions')); - this._reactionsActionBar = new ActionBar(this._actionsContainer, {}); - this._toDispose.push(this._reactionsActionBar); - - let reactionActions = this.comment.commentReactions.map(reaction => { - return new Action(`reaction.${reaction.label}`, `${reaction.label}`, reaction.hasReacted ? 'active' : '', true, async () => { - try { - if (reaction.hasReacted) { - await this.commentService.deleteReaction(this.owner, this.resource, this.comment, reaction); - } else { - await this.commentService.addReaction(this.owner, this.resource, this.comment, reaction); - } - } catch (e) { - let error: string; - - if (reaction.hasReacted) { - error = e.message - ? nls.localize('commentDeleteReactionError', "Deleting the comment reaction failed: {0}.", e.message) - : nls.localize('commentDeleteReactionDefaultError', "Deleting the comment reaction failed"); - } else { - error = e.message - ? nls.localize('commentAddReactionError', "Deleting the comment reaction failed: {0}.", e.message) - : nls.localize('commentAddReactionDefaultError', "Deleting the comment reaction failed"); - } - this.notificationService.error(error); - } - }); - }); - - reactionActions.forEach(action => this._reactionsActionBar.push(action, { label: true, icon: true })); - } - - private createCommentEditor(): void { - const container = dom.append(this._commentEditContainer, dom.$('.edit-textarea')); - this._commentEditor = this.instantiationService.createInstance(SimpleCommentEditor, container, SimpleCommentEditor.getEditorOptions()); - const resource = URI.parse(`comment:commentinput-${this.comment.commentId}-${Date.now()}.md`); - this._commentEditorModel = this.modelService.createModel('', this.modeService.createByFilepathOrFirstLine(resource.path), resource, false); - - this._commentEditor.setModel(this._commentEditorModel); - this._commentEditor.setValue(this.comment.body.value); - this._commentEditor.layout({ width: container.clientWidth - 14, height: 90 }); - this._commentEditor.focus(); - const lastLine = this._commentEditorModel.getLineCount(); - const lastColumn = this._commentEditorModel.getLineContent(lastLine).length + 1; - this._commentEditor.setSelection(new Selection(lastLine, lastColumn, lastLine, lastColumn)); - - this._toDispose.push(this._commentEditor.onKeyDown((e: IKeyboardEvent) => { - const isCmdOrCtrl = isMacintosh ? e.metaKey : e.ctrlKey; - if (this._updateCommentButton.enabled && e.keyCode === KeyCode.Enter && isCmdOrCtrl) { - this.editComment(); - } - })); - - this._toDispose.push(this._commentEditor); - this._toDispose.push(this._commentEditorModel); - } - - private removeCommentEditor() { - this._editAction.enabled = true; - this._body.classList.remove('hidden'); - - this._commentEditorModel.dispose(); - this._commentEditor.dispose(); - this._commentEditor = null; - - this._commentEditContainer.remove(); - } - - private async editComment(): Promise { - this._updateCommentButton.enabled = false; - this._updateCommentButton.label = UPDATE_IN_PROGRESS_LABEL; - - try { - const newBody = this._commentEditor.getValue(); - await this.commentService.editComment(this.owner, this.resource, this.comment, newBody); - - this._updateCommentButton.enabled = true; - this._updateCommentButton.label = UPDATE_COMMENT_LABEL; - this._commentEditor.getDomNode().style.outline = ''; - this.removeCommentEditor(); - const editedComment = assign({}, this.comment, { body: new MarkdownString(newBody) }); - this.update(editedComment); - } catch (e) { - this._updateCommentButton.enabled = true; - this._updateCommentButton.label = UPDATE_COMMENT_LABEL; - - this._commentEditor.getDomNode().style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`; - this._errorEditingContainer.textContent = e.message - ? nls.localize('commentEditError', "Updating the comment failed: {0}.", e.message) - : nls.localize('commentEditDefaultError', "Updating the comment failed."); - this._errorEditingContainer.classList.remove('hidden'); - this._commentEditor.focus(); - } - } - - private createDeleteAction(): Action { - return new Action('comment.delete', nls.localize('label.delete', "Delete"), 'octicon octicon-x', true, () => { - return this.dialogService.confirm({ - message: nls.localize('confirmDelete', "Delete comment?"), - type: 'question', - primaryButton: nls.localize('label.delete', "Delete") - }).then(async result => { - if (result.confirmed) { - try { - const didDelete = await this.commentService.deleteComment(this.owner, this.resource, this.comment); - if (didDelete) { - this._onDidDelete.fire(this); - } else { - throw Error(); - } - } catch (e) { - const error = e.message - ? nls.localize('commentDeletionError', "Deleting the comment failed: {0}.", e.message) - : nls.localize('commentDeletionDefaultError', "Deleting the comment failed"); - this.notificationService.error(error); - } - } - }); - }); - } - - private createEditAction(commentDetailsContainer: HTMLElement): Action { - return new Action('comment.edit', nls.localize('label.edit', "Edit"), 'octicon octicon-pencil', true, () => { - this._body.classList.add('hidden'); - this._commentEditContainer = dom.append(commentDetailsContainer, dom.$('.edit-container')); - this.createCommentEditor(); - - this._errorEditingContainer = dom.append(this._commentEditContainer, dom.$('.validation-error.hidden')); - const formActions = dom.append(this._commentEditContainer, dom.$('.form-actions')); - - const cancelEditButton = new Button(formActions); - cancelEditButton.label = nls.localize('label.cancel', "Cancel"); - this._toDispose.push(attachButtonStyler(cancelEditButton, this.themeService)); - - this._toDispose.push(cancelEditButton.onDidClick(_ => { - this.removeCommentEditor(); - })); - - this._updateCommentButton = new Button(formActions); - this._updateCommentButton.label = UPDATE_COMMENT_LABEL; - this._toDispose.push(attachButtonStyler(this._updateCommentButton, this.themeService)); - - this._toDispose.push(this._updateCommentButton.onDidClick(_ => { - this.editComment(); - })); - - this._toDispose.push(this._commentEditor.onDidChangeModelContent(_ => { - this._updateCommentButton.enabled = !!this._commentEditor.getValue(); - })); - - this._editAction.enabled = false; - return null; - }); - } - - private registerActionBarListeners(actionsContainer: HTMLElement): void { - this._toDispose.push(dom.addDisposableListener(this._domNode, 'mouseenter', () => { - actionsContainer.classList.remove('hidden'); - })); - - this._toDispose.push(dom.addDisposableListener(this._domNode, 'focus', () => { - actionsContainer.classList.remove('hidden'); - })); - - this._toDispose.push(dom.addDisposableListener(this._domNode, 'mouseleave', () => { - if (!this._domNode.contains(document.activeElement)) { - actionsContainer.classList.add('hidden'); - } - })); - - this._toDispose.push(dom.addDisposableListener(this._domNode, 'focusout', (e: FocusEvent) => { - if (!this._domNode.contains((e.relatedTarget))) { - actionsContainer.classList.add('hidden'); - - if (this._commentEditor && this._commentEditor.getValue() === this.comment.body.value) { - this.removeCommentEditor(); - } - } - })); - } - - update(newComment: modes.Comment) { - this.comment = newComment; - - if (newComment.body !== this.comment.body) { - this._body.removeChild(this._md); - this._md = this.markdownRenderer.render(newComment.body).element; - this._body.appendChild(this._md); - } - - if (newComment.isDraft) { - this._isPendingLabel.innerText = 'Pending'; - } else { - this._isPendingLabel.innerText = ''; - } - - // update comment reactions - if (this._actionsContainer) { - this._actionsContainer.remove(); - } - - if (this._reactionsActionBar) { - this._reactionsActionBar.clear(); - } - - if (this.comment.commentReactions && this.comment.commentReactions.length) { - this.createReactions(this._commentDetailsContainer); - } - } - - focus() { - this.domNode.focus(); - if (!this._clearTimeout) { - dom.addClass(this.domNode, 'focus'); - this._clearTimeout = setTimeout(() => { - dom.removeClass(this.domNode, 'focus'); - }, 3000); - } - } - - dispose() { - this._toDispose.forEach(disposeable => disposeable.dispose()); - } -} \ No newline at end of file diff --git a/src/vs/workbench/parts/debug/browser/media/debugToolbar.css b/src/vs/workbench/parts/debug/browser/media/debugToolbar.css deleted file mode 100644 index 7f031e5943..0000000000 --- a/src/vs/workbench/parts/debug/browser/media/debugToolbar.css +++ /dev/null @@ -1,130 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -/* Debug actions widget */ - -.monaco-workbench .debug-toolbar { - position: absolute; - z-index: 200; - height: 32px; - display: flex; - padding-left: 7px; -} - -.monaco-workbench .debug-toolbar .monaco-action-bar .action-item { - height: 32px; -} - -.monaco-workbench .debug-toolbar .monaco-action-bar .action-item.select-container { - margin-right: 7px; -} - -.monaco-workbench .debug-toolbar .drag-area { - cursor: -webkit-grab; - height: 32px; - width: 16px; - background: url('drag.svg') center center no-repeat; - background-size: 16px 16px; -} - -.monaco-workbench .debug-toolbar .drag-area.dragged { - cursor: -webkit-grabbing; -} - -.monaco-workbench .debug-toolbar .monaco-action-bar .action-item > .action-label { - width: 32px; - height: 32px; - margin-right: 0; - background-size: 16px; - background-position: center center; - background-repeat: no-repeat; -} - -/* Debug actionbar actions */ - -.monaco-workbench .debug-action.step-over, -.monaco-workbench .debug-action.step-back { - background-image: url('step-over.svg'); -} - -.monaco-workbench .debug-action.step-into { - background-image: url('step-into.svg'); -} - -.monaco-workbench .debug-action.step-out { - background-image: url('step-out.svg'); -} - -.monaco-workbench .debug-action.step-back, -.monaco-workbench .debug-action.reverse-continue { - transform: scaleX(-1); -} - -.monaco-workbench .debug-action.continue, -.monaco-workbench .debug-action.reverse-continue { - background-image: url('continue.svg'); -} - -.monaco-workbench .debug-action.restart { - background-image: url('restart.svg'); -} - -.monaco-workbench .debug-action.pause { - background-image: url('pause.svg'); -} - -.monaco-workbench .debug-action.stop { - background-image: url('stop.svg'); -} - -.monaco-workbench .debug-action.disconnect { - background-image: url('disconnect.svg'); -} - -/* Dark and hc theme actions */ - -.vs-dark .monaco-workbench .debug-action.step-over, -.vs-dark .monaco-workbench .debug-action.step-back, -.hc-black .monaco-workbench .debug-action.step-over, -.hc-black .monaco-workbench .debug-action.step-back { - background-image: url('step-over-inverse.svg'); -} - -.vs-dark .monaco-workbench .debug-action.step-into, -.hc-black .monaco-workbench .debug-action.step-into { - background-image: url('step-into-inverse.svg'); -} - -.vs-dark .monaco-workbench .debug-action.step-out, -.hc-black .monaco-workbench .debug-action.step-out { - background-image: url('step-out-inverse.svg'); -} - -.vs-dark .monaco-workbench .debug-action.continue, -.vs-dark .monaco-workbench .debug-action.reverse-continue, -.hc-black .monaco-workbench .debug-action.continue, -.hc-black .monaco-workbench .debug-action.reverse-continue { - background-image: url('continue-inverse.svg'); -} - -.vs-dark .monaco-workbench .debug-action.restart, -.hc-black .monaco-workbench .debug-action.restart { - background-image: url('restart-inverse.svg'); -} - -.vs-dark .monaco-workbench .debug-action.pause, -.hc-black .monaco-workbench .debug-action.pause { - background-image: url('pause-inverse.svg'); -} - -.vs-dark .monaco-workbench .debug-action.stop, -.hc-black .monaco-workbench .debug-action.stop { - background-image: url('stop-inverse.svg'); -} - -.vs-dark .monaco-workbench .debug-action.disconnect, -.hc-black .monaco-workbench .debug-action.disconnect { - background-image: url('disconnect-inverse.svg'); -} diff --git a/src/vs/workbench/parts/debug/electron-browser/electronDebugActions.ts b/src/vs/workbench/parts/debug/electron-browser/electronDebugActions.ts deleted file mode 100644 index 1cea66cee4..0000000000 --- a/src/vs/workbench/parts/debug/electron-browser/electronDebugActions.ts +++ /dev/null @@ -1,71 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { Action } from 'vs/base/common/actions'; -import { Variable } from 'vs/workbench/parts/debug/common/debugModel'; -import { IDebugService, IStackFrame } from 'vs/workbench/parts/debug/common/debug'; -import { clipboard } from 'electron'; -import { isWindows } from 'vs/base/common/platform'; - -export class CopyValueAction extends Action { - static readonly ID = 'workbench.debug.viewlet.action.copyValue'; - static LABEL = nls.localize('copyValue', "Copy Value"); - - constructor(id: string, label: string, private value: any, @IDebugService private readonly debugService: IDebugService) { - super(id, label, 'debug-action copy-value'); - this._enabled = typeof this.value === 'string' || (this.value instanceof Variable && !!this.value.evaluateName); - } - - public run(): Promise { - if (this.value instanceof Variable) { - const frameId = this.debugService.getViewModel().focusedStackFrame.frameId; - const session = this.debugService.getViewModel().focusedSession; - return session.evaluate(this.value.evaluateName, frameId).then(result => { - clipboard.writeText(result.body.result); - }, err => clipboard.writeText(this.value.value)); - } - - clipboard.writeText(this.value); - return Promise.resolve(undefined); - } -} - -export class CopyEvaluatePathAction extends Action { - static readonly ID = 'workbench.debug.viewlet.action.copyEvaluatePath'; - static LABEL = nls.localize('copyAsExpression', "Copy as Expression"); - - constructor(id: string, label: string, private value: Variable) { - super(id, label); - this._enabled = this.value && !!this.value.evaluateName; - } - - public run(): Promise { - clipboard.writeText(this.value.evaluateName); - return Promise.resolve(undefined); - } -} - -export class CopyAction extends Action { - static readonly ID = 'workbench.debug.action.copy'; - static LABEL = nls.localize('copy', "Copy"); - - public run(): Promise { - clipboard.writeText(window.getSelection().toString()); - return Promise.resolve(undefined); - } -} - -const lineDelimiter = isWindows ? '\r\n' : '\n'; - -export class CopyStackTraceAction extends Action { - static readonly ID = 'workbench.action.debug.copyStackTrace'; - static LABEL = nls.localize('copyStackTrace', "Copy Call Stack"); - - public run(frame: IStackFrame): Promise { - clipboard.writeText(frame.thread.getCallStack().map(sf => sf.toString()).join(lineDelimiter)); - return Promise.resolve(undefined); - } -} diff --git a/src/vs/workbench/parts/html/common/htmlInput.ts b/src/vs/workbench/parts/html/common/htmlInput.ts deleted file mode 100644 index 51a12e3cfd..0000000000 --- a/src/vs/workbench/parts/html/common/htmlInput.ts +++ /dev/null @@ -1,32 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from 'vs/base/common/uri'; -import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; -import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { IHashService } from 'vs/workbench/services/hash/common/hashService'; - -export interface HtmlInputOptions { - readonly allowScripts?: boolean; - readonly allowSvgs?: boolean; - readonly svgWhiteList?: string[]; -} - -export function areHtmlInputOptionsEqual(left: HtmlInputOptions, right: HtmlInputOptions) { - return left.allowScripts === right.allowScripts && left.allowSvgs === right.allowSvgs; -} - -export class HtmlInput extends ResourceEditorInput { - constructor( - name: string, - description: string, - resource: URI, - public readonly options: HtmlInputOptions, - @ITextModelService textModelResolverService: ITextModelService, - @IHashService hashService: IHashService - ) { - super(name, description, resource, textModelResolverService, hashService); - } -} diff --git a/src/vs/workbench/parts/html/electron-browser/html.contribution.ts b/src/vs/workbench/parts/html/electron-browser/html.contribution.ts deleted file mode 100644 index 3ba4152572..0000000000 --- a/src/vs/workbench/parts/html/electron-browser/html.contribution.ts +++ /dev/null @@ -1,101 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { URI } from 'vs/base/common/uri'; -import { localize } from 'vs/nls'; -import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { EditorViewColumn, viewColumnToEditorGroup } from 'vs/workbench/api/shared/editor'; -import { HtmlInput, HtmlInputOptions } from '../common/htmlInput'; -import { HtmlPreviewPart } from './htmlPreviewPart'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; -import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; -import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; -import { registerWebViewCommands } from 'vs/workbench/parts/webview/electron-browser/webview.contribution'; - -function getActivePreviewsForResource(accessor: ServicesAccessor, resource: URI | string) { - const uri = resource instanceof URI ? resource : URI.parse(resource); - return accessor.get(IEditorService).visibleControls - .filter(c => c instanceof HtmlPreviewPart && c.model) - .map(e => e as HtmlPreviewPart) - .filter(e => e.model.uri.scheme === uri.scheme && e.model.uri.toString() === uri.toString()); -} - -// --- Register Editor - -(Registry.as(EditorExtensions.Editors)).registerEditor(new EditorDescriptor( - HtmlPreviewPart, - HtmlPreviewPart.ID, - localize('html.editor.label', "Html Preview")), - [new SyncDescriptor(HtmlInput)]); - -// --- Register Commands - -CommandsRegistry.registerCommand('_workbench.previewHtml', function ( - accessor: ServicesAccessor, - resource: URI | string, - position?: EditorViewColumn, - label?: string -) { - const uri = resource instanceof URI ? resource : URI.parse(resource); - label = label || uri.fsPath; - - let input: HtmlInput; - - const editorGroupService = accessor.get(IEditorGroupsService); - - let targetGroup: IEditorGroup = editorGroupService.getGroup(viewColumnToEditorGroup(editorGroupService, position)); - if (!targetGroup) { - targetGroup = editorGroupService.activeGroup; - } - - // Find already opened HTML input if any - if (targetGroup) { - const editors = targetGroup.editors; - for (const editor of editors) { - const editorResource = editor.getResource(); - if (editor instanceof HtmlInput && editorResource && editorResource.toString() === resource.toString()) { - input = editor; - break; - } - } - } - - const extensionsWorkbenchService = accessor.get(IExtensionsWorkbenchService); - - const inputOptions: HtmlInputOptions = { - allowScripts: true, - allowSvgs: true, - svgWhiteList: extensionsWorkbenchService.allowedBadgeProviders - }; - - // Otherwise, create new input and open it - if (!input) { - input = accessor.get(IInstantiationService).createInstance(HtmlInput, label, '', uri, inputOptions); - } else { - input.setName(label); // make sure to use passed in label - } - - return accessor.get(IEditorService) - .openEditor(input, { pinned: true }, viewColumnToEditorGroup(editorGroupService, position)) - .then(editor => true); -}); - -CommandsRegistry.registerCommand('_workbench.htmlPreview.postMessage', function ( - accessor: ServicesAccessor, - resource: URI | string, - message: any -) { - const activePreviews = getActivePreviewsForResource(accessor, resource); - for (const preview of activePreviews) { - preview.sendMessage(message); - } - return activePreviews.length > 0; -}); - -registerWebViewCommands(HtmlPreviewPart.ID); \ No newline at end of file diff --git a/src/vs/workbench/parts/html/electron-browser/htmlPreviewPart.ts b/src/vs/workbench/parts/html/electron-browser/htmlPreviewPart.ts deleted file mode 100644 index 979436010c..0000000000 --- a/src/vs/workbench/parts/html/electron-browser/htmlPreviewPart.ts +++ /dev/null @@ -1,257 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { localize } from 'vs/nls'; -import { ITextModel } from 'vs/editor/common/model'; -import { Disposable, IDisposable, dispose, IReference } from 'vs/base/common/lifecycle'; -import { EditorOptions, EditorInput, IEditorMemento } from 'vs/workbench/common/editor'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; -import { HtmlInput, HtmlInputOptions, areHtmlInputOptionsEqual } from 'vs/workbench/parts/html/common/htmlInput'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService'; -import { Parts, IPartService } from 'vs/workbench/services/part/common/partService'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { Dimension } from 'vs/base/browser/dom'; -import { BaseWebviewEditor } from 'vs/workbench/parts/webview/electron-browser/baseWebviewEditor'; -import { WebviewElement, WebviewOptions } from 'vs/workbench/parts/webview/electron-browser/webviewElement'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { Event, Emitter } from 'vs/base/common/event'; - -export interface HtmlPreviewEditorViewState { - scrollYPercentage: number; -} - -/** - * An implementation of editor for showing HTML content in an IFrame by leveraging the HTML input. - */ -export class HtmlPreviewPart extends BaseWebviewEditor { - - static readonly ID: string = 'workbench.editor.htmlPreviewPart'; - static class: string = 'htmlPreviewPart'; - - private _webviewDisposables: IDisposable[]; - - private _modelRef: IReference; - public get model(): ITextModel { return this._modelRef && this._modelRef.object.textEditorModel; } - private _modelChangeSubscription = Disposable.None; - private _themeChangeSubscription = Disposable.None; - - private _content: HTMLElement; - private _scrollYPercentage: number = 0; - - private editorMemento: IEditorMemento; - - private readonly _onDidFocusWebview = this._register(new Emitter()); - public get onDidFocus(): Event { return this._onDidFocusWebview.event; } - - constructor( - @ITelemetryService telemetryService: ITelemetryService, - @IThemeService themeService: IThemeService, - @IContextKeyService contextKeyService: IContextKeyService, - @IOpenerService private readonly _openerService: IOpenerService, - @IPartService private readonly _partService: IPartService, - @IStorageService readonly _storageService: IStorageService, - @ITextModelService private readonly _textModelResolverService: ITextModelService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IEditorGroupsService readonly editorGroupService: IEditorGroupsService - ) { - super(HtmlPreviewPart.ID, telemetryService, themeService, contextKeyService, _storageService); - - this.editorMemento = this.getEditorMemento(editorGroupService, this.viewStateStorageKey); - } - - dispose(): void { - // remove from dom - this._webviewDisposables = dispose(this._webviewDisposables); - - // unhook listeners - this._themeChangeSubscription.dispose(); - this._modelChangeSubscription.dispose(); - - // dispose model ref - dispose(this._modelRef); - super.dispose(); - } - - protected createEditor(parent: HTMLElement): void { - this._content = document.createElement('div'); - this._content.style.position = 'absolute'; - this._content.classList.add(HtmlPreviewPart.class); - parent.appendChild(this._content); - } - - private get webview(): WebviewElement { - if (!this._webview) { - let webviewOptions: WebviewOptions = {}; - if (this.input && this.input instanceof HtmlInput) { - webviewOptions = this.input.options; - } - - this._webview = this._instantiationService.createInstance(WebviewElement, - this._partService.getContainer(Parts.EDITOR_PART), - { - ...webviewOptions, - useSameOriginForRoot: true - }); - this._webview.mountTo(this._content); - - if (this.input && this.input instanceof HtmlInput) { - const state = this.loadHTMLPreviewViewState(this.input); - this._scrollYPercentage = state ? state.scrollYPercentage : 0; - this.webview.initialScrollProgress = this._scrollYPercentage; - - const resourceUri = this.input.getResource(); - this.webview.baseUrl = resourceUri.toString(true); - } - this._webviewDisposables = [ - this._webview, - this._webview.onDidClickLink(uri => this._openerService.open(uri)), - this._webview.onDidScroll(data => { - this._scrollYPercentage = data.scrollYPercentage; - }), - ]; - - this._register(this._webview.onDidFocus(() => this._onDidFocusWebview.fire())); - } - return this._webview; - } - - protected setEditorVisible(visible: boolean, group: IEditorGroup): void { - this._doSetVisible(visible); - super.setEditorVisible(visible, group); - } - - private _doSetVisible(visible: boolean): void { - if (!visible) { - this._themeChangeSubscription.dispose(); - this._modelChangeSubscription.dispose(); - this._webviewDisposables = dispose(this._webviewDisposables); - this._webview = undefined; - } else { - this._themeChangeSubscription = this.themeService.onThemeChange(this.onThemeChange.bind(this)); - - if (this._hasValidModel()) { - this._modelChangeSubscription = this.model.onDidChangeContent(() => this.webview.contents = this.model.getLinesContent().join('\n')); - this.webview.contents = this.model.getLinesContent().join('\n'); - } - } - } - - private _hasValidModel(): boolean { - return this._modelRef && this.model && !this.model.isDisposed(); - } - - public layout(dimension: Dimension): void { - const { width, height } = dimension; - this._content.style.width = `${width}px`; - this._content.style.height = `${height}px`; - - super.layout(dimension); - } - - public clearInput(): void { - if (this.input instanceof HtmlInput) { - this.saveHTMLPreviewViewState(this.input, { - scrollYPercentage: this._scrollYPercentage - }); - } - dispose(this._modelRef); - this._modelRef = undefined; - super.clearInput(); - } - - protected saveState(): void { - if (this.input instanceof HtmlInput) { - this.saveHTMLPreviewViewState(this.input, { - scrollYPercentage: this._scrollYPercentage - }); - } - - super.saveState(); - } - - public sendMessage(data: any): void { - this.webview.sendMessage(data); - } - - public setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Promise { - - if (this.input && this.input.matches(input) && this._hasValidModel() && this.input instanceof HtmlInput && input instanceof HtmlInput && areHtmlInputOptionsEqual(this.input.options, input.options)) { - return Promise.resolve(undefined); - } - - let oldOptions: HtmlInputOptions | undefined = undefined; - - if (this.input instanceof HtmlInput) { - oldOptions = this.input.options; - this.saveHTMLPreviewViewState(this.input, { - scrollYPercentage: this._scrollYPercentage - }); - } - - if (this._modelRef) { - this._modelRef.dispose(); - } - this._modelChangeSubscription.dispose(); - - if (!(input instanceof HtmlInput)) { - return Promise.reject(new Error('Invalid input')); - } - - return super.setInput(input, options, token).then(() => { - const resourceUri = input.getResource(); - return this._textModelResolverService.createModelReference(resourceUri).then(ref => { - if (token.isCancellationRequested) { - return undefined; - } - - const model = ref.object; - if (model instanceof BaseTextEditorModel) { - this._modelRef = ref; - } - - if (!this.model) { - return Promise.reject(new Error(localize('html.voidInput', "Invalid editor input."))); - } - - if (oldOptions && !areHtmlInputOptionsEqual(oldOptions, input.options)) { - this._doSetVisible(false); - } - - this._modelChangeSubscription = this.model.onDidChangeContent(() => { - if (this.model) { - this._scrollYPercentage = 0; - this.webview.contents = this.model.getLinesContent().join('\n'); - } - }); - const state = this.loadHTMLPreviewViewState(input); - this._scrollYPercentage = state ? state.scrollYPercentage : 0; - this.webview.baseUrl = resourceUri.toString(true); - this.webview.options = input.options; - this.webview.contents = this.model.getLinesContent().join('\n'); - this.webview.initialScrollProgress = this._scrollYPercentage; - return undefined; - }); - }); - } - - - private get viewStateStorageKey(): string { - return this.getId() + '.editorViewState'; - } - - private saveHTMLPreviewViewState(input: HtmlInput, editorViewState: HtmlPreviewEditorViewState): void { - this.editorMemento.saveEditorState(this.group, input, editorViewState); - } - - private loadHTMLPreviewViewState(input: HtmlInput): HtmlPreviewEditorViewState { - return this.editorMemento.loadEditorState(this.group, input); - } -} diff --git a/src/vs/workbench/parts/localizations/electron-browser/localizationsActions.ts b/src/vs/workbench/parts/localizations/electron-browser/localizationsActions.ts deleted file mode 100644 index 5514fe0683..0000000000 --- a/src/vs/workbench/parts/localizations/electron-browser/localizationsActions.ts +++ /dev/null @@ -1,55 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { localize } from 'vs/nls'; -import { Action } from 'vs/base/common/actions'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IEditor } from 'vs/workbench/common/editor'; -import { join } from 'vs/base/common/paths'; -import { URI } from 'vs/base/common/uri'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { language } from 'vs/base/common/platform'; -import { ILabelService } from 'vs/platform/label/common/label'; - -export class ConfigureLocaleAction extends Action { - public static readonly ID = 'workbench.action.configureLocale'; - public static readonly LABEL = localize('configureLocale', "Configure Display Language"); - - // {{SQL CARBON EDIT}} - private static DEFAULT_CONTENT: string = [ - '{', - `\t// ${localize('displayLanguage', 'Defines Azure Data Studio\'s display language.')}`, - `\t// ${localize('doc', 'See {0} for a list of supported languages.', 'https://go.microsoft.com/fwlink/?LinkId=761051')}`, - `\t`, - `\t// ${localize('restart', 'Changing the value requires restarting Azure Data Studio.')}`, - '}' - ].join('\n'); - - constructor(id: string, label: string, - @IFileService private readonly fileService: IFileService, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IEditorService private readonly editorService: IEditorService, - @ILabelService private readonly labelService: ILabelService - ) { - super(id, label); - } - - public run(event?: any): Promise { - const file = URI.file(join(this.environmentService.appSettingsHome, 'locale.json')); - return this.fileService.resolveFile(file).then(undefined, (error) => { - return this.fileService.createFile(file, ConfigureLocaleAction.DEFAULT_CONTENT); - }).then((stat): Promise | undefined => { - if (!stat) { - return undefined; - } - return this.editorService.openEditor({ - resource: stat.resource - }); - }, (error) => { - throw new Error(localize('fail.createSettings', "Unable to create '{0}' ({1}).", this.labelService.getUriLabel(file, { relative: true }), error)); - }); - } -} \ No newline at end of file diff --git a/src/vs/workbench/parts/markers/electron-browser/markers.ts b/src/vs/workbench/parts/markers/electron-browser/markers.ts deleted file mode 100644 index 0124b88aac..0000000000 --- a/src/vs/workbench/parts/markers/electron-browser/markers.ts +++ /dev/null @@ -1,195 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { MarkersModel, compareMarkersByUri, Marker } from './markersModel'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IMarkerService, MarkerSeverity, IMarker, IMarkerData } from 'vs/platform/markers/common/markers'; -import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity'; -import { localize } from 'vs/nls'; -import Constants from './constants'; -import { URI } from 'vs/base/common/uri'; -import { groupBy } from 'vs/base/common/arrays'; -import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; -import { IAction, Action } from 'vs/base/common/actions'; -import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands'; -import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; -import { ICommandService } from 'vs/platform/commands/common/commands'; -import { IEditorService, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { CodeAction } from 'vs/editor/common/modes'; -import { Range } from 'vs/editor/common/core/range'; -import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; -import { CodeActionKind } from 'vs/editor/contrib/codeAction/codeActionTrigger'; -import { timeout, CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; - -export const IMarkersWorkbenchService = createDecorator('markersWorkbenchService'); - -export interface IFilter { - filterText: string; - useFilesExclude: boolean; -} - -export interface IMarkersWorkbenchService { - _serviceBrand: any; - readonly markersModel: MarkersModel; - hasQuickFixes(marker: Marker): Promise; - getQuickFixActions(marker: Marker): Promise; -} - -export class MarkersWorkbenchService extends Disposable implements IMarkersWorkbenchService { - _serviceBrand: any; - - readonly markersModel: MarkersModel; - - private readonly allFixesCache: Map> = new Map>(); - private readonly codeActionsPromises: Map>> = new Map>>(); - private readonly codeActions: Map> = new Map>(); - - constructor( - @IMarkerService private readonly markerService: IMarkerService, - @IInstantiationService instantiationService: IInstantiationService, - @IBulkEditService private readonly bulkEditService: IBulkEditService, - @ICommandService private readonly commandService: ICommandService, - @IEditorService private readonly editorService: IEditorService, - @IModelService private readonly modelService: IModelService - ) { - super(); - this.markersModel = this._register(instantiationService.createInstance(MarkersModel, this.readMarkers())); - - for (const group of groupBy(this.readMarkers(), compareMarkersByUri)) { - this.markersModel.setResourceMarkers(group[0].resource, group); - } - - this._register(markerService.onMarkerChanged(resources => this.onMarkerChanged(resources))); - } - - private onMarkerChanged(resources: URI[]): void { - for (const resource of resources) { - const allFixes = this.allFixesCache.get(resource.toString()); - if (allFixes) { - allFixes.cancel(); - this.allFixesCache.delete(resource.toString()); - } - const codeActions = this.codeActionsPromises.get(resource.toString()); - if (codeActions) { - codeActions.forEach(promise => promise.cancel()); - this.codeActionsPromises.delete(resource.toString()); - } - this.codeActions.delete(resource.toString()); - this.markersModel.setResourceMarkers(resource, this.readMarkers(resource)); - } - } - - private readMarkers(resource?: URI): IMarker[] { - return this.markerService.read({ resource, severities: MarkerSeverity.Error | MarkerSeverity.Warning | MarkerSeverity.Info }); - } - - getQuickFixActions(marker: Marker): Promise { - const markerKey = IMarkerData.makeKey(marker.marker); - let codeActionsPerMarker = this.codeActions.get(marker.resource.toString()); - if (!codeActionsPerMarker) { - codeActionsPerMarker = new Map(); - this.codeActions.set(marker.resource.toString(), codeActionsPerMarker); - } - const codeActions = codeActionsPerMarker.get(markerKey); - if (codeActions) { - return Promise.resolve(this.toActions(codeActions, marker)); - } else { - let codeActionsPromisesPerMarker = this.codeActionsPromises.get(marker.resource.toString()); - if (!codeActionsPromisesPerMarker) { - codeActionsPromisesPerMarker = new Map>(); - this.codeActionsPromises.set(marker.resource.toString(), codeActionsPromisesPerMarker); - } - if (!codeActionsPromisesPerMarker.has(markerKey)) { - const codeActionsPromise = this.getFixes(marker); - codeActionsPromisesPerMarker.set(markerKey, codeActionsPromise); - codeActionsPromise.then(codeActions => codeActionsPerMarker!.set(markerKey, codeActions)); - } - // Wait for 100ms for code actions fetching. - return timeout(100).then(() => this.toActions(codeActionsPerMarker!.get(markerKey) || [], marker)); - } - } - - private toActions(codeActions: CodeAction[], marker: Marker): IAction[] { - return codeActions.map(codeAction => new Action( - codeAction.command ? codeAction.command.id : codeAction.title, - codeAction.title, - undefined, - true, - () => { - return this.openFileAtMarker(marker) - .then(() => applyCodeAction(codeAction, this.bulkEditService, this.commandService)); - })); - } - - async hasQuickFixes(marker: Marker): Promise { - if (!this.modelService.getModel(marker.resource)) { - // Return early, If the model is not yet created - return false; - } - let allFixesPromise = this.allFixesCache.get(marker.resource.toString()); - if (!allFixesPromise) { - allFixesPromise = this._getFixes(marker.resource); - this.allFixesCache.set(marker.resource.toString(), allFixesPromise); - } - const allFixes = await allFixesPromise; - if (allFixes.length) { - const markerKey = IMarkerData.makeKey(marker.marker); - for (const fix of allFixes) { - if (fix.diagnostics && fix.diagnostics.some(d => IMarkerData.makeKey(d) === markerKey)) { - return true; - } - } - } - return false; - } - - private openFileAtMarker(element: Marker): Promise { - const { resource, selection } = { resource: element.resource, selection: element.range }; - return this.editorService.openEditor({ - resource, - options: { - selection, - preserveFocus: true, - pinned: false, - revealIfVisible: true - }, - }, ACTIVE_GROUP).then(() => undefined); - } - - private getFixes(marker: Marker): CancelablePromise { - return this._getFixes(marker.resource, new Range(marker.range.startLineNumber, marker.range.startColumn, marker.range.endLineNumber, marker.range.endColumn)); - } - - private _getFixes(uri: URI, range?: Range): CancelablePromise { - return createCancelablePromise(cancellationToken => { - const model = this.modelService.getModel(uri); - if (model) { - return getCodeActions(model, range ? range : model.getFullModelRange(), { type: 'manual', filter: { kind: CodeActionKind.QuickFix } }, cancellationToken); - } - return Promise.resolve([]); - }); - } - -} - -export class ActivityUpdater extends Disposable implements IWorkbenchContribution { - - constructor( - @IActivityService private readonly activityService: IActivityService, - @IMarkersWorkbenchService private readonly markersWorkbenchService: IMarkersWorkbenchService - ) { - super(); - this._register(this.markersWorkbenchService.markersModel.onDidChange(() => this.updateBadge())); - this.updateBadge(); - } - - private updateBadge(): void { - const total = this.markersWorkbenchService.markersModel.resourceMarkers.reduce((r, rm) => r + rm.markers.length, 0); - const message = localize('totalProblems', 'Total {0} Problems', total); - this.activityService.showActivity(Constants.MARKERS_PANEL_ID, new NumberBadge(total, () => message)); - } -} \ No newline at end of file diff --git a/src/vs/workbench/parts/output/electron-browser/outputServices.ts b/src/vs/workbench/parts/output/electron-browser/outputServices.ts deleted file mode 100644 index 419d10ff41..0000000000 --- a/src/vs/workbench/parts/output/electron-browser/outputServices.ts +++ /dev/null @@ -1,806 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import * as paths from 'vs/base/common/paths'; -import * as strings from 'vs/base/common/strings'; -import * as extfs from 'vs/base/node/extfs'; -import { Event, Emitter } from 'vs/base/common/event'; -import { URI } from 'vs/base/common/uri'; -import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorOptions } from 'vs/workbench/common/editor'; -import { IOutputChannelDescriptor, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, OUTPUT_SCHEME, OUTPUT_MIME, LOG_SCHEME, LOG_MIME, CONTEXT_ACTIVE_LOG_OUTPUT, MAX_OUTPUT_LENGTH } from 'vs/workbench/parts/output/common/output'; -import { OutputPanel } from 'vs/workbench/parts/output/browser/outputPanel'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { OutputLinkProvider } from 'vs/workbench/parts/output/common/outputLinkProvider'; -import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; -import { ITextModel } from 'vs/editor/common/model'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { RunOnceScheduler, ThrottledDelayer } from 'vs/base/common/async'; -import { EditOperation } from 'vs/editor/common/core/editOperation'; -import { Position } from 'vs/editor/common/core/position'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IPanel } from 'vs/workbench/common/panel'; -import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { toLocalISOString } from 'vs/base/common/date'; -import { IWindowService } from 'vs/platform/windows/common/windows'; -import { ILogService } from 'vs/platform/log/common/log'; -import { binarySearch } from 'vs/base/common/arrays'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { OutputAppender } from 'vs/platform/output/node/outputAppender'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { isNumber } from 'vs/base/common/types'; - -const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel'; - -let watchingOutputDir = false; -let callbacks: ((eventType: string, fileName: string) => void)[] = []; -function watchOutputDirectory(outputDir: string, logService: ILogService, onChange: (eventType: string, fileName: string) => void): IDisposable { - callbacks.push(onChange); - if (!watchingOutputDir) { - const watcherDisposable = extfs.watch(outputDir, (eventType, fileName) => { - for (const callback of callbacks) { - callback(eventType, fileName); - } - }, (error: string) => { - logService.error(error); - }); - watchingOutputDir = true; - return toDisposable(() => { - callbacks = []; - watcherDisposable.dispose(); - }); - } - return toDisposable(() => { }); -} - -interface OutputChannel extends IOutputChannel { - readonly file: URI; - readonly onDidAppendedContent: Event; - readonly onDispose: Event; - loadModel(): Promise; -} - -abstract class AbstractFileOutputChannel extends Disposable implements OutputChannel { - - scrollLock: boolean = false; - - protected _onDidAppendedContent = new Emitter(); - readonly onDidAppendedContent: Event = this._onDidAppendedContent.event; - - protected _onDispose = new Emitter(); - readonly onDispose: Event = this._onDispose.event; - - private readonly mimeType: string; - protected modelUpdater: RunOnceScheduler; - protected model: ITextModel; - readonly file: URI; - - protected startOffset: number = 0; - protected endOffset: number = 0; - - constructor( - readonly outputChannelDescriptor: IOutputChannelDescriptor, - private readonly modelUri: URI, - protected fileService: IFileService, - protected modelService: IModelService, - protected modeService: IModeService, - ) { - super(); - this.mimeType = outputChannelDescriptor.log ? LOG_MIME : OUTPUT_MIME; - this.file = this.outputChannelDescriptor.file; - this.modelUpdater = new RunOnceScheduler(() => this.updateModel(), 300); - this._register(toDisposable(() => this.modelUpdater.cancel())); - } - - get id(): string { - return this.outputChannelDescriptor.id; - } - - get label(): string { - return this.outputChannelDescriptor.label; - } - - clear(till?: number): void { - if (this.modelUpdater.isScheduled()) { - this.modelUpdater.cancel(); - this.onUpdateModelCancelled(); - } - if (this.model) { - this.model.setValue(''); - } - this.endOffset = isNumber(till) ? till : this.endOffset; - this.startOffset = this.endOffset; - } - - update(): void { } - - protected createModel(content: string): ITextModel { - if (this.model) { - this.model.setValue(content); - } else { - this.model = this.modelService.createModel(content, this.modeService.create(this.mimeType), this.modelUri); - this.onModelCreated(this.model); - const disposables: IDisposable[] = []; - disposables.push(this.model.onWillDispose(() => { - this.onModelWillDispose(this.model); - this.model = null; - dispose(disposables); - })); - } - return this.model; - } - - appendToModel(content: string): void { - if (this.model && content) { - const lastLine = this.model.getLineCount(); - const lastLineMaxColumn = this.model.getLineMaxColumn(lastLine); - this.model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), content)]); - this._onDidAppendedContent.fire(); - } - } - - abstract loadModel(): Promise; - abstract append(message: string); - - protected onModelCreated(model: ITextModel) { } - protected onModelWillDispose(model: ITextModel) { } - protected onUpdateModelCancelled() { } - protected updateModel() { } - - dispose(): void { - this._onDispose.fire(); - super.dispose(); - } -} - -/** - * An output channel that stores appended messages in a backup file. - */ -class OutputChannelBackedByFile extends AbstractFileOutputChannel implements OutputChannel { - - private appender: OutputAppender; - private appendedMessage = ''; - private loadingFromFileInProgress: boolean = false; - private resettingDelayer: ThrottledDelayer; - private readonly rotatingFilePath: string; - - constructor( - outputChannelDescriptor: IOutputChannelDescriptor, - outputDir: string, - modelUri: URI, - @IFileService fileService: IFileService, - @IModelService modelService: IModelService, - @IModeService modeService: IModeService, - @ILogService logService: ILogService - ) { - super({ ...outputChannelDescriptor, file: URI.file(paths.join(outputDir, `${outputChannelDescriptor.id}.log`)) }, modelUri, fileService, modelService, modeService); - - // Use one rotating file to check for main file reset - this.appender = new OutputAppender(this.id, this.file.fsPath); - this.rotatingFilePath = `${outputChannelDescriptor.id}.1.log`; - this._register(watchOutputDirectory(paths.dirname(this.file.fsPath), logService, (eventType, file) => this.onFileChangedInOutputDirector(eventType, file))); - - this.resettingDelayer = new ThrottledDelayer(50); - } - - append(message: string): void { - // update end offset always as message is read - this.endOffset = this.endOffset + Buffer.from(message).byteLength; - if (this.loadingFromFileInProgress) { - this.appendedMessage += message; - } else { - this.write(message); - if (this.model) { - this.appendedMessage += message; - if (!this.modelUpdater.isScheduled()) { - this.modelUpdater.schedule(); - } - } - } - } - - clear(till?: number): void { - super.clear(till); - this.appendedMessage = ''; - } - - loadModel(): Promise { - this.loadingFromFileInProgress = true; - if (this.modelUpdater.isScheduled()) { - this.modelUpdater.cancel(); - } - this.appendedMessage = ''; - return this.loadFile() - .then(content => { - if (this.endOffset !== this.startOffset + Buffer.from(content).byteLength) { - // Queue content is not written into the file - // Flush it and load file again - this.flush(); - return this.loadFile(); - } - return content; - }) - .then(content => { - if (this.appendedMessage) { - this.write(this.appendedMessage); - this.appendedMessage = ''; - } - this.loadingFromFileInProgress = false; - return this.createModel(content); - }); - } - - private resetModel(): Promise { - this.startOffset = 0; - this.endOffset = 0; - if (this.model) { - return this.loadModel().then(() => undefined); - } - return Promise.resolve(undefined); - } - - private loadFile(): Promise { - return this.fileService.resolveContent(this.file, { position: this.startOffset, encoding: 'utf8' }) - .then(content => this.appendedMessage ? content.value + this.appendedMessage : content.value); - } - - protected updateModel(): void { - if (this.model && this.appendedMessage) { - this.appendToModel(this.appendedMessage); - this.appendedMessage = ''; - } - } - - private onFileChangedInOutputDirector(eventType: string, fileName: string): void { - // Check if rotating file has changed. It changes only when the main file exceeds its limit. - if (this.rotatingFilePath === fileName) { - this.resettingDelayer.trigger(() => this.resetModel()); - } - } - - private write(content: string): void { - this.appender.append(content); - } - - private flush(): void { - this.appender.flush(); - } -} - -class OutputFileListener extends Disposable { - - private readonly _onDidContentChange = new Emitter(); - readonly onDidContentChange: Event = this._onDidContentChange.event; - - private watching: boolean = false; - private syncDelayer: ThrottledDelayer; - private etag: string; - - constructor( - private readonly file: URI, - private readonly fileService: IFileService - ) { - super(); - this.syncDelayer = new ThrottledDelayer(500); - } - - watch(eTag: string): void { - if (!this.watching) { - this.etag = eTag; - this.poll(); - this.watching = true; - } - } - - private poll(): void { - const loop = () => this.doWatch().then(() => this.poll()); - this.syncDelayer.trigger(loop); - } - - private doWatch(): Promise { - return this.fileService.resolveFile(this.file) - .then(stat => { - if (stat.etag !== this.etag) { - this.etag = stat.etag; - this._onDidContentChange.fire(stat.size); - } - }); - } - - unwatch(): void { - if (this.watching) { - this.syncDelayer.cancel(); - this.watching = false; - } - } - - dispose(): void { - this.unwatch(); - super.dispose(); - } -} - -/** - * An output channel driven by a file and does not support appending messages. - */ -class FileOutputChannel extends AbstractFileOutputChannel implements OutputChannel { - - private readonly fileHandler: OutputFileListener; - - private updateInProgress: boolean = false; - private etag: string = ''; - private loadModelPromise: Promise = Promise.resolve(); - - constructor( - outputChannelDescriptor: IOutputChannelDescriptor, - modelUri: URI, - @IFileService fileService: IFileService, - @IModelService modelService: IModelService, - @IModeService modeService: IModeService - ) { - super(outputChannelDescriptor, modelUri, fileService, modelService, modeService); - - this.fileHandler = this._register(new OutputFileListener(this.file, this.fileService)); - this._register(this.fileHandler.onDidContentChange(size => this.update(size))); - this._register(toDisposable(() => this.fileHandler.unwatch())); - } - - loadModel(): Promise { - this.loadModelPromise = this.fileService.resolveContent(this.file, { position: this.startOffset, encoding: 'utf8' }) - .then(content => { - this.endOffset = this.startOffset + Buffer.from(content.value).byteLength; - this.etag = content.etag; - return this.createModel(content.value); - }); - return this.loadModelPromise; - } - - clear(till?: number): void { - this.loadModelPromise.then(() => { - super.clear(till); - this.update(); - }); - } - - append(message: string): void { - throw new Error('Not supported'); - } - - protected updateModel(): void { - if (this.model) { - this.fileService.resolveContent(this.file, { position: this.endOffset, encoding: 'utf8' }) - .then(content => { - this.etag = content.etag; - if (content.value) { - this.endOffset = this.endOffset + Buffer.from(content.value).byteLength; - this.appendToModel(content.value); - } - this.updateInProgress = false; - }, () => this.updateInProgress = false); - } else { - this.updateInProgress = false; - } - } - - protected onModelCreated(model: ITextModel): void { - this.fileHandler.watch(this.etag); - } - - protected onModelWillDispose(model: ITextModel): void { - this.fileHandler.unwatch(); - } - - protected onUpdateModelCancelled(): void { - this.updateInProgress = false; - } - - update(size?: number): void { - if (!this.updateInProgress) { - this.updateInProgress = true; - if (isNumber(size) && this.endOffset > size) { // Reset - Content is removed - this.startOffset = this.endOffset = 0; - this.model.setValue(''); - } - this.modelUpdater.schedule(); - } - } -} - -export class OutputService extends Disposable implements IOutputService, ITextModelContentProvider { - - public _serviceBrand: any; - - private channels: Map = new Map(); - private activeChannelIdInStorage: string; - private activeChannel: IOutputChannel; - private readonly outputDir: string; - - private readonly _onActiveOutputChannel = new Emitter(); - readonly onActiveOutputChannel: Event = this._onActiveOutputChannel.event; - - private _outputPanel: OutputPanel; - - constructor( - @IStorageService private readonly storageService: IStorageService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IPanelService private readonly panelService: IPanelService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @ITextModelService textModelResolverService: ITextModelService, - @IEnvironmentService environmentService: IEnvironmentService, - @IWindowService windowService: IWindowService, - @ILogService private readonly logService: ILogService, - @ITelemetryService private readonly telemetryService: ITelemetryService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IContextKeyService private readonly contextKeyService: IContextKeyService, - ) { - super(); - this.activeChannelIdInStorage = this.storageService.get(OUTPUT_ACTIVE_CHANNEL_KEY, StorageScope.WORKSPACE, null); - this.outputDir = paths.join(environmentService.logsPath, `output_${windowService.getCurrentWindowId()}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); - - // Register as text model content provider for output - textModelResolverService.registerTextModelContentProvider(OUTPUT_SCHEME, this); - instantiationService.createInstance(OutputLinkProvider); - - // Create output channels for already registered channels - const registry = Registry.as(Extensions.OutputChannels); - for (const channelIdentifier of registry.getChannels()) { - this.onDidRegisterChannel(channelIdentifier.id); - } - this._register(registry.onDidRegisterChannel(this.onDidRegisterChannel, this)); - - this._register(panelService.onDidPanelOpen(({ panel, focus }) => this.onDidPanelOpen(panel, !focus), this)); - this._register(panelService.onDidPanelClose(this.onDidPanelClose, this)); - - // Set active channel to first channel if not set - if (!this.activeChannel) { - const channels = this.getChannelDescriptors(); - this.activeChannel = channels && channels.length > 0 ? this.getChannel(channels[0].id) : null; - } - - this._register(this.lifecycleService.onShutdown(() => this.dispose())); - this._register(this.storageService.onWillSaveState(() => this.saveState())); - } - - provideTextContent(resource: URI): Promise { - const channel = this.getChannel(resource.path); - if (channel) { - return channel.loadModel(); - } - return null; - } - - showChannel(id: string, preserveFocus?: boolean): Promise { - const channel = this.getChannel(id); - if (!channel || this.isChannelShown(channel)) { - if (this._outputPanel && !preserveFocus) { - this._outputPanel.focus(); - } - return Promise.resolve(undefined); - } - - this.activeChannel = channel; - let promise: Promise; - if (this.isPanelShown()) { - promise = this.doShowChannel(channel, preserveFocus); - } else { - this.panelService.openPanel(OUTPUT_PANEL_ID); - promise = this.doShowChannel(this.activeChannel, preserveFocus); - } - return promise.then(() => this._onActiveOutputChannel.fire(id)); - } - - getChannel(id: string): IOutputChannel { - return this.channels.get(id); - } - - getChannelDescriptors(): IOutputChannelDescriptor[] { - return Registry.as(Extensions.OutputChannels).getChannels(); - } - - getActiveChannel(): IOutputChannel { - return this.activeChannel; - } - - private onDidRegisterChannel(channelId: string): void { - const channel = this.createChannel(channelId); - this.channels.set(channelId, channel); - if (this.activeChannelIdInStorage === channelId) { - this.activeChannel = channel; - this.onDidPanelOpen(this.panelService.getActivePanel(), true) - .then(() => this._onActiveOutputChannel.fire(channelId)); - } - } - - private onDidPanelOpen(panel: IPanel, preserveFocus: boolean): Promise { - if (panel && panel.getId() === OUTPUT_PANEL_ID) { - this._outputPanel = this.panelService.getActivePanel(); - if (this.activeChannel) { - return this.doShowChannel(this.activeChannel, preserveFocus); - } - } - return Promise.resolve(undefined); - } - - private onDidPanelClose(panel: IPanel): void { - if (this._outputPanel && panel.getId() === OUTPUT_PANEL_ID) { - CONTEXT_ACTIVE_LOG_OUTPUT.bindTo(this.contextKeyService).set(false); - this._outputPanel.clearInput(); - } - } - - private createChannel(id: string): OutputChannel { - const channelDisposables: IDisposable[] = []; - const channel = this.instantiateChannel(id); - channel.onDidAppendedContent(() => { - if (!channel.scrollLock) { - const panel = this.panelService.getActivePanel(); - if (panel && panel.getId() === OUTPUT_PANEL_ID && this.isChannelShown(channel)) { - let outputPanel = panel; - outputPanel.revealLastLine(); - } - } - }, channelDisposables); - channel.onDispose(() => { - if (this.activeChannel === channel) { - const channels = this.getChannelDescriptors(); - const channel = channels.length ? this.getChannel(channels[0].id) : null; - if (channel && this.isPanelShown()) { - this.showChannel(channel.id, true); - } else { - this.activeChannel = channel; - this._onActiveOutputChannel.fire(channel ? channel.id : undefined); - } - } - Registry.as(Extensions.OutputChannels).removeChannel(id); - dispose(channelDisposables); - }, channelDisposables); - - return channel; - } - - private instantiateChannel(id: string): OutputChannel { - const channelData = Registry.as(Extensions.OutputChannels).getChannel(id); - if (!channelData) { - this.logService.error(`Channel '${id}' is not registered yet`); - throw new Error(`Channel '${id}' is not registered yet`); - } - - const uri = URI.from({ scheme: OUTPUT_SCHEME, path: id }); - if (channelData && channelData.file) { - return this.instantiationService.createInstance(FileOutputChannel, channelData, uri); - } - try { - return this.instantiationService.createInstance(OutputChannelBackedByFile, { id, label: channelData ? channelData.label : '' }, this.outputDir, uri); - } catch (e) { - // Do not crash if spdlog rotating logger cannot be loaded (workaround for https://github.com/Microsoft/vscode/issues/47883) - this.logService.error(e); - /* __GDPR__ - "output.channel.creation.error" : {} - */ - this.telemetryService.publicLog('output.channel.creation.error'); - return this.instantiationService.createInstance(BufferredOutputChannel, { id, label: channelData ? channelData.label : '' }); - } - } - - private doShowChannel(channel: IOutputChannel, preserveFocus: boolean): Promise { - if (this._outputPanel) { - CONTEXT_ACTIVE_LOG_OUTPUT.bindTo(this.contextKeyService).set(channel instanceof FileOutputChannel && channel.outputChannelDescriptor.log); - return this._outputPanel.setInput(this.createInput(channel), EditorOptions.create({ preserveFocus }), CancellationToken.None) - .then(() => { - if (!preserveFocus) { - this._outputPanel.focus(); - } - }); - } - return Promise.resolve(undefined); - } - - private isChannelShown(channel: IOutputChannel): boolean { - return this.isPanelShown() && this.activeChannel === channel; - } - - private isPanelShown(): boolean { - const panel = this.panelService.getActivePanel(); - return panel && panel.getId() === OUTPUT_PANEL_ID; - } - - private createInput(channel: IOutputChannel): ResourceEditorInput { - const resource = URI.from({ scheme: OUTPUT_SCHEME, path: channel.id }); - return this.instantiationService.createInstance(ResourceEditorInput, nls.localize('output', "{0} - Output", channel.label), nls.localize('channel', "Output channel for '{0}'", channel.label), resource); - } - - private saveState(): void { - if (this.activeChannel) { - this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannel.id, StorageScope.WORKSPACE); - } - } -} - -export class LogContentProvider { - - private channels: Map = new Map(); - - constructor( - @IOutputService private readonly outputService: IOutputService, - @IInstantiationService private readonly instantiationService: IInstantiationService - ) { - } - - provideTextContent(resource: URI): Promise { - if (resource.scheme === LOG_SCHEME) { - let channel = this.getChannel(resource); - if (channel) { - return channel.loadModel(); - } - } - return null; - } - - private getChannel(resource: URI): OutputChannel { - const channelId = resource.path; - let channel = this.channels.get(channelId); - if (!channel) { - const channelDisposables: IDisposable[] = []; - const outputChannelDescriptor = this.outputService.getChannelDescriptors().filter(({ id }) => id === channelId)[0]; - if (outputChannelDescriptor && outputChannelDescriptor.file) { - channel = this.instantiationService.createInstance(FileOutputChannel, outputChannelDescriptor, resource); - channel.onDispose(() => dispose(channelDisposables), channelDisposables); - this.channels.set(channelId, channel); - } - } - return channel; - } -} -// Remove this channel when https://github.com/Microsoft/vscode/issues/47883 is fixed -class BufferredOutputChannel extends Disposable implements OutputChannel { - - readonly id: string; - readonly label: string; - readonly file: URI | null = null; - scrollLock: boolean = false; - - protected _onDidAppendedContent = new Emitter(); - readonly onDidAppendedContent: Event = this._onDidAppendedContent.event; - - private readonly _onDispose = new Emitter(); - readonly onDispose: Event = this._onDispose.event; - - private modelUpdater: RunOnceScheduler; - private model: ITextModel; - private readonly bufferredContent: BufferedContent; - private lastReadId: number = undefined; - - constructor( - protected readonly outputChannelIdentifier: IOutputChannelDescriptor, - @IModelService private readonly modelService: IModelService, - @IModeService private readonly modeService: IModeService - ) { - super(); - - this.id = outputChannelIdentifier.id; - this.label = outputChannelIdentifier.label; - - this.modelUpdater = new RunOnceScheduler(() => this.updateModel(), 300); - this._register(toDisposable(() => this.modelUpdater.cancel())); - - this.bufferredContent = new BufferedContent(); - this._register(toDisposable(() => this.bufferredContent.clear())); - } - - append(output: string) { - this.bufferredContent.append(output); - if (!this.modelUpdater.isScheduled()) { - this.modelUpdater.schedule(); - } - } - - update(): void { } - - clear(): void { - if (this.modelUpdater.isScheduled()) { - this.modelUpdater.cancel(); - } - if (this.model) { - this.model.setValue(''); - } - this.bufferredContent.clear(); - this.lastReadId = undefined; - } - - loadModel(): Promise { - const { value, id } = this.bufferredContent.getDelta(this.lastReadId); - if (this.model) { - this.model.setValue(value); - } else { - this.model = this.createModel(value); - } - this.lastReadId = id; - return Promise.resolve(this.model); - } - - private createModel(content: string): ITextModel { - const model = this.modelService.createModel(content, this.modeService.create(OUTPUT_MIME), URI.from({ scheme: OUTPUT_SCHEME, path: this.id })); - const disposables: IDisposable[] = []; - disposables.push(model.onWillDispose(() => { - this.model = null; - dispose(disposables); - })); - return model; - } - - private updateModel(): void { - if (this.model) { - const { value, id } = this.bufferredContent.getDelta(this.lastReadId); - this.lastReadId = id; - const lastLine = this.model.getLineCount(); - const lastLineMaxColumn = this.model.getLineMaxColumn(lastLine); - this.model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), value)]); - this._onDidAppendedContent.fire(); - } - } - - dispose(): void { - this._onDispose.fire(); - super.dispose(); - } -} - -class BufferedContent { - - private data: string[] = []; - private dataIds: number[] = []; - private idPool = 0; - private length = 0; - - public append(content: string): void { - this.data.push(content); - this.dataIds.push(++this.idPool); - this.length += content.length; - this.trim(); - } - - public clear(): void { - this.data.length = 0; - this.dataIds.length = 0; - this.length = 0; - } - - private trim(): void { - if (this.length < MAX_OUTPUT_LENGTH * 1.2) { - return; - } - - while (this.length > MAX_OUTPUT_LENGTH) { - this.dataIds.shift(); - const removed = this.data.shift(); - this.length -= removed.length; - } - } - - public getDelta(previousId?: number): { value: string, id: number } { - let idx = -1; - if (previousId !== undefined) { - idx = binarySearch(this.dataIds, previousId, (a, b) => a - b); - } - - const id = this.idPool; - if (idx >= 0) { - const value = strings.removeAnsiEscapeCodes(this.data.slice(idx + 1).join('')); - return { value, id }; - } else { - const value = strings.removeAnsiEscapeCodes(this.data.join('')); - return { value, id }; - } - } -} diff --git a/src/vs/workbench/parts/scm/common/scm.ts b/src/vs/workbench/parts/scm/common/scm.ts deleted file mode 100644 index 3bc70038e1..0000000000 --- a/src/vs/workbench/parts/scm/common/scm.ts +++ /dev/null @@ -1,10 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Registry } from 'vs/platform/registry/common/platform'; -import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; - -export const VIEWLET_ID = 'workbench.view.scm'; -export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID); \ No newline at end of file diff --git a/src/vs/workbench/parts/terminal/browser/terminalFindWidget.css b/src/vs/workbench/parts/terminal/browser/terminalFindWidget.css deleted file mode 100644 index b4f07c7ea1..0000000000 --- a/src/vs/workbench/parts/terminal/browser/terminalFindWidget.css +++ /dev/null @@ -1,9 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-workbench .simple-find-part .monaco-inputbox > .wrapper > .input { - width: 100% !important; - padding-right: 66px; -} \ No newline at end of file diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts deleted file mode 100644 index eb940fed79..0000000000 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ /dev/null @@ -1,314 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import * as pfs from 'vs/base/node/pfs'; -import * as platform from 'vs/base/common/platform'; -import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; -import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { ITerminalInstance, ITerminalService, IShellLaunchConfig, ITerminalConfigHelper, NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, TERMINAL_PANEL_ID, ITerminalProcessExtHostProxy } from 'vs/workbench/parts/terminal/common/terminal'; -import { TerminalService as AbstractTerminalService } from 'vs/workbench/parts/terminal/common/terminalService'; -import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; -import Severity from 'vs/base/common/severity'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { getDefaultShell } from 'vs/workbench/parts/terminal/node/terminal'; -import { TerminalPanel } from 'vs/workbench/parts/terminal/electron-browser/terminalPanel'; -import { TerminalTab } from 'vs/workbench/parts/terminal/browser/terminalTab'; -import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { ipcRenderer as ipc } from 'electron'; -import { IOpenFileRequest, IWindowService } from 'vs/platform/windows/common/windows'; -import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance'; -import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { URI } from 'vs/base/common/uri'; -import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput'; -import { coalesce } from 'vs/base/common/arrays'; - -export class TerminalService extends AbstractTerminalService implements ITerminalService { - private _configHelper: TerminalConfigHelper; - public get configHelper(): ITerminalConfigHelper { return this._configHelper; } - - protected _terminalTabs: TerminalTab[]; - protected get _terminalInstances(): ITerminalInstance[] { - return this._terminalTabs.reduce((p, c) => p.concat(c.terminalInstances), []); - } - - constructor( - @IContextKeyService contextKeyService: IContextKeyService, - @IPanelService panelService: IPanelService, - @IPartService partService: IPartService, - @IStorageService storageService: IStorageService, - @ILifecycleService lifecycleService: ILifecycleService, - @IConfigurationService private readonly _configurationService: IConfigurationService, - @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IQuickInputService private readonly _quickInputService: IQuickInputService, - @INotificationService private readonly _notificationService: INotificationService, - @IDialogService private readonly _dialogService: IDialogService, - @IExtensionService private readonly _extensionService: IExtensionService, - @IWindowService private readonly _windowService: IWindowService, - ) { - super(contextKeyService, panelService, partService, lifecycleService, storageService); - - this._terminalTabs = []; - this._configHelper = this._instantiationService.createInstance(TerminalConfigHelper); - ipc.on('vscode:openFiles', (_event: any, request: IOpenFileRequest) => { - // if the request to open files is coming in from the integrated terminal (identified though - // the termProgram variable) and we are instructed to wait for editors close, wait for the - // marker file to get deleted and then focus back to the integrated terminal. - if (request.termProgram === 'vscode' && request.filesToWait) { - pfs.whenDeleted(request.filesToWait.waitMarkerFilePath).then(() => { - if (this.terminalInstances.length > 0) { - this.getActiveInstance().focus(); - } - }); - } - }); - ipc.on('vscode:osResume', () => { - const activeTab = this.getActiveTab(); - if (!activeTab) { - return; - } - activeTab.terminalInstances.forEach(instance => instance.forceRedraw()); - }); - } - - public createTerminal(shell: IShellLaunchConfig = {}, wasNewTerminalAction?: boolean): ITerminalInstance { - const terminalTab = this._instantiationService.createInstance(TerminalTab, - this._terminalFocusContextKey, - this._configHelper, - this._terminalContainer, - shell); - this._terminalTabs.push(terminalTab); - const instance = terminalTab.terminalInstances[0]; - terminalTab.addDisposable(terminalTab.onDisposed(this._onTabDisposed.fire, this._onTabDisposed)); - terminalTab.addDisposable(terminalTab.onInstancesChanged(this._onInstancesChanged.fire, this._onInstancesChanged)); - this._initInstanceListeners(instance); - if (this.terminalInstances.length === 1) { - // It's the first instance so it should be made active automatically - this.setActiveInstanceByIndex(0); - } - this._onInstancesChanged.fire(); - this._suggestShellChange(wasNewTerminalAction); - return instance; - } - - public createTerminalRenderer(name: string): ITerminalInstance { - return this.createTerminal({ name, isRendererOnly: true }); - } - - public createInstance(terminalFocusContextKey: IContextKey, configHelper: ITerminalConfigHelper, container: HTMLElement | undefined, shellLaunchConfig: IShellLaunchConfig, doCreateProcess: boolean): ITerminalInstance { - const instance = this._instantiationService.createInstance(TerminalInstance, terminalFocusContextKey, configHelper, container, shellLaunchConfig); - this._onInstanceCreated.fire(instance); - return instance; - } - - public requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, activeWorkspaceRootUri: URI, cols: number, rows: number): void { - // Ensure extension host is ready before requesting a process - this._extensionService.whenInstalledExtensionsRegistered().then(() => { - // TODO: MainThreadTerminalService is not ready at this point, fix this - setTimeout(() => { - this._onInstanceRequestExtHostProcess.fire({ proxy, shellLaunchConfig, activeWorkspaceRootUri, cols, rows }); - }, 500); - }); - } - - public focusFindWidget(): Promise { - return this.showPanel(false).then(() => { - const panel = this._panelService.getActivePanel() as TerminalPanel; - panel.focusFindWidget(); - this._findWidgetVisible.set(true); - }); - } - - public hideFindWidget(): void { - const panel = this._panelService.getActivePanel() as TerminalPanel; - if (panel && panel.getId() === TERMINAL_PANEL_ID) { - panel.hideFindWidget(); - this._findWidgetVisible.reset(); - panel.focus(); - } - } - - public findNext(): void { - const panel = this._panelService.getActivePanel() as TerminalPanel; - if (panel && panel.getId() === TERMINAL_PANEL_ID) { - panel.showFindWidget(); - panel.getFindWidget().find(false); - } - } - - public findPrevious(): void { - const panel = this._panelService.getActivePanel() as TerminalPanel; - if (panel && panel.getId() === TERMINAL_PANEL_ID) { - panel.showFindWidget(); - panel.getFindWidget().find(true); - } - } - - private _suggestShellChange(wasNewTerminalAction?: boolean): void { - // Only suggest on Windows since $SHELL works great for macOS/Linux - if (!platform.isWindows) { - return; - } - - if (this._windowService.getConfiguration().remoteAuthority) { - // Don't suggest if the opened workspace is remote - return; - } - - // Only suggest when the terminal instance is being created by an explicit user action to - // launch a terminal, as opposed to something like tasks, debug, panel restore, etc. - if (!wasNewTerminalAction) { - return; - } - - if (this._windowService.getConfiguration().remoteAuthority) { - // Don't suggest if the opened workspace is remote - return; - } - - // Don't suggest if the user has explicitly opted out - const neverSuggest = this._storageService.getBoolean(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, StorageScope.GLOBAL, false); - if (neverSuggest) { - return; - } - - // Never suggest if the setting is non-default already (ie. they set the setting manually) - if (this._configHelper.config.shell.windows !== getDefaultShell(platform.Platform.Windows)) { - this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true, StorageScope.GLOBAL); - return; - } - - this._notificationService.prompt( - Severity.Info, - nls.localize('terminal.integrated.chooseWindowsShellInfo', "You can change the default terminal shell by selecting the customize button."), - [{ - label: nls.localize('customize', "Customize"), - run: () => { - this.selectDefaultWindowsShell().then(shell => { - if (!shell) { - return Promise.resolve(null); - } - // Launch a new instance with the newly selected shell - const instance = this.createTerminal({ - executable: shell, - args: this._configHelper.config.shellArgs.windows - }); - if (instance) { - this.setActiveInstance(instance); - } - return Promise.resolve(null); - }); - } - }, - { - label: nls.localize('never again', "Don't Show Again"), - isSecondary: true, - run: () => this._storageService.store(NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, true, StorageScope.GLOBAL) - }] - ); - } - - public selectDefaultWindowsShell(): Promise { - return this._detectWindowsShells().then(shells => { - const options: IPickOptions = { - placeHolder: nls.localize('terminal.integrated.chooseWindowsShell', "Select your preferred terminal shell, you can change this later in your settings") - }; - return this._quickInputService.pick(shells, options).then(value => { - if (!value) { - return null; - } - const shell = value.description; - return this._configurationService.updateValue('terminal.integrated.shell.windows', shell, ConfigurationTarget.USER).then(() => shell); - }); - }); - } - - private _detectWindowsShells(): Promise { - // Determine the correct System32 path. We want to point to Sysnative - // when the 32-bit version of VS Code is running on a 64-bit machine. - // The reason for this is because PowerShell's important PSReadline - // module doesn't work if this is not the case. See #27915. - const is32ProcessOn64Windows = process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432'); - const system32Path = `${process.env['windir']}\\${is32ProcessOn64Windows ? 'Sysnative' : 'System32'}`; - - let useWSLexe = false; - - if (TerminalInstance.getWindowsBuildNumber() >= 16299) { - useWSLexe = true; - } - - const expectedLocations = { - 'Command Prompt': [`${system32Path}\\cmd.exe`], - PowerShell: [`${system32Path}\\WindowsPowerShell\\v1.0\\powershell.exe`], - 'WSL Bash': [`${system32Path}\\${useWSLexe ? 'wsl.exe' : 'bash.exe'}`], - 'Git Bash': [ - `${process.env['ProgramW6432']}\\Git\\bin\\bash.exe`, - `${process.env['ProgramW6432']}\\Git\\usr\\bin\\bash.exe`, - `${process.env['ProgramFiles']}\\Git\\bin\\bash.exe`, - `${process.env['ProgramFiles']}\\Git\\usr\\bin\\bash.exe`, - `${process.env['LocalAppData']}\\Programs\\Git\\bin\\bash.exe`, - ] - }; - const promises: PromiseLike<[string, string]>[] = []; - Object.keys(expectedLocations).forEach(key => promises.push(this._validateShellPaths(key, expectedLocations[key]))); - return Promise.all(promises) - .then(coalesce) - .then(results => { - return results.map(result => { - return { - label: result[0], - description: result[1] - }; - }); - }); - } - - private _validateShellPaths(label: string, potentialPaths: string[]): Promise<[string, string]> { - const current = potentialPaths.shift(); - return pfs.fileExists(current).then(exists => { - if (!exists) { - if (potentialPaths.length === 0) { - return null; - } - return this._validateShellPaths(label, potentialPaths); - } - return [label, current] as [string, string]; - }); - } - - public getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance { - const activeInstance = this.getActiveInstance(); - return activeInstance ? activeInstance : this.createTerminal(undefined, wasNewTerminalAction); - } - - protected _showTerminalCloseConfirmation(): Promise { - let message; - if (this.terminalInstances.length === 1) { - message = nls.localize('terminalService.terminalCloseConfirmationSingular', "There is an active terminal session, do you want to kill it?"); - } else { - message = nls.localize('terminalService.terminalCloseConfirmationPlural', "There are {0} active terminal sessions, do you want to kill them?", this.terminalInstances.length); - } - - return this._dialogService.confirm({ - message, - type: 'warning', - }).then(res => !res.confirmed); - } - - protected _showNotEnoughSpaceToast(): void { - this._notificationService.info(nls.localize('terminal.minWidth', "Not enough space to split terminal.")); - } - - public setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void { - this._configHelper.panelContainer = panelContainer; - this._terminalContainer = terminalContainer; - this._terminalTabs.forEach(tab => tab.attachToElement(this._terminalContainer)); - } -} diff --git a/src/vs/workbench/parts/webview/electron-browser/baseWebviewEditor.ts b/src/vs/workbench/parts/webview/electron-browser/baseWebviewEditor.ts deleted file mode 100644 index 9be1646398..0000000000 --- a/src/vs/workbench/parts/webview/electron-browser/baseWebviewEditor.ts +++ /dev/null @@ -1,98 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Dimension } from 'vs/base/browser/dom'; -import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { WebviewElement } from './webviewElement'; -import { IStorageService } from 'vs/platform/storage/common/storage'; - -/** A context key that is set when the find widget in a webview is visible. */ -export const KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE = new RawContextKey('webviewFindWidgetVisible', false); - - -/** - * This class is only intended to be subclassed and not instantiated. - */ -export abstract class BaseWebviewEditor extends BaseEditor { - - protected _webview: WebviewElement | undefined; - protected findWidgetVisible: IContextKey; - - constructor( - id: string, - telemetryService: ITelemetryService, - themeService: IThemeService, - contextKeyService: IContextKeyService, - storageService: IStorageService - ) { - super(id, telemetryService, themeService, storageService); - if (contextKeyService) { - this.findWidgetVisible = KEYBINDING_CONTEXT_WEBVIEW_FIND_WIDGET_VISIBLE.bindTo(contextKeyService); - } - } - - public showFind() { - if (this._webview) { - this._webview.showFind(); - this.findWidgetVisible.set(true); - } - } - - public hideFind() { - this.findWidgetVisible.reset(); - if (this._webview) { - this._webview.hideFind(); - } - } - - public get isWebviewEditor() { - return true; - } - - public reload() { - this.withWebviewElement(webview => webview.reload()); - } - - public layout(dimension: Dimension): void { - this.withWebviewElement(webview => webview.layout()); - } - - public focus(): void { - this.withWebviewElement(webview => webview.focus()); - } - - public selectAll(): void { - this.withWebviewElement(webview => webview.selectAll()); - } - - public copy(): void { - this.withWebviewElement(webview => webview.copy()); - } - - public paste(): void { - this.withWebviewElement(webview => webview.paste()); - } - - public cut(): void { - this.withWebviewElement(webview => webview.cut()); - } - - public undo(): void { - this.withWebviewElement(webview => webview.undo()); - } - - public redo(): void { - this.withWebviewElement(webview => webview.redo()); - } - - private withWebviewElement(f: (element: WebviewElement) => void): void { - if (this._webview) { - f(this._webview); - } - } -} diff --git a/src/vs/workbench/parts/webview/electron-browser/webview.html b/src/vs/workbench/parts/webview/electron-browser/webview.html deleted file mode 100644 index 04e44c27fd..0000000000 --- a/src/vs/workbench/parts/webview/electron-browser/webview.html +++ /dev/null @@ -1,8 +0,0 @@ - - - - Virtual Document - - - - \ No newline at end of file diff --git a/src/vs/workbench/services/activity/browser/activityService.ts b/src/vs/workbench/services/activity/browser/activityService.ts index 445fb46cca..aedbcecc22 100644 --- a/src/vs/workbench/services/activity/browser/activityService.ts +++ b/src/vs/workbench/services/activity/browser/activityService.ts @@ -4,31 +4,27 @@ *--------------------------------------------------------------------------------------------*/ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; -import { PanelPart } from 'vs/workbench/browser/parts/panel/panelPart'; import { IActivityService, IBadge } from 'vs/workbench/services/activity/common/activity'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { IActivityBarService } from 'vs/workbench/services/activityBar/browser/activityBarService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class ActivityService implements IActivityService { public _serviceBrand: any; constructor( - private activitybarPart: ActivitybarPart, - private panelPart: PanelPart, - @IPanelService private readonly panelService: IPanelService + @IPanelService private readonly panelService: IPanelService, + @IActivityBarService private readonly activityBarService: IActivityBarService ) { } showActivity(compositeOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable { if (this.panelService.getPanels().filter(p => p.id === compositeOrActionId).length) { - return this.panelPart.showActivity(compositeOrActionId, badge, clazz); + return this.panelService.showActivity(compositeOrActionId, badge, clazz); } - return this.activitybarPart.showActivity(compositeOrActionId, badge, clazz, priority); + return this.activityBarService.showActivity(compositeOrActionId, badge, clazz, priority); } - - getPinnedViewletIds(): string[] { - return this.activitybarPart.getPinnedViewletIds(); - } - } + +registerSingleton(IActivityService, ActivityService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/activity/common/activity.ts b/src/vs/workbench/services/activity/common/activity.ts index 5d463cb2a7..b8554edef7 100644 --- a/src/vs/workbench/services/activity/common/activity.ts +++ b/src/vs/workbench/services/activity/common/activity.ts @@ -53,8 +53,7 @@ export class IconBadge extends BaseBadge { } } -export class ProgressBadge extends BaseBadge { -} +export class ProgressBadge extends BaseBadge { } export const IActivityService = createDecorator('activityService'); @@ -65,9 +64,4 @@ export interface IActivityService { * Show activity in the panel for the given panel or in the activitybar for the given viewlet or global action. */ showActivity(compositeOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable; - - /** - * Returns id of pinned viewlets following the visual order - */ - getPinnedViewletIds(): string[]; } diff --git a/src/vs/workbench/services/activityBar/browser/activityBarService.ts b/src/vs/workbench/services/activityBar/browser/activityBarService.ts new file mode 100644 index 0000000000..7146adc8c4 --- /dev/null +++ b/src/vs/workbench/services/activityBar/browser/activityBarService.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { IBadge } from 'vs/workbench/services/activity/common/activity'; +import { IDisposable } from 'vs/base/common/lifecycle'; + +export const IActivityBarService = createDecorator('activityBarService'); + +export interface IActivityBarService { + _serviceBrand: any; + + /** + * Show an activity in a viewlet. + */ + showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable; + + /** + * Returns id of pinned viewlets following the visual order. + */ + getPinnedViewletIds(): string[]; +} diff --git a/src/vs/workbench/services/backup/node/backupFileService.ts b/src/vs/workbench/services/backup/node/backupFileService.ts index c8efee3e30..1271565027 100644 --- a/src/vs/workbench/services/backup/node/backupFileService.ts +++ b/src/vs/workbench/services/backup/node/backupFileService.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as crypto from 'crypto'; import * as pfs from 'vs/base/node/pfs'; import { URI as Uri } from 'vs/base/common/uri'; @@ -14,6 +14,9 @@ import { readToMatchingString } from 'vs/base/node/stream'; import { ITextBufferFactory } from 'vs/editor/common/model'; import { createTextBufferFactoryFromStream, createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; import { keys } from 'vs/base/common/map'; +import { Schemas } from 'vs/base/common/network'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export interface IBackupFilesModel { resolve(backupRoot: string): Promise; @@ -106,6 +109,63 @@ export class BackupFilesModel implements IBackupFilesModel { export class BackupFileService implements IBackupFileService { + _serviceBrand: any; + + private impl: IBackupFileService; + + constructor( + @IWindowService windowService: IWindowService, + @IFileService fileService: IFileService + ) { + const backupWorkspacePath = windowService.getConfiguration().backupPath; + if (backupWorkspacePath) { + this.impl = new BackupFileServiceImpl(backupWorkspacePath, fileService); + } else { + this.impl = new InMemoryBackupFileService(); + } + } + + initialize(backupWorkspacePath: string): void { + if (this.impl instanceof BackupFileServiceImpl) { + this.impl.initialize(backupWorkspacePath); + } + } + + hasBackups(): Promise { + return this.impl.hasBackups(); + } + + loadBackupResource(resource: Uri): Promise { + return this.impl.loadBackupResource(resource); + } + + backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): Promise { + return this.impl.backupResource(resource, content, versionId); + } + + discardResourceBackup(resource: Uri): Promise { + return this.impl.discardResourceBackup(resource); + } + + discardAllWorkspaceBackups(): Promise { + return this.impl.discardAllWorkspaceBackups(); + } + + getWorkspaceFileBackups(): Promise { + return this.impl.getWorkspaceFileBackups(); + } + + resolveBackupContent(backup: Uri): Promise { + return this.impl.resolveBackupContent(backup); + } + + toBackupResource(resource: Uri): Uri { + return this.impl.toBackupResource(resource); + } +} + +class BackupFileServiceImpl implements IBackupFileService { + private static readonly META_MARKER = '\n'; _serviceBrand: any; @@ -169,7 +229,7 @@ export class BackupFileService implements IBackupFileService { } return this.ioOperationQueues.queueFor(backupResource).queue(() => { - const preamble = `${resource.toString()}${BackupFileService.META_MARKER}`; + const preamble = `${resource.toString()}${BackupFileServiceImpl.META_MARKER}`; // Update content with value return this.fileService.updateContent(backupResource, new BackupSnapshot(content, preamble), BACKUP_FILE_UPDATE_OPTIONS).then(() => model.add(backupResource, versionId)); @@ -201,7 +261,7 @@ export class BackupFileService implements IBackupFileService { model.get().forEach(fileBackup => { readPromises.push( - readToMatchingString(fileBackup.fsPath, BackupFileService.META_MARKER, 2000, 10000).then(Uri.parse) + readToMatchingString(fileBackup.fsPath, BackupFileServiceImpl.META_MARKER, 2000, 10000).then(Uri.parse) ); }); @@ -216,7 +276,7 @@ export class BackupFileService implements IBackupFileService { let metaFound = false; const metaPreambleFilter = (chunk: string) => { if (!metaFound && chunk) { - const metaIndex = chunk.indexOf(BackupFileService.META_MARKER); + const metaIndex = chunk.indexOf(BackupFileServiceImpl.META_MARKER); if (metaIndex === -1) { return ''; // meta not yet found, return empty string } @@ -233,11 +293,7 @@ export class BackupFileService implements IBackupFileService { } toBackupResource(resource: Uri): Uri { - return Uri.file(path.join(this.backupWorkspacePath, resource.scheme, this.hashPath(resource))); - } - - private hashPath(resource: Uri): string { - return crypto.createHash('md5').update(resource.fsPath).digest('hex'); + return Uri.file(path.join(this.backupWorkspacePath, resource.scheme, hashPath(resource))); } } @@ -257,7 +313,7 @@ export class InMemoryBackupFileService implements IBackupFileService { return Promise.resolve(backupResource); } - return Promise.resolve(); + return Promise.resolve(undefined); } backupResource(resource: Uri, content: ITextSnapshot, versionId?: number): Promise { @@ -273,7 +329,7 @@ export class InMemoryBackupFileService implements IBackupFileService { return Promise.resolve(createTextBufferFactoryFromSnapshot(snapshot)); } - return Promise.resolve(); + return Promise.resolve(undefined); } getWorkspaceFileBackups(): Promise { @@ -293,10 +349,16 @@ export class InMemoryBackupFileService implements IBackupFileService { } toBackupResource(resource: Uri): Uri { - return Uri.file(path.join(resource.scheme, this.hashPath(resource))); + return Uri.file(path.join(resource.scheme, hashPath(resource))); } +} - private hashPath(resource: Uri): string { - return crypto.createHash('md5').update(resource.fsPath).digest('hex'); - } -} \ No newline at end of file +/* + * Exported only for testing + */ +export function hashPath(resource: Uri): string { + const str = resource.scheme === Schemas.file || resource.scheme === Schemas.untitled ? resource.fsPath : resource.toString(); + return crypto.createHash('md5').update(str).digest('hex'); +} + +registerSingleton(IBackupFileService, BackupFileService); \ No newline at end of file diff --git a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts index aee992ad91..8143c0261e 100644 --- a/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/electron-browser/backupFileService.test.ts @@ -8,38 +8,57 @@ import * as platform from 'vs/base/common/platform'; import * as crypto from 'crypto'; import * as os from 'os'; import * as fs from 'fs'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { URI as Uri } from 'vs/base/common/uri'; -import { BackupFileService, BackupFilesModel } from 'vs/workbench/services/backup/node/backupFileService'; -import { FileService } from 'vs/workbench/services/files/electron-browser/fileService'; +import { BackupFileService, BackupFilesModel, hashPath } from 'vs/workbench/services/backup/node/backupFileService'; +import { FileService } from 'vs/workbench/services/files/node/fileService'; import { TextModel, createTextBufferFactory } from 'vs/editor/common/model/textModel'; -import { TestContextService, TestTextResourceConfigurationService, getRandomTestPath, TestLifecycleService, TestEnvironmentService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { TestContextService, TestTextResourceConfigurationService, TestLifecycleService, TestEnvironmentService, TestStorageService, TestWindowService } from 'vs/workbench/test/workbenchTestServices'; +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { DefaultEndOfLine } from 'vs/editor/common/model'; import { snapshotToString } from 'vs/platform/files/common/files'; import { Schemas } from 'vs/base/common/network'; +import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; const parentDir = getRandomTestPath(os.tmpdir(), 'vsctests', 'backupfileservice'); const backupHome = path.join(parentDir, 'Backups'); const workspacesJsonPath = path.join(backupHome, 'workspaces.json'); const workspaceResource = Uri.file(platform.isWindows ? 'c:\\workspace' : '/workspace'); -const workspaceBackupPath = path.join(backupHome, crypto.createHash('md5').update(workspaceResource.fsPath).digest('hex')); +const workspaceBackupPath = path.join(backupHome, hashPath(workspaceResource)); const fooFile = Uri.file(platform.isWindows ? 'c:\\Foo' : '/Foo'); const barFile = Uri.file(platform.isWindows ? 'c:\\Bar' : '/Bar'); const untitledFile = Uri.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); -const fooBackupPath = path.join(workspaceBackupPath, 'file', crypto.createHash('md5').update(fooFile.fsPath).digest('hex')); -const barBackupPath = path.join(workspaceBackupPath, 'file', crypto.createHash('md5').update(barFile.fsPath).digest('hex')); -const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', crypto.createHash('md5').update(untitledFile.fsPath).digest('hex')); +const fooBackupPath = path.join(workspaceBackupPath, 'file', hashPath(fooFile)); +const barBackupPath = path.join(workspaceBackupPath, 'file', hashPath(barFile)); +const untitledBackupPath = path.join(workspaceBackupPath, 'untitled', hashPath(untitledFile)); + +class TestBackupWindowService extends TestWindowService { + + private config: IWindowConfiguration; + + constructor(workspaceBackupPath: string) { + super(); + + this.config = Object.create(null); + this.config.backupPath = workspaceBackupPath; + } + + getConfiguration(): IWindowConfiguration { + return this.config; + } +} class TestBackupFileService extends BackupFileService { constructor(workspace: Uri, backupHome: string, workspacesJsonPath: string) { const fileService = new FileService(new TestContextService(new Workspace(workspace.fsPath, toWorkspaceFolders([{ path: workspace.fsPath }]))), TestEnvironmentService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true }); + const windowService = new TestBackupWindowService(workspaceBackupPath); - super(workspaceBackupPath, fileService); + super(windowService, fileService); } public toBackupResource(resource: Uri): Uri { @@ -65,12 +84,37 @@ suite('BackupFileService', () => { return pfs.del(backupHome, os.tmpdir()); }); + suite('hashPath', () => { + test('should correctly hash the path for untitled scheme URIs', () => { + const uri = Uri.from({ + scheme: 'untitled', + path: 'Untitled-1' + }); + const actual = hashPath(uri); + // If these hashes change people will lose their backed up files! + assert.equal(actual, '13264068d108c6901b3592ea654fcd57'); + assert.equal(actual, crypto.createHash('md5').update(uri.fsPath).digest('hex')); + }); + + test('should correctly hash the path for file scheme URIs', () => { + const uri = Uri.file('/foo'); + const actual = hashPath(uri); + // If these hashes change people will lose their backed up files! + if (platform.isWindows) { + assert.equal(actual, 'dec1a583f52468a020bd120c3f01d812'); + } else { + assert.equal(actual, '1effb2475fcfba4f9e8b8a1dbc8f3caf'); + } + assert.equal(actual, crypto.createHash('md5').update(uri.fsPath).digest('hex')); + }); + }); + suite('getBackupResource', () => { test('should get the correct backup path for text files', () => { // Format should be: /// const backupResource = fooFile; - const workspaceHash = crypto.createHash('md5').update(workspaceResource.fsPath).digest('hex'); - const filePathHash = crypto.createHash('md5').update(backupResource.fsPath).digest('hex'); + const workspaceHash = hashPath(workspaceResource); + const filePathHash = hashPath(backupResource); const expectedPath = Uri.file(path.join(backupHome, workspaceHash, 'file', filePathHash)).fsPath; assert.equal(service.toBackupResource(backupResource).fsPath, expectedPath); }); @@ -78,8 +122,8 @@ suite('BackupFileService', () => { test('should get the correct backup path for untitled files', () => { // Format should be: /// const backupResource = Uri.from({ scheme: Schemas.untitled, path: 'Untitled-1' }); - const workspaceHash = crypto.createHash('md5').update(workspaceResource.fsPath).digest('hex'); - const filePathHash = crypto.createHash('md5').update(backupResource.fsPath).digest('hex'); + const workspaceHash = hashPath(workspaceResource); + const filePathHash = hashPath(backupResource); const expectedPath = Uri.file(path.join(backupHome, workspaceHash, 'untitled', filePathHash)).fsPath; assert.equal(service.toBackupResource(backupResource).fsPath, expectedPath); }); @@ -257,7 +301,7 @@ suite('BackupFileService', () => { const contents = 'test\nand more stuff'; service.backupResource(untitledFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { service.resolveBackupContent(service.toBackupResource(untitledFile)).then(factory => { - assert.equal(contents, snapshotToString(factory.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(true))); + assert.equal(contents, snapshotToString(factory!.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(true))); }); }); }); @@ -272,7 +316,7 @@ suite('BackupFileService', () => { service.backupResource(fooFile, createTextBufferFactory(contents).create(DefaultEndOfLine.LF).createSnapshot(false)).then(() => { service.resolveBackupContent(service.toBackupResource(untitledFile)).then(factory => { - assert.equal(contents, snapshotToString(factory.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(true))); + assert.equal(contents, snapshotToString(factory!.create(platform.isWindows ? DefaultEndOfLine.CRLF : DefaultEndOfLine.LF).createSnapshot(true))); }); }); }); diff --git a/src/vs/platform/broadcast/electron-browser/broadcastService.ts b/src/vs/workbench/services/broadcast/electron-browser/broadcastService.ts similarity index 66% rename from src/vs/platform/broadcast/electron-browser/broadcastService.ts rename to src/vs/workbench/services/broadcast/electron-browser/broadcastService.ts index 5e204a527b..33429832b5 100644 --- a/src/vs/platform/broadcast/electron-browser/broadcastService.ts +++ b/src/vs/workbench/services/broadcast/electron-browser/broadcastService.ts @@ -5,9 +5,11 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event, Emitter } from 'vs/base/common/event'; - import { ipcRenderer as ipc } from 'electron'; import { ILogService } from 'vs/platform/log/common/log'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IWindowService } from 'vs/platform/windows/common/windows'; export const IBroadcastService = createDecorator('broadcastService'); @@ -19,21 +21,26 @@ export interface IBroadcast { export interface IBroadcastService { _serviceBrand: any; - broadcast(b: IBroadcast): void; - onBroadcast: Event; + + broadcast(b: IBroadcast): void; } -export class BroadcastService implements IBroadcastService { - public _serviceBrand: any; +export class BroadcastService extends Disposable implements IBroadcastService { + _serviceBrand: any; - private readonly _onBroadcast: Emitter; + private readonly _onBroadcast: Emitter = this._register(new Emitter()); + get onBroadcast(): Event { return this._onBroadcast.event; } + + private windowId: number; constructor( - private windowId: number, + @IWindowService readonly windowService: IWindowService, @ILogService private readonly logService: ILogService ) { - this._onBroadcast = new Emitter(); + super(); + + this.windowId = windowService.getCurrentWindowId(); this.registerListeners(); } @@ -46,11 +53,7 @@ export class BroadcastService implements IBroadcastService { }); } - public get onBroadcast(): Event { - return this._onBroadcast.event; - } - - public broadcast(b: IBroadcast): void { + broadcast(b: IBroadcast): void { this.logService.trace(`Sending broadcast to main from window ${this.windowId}: `, b); ipc.send('vscode:broadcast', this.windowId, { @@ -58,4 +61,6 @@ export class BroadcastService implements IBroadcastService { payload: b.payload }); } -} \ No newline at end of file +} + +registerSingleton(IBroadcastService, BroadcastService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts similarity index 96% rename from src/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts rename to src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts index afae31b0eb..66e1ca3b2f 100644 --- a/src/vs/workbench/services/bulkEdit/electron-browser/bulkEditService.ts +++ b/src/vs/workbench/services/bulkEdit/browser/bulkEditService.ts @@ -13,7 +13,7 @@ import { Range } from 'vs/editor/common/core/range'; import { EndOfLineSequence, IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { isResourceFileEdit, isResourceTextEdit, ResourceFileEdit, ResourceTextEdit, WorkspaceEdit } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { localize } from 'vs/nls'; import { IFileService } from 'vs/platform/files/common/files'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; @@ -53,7 +53,7 @@ class ModelEditTask implements IDisposable { private _expectedModelVersionId: number | undefined; protected _newEol: EndOfLineSequence; - constructor(private readonly _modelReference: IReference) { + constructor(private readonly _modelReference: IReference) { this._model = this._modelReference.object.textEditorModel; this._edits = []; } @@ -115,7 +115,7 @@ class EditorEditTask extends ModelEditTask { private _editor: ICodeEditor; - constructor(modelReference: IReference, editor: ICodeEditor) { + constructor(modelReference: IReference, editor: ICodeEditor) { super(modelReference); this._editor = editor; } @@ -410,6 +410,10 @@ export class BulkEditService implements IBulkEditService { } } + if (codeEditor && codeEditor.getConfiguration().readOnly) { + // If the code editor is readonly still allow bulk edits to be applied #68549 + codeEditor = undefined; + } const bulkEdit = new BulkEdit(codeEditor, options.progress, this._logService, this._textModelService, this._fileService, this._textFileService, this._labelService, this._configurationService); bulkEdit.add(edits); @@ -424,5 +428,4 @@ export class BulkEditService implements IBulkEditService { } } - registerSingleton(IBulkEditService, BulkEditService, true); diff --git a/src/vs/workbench/services/commands/common/commandService.ts b/src/vs/workbench/services/commands/common/commandService.ts index 58c4813e8f..24b04735f7 100644 --- a/src/vs/workbench/services/commands/common/commandService.ts +++ b/src/vs/workbench/services/commands/common/commandService.ts @@ -9,6 +9,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class CommandService extends Disposable implements ICommandService { @@ -67,3 +68,5 @@ export class CommandService extends Disposable implements ICommandService { } } } + +registerSingleton(ICommandService, CommandService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/commands/test/common/commandService.test.ts b/src/vs/workbench/services/commands/test/common/commandService.test.ts index d9476716b1..78786b0781 100644 --- a/src/vs/workbench/services/commands/test/common/commandService.test.ts +++ b/src/vs/workbench/services/commands/test/common/commandService.test.ts @@ -6,59 +6,10 @@ import * as assert from 'assert'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { CommandService } from 'vs/workbench/services/commands/common/commandService'; -import { IExtensionService, ExtensionPointContribution, IExtensionDescription, ProfileSession } from 'vs/workbench/services/extensions/common/extensions'; +import { NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; -import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { Event, Emitter } from 'vs/base/common/event'; import { NullLogService } from 'vs/platform/log/common/log'; -class SimpleExtensionService implements IExtensionService { - _serviceBrand: any; - private _onDidRegisterExtensions = new Emitter(); - get onDidRegisterExtensions(): Event { - return this._onDidRegisterExtensions.event; - } - onDidChangeExtensionsStatus = null!; - onDidChangeExtensions = null!; - onWillActivateByEvent = null!; - onDidChangeResponsiveChange = null!; - activateByEvent(activationEvent: string): Promise { - return this.whenInstalledExtensionsRegistered().then(() => { }); - } - whenInstalledExtensionsRegistered(): Promise { - return Promise.resolve(true); - } - readExtensionPointContributions(extPoint: IExtensionPoint): Promise[]> { - return Promise.resolve([]); - } - getExtensionsStatus() { - return undefined!; - } - getExtensions(): Promise { - return Promise.resolve([]); - } - getExtension() { - return Promise.resolve(undefined); - } - canProfileExtensionHost() { - return false; - } - startExtensionHostProfile(): Promise { - throw new Error('Not implemented'); - } - getInspectPort(): number { - return 0; - } - restartExtensionHost(): void { - } - startExtensionHost(): void { - } - stopExtensionHost(): void { - } - canAddExtension(): boolean { return false; } - canRemoveExtension(): boolean { return false; } -} - suite('CommandService', function () { let commandRegistration: IDisposable; @@ -75,7 +26,7 @@ suite('CommandService', function () { let lastEvent: string; - let service = new CommandService(new InstantiationService(), new class extends SimpleExtensionService { + let service = new CommandService(new InstantiationService(), new class extends NullExtensionService { activateByEvent(activationEvent: string): Promise { lastEvent = activationEvent; return super.activateByEvent(activationEvent); @@ -94,7 +45,7 @@ suite('CommandService', function () { test('fwd activation error', async function () { - const extensionService = new class extends SimpleExtensionService { + const extensionService = new class extends NullExtensionService { activateByEvent(activationEvent: string): Promise { return Promise.reject(new Error('bad_activate')); } @@ -114,7 +65,7 @@ suite('CommandService', function () { let callCounter = 0; let reg = CommandsRegistry.registerCommand('bar', () => callCounter += 1); - let service = new CommandService(new InstantiationService(), new class extends SimpleExtensionService { + let service = new CommandService(new InstantiationService(), new class extends NullExtensionService { whenInstalledExtensionsRegistered() { return new Promise(_resolve => { /*ignore*/ }); } @@ -131,7 +82,7 @@ suite('CommandService', function () { let resolveFunc: Function; const whenInstalledExtensionsRegistered = new Promise(_resolve => { resolveFunc = _resolve; }); - let service = new CommandService(new InstantiationService(), new class extends SimpleExtensionService { + let service = new CommandService(new InstantiationService(), new class extends NullExtensionService { whenInstalledExtensionsRegistered() { return whenInstalledExtensionsRegistered; } @@ -154,7 +105,7 @@ suite('CommandService', function () { let callCounter = 0; let dispoables: IDisposable[] = []; let events: string[] = []; - let service = new CommandService(new InstantiationService(), new class extends SimpleExtensionService { + let service = new CommandService(new InstantiationService(), new class extends NullExtensionService { activateByEvent(event: string): Promise { events.push(event); diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 5938178b26..b03ffaab81 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -3,19 +3,10 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; - -// {{SQL CARBON EDIT}} export const FOLDER_CONFIG_FOLDER_NAME = '.azuredatastudio'; export const FOLDER_SETTINGS_NAME = 'settings'; export const FOLDER_SETTINGS_PATH = `${FOLDER_CONFIG_FOLDER_NAME}/${FOLDER_SETTINGS_NAME}.json`; -export const IWorkspaceConfigurationService = createDecorator('configurationService'); - -export interface IWorkspaceConfigurationService extends IConfigurationService { -} - export const defaultSettingsSchemaId = 'vscode://schemas/settings/default'; export const userSettingsSchemaId = 'vscode://schemas/settings/user'; export const workspaceSettingsSchemaId = 'vscode://schemas/settings/workspace'; diff --git a/src/vs/workbench/services/configuration/node/configurationEditingService.ts b/src/vs/workbench/services/configuration/common/configurationEditingService.ts similarity index 92% rename from src/vs/workbench/services/configuration/node/configurationEditingService.ts rename to src/vs/workbench/services/configuration/common/configurationEditingService.ts index 064f01bb62..e8b5f29cdd 100644 --- a/src/vs/workbench/services/configuration/node/configurationEditingService.ts +++ b/src/vs/workbench/services/configuration/common/configurationEditingService.ts @@ -6,7 +6,6 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import * as json from 'vs/base/common/json'; -import * as encoding from 'vs/base/node/encoding'; import * as strings from 'vs/base/common/strings'; import { setProperty } from 'vs/base/common/jsonEdit'; import { Queue } from 'vs/base/common/async'; @@ -22,7 +21,7 @@ import { ITextFileService } from 'vs/workbench/services/textfile/common/textfile import { IConfigurationService, IConfigurationOverrides, keyFromOverrideIdentifier, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { FOLDER_SETTINGS_PATH, WORKSPACE_STANDALONE_CONFIGURATIONS, TASKS_CONFIGURATION_KEY, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; -import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService'; +import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { OVERRIDE_PROPERTY_PATTERN, IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ITextModel } from 'vs/editor/common/model'; @@ -106,7 +105,7 @@ export interface IConfigurationEditingOptions { interface IConfigurationEditOperation extends IConfigurationValue { target: ConfigurationTarget; jsonPath: json.JSONPath; - resource: URI; + resource?: URI; workspaceStandAloneConfigurationKey?: string; } @@ -158,7 +157,7 @@ export class ConfigurationEditingService { private async writeToBuffer(model: ITextModel, operation: IConfigurationEditOperation, save: boolean): Promise { const edit = this.getEdits(model, operation)[0]; if (edit && this.applyEditsToBuffer(edit, model) && save) { - return this.textFileService.save(operation.resource, { skipSaveParticipants: true /* programmatic change */ }); + return this.textFileService.save(operation.resource!, { skipSaveParticipants: true /* programmatic change */ }); } } @@ -175,7 +174,7 @@ export class ConfigurationEditingService { return false; } - private onError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, scopes: IConfigurationOverrides): void { + private onError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, scopes: IConfigurationOverrides | undefined): void { switch (error.code) { case ConfigurationEditingErrorCode.ERROR_INVALID_CONFIGURATION: this.onInvalidConfigurationError(error, operation); @@ -196,7 +195,7 @@ export class ConfigurationEditingService { this.notificationService.prompt(Severity.Error, error.message, [{ label: openStandAloneConfigurationActionLabel, - run: () => this.openFile(operation.resource) + run: () => this.openFile(operation.resource!) }] ); } else { @@ -209,7 +208,7 @@ export class ConfigurationEditingService { } } - private onConfigurationFileDirtyError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, scopes: IConfigurationOverrides): void { + private onConfigurationFileDirtyError(error: ConfigurationEditingError, operation: IConfigurationEditOperation, scopes: IConfigurationOverrides | undefined): void { const openStandAloneConfigurationActionLabel = operation.workspaceStandAloneConfigurationKey === TASKS_CONFIGURATION_KEY ? nls.localize('openTasksConfiguration', "Open Tasks Configuration") : operation.workspaceStandAloneConfigurationKey === LAUNCH_CONFIGURATION_KEY ? nls.localize('openLaunchConfiguration', "Open Launch Configuration") : null; @@ -218,13 +217,13 @@ export class ConfigurationEditingService { [{ label: nls.localize('saveAndRetry', "Save and Retry"), run: () => { - const key = operation.key ? `${operation.workspaceStandAloneConfigurationKey}.${operation.key}` : operation.workspaceStandAloneConfigurationKey; + const key = operation.key ? `${operation.workspaceStandAloneConfigurationKey}.${operation.key}` : operation.workspaceStandAloneConfigurationKey!; this.writeConfiguration(operation.target, { key, value: operation.value }, { force: true, scopes }); } }, { label: openStandAloneConfigurationActionLabel, - run: () => this.openFile(operation.resource) + run: () => this.openFile(operation.resource!) }] ); } else { @@ -296,7 +295,13 @@ export class ConfigurationEditingService { case ConfigurationTarget.WORKSPACE: return nls.localize('errorInvalidConfigurationWorkspace', "Unable to write into workspace settings. Please open the workspace settings to correct errors/warnings in the file and try again."); case ConfigurationTarget.WORKSPACE_FOLDER: - const workspaceFolderName = this.contextService.getWorkspaceFolder(operation.resource).name; + let workspaceFolderName: string = '<>'; + if (operation.resource) { + const folder = this.contextService.getWorkspaceFolder(operation.resource); + if (folder) { + workspaceFolderName = folder.name; + } + } return nls.localize('errorInvalidConfigurationFolder', "Unable to write into folder settings. Please open the '{0}' folder settings to correct errors/warnings in it and try again.", workspaceFolderName); } return ''; @@ -314,7 +319,13 @@ export class ConfigurationEditingService { case ConfigurationTarget.WORKSPACE: return nls.localize('errorConfigurationFileDirtyWorkspace', "Unable to write into workspace settings because the file is dirty. Please save the workspace settings file first and then try again."); case ConfigurationTarget.WORKSPACE_FOLDER: - const workspaceFolderName = this.contextService.getWorkspaceFolder(operation.resource).name; + let workspaceFolderName: string = '<>'; + if (operation.resource) { + const folder = this.contextService.getWorkspaceFolder(operation.resource); + if (folder) { + workspaceFolderName = folder.name; + } + } return nls.localize('errorConfigurationFileDirtyFolder', "Unable to write into folder settings because the file is dirty. Please save the '{0}' folder settings file first and then try again.", workspaceFolderName); } return ''; @@ -352,10 +363,10 @@ export class ConfigurationEditingService { return setProperty(model.getValue(), jsonPath, value, { tabSize, insertSpaces, eol }); } - private async resolveModelReference(resource: URI): Promise> { + private async resolveModelReference(resource: URI): Promise> { const exists = await this.fileService.existsFile(resource); if (!exists) { - await this.fileService.updateContent(resource, '{}', { encoding: encoding.UTF8 }); + await this.fileService.updateContent(resource, '{}', { encoding: 'utf8' }); } return this.textModelResolverService.createModelReference(resource); } @@ -371,7 +382,7 @@ export class ConfigurationEditingService { return parseErrors.length > 0; } - private resolveAndValidate(target: ConfigurationTarget, operation: IConfigurationEditOperation, checkDirty: boolean, overrides: IConfigurationOverrides): Promise> { + private resolveAndValidate(target: ConfigurationTarget, operation: IConfigurationEditOperation, checkDirty: boolean, overrides: IConfigurationOverrides): Promise> { // Any key must be a known setting from the registry (unless this is a standalone config) if (!operation.workspaceStandAloneConfigurationKey) { @@ -420,6 +431,10 @@ export class ConfigurationEditingService { } } + if (!operation.resource) { + return this.reject(ConfigurationEditingErrorCode.ERROR_INVALID_FOLDER_TARGET, target, operation); + } + return this.resolveModelReference(operation.resource) .then(reference => { const model = reference.object.textEditorModel; @@ -447,14 +462,14 @@ export class ConfigurationEditingService { // Check for prefix if (config.key === key) { const jsonPath = this.isWorkspaceConfigurationResource(resource) ? [key] : []; - return { key: jsonPath[jsonPath.length - 1], jsonPath, value: config.value, resource, workspaceStandAloneConfigurationKey: key, target }; + return { key: jsonPath[jsonPath.length - 1], jsonPath, value: config.value, resource: resource || undefined, workspaceStandAloneConfigurationKey: key, target }; } // Check for prefix. const keyPrefix = `${key}.`; if (config.key.indexOf(keyPrefix) === 0) { const jsonPath = this.isWorkspaceConfigurationResource(resource) ? [key, config.key.substr(keyPrefix.length)] : [config.key.substr(keyPrefix.length)]; - return { key: jsonPath[jsonPath.length - 1], jsonPath, value: config.value, resource, workspaceStandAloneConfigurationKey: key, target }; + return { key: jsonPath[jsonPath.length - 1], jsonPath, value: config.value, resource: resource || undefined, workspaceStandAloneConfigurationKey: key, target }; } } } @@ -469,15 +484,15 @@ export class ConfigurationEditingService { if (this.isWorkspaceConfigurationResource(resource)) { jsonPath = ['settings', ...jsonPath]; } - return { key, jsonPath, value: config.value, resource, target }; + return { key, jsonPath, value: config.value, resource: resource || undefined, target }; } - private isWorkspaceConfigurationResource(resource: URI): boolean { + private isWorkspaceConfigurationResource(resource: URI | null | undefined): boolean { const workspace = this.contextService.getWorkspace(); - return workspace.configuration && resource && workspace.configuration.fsPath === resource.fsPath; + return !!(workspace.configuration && resource && workspace.configuration.fsPath === resource.fsPath); } - private getConfigurationFileResource(target: ConfigurationTarget, relativePath: string, resource: URI): URI { + private getConfigurationFileResource(target: ConfigurationTarget, relativePath: string, resource: URI | null | undefined): URI | null { if (target === ConfigurationTarget.USER) { return URI.file(this.environmentService.appSettingsPath); } @@ -489,7 +504,7 @@ export class ConfigurationEditingService { if (target === ConfigurationTarget.WORKSPACE) { if (workbenchState === WorkbenchState.WORKSPACE) { - return workspace.configuration; + return workspace.configuration || null; } if (workbenchState === WorkbenchState.FOLDER) { return workspace.folders[0].toResource(relativePath); diff --git a/src/vs/workbench/services/configuration/common/configurationModels.ts b/src/vs/workbench/services/configuration/common/configurationModels.ts index 9f6b93e3e9..c96079bf39 100644 --- a/src/vs/workbench/services/configuration/common/configurationModels.ts +++ b/src/vs/workbench/services/configuration/common/configurationModels.ts @@ -136,11 +136,11 @@ export class Configuration extends BaseConfiguration { folders: ResourceMap, memoryConfiguration: ConfigurationModel, memoryConfigurationByResource: ResourceMap, - private readonly _workspace: Workspace) { + private readonly _workspace?: Workspace) { super(defaults, user, workspaceConfiguration, folders, memoryConfiguration, memoryConfigurationByResource); } - getValue(key: string, overrides: IConfigurationOverrides = {}): any { + getValue(key: string | undefined, overrides: IConfigurationOverrides = {}): any { return super.getValue(key, overrides, this._workspace); } @@ -256,7 +256,7 @@ export class AllKeysConfigurationChangeEvent extends AbstractConfigurationChange export class WorkspaceConfigurationChangeEvent implements IConfigurationChangeEvent { - constructor(private configurationChangeEvent: IConfigurationChangeEvent, private workspace: Workspace) { } + constructor(private configurationChangeEvent: IConfigurationChangeEvent, private workspace: Workspace | undefined) { } get changedConfiguration(): IConfigurationModel { return this.configurationChangeEvent.changedConfiguration; diff --git a/src/vs/workbench/services/configuration/node/jsonEditingService.ts b/src/vs/workbench/services/configuration/common/jsonEditingService.ts similarity index 90% rename from src/vs/workbench/services/configuration/node/jsonEditingService.ts rename to src/vs/workbench/services/configuration/common/jsonEditingService.ts index e08ed71bd2..ab1994b524 100644 --- a/src/vs/workbench/services/configuration/node/jsonEditingService.ts +++ b/src/vs/workbench/services/configuration/common/jsonEditingService.ts @@ -6,7 +6,6 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; import * as json from 'vs/base/common/json'; -import * as encoding from 'vs/base/node/encoding'; import * as strings from 'vs/base/common/strings'; import { setProperty } from 'vs/base/common/jsonEdit'; import { Queue } from 'vs/base/common/async'; @@ -17,9 +16,10 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService } from 'vs/platform/files/common/files'; -import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService'; +import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { IJSONEditingService, IJSONValue, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing'; import { ITextModel } from 'vs/editor/common/model'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class JSONEditingService implements IJSONEditingService { @@ -83,10 +83,10 @@ export class JSONEditingService implements IJSONEditingService { return setProperty(model.getValue(), [key], value, { tabSize, insertSpaces, eol }); } - private async resolveModelReference(resource: URI): Promise> { + private async resolveModelReference(resource: URI): Promise> { const exists = await this.fileService.existsFile(resource); if (!exists) { - await this.fileService.updateContent(resource, '{}', { encoding: encoding.UTF8 }); + await this.fileService.updateContent(resource, '{}', { encoding: 'utf8' }); } return this.textModelResolverService.createModelReference(resource); } @@ -97,18 +97,18 @@ export class JSONEditingService implements IJSONEditingService { return parseErrors.length > 0; } - private resolveAndValidate(resource: URI, checkDirty: boolean): Promise> { + private resolveAndValidate(resource: URI, checkDirty: boolean): Promise> { return this.resolveModelReference(resource) .then(reference => { const model = reference.object.textEditorModel; if (this.hasParseErrors(model)) { - return this.reject>(JSONEditingErrorCode.ERROR_INVALID_FILE); + return this.reject>(JSONEditingErrorCode.ERROR_INVALID_FILE); } // Target cannot be dirty if not writing into buffer if (checkDirty && this.textFileService.isDirty(resource)) { - return this.reject>(JSONEditingErrorCode.ERROR_FILE_DIRTY); + return this.reject>(JSONEditingErrorCode.ERROR_FILE_DIRTY); } return reference; }); @@ -131,3 +131,5 @@ export class JSONEditingService implements IJSONEditingService { } } } + +registerSingleton(IJSONEditingService, JSONEditingService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/node/configuration.ts b/src/vs/workbench/services/configuration/node/configuration.ts index 26684b238d..920b69613d 100644 --- a/src/vs/workbench/services/configuration/node/configuration.ts +++ b/src/vs/workbench/services/configuration/node/configuration.ts @@ -5,25 +5,23 @@ import { URI } from 'vs/base/common/uri'; import { createHash } from 'crypto'; -import * as paths from 'vs/base/common/paths'; import * as resources from 'vs/base/common/resources'; import { Event, Emitter } from 'vs/base/common/event'; import * as pfs from 'vs/base/node/pfs'; import * as errors from 'vs/base/common/errors'; import * as collections from 'vs/base/common/collections'; -import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { RunOnceScheduler, Delayer } from 'vs/base/common/async'; import { FileChangeType, FileChangesEvent, IContent, IFileService } from 'vs/platform/files/common/files'; -import { isLinux } from 'vs/base/common/platform'; import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; import { WorkspaceConfigurationModelParser, FolderSettingsModelParser, StandaloneConfigurationModelParser } from 'vs/workbench/services/configuration/common/configurationModels'; import { FOLDER_SETTINGS_PATH, TASKS_CONFIGURATION_KEY, FOLDER_SETTINGS_NAME, LAUNCH_CONFIGURATION_KEY } from 'vs/workbench/services/configuration/common/configuration'; import { IStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import * as extfs from 'vs/base/node/extfs'; -import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService'; +import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; -import { relative } from 'path'; +import { extname, join } from 'vs/base/common/path'; import { equals } from 'vs/base/common/objects'; import { Schemas } from 'vs/base/common/network'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -37,7 +35,7 @@ export interface IWorkspaceIdentifier { export class WorkspaceConfiguration extends Disposable { private readonly _cachedConfiguration: CachedWorkspaceConfiguration; - private _workspaceConfiguration: IWorkspaceConfiguration | null; + private _workspaceConfiguration: IWorkspaceConfiguration; private _workspaceIdentifier: IWorkspaceIdentifier | null = null; private _fileService: IFileService | null = null; @@ -99,11 +97,16 @@ export class WorkspaceConfiguration extends Disposable { if (this._workspaceIdentifier) { if (this._fileService) { if (!(this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration)) { - dispose(this._workspaceConfiguration); - const nodeBasedWorkspaceConfiguration = this._workspaceConfiguration instanceof NodeBasedWorkspaceConfiguration ? this._workspaceConfiguration : undefined; - this._workspaceConfiguration = new FileServiceBasedWorkspaceConfiguration(this._fileService, nodeBasedWorkspaceConfiguration); + const oldWorkspaceConfiguration = this._workspaceConfiguration; + this._workspaceConfiguration = new FileServiceBasedWorkspaceConfiguration(this._fileService, oldWorkspaceConfiguration); this._register(this._workspaceConfiguration.onDidChange(e => this.onDidWorkspaceConfigurationChange())); - return !nodeBasedWorkspaceConfiguration; + if (oldWorkspaceConfiguration instanceof CachedWorkspaceConfiguration) { + this.updateCache(); + return true; + } else { + dispose(oldWorkspaceConfiguration); + return false; + } } return false; } @@ -127,7 +130,7 @@ export class WorkspaceConfiguration extends Disposable { private updateCache(): Promise { if (this._workspaceIdentifier && this._workspaceIdentifier.configPath.scheme !== Schemas.file && this._workspaceConfiguration instanceof FileServiceBasedWorkspaceConfiguration) { return this._workspaceConfiguration.load(this._workspaceIdentifier) - .then(() => this._cachedConfiguration.updateWorkspace(this._workspaceIdentifier, this._workspaceConfiguration.getConfigurationModel())); + .then(() => this._cachedConfiguration.updateWorkspace(this._workspaceIdentifier!, this._workspaceConfiguration.getConfigurationModel())); } return Promise.resolve(undefined); } @@ -135,6 +138,9 @@ export class WorkspaceConfiguration extends Disposable { interface IWorkspaceConfiguration extends IDisposable { readonly onDidChange: Event; + workspaceConfigurationModelParser: WorkspaceConfigurationModelParser; + workspaceSettings: ConfigurationModel; + workspaceIdentifier: IWorkspaceIdentifier | null; load(workspaceIdentifier: IWorkspaceIdentifier): Promise; getConfigurationModel(): ConfigurationModel; getFolders(): IStoredWorkspaceFolder[]; @@ -144,18 +150,18 @@ interface IWorkspaceConfiguration extends IDisposable { abstract class AbstractWorkspaceConfiguration extends Disposable implements IWorkspaceConfiguration { - private _workspaceConfigurationModelParser: WorkspaceConfigurationModelParser; - private _workspaceSettings: ConfigurationModel; + workspaceConfigurationModelParser: WorkspaceConfigurationModelParser; + workspaceSettings: ConfigurationModel; private _workspaceIdentifier: IWorkspaceIdentifier | null = null; protected readonly _onDidChange: Emitter = this._register(new Emitter()); readonly onDidChange: Event = this._onDidChange.event; - constructor(from?: AbstractWorkspaceConfiguration) { + constructor(from?: IWorkspaceConfiguration) { super(); - this._workspaceConfigurationModelParser = from ? from._workspaceConfigurationModelParser : new WorkspaceConfigurationModelParser(''); - this._workspaceSettings = new ConfigurationModel(); + this.workspaceConfigurationModelParser = from ? from.workspaceConfigurationModelParser : new WorkspaceConfigurationModelParser(''); + this.workspaceSettings = from ? from.workspaceSettings : new ConfigurationModel(); } get workspaceIdentifier(): IWorkspaceIdentifier | null { @@ -166,32 +172,32 @@ abstract class AbstractWorkspaceConfiguration extends Disposable implements IWor this._workspaceIdentifier = workspaceIdentifier; return this.loadWorkspaceConfigurationContents(workspaceIdentifier) .then(contents => { - this._workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(workspaceIdentifier.id); - this._workspaceConfigurationModelParser.parse(contents); + this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(workspaceIdentifier.id); + this.workspaceConfigurationModelParser.parse(contents); this.consolidate(); }); } getConfigurationModel(): ConfigurationModel { - return this._workspaceConfigurationModelParser.configurationModel; + return this.workspaceConfigurationModelParser.configurationModel; } getFolders(): IStoredWorkspaceFolder[] { - return this._workspaceConfigurationModelParser.folders; + return this.workspaceConfigurationModelParser.folders; } getWorkspaceSettings(): ConfigurationModel { - return this._workspaceSettings; + return this.workspaceSettings; } reprocessWorkspaceSettings(): ConfigurationModel { - this._workspaceConfigurationModelParser.reprocessWorkspaceSettings(); + this.workspaceConfigurationModelParser.reprocessWorkspaceSettings(); this.consolidate(); return this.getWorkspaceSettings(); } private consolidate(): void { - this._workspaceSettings = this._workspaceConfigurationModelParser.settingsModel.merge(this._workspaceConfigurationModelParser.launchModel); + this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel); } protected abstract loadWorkspaceConfigurationContents(workspaceIdentifier: IWorkspaceIdentifier): Promise; @@ -214,15 +220,33 @@ class FileServiceBasedWorkspaceConfiguration extends AbstractWorkspaceConfigurat private workspaceConfig: URI | null = null; private readonly reloadConfigurationScheduler: RunOnceScheduler; - constructor(private fileService: IFileService, from?: AbstractWorkspaceConfiguration) { + constructor(private fileService: IFileService, from?: IWorkspaceConfiguration) { super(from); - this.workspaceConfig = from ? from.workspaceIdentifier.configPath : null; + this.workspaceConfig = from && from.workspaceIdentifier ? from.workspaceIdentifier.configPath : null; this._register(fileService.onFileChanges(e => this.handleWorkspaceFileEvents(e))); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); + this.watchWorkspaceConfigurationFile(); + this._register(toDisposable(() => this.unWatchWorkspaceConfigurtionFile())); + } + + private watchWorkspaceConfigurationFile(): void { + if (this.workspaceConfig) { + this.fileService.watchFileChanges(this.workspaceConfig); + } + } + + private unWatchWorkspaceConfigurtionFile(): void { + if (this.workspaceConfig) { + this.fileService.unwatchFileChanges(this.workspaceConfig); + } } protected loadWorkspaceConfigurationContents(workspaceIdentifier: IWorkspaceIdentifier): Promise { - this.workspaceConfig = workspaceIdentifier.configPath; + if (!(this.workspaceConfig && resources.isEqual(this.workspaceConfig, workspaceIdentifier.configPath))) { + this.unWatchWorkspaceConfigurtionFile(); + this.workspaceConfig = workspaceIdentifier.configPath; + this.watchWorkspaceConfigurationFile(); + } return this.fileService.resolveContent(this.workspaceConfig) .then(content => content.value, e => { errors.onUnexpectedError(e); @@ -254,39 +278,43 @@ class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfi private cachedWorkspacePath: string; private cachedConfigurationPath: string; - private _workspaceConfigurationModelParser: WorkspaceConfigurationModelParser; - private _workspaceSettings: ConfigurationModel; + workspaceConfigurationModelParser: WorkspaceConfigurationModelParser; + workspaceSettings: ConfigurationModel; constructor(private environmentService: IEnvironmentService) { super(); - this._workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(''); - this._workspaceSettings = new ConfigurationModel(); + this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(''); + this.workspaceSettings = new ConfigurationModel(); } load(workspaceIdentifier: IWorkspaceIdentifier): Promise { this.createPaths(workspaceIdentifier); return pfs.readFile(this.cachedConfigurationPath) .then(contents => { - this._workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(this.cachedConfigurationPath); - this._workspaceConfigurationModelParser.parse(contents.toString()); - this._workspaceSettings = this._workspaceConfigurationModelParser.settingsModel.merge(this._workspaceConfigurationModelParser.launchModel); - }, () => null); + this.workspaceConfigurationModelParser = new WorkspaceConfigurationModelParser(this.cachedConfigurationPath); + this.workspaceConfigurationModelParser.parse(contents.toString()); + this.workspaceSettings = this.workspaceConfigurationModelParser.settingsModel.merge(this.workspaceConfigurationModelParser.launchModel); + }, () => { }); + } + + get workspaceIdentifier(): IWorkspaceIdentifier | null { + return null; } getConfigurationModel(): ConfigurationModel { - return this._workspaceConfigurationModelParser.configurationModel; + return this.workspaceConfigurationModelParser.configurationModel; } getFolders(): IStoredWorkspaceFolder[] { - return this._workspaceConfigurationModelParser.folders; + return this.workspaceConfigurationModelParser.folders; } getWorkspaceSettings(): ConfigurationModel { - return this._workspaceSettings; + return this.workspaceSettings; } reprocessWorkspaceSettings(): ConfigurationModel { - return this._workspaceSettings; + return this.workspaceSettings; } async updateWorkspace(workspaceIdentifier: IWorkspaceIdentifier, configurationModel: ConfigurationModel): Promise { @@ -297,7 +325,7 @@ class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfi if (!exists) { await pfs.mkdirp(this.cachedWorkspacePath); } - const raw = JSON.stringify(configurationModel.toJSON()); + const raw = JSON.stringify(configurationModel.toJSON().contents); await pfs.writeFile(this.cachedConfigurationPath, raw); } else { pfs.rimraf(this.cachedWorkspacePath); @@ -308,8 +336,8 @@ class CachedWorkspaceConfiguration extends Disposable implements IWorkspaceConfi } private createPaths(workspaceIdentifier: IWorkspaceIdentifier) { - this.cachedWorkspacePath = paths.join(this.environmentService.userDataPath, 'CachedConfigurations', 'workspaces', workspaceIdentifier.id); - this.cachedConfigurationPath = paths.join(this.cachedWorkspacePath, 'workspace.json'); + this.cachedWorkspacePath = join(this.environmentService.userDataPath, 'CachedConfigurations', 'workspaces', workspaceIdentifier.id); + this.cachedConfigurationPath = join(this.cachedWorkspacePath, 'workspace.json'); } } @@ -471,7 +499,7 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura } private doLoadFolderConfigurationContents(): Promise> { - const workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: Promise } = Object.create(null); + const workspaceFilePathToConfiguration: { [relativeWorkspacePath: string]: Promise } = Object.create(null); const bulkContentFetchromise = Promise.resolve(this.fileService.resolveFile(this.folderConfigurationPath)) .then(stat => { if (stat.isDirectory && stat.children) { @@ -486,7 +514,7 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura } }).then(undefined, err => [] /* never fail this call */); - return bulkContentFetchromise.then(() => Promise.all(collections.values(workspaceFilePathToConfiguration))); + return bulkContentFetchromise.then(() => Promise.all(collections.values(workspaceFilePathToConfiguration))).then(contents => contents.filter(content => content !== undefined)); } private handleWorkspaceFileEvents(event: FileChangesEvent): void { @@ -497,7 +525,7 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura for (let i = 0, len = events.length; i < len; i++) { const resource = events[i].resource; const basename = resources.basename(resource); - const isJson = paths.extname(basename) === '.json'; + const isJson = extname(basename) === '.json'; const isDeletedSettingsFolder = (events[i].type === FileChangeType.DELETED && basename === this.configFolderRelativePath); if (!isJson && !isDeletedSettingsFolder) { @@ -529,17 +557,11 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura } } - private toFolderRelativePath(resource: URI): string | null { - if (resource.scheme === Schemas.file) { - if (paths.isEqualOrParent(resource.fsPath, this.folderConfigurationPath.fsPath, !isLinux /* ignorecase */)) { - return paths.normalize(relative(this.folderConfigurationPath.fsPath, resource.fsPath)); - } - } else { - if (resources.isEqualOrParent(resource, this.folderConfigurationPath)) { - return paths.normalize(relative(this.folderConfigurationPath.path, resource.path)); - } + private toFolderRelativePath(resource: URI): string | undefined { + if (resources.isEqualOrParent(resource, this.folderConfigurationPath)) { + return resources.relativePath(this.folderConfigurationPath, resource); } - return null; + return undefined; } } @@ -559,8 +581,8 @@ export class CachedFolderConfiguration extends Disposable implements IFolderConf configFolderRelativePath: string, environmentService: IEnvironmentService) { super(); - this.cachedFolderPath = paths.join(environmentService.userDataPath, 'CachedConfigurations', 'folders', createHash('md5').update(paths.join(folder.path, configFolderRelativePath)).digest('hex')); - this.cachedConfigurationPath = paths.join(this.cachedFolderPath, 'configuration.json'); + this.cachedFolderPath = join(environmentService.userDataPath, 'CachedConfigurations', 'folders', createHash('md5').update(join(folder.path, configFolderRelativePath)).digest('hex')); + this.cachedConfigurationPath = join(this.cachedFolderPath, 'configuration.json'); this.configurationModel = new ConfigurationModel(); } diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index cd86b537b8..9b97dddd26 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -9,7 +9,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { ResourceMap } from 'vs/base/common/map'; import { equals, deepClone } from 'vs/base/common/objects'; import { Disposable } from 'vs/base/common/lifecycle'; -import { Queue } from 'vs/base/common/async'; +import { Queue, Barrier } from 'vs/base/common/async'; import { writeFile } from 'vs/base/node/pfs'; import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { IWorkspaceContextService, Workspace, WorkbenchState, IWorkspaceFolder, toWorkspaceFolders, IWorkspaceFoldersChangeEvent, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; @@ -17,34 +17,32 @@ import { isLinux } from 'vs/base/common/platform'; import { IFileService } from 'vs/platform/files/common/files'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ConfigurationChangeEvent, ConfigurationModel, DefaultConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; -import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData } from 'vs/platform/configuration/common/configuration'; +import { IConfigurationChangeEvent, ConfigurationTarget, IConfigurationOverrides, keyFromOverrideIdentifier, isConfigurationOverrides, IConfigurationData, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Configuration, WorkspaceConfigurationChangeEvent, AllKeysConfigurationChangeEvent } from 'vs/workbench/services/configuration/common/configurationModels'; -import { IWorkspaceConfigurationService, FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; +import { FOLDER_CONFIG_FOLDER_NAME, defaultSettingsSchemaId, userSettingsSchemaId, workspaceSettingsSchemaId, folderSettingsSchemaId } from 'vs/workbench/services/configuration/common/configuration'; import { Registry } from 'vs/platform/registry/common/platform'; import { IConfigurationNode, IConfigurationRegistry, Extensions, IConfigurationPropertySchema, allSettings, windowSettings, resourceSettings, applicationSettings } from 'vs/platform/configuration/common/configurationRegistry'; -import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, isSingleFolderWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; +import { IWorkspaceIdentifier, isWorkspaceIdentifier, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, isSingleFolderWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, IEmptyWorkspaceInitializationPayload, useSlashForPath, getStoredWorkspaceFolder } from 'vs/platform/workspaces/common/workspaces'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService'; +import { ConfigurationEditingService } from 'vs/workbench/services/configuration/common/configurationEditingService'; import { WorkspaceConfiguration, FolderConfiguration } from 'vs/workbench/services/configuration/node/configuration'; -import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService'; -import { Schemas } from 'vs/base/common/network'; -import { massageFolderPathForWorkspace } from 'vs/platform/workspaces/node/workspaces'; +import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { UserConfiguration } from 'vs/platform/configuration/node/configuration'; import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema'; import { localize } from 'vs/nls'; import { isEqual, dirname } from 'vs/base/common/resources'; import { mark } from 'vs/base/common/performance'; +import { Schemas } from 'vs/base/common/network'; -export class WorkspaceService extends Disposable implements IWorkspaceConfigurationService, IWorkspaceContextService { +export class WorkspaceService extends Disposable implements IConfigurationService, IWorkspaceContextService { public _serviceBrand: any; private workspace: Workspace; - private resolvePromise: Promise; - private resolveCallback: () => void; + private completeWorkspaceBarrier: Barrier; private _configuration: Configuration; private defaultConfiguration: DefaultConfigurationModel; private userConfiguration: UserConfiguration; @@ -72,7 +70,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat constructor(private environmentService: IEnvironmentService, private workspaceSettingsRootFolder: string = FOLDER_CONFIG_FOLDER_NAME) { super(); - this.resolvePromise = new Promise(c => this.resolveCallback = c); + this.completeWorkspaceBarrier = new Barrier(); this.defaultConfiguration = new DefaultConfigurationModel(); this.userConfiguration = this._register(new UserConfiguration(environmentService.appSettingsPath)); this.workspaceConfiguration = this._register(new WorkspaceConfiguration(environmentService)); @@ -88,7 +86,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat // Workspace Context Service Impl public getCompleteWorkspace(): Promise { - return this.resolvePromise.then(() => this.getWorkspace()); + return this.completeWorkspaceBarrier.wait().then(() => this.getWorkspace()); } public getWorkspace(): Workspace { @@ -110,7 +108,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return WorkbenchState.EMPTY; } - public getWorkspaceFolder(resource: URI): IWorkspaceFolder { + public getWorkspaceFolder(resource: URI): IWorkspaceFolder | null { return this.workspace.getFolder(resource); } @@ -154,7 +152,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat // Remove first (if any) let currentWorkspaceFolders = this.getWorkspace().folders; - let newStoredFolders: IStoredWorkspaceFolder[] = currentWorkspaceFolders.map(f => f.raw).filter((folder, index) => { + let newStoredFolders: IStoredWorkspaceFolder[] = currentWorkspaceFolders.map(f => f.raw).filter((folder, index): folder is IStoredWorkspaceFolder => { if (!isStoredWorkspaceFolder(folder)) { return true; // keep entries which are unrelated } @@ -162,44 +160,26 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat return !this.contains(foldersToRemove, currentWorkspaceFolders[index].uri); // keep entries which are unrelated }); + const slashForPath = useSlashForPath(newStoredFolders); + foldersHaveChanged = currentWorkspaceFolders.length !== newStoredFolders.length; // Add afterwards (if any) if (foldersToAdd.length) { // Recompute current workspace folders if we have folders to add - const workspaceConfigFolder = dirname(this.getWorkspace().configuration); + const workspaceConfigFolder = dirname(this.getWorkspace().configuration!); currentWorkspaceFolders = toWorkspaceFolders(newStoredFolders, workspaceConfigFolder); const currentWorkspaceFolderUris = currentWorkspaceFolders.map(folder => folder.uri); const storedFoldersToAdd: IStoredWorkspaceFolder[] = []; foldersToAdd.forEach(folderToAdd => { - if (this.contains(currentWorkspaceFolderUris, folderToAdd.uri)) { + const folderURI = folderToAdd.uri; + if (this.contains(currentWorkspaceFolderUris, folderURI)) { return; // already existing } - - let storedFolder: IStoredWorkspaceFolder; - - // File resource: use "path" property - if (folderToAdd.uri.scheme === Schemas.file) { - storedFolder = { - path: massageFolderPathForWorkspace(folderToAdd.uri.fsPath, workspaceConfigFolder, newStoredFolders) - }; - } - - // Any other resource: use "uri" property - else { - storedFolder = { - uri: folderToAdd.uri.toString(true) - }; - } - - if (folderToAdd.name) { - storedFolder.name = folderToAdd.name; - } - - storedFoldersToAdd.push(storedFolder); + storedFoldersToAdd.push(getStoredWorkspaceFolder(folderURI, folderToAdd.name, workspaceConfigFolder, slashForPath)); }); // Apply to array of newStoredFolders @@ -266,7 +246,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat const overrides = isConfigurationOverrides(arg3) ? arg3 : undefined; const target = this.deriveConfigurationTarget(key, value, overrides, overrides ? arg4 : arg3); return target ? this.writeConfigurationValue(key, value, target, overrides, donotNotifyError) - : Promise.resolve(null); + : Promise.resolve(); } reloadConfiguration(folder?: IWorkspaceFolder, key?: string): Promise { @@ -324,7 +304,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat for (const workspaceFolder of changedWorkspaceFolders) { this.onWorkspaceFolderConfigurationChanged(workspaceFolder); } - this.resolveCallback(); + this.releaseWorkspaceBarrier(); }); } @@ -346,12 +326,16 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat } private createMultiFolderWorkspace(workspaceIdentifier: IWorkspaceIdentifier): Promise { - return this.workspaceConfiguration.load({ id: workspaceIdentifier.id, configPath: URI.file(workspaceIdentifier.configPath) }) + return this.workspaceConfiguration.load({ id: workspaceIdentifier.id, configPath: workspaceIdentifier.configPath }) .then(() => { - const workspaceConfigPath = URI.file(workspaceIdentifier.configPath); + const workspaceConfigPath = workspaceIdentifier.configPath; const workspaceFolders = toWorkspaceFolders(this.workspaceConfiguration.getFolders(), dirname(workspaceConfigPath)); const workspaceId = workspaceIdentifier.id; - return new Workspace(workspaceId, workspaceFolders, workspaceConfigPath); + const workspace = new Workspace(workspaceId, workspaceFolders, workspaceConfigPath); + if (workspace.configuration!.scheme === Schemas.file) { + this.releaseWorkspaceBarrier(); // Release barrier as workspace is complete because it is from disk. + } + return workspace; }); } @@ -365,17 +349,27 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat configuredFolders = [{ uri: folder.toString() }]; } - return Promise.resolve(new Workspace(singleFolder.id, toWorkspaceFolders(configuredFolders))); + const workspace = new Workspace(singleFolder.id, toWorkspaceFolders(configuredFolders)); + this.releaseWorkspaceBarrier(); // Release barrier as workspace is complete because it is single folder. + return Promise.resolve(workspace); } private createEmptyWorkspace(emptyWorkspace: IEmptyWorkspaceInitializationPayload): Promise { - return Promise.resolve(new Workspace(emptyWorkspace.id)); + const workspace = new Workspace(emptyWorkspace.id); + this.releaseWorkspaceBarrier(); // Release barrier as workspace is complete because it is an empty workspace. + return Promise.resolve(workspace); + } + + private releaseWorkspaceBarrier(): void { + if (!this.completeWorkspaceBarrier.isOpen()) { + this.completeWorkspaceBarrier.open(); + } } private updateWorkspaceAndInitializeConfiguration(workspace: Workspace, postInitialisationTask: () => void): Promise { const hasWorkspaceBefore = !!this.workspace; let previousState: WorkbenchState; - let previousWorkspacePath: string; + let previousWorkspacePath: string | undefined; let previousFolders: WorkspaceFolder[]; if (hasWorkspaceBefore) { @@ -467,7 +461,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat folderConfigurations.forEach((folderConfiguration, index) => folderConfigurationModels.set(folders[index].uri, folderConfiguration)); const currentConfiguration = this._configuration; - this._configuration = new Configuration(this.defaultConfiguration, userConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.getWorkbenchState() !== WorkbenchState.EMPTY ? this.workspace : null); //TODO: Sandy Avoid passing null + this._configuration = new Configuration(this.defaultConfiguration, userConfigurationModel, workspaceConfiguration, folderConfigurationModels, new ConfigurationModel(), new ResourceMap(), this.workspace); if (currentConfiguration) { const changedKeys = this._configuration.compare(currentConfiguration); @@ -495,10 +489,10 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat if (this.workspace && this._configuration) { this._configuration.updateDefaultConfiguration(this.defaultConfiguration); if (this.getWorkbenchState() === WorkbenchState.FOLDER) { - this._configuration.updateWorkspaceConfiguration(this.cachedFolderConfigs.get(this.workspace.folders[0].uri).reprocess()); + this._configuration.updateWorkspaceConfiguration(this.cachedFolderConfigs.get(this.workspace.folders[0].uri)!.reprocess()); } else { this._configuration.updateWorkspaceConfiguration(this.workspaceConfiguration.reprocessWorkspaceSettings()); - this.workspace.folders.forEach(folder => this._configuration.updateFolderConfiguration(folder.uri, this.cachedFolderConfigs.get(folder.uri).reprocess())); + this.workspace.folders.forEach(folder => this._configuration.updateFolderConfiguration(folder.uri, this.cachedFolderConfigs.get(folder.uri)!.reprocess())); } this.triggerConfigurationChange(new ConfigurationChangeEvent().change(keys), ConfigurationTarget.DEFAULT); } @@ -578,7 +572,7 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat for (const key of this.cachedFolderConfigs.keys()) { if (!this.workspace.folders.filter(folder => folder.uri.toString() === key.toString())[0]) { const folderConfiguration = this.cachedFolderConfigs.get(key); - folderConfiguration.dispose(); + folderConfiguration!.dispose(); this.cachedFolderConfigs.delete(key); changeEvent = changeEvent.change(this._configuration.compareAndDeleteFolderConfiguration(key)); } @@ -609,14 +603,14 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat })]); } - private writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationOverrides, donotNotifyError: boolean): Promise { + private writeConfigurationValue(key: string, value: any, target: ConfigurationTarget, overrides: IConfigurationOverrides | undefined, donotNotifyError: boolean): Promise { if (target === ConfigurationTarget.DEFAULT) { return Promise.reject(new Error('Invalid configuration target')); } if (target === ConfigurationTarget.MEMORY) { this._configuration.updateValue(key, value, overrides); - this.triggerConfigurationChange(new ConfigurationChangeEvent().change(overrides.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier)] : [key], overrides.resource), target); + this.triggerConfigurationChange(new ConfigurationChangeEvent().change(overrides && overrides.overrideIdentifier ? [keyFromOverrideIdentifier(overrides.overrideIdentifier)] : [key], overrides && overrides.resource || undefined), target); return Promise.resolve(undefined); } @@ -624,20 +618,20 @@ export class WorkspaceService extends Disposable implements IWorkspaceConfigurat .then(() => { switch (target) { case ConfigurationTarget.USER: - return this.reloadUserConfiguration(); + return this.reloadUserConfiguration().then(_ => Promise.resolve()); case ConfigurationTarget.WORKSPACE: return this.reloadWorkspaceConfiguration(); case ConfigurationTarget.WORKSPACE_FOLDER: const workspaceFolder = overrides && overrides.resource ? this.workspace.getFolder(overrides.resource) : null; if (workspaceFolder) { - return this.reloadWorkspaceFolderConfiguration(this.workspace.getFolder(overrides.resource), key).then(() => null); + return this.reloadWorkspaceFolderConfiguration(workspaceFolder, key); } } - return null; + return Promise.resolve(); }); } - private deriveConfigurationTarget(key: string, value: any, overrides: IConfigurationOverrides, target: ConfigurationTarget): ConfigurationTarget { + private deriveConfigurationTarget(key: string, value: any, overrides: IConfigurationOverrides | undefined, target: ConfigurationTarget): ConfigurationTarget | undefined { if (target) { return target; } @@ -688,7 +682,7 @@ interface IExportedConfigurationNode { name: string; description: string; default: any; - type: string | string[]; + type?: string | string[]; enum?: any[]; enumDescriptions?: string[]; } @@ -696,8 +690,8 @@ interface IExportedConfigurationNode { interface IConfigurationExport { settings: IExportedConfigurationNode[]; buildTime: number; - commit: string; - buildNumber: number; + commit?: string; + buildNumber?: number; } export class DefaultConfigurationExportHelper { diff --git a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts index a65e41bec4..55c92bf338 100644 --- a/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts +++ b/src/vs/workbench/services/configuration/test/common/configurationModels.test.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { join } from 'vs/base/common/paths'; +import { join } from 'vs/base/common/path'; import { Registry } from 'vs/platform/registry/common/platform'; import { FolderSettingsModelParser, WorkspaceConfigurationChangeEvent, StandaloneConfigurationModelParser, AllKeysConfigurationChangeEvent, Configuration } from 'vs/workbench/services/configuration/common/configurationModels'; import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts index 76897a71e7..740d2dcb18 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationEditingService.test.ts @@ -6,7 +6,7 @@ import * as sinon from 'sinon'; import * as assert from 'assert'; import * as os from 'os'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as fs from 'fs'; import * as json from 'vs/base/common/json'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -20,8 +20,8 @@ import { TestNotificationService } from 'vs/platform/notification/test/common/te import * as uuid from 'vs/base/common/uuid'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; -import { FileService } from 'vs/workbench/services/files/electron-browser/fileService'; -import { ConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/node/configurationEditingService'; +import { FileService } from 'vs/workbench/services/files/node/fileService'; +import { ConfigurationEditingService, ConfigurationEditingError, ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/common/configurationEditingService'; import { IFileService } from 'vs/platform/files/common/files'; import { WORKSPACE_STANDALONE_CONFIGURATIONS } from 'vs/workbench/services/configuration/common/configuration'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; diff --git a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts index 0c6be4ffe4..8c6e92b9b6 100644 --- a/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/electron-browser/configurationService.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import * as fs from 'fs'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as os from 'os'; import { URI } from 'vs/base/common/uri'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -18,23 +18,25 @@ import * as uuid from 'vs/base/common/uuid'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; import { ISingleFolderWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; -import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/node/configurationEditingService'; +import { ConfigurationEditingErrorCode } from 'vs/workbench/services/configuration/common/configurationEditingService'; import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace'; import { ConfigurationTarget, IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { workbenchInstantiationService, TestTextResourceConfigurationService, TestTextFileService, TestLifecycleService, TestEnvironmentService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; -import { FileService } from 'vs/workbench/services/files/electron-browser/fileService'; +import { FileService } from 'vs/workbench/services/files/node/fileService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { JSONEditingService } from 'vs/workbench/services/configuration/node/jsonEditingService'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; -import { Uri } from 'vscode'; +import { JSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditingService'; import { createHash } from 'crypto'; import { Emitter, Event } from 'vs/base/common/event'; +import { Schemas } from 'vs/base/common/network'; +import { originalFSPath } from 'vs/base/common/resources'; +import { isLinux } from 'vs/base/common/platform'; +import { IWorkspaceIdentifier } from 'vs/workbench/services/configuration/node/configuration'; class SettingsTestEnvironmentService extends EnvironmentService { @@ -58,14 +60,14 @@ function setUpFolder(folderName: string, parentDir: string): Promise { return Promise.resolve(pfs.mkdirp(workspaceSettingsDir, 493).then(() => folderDir)); } -function convertToWorkspacePayload(folder: Uri): ISingleFolderWorkspaceInitializationPayload { +function convertToWorkspacePayload(folder: URI): ISingleFolderWorkspaceInitializationPayload { return { id: createHash('md5').update(folder.fsPath).digest('hex'), folder } as ISingleFolderWorkspaceInitializationPayload; } -function setUpWorkspace(folders: string[]): Promise<{ parentDir: string, configPath: string }> { +function setUpWorkspace(folders: string[]): Promise<{ parentDir: string, configPath: URI }> { const id = uuid.generateUuid(); const parentDir = path.join(os.tmpdir(), 'vsctests', id); @@ -77,7 +79,7 @@ function setUpWorkspace(folders: string[]): Promise<{ parentDir: string, configP fs.writeFileSync(configPath, JSON.stringify(workspace, null, '\t')); return Promise.all(folders.map(folder => setUpFolder(folder, parentDir))) - .then(() => ({ parentDir, configPath })); + .then(() => ({ parentDir, configPath: URI.file(configPath) })); })); } @@ -89,3 +91,19 @@ suite('WorkspaceContextService - Folder', () => { assert.equal(0, 0); }); }); + +function getWorkspaceId(configPath: URI): string { + let workspaceConfigPath = configPath.scheme === Schemas.file ? originalFSPath(configPath) : configPath.toString(); + if (!isLinux) { + workspaceConfigPath = workspaceConfigPath.toLowerCase(); // sanitize for platform file system + } + + return createHash('md5').update(workspaceConfigPath).digest('hex'); +} + +export function getWorkspaceIdentifier(configPath: URI): IWorkspaceIdentifier { + return { + configPath, + id: getWorkspaceId(configPath) + }; +} diff --git a/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts similarity index 94% rename from src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts rename to src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index 808c3f7dfa..2768adbef3 100644 --- a/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -5,8 +5,7 @@ import { URI as uri } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; -import * as paths from 'vs/base/common/paths'; -import * as platform from 'vs/base/common/platform'; +import * as path from 'vs/base/common/path'; import * as Types from 'vs/base/common/types'; import { Schemas } from 'vs/base/common/network'; import { toResource } from 'vs/workbench/common/editor'; @@ -16,18 +15,20 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ICommandService } from 'vs/platform/commands/common/commands'; import { IWorkspaceFolder, IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/node/variableResolver'; +import { AbstractVariableResolverService } from 'vs/workbench/services/configurationResolver/common/variableResolver'; import { isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { IQuickInputService, IInputOptions, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput'; -import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { ConfiguredInput, IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class ConfigurationResolverService extends AbstractVariableResolverService { static INPUT_OR_COMMAND_VARIABLES_PATTERN = /\${((input|command):(.*?))}/g; constructor( - envVariables: platform.IProcessEnvironment, + @IWindowService windowService: IWindowService, @IEditorService editorService: IEditorService, @IEnvironmentService environmentService: IEnvironmentService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -58,7 +59,7 @@ export class ConfigurationResolverService extends AbstractVariableResolverServic if (!fileResource) { return undefined; } - return paths.normalize(fileResource.fsPath, true); + return path.normalize(fileResource.fsPath); }, getSelectedText: (): string | undefined => { const activeTextEditorWidget = editorService.activeTextEditorWidget; @@ -79,7 +80,7 @@ export class ConfigurationResolverService extends AbstractVariableResolverServic } return undefined; } - }, envVariables); + }, windowService.getConfiguration().userEnv); } public resolveWithInteractionReplace(folder: IWorkspaceFolder, config: any, section?: string, variables?: IStringDictionary): Promise { @@ -287,4 +288,6 @@ export class ConfigurationResolverService extends AbstractVariableResolverServic } return Promise.reject(new Error(nls.localize('inputVariable.undefinedVariable', "Undefined input variable '{0}' encountered. Remove or define '{0}' to continue.", variable))); } -} \ No newline at end of file +} + +registerSingleton(IConfigurationResolverService, ConfigurationResolverService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts index 1e26c42925..22b05e3037 100644 --- a/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts +++ b/src/vs/workbench/services/configurationResolver/common/configurationResolver.ts @@ -12,15 +12,15 @@ export const IConfigurationResolverService = createDecorator): IStringDictionary; + resolve(folder: IWorkspaceFolder | undefined, value: string): string; + resolve(folder: IWorkspaceFolder | undefined, value: string[]): string[]; + resolve(folder: IWorkspaceFolder | undefined, value: IStringDictionary): IStringDictionary; /** * Recursively resolves all variables in the given config and returns a copy of it with substituted values. * Command variables are only substituted if a "commandValueMapping" dictionary is given and if it contains an entry for the command. */ - resolveAny(folder: IWorkspaceFolder, config: any, commandValueMapping?: IStringDictionary): any; + resolveAny(folder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary): any; /** * Recursively resolves all variables (including commands and user input) in the given config and returns a copy of it with substituted values. @@ -29,13 +29,13 @@ export interface IConfigurationResolverService { * @param section For example, 'tasks' or 'debug'. Used for resolving inputs. * @param variables Aliases for commands. */ - resolveWithInteractionReplace(folder: IWorkspaceFolder, config: any, section?: string, variables?: IStringDictionary): Promise; + resolveWithInteractionReplace(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary): Promise; /** * Similar to resolveWithInteractionReplace, except without the replace. Returns a map of variables and their resolution. * Keys in the map will be of the format input:variableName or command:variableName. */ - resolveWithInteraction(folder: IWorkspaceFolder, config: any, section?: string, variables?: IStringDictionary): Promise>; + resolveWithInteraction(folder: IWorkspaceFolder | undefined, config: any, section?: string, variables?: IStringDictionary): Promise>; } export interface PromptStringInputInfo { diff --git a/src/vs/workbench/services/configurationResolver/common/variableResolver.ts b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts new file mode 100644 index 0000000000..41a379af10 --- /dev/null +++ b/src/vs/workbench/services/configurationResolver/common/variableResolver.ts @@ -0,0 +1,277 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as paths from 'vs/base/common/path'; +import * as process from 'vs/base/common/process'; +import * as types from 'vs/base/common/types'; +import * as objects from 'vs/base/common/objects'; +import { IStringDictionary } from 'vs/base/common/collections'; +import { IProcessEnvironment, isWindows, isMacintosh, isLinux } from 'vs/base/common/platform'; +import { normalizeDriveLetter } from 'vs/base/common/labels'; +import { localize } from 'vs/nls'; +import { URI as uri } from 'vs/base/common/uri'; +import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; + +export interface IVariableResolveContext { + getFolderUri(folderName: string): uri | undefined; + getWorkspaceFolderCount(): number; + getConfigurationValue(folderUri: uri, section: string): string | undefined; + getExecPath(): string | undefined; + getFilePath(): string | undefined; + getSelectedText(): string | undefined; + getLineNumber(): string | undefined; +} + +export class AbstractVariableResolverService implements IConfigurationResolverService { + + static VARIABLE_REGEXP = /\$\{(.*?)\}/g; + + _serviceBrand: any; + + constructor( + private _context: IVariableResolveContext, + private _envVariables: IProcessEnvironment + ) { + if (isWindows) { + this._envVariables = Object.create(null); + Object.keys(_envVariables).forEach(key => { + this._envVariables[key.toLowerCase()] = _envVariables[key]; + }); + } + } + + public resolve(root: IWorkspaceFolder | undefined, value: string): string; + public resolve(root: IWorkspaceFolder | undefined, value: string[]): string[]; + public resolve(root: IWorkspaceFolder | undefined, value: IStringDictionary): IStringDictionary; + public resolve(root: IWorkspaceFolder | undefined, value: any): any { + return this.recursiveResolve(root ? root.uri : undefined, value); + } + + public resolveAnyBase(workspaceFolder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary, resolvedVariables?: Map): any { + + const result = objects.deepClone(config) as any; + + // hoist platform specific attributes to top level + if (isWindows && result.windows) { + Object.keys(result.windows).forEach(key => result[key] = result.windows[key]); + } else if (isMacintosh && result.osx) { + Object.keys(result.osx).forEach(key => result[key] = result.osx[key]); + } else if (isLinux && result.linux) { + Object.keys(result.linux).forEach(key => result[key] = result.linux[key]); + } + + // delete all platform specific sections + delete result.windows; + delete result.osx; + delete result.linux; + + // substitute all variables recursively in string values + return this.recursiveResolve(workspaceFolder ? workspaceFolder.uri : undefined, result, commandValueMapping, resolvedVariables); + } + + public resolveAny(workspaceFolder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary): any { + return this.resolveAnyBase(workspaceFolder, config, commandValueMapping); + } + + public resolveAnyMap(workspaceFolder: IWorkspaceFolder | undefined, config: any, commandValueMapping?: IStringDictionary): { newConfig: any, resolvedVariables: Map } { + const resolvedVariables = new Map(); + const newConfig = this.resolveAnyBase(workspaceFolder, config, commandValueMapping, resolvedVariables); + return { newConfig, resolvedVariables }; + } + + public resolveWithInteractionReplace(folder: IWorkspaceFolder, config: any): Promise { + throw new Error('resolveWithInteractionReplace not implemented.'); + } + + public resolveWithInteraction(folder: IWorkspaceFolder, config: any): Promise { + throw new Error('resolveWithInteraction not implemented.'); + } + + private recursiveResolve(folderUri: uri | undefined, value: any, commandValueMapping?: IStringDictionary, resolvedVariables?: Map): any { + if (types.isString(value)) { + return this.resolveString(folderUri, value, commandValueMapping, resolvedVariables); + } else if (types.isArray(value)) { + return value.map(s => this.recursiveResolve(folderUri, s, commandValueMapping, resolvedVariables)); + } else if (types.isObject(value)) { + let result: IStringDictionary | string[]> = Object.create(null); + Object.keys(value).forEach(key => { + const replaced = this.resolveString(folderUri, key, commandValueMapping, resolvedVariables); + result[replaced] = this.recursiveResolve(folderUri, value[key], commandValueMapping, resolvedVariables); + }); + return result; + } + return value; + } + + private resolveString(folderUri: uri | undefined, value: string, commandValueMapping: IStringDictionary | undefined, resolvedVariables?: Map): string { + + // loop through all variables occurrences in 'value' + const replaced = value.replace(AbstractVariableResolverService.VARIABLE_REGEXP, (match: string, variable: string) => { + + let resolvedValue = this.evaluateSingleVariable(match, variable, folderUri, commandValueMapping); + + if (resolvedVariables) { + resolvedVariables.set(variable, resolvedValue); + } + + return resolvedValue; + }); + + return replaced; + } + + private evaluateSingleVariable(match: string, variable: string, folderUri: uri | undefined, commandValueMapping: IStringDictionary | undefined): string { + + // try to separate variable arguments from variable name + let argument: string | undefined; + const parts = variable.split(':'); + if (parts.length > 1) { + variable = parts[0]; + argument = parts[1]; + } + + // common error handling for all variables that require an open editor + const getFilePath = (): string => { + + const filePath = this._context.getFilePath(); + if (filePath) { + return filePath; + } + throw new Error(localize('canNotResolveFile', "'{0}' can not be resolved. Please open an editor.", match)); + }; + + // common error handling for all variables that require an open folder and accept a folder name argument + const getFolderUri = (withArg = true): uri => { + + if (withArg && argument) { + const folder = this._context.getFolderUri(argument); + if (folder) { + return folder; + } + throw new Error(localize('canNotFindFolder', "'{0}' can not be resolved. No such folder '{1}'.", match, argument)); + } + + if (folderUri) { + return folderUri; + } + + if (this._context.getWorkspaceFolderCount() > 1) { + throw new Error(localize('canNotResolveWorkspaceFolderMultiRoot', "'{0}' can not be resolved in a multi folder workspace. Scope this variable using ':' and a workspace folder name.", match)); + } + throw new Error(localize('canNotResolveWorkspaceFolder', "'{0}' can not be resolved. Please open a folder.", match)); + }; + + + switch (variable) { + + case 'env': + if (argument) { + if (isWindows) { + argument = argument.toLowerCase(); + } + const env = this._envVariables[argument]; + if (types.isString(env)) { + return env; + } + // For `env` we should do the same as a normal shell does - evaluates missing envs to an empty string #46436 + return ''; + } + throw new Error(localize('missingEnvVarName', "'{0}' can not be resolved because no environment variable name is given.", match)); + + case 'config': + if (argument) { + const config = this._context.getConfigurationValue(getFolderUri(false), argument); + if (types.isUndefinedOrNull(config)) { + throw new Error(localize('configNotFound', "'{0}' can not be resolved because setting '{1}' not found.", match, argument)); + } + if (types.isObject(config)) { + throw new Error(localize('configNoString', "'{0}' can not be resolved because '{1}' is a structured value.", match, argument)); + } + return config; + } + throw new Error(localize('missingConfigName', "'{0}' can not be resolved because no settings name is given.", match)); + + case 'command': + return this.resolveFromMap(match, argument, commandValueMapping, 'command'); + + case 'input': + return this.resolveFromMap(match, argument, commandValueMapping, 'input'); + + default: { + + switch (variable) { + case 'workspaceRoot': + case 'workspaceFolder': + return normalizeDriveLetter(getFolderUri().fsPath); + + case 'cwd': + return (folderUri ? normalizeDriveLetter(getFolderUri().fsPath) : process.cwd()); + + case 'workspaceRootFolderName': + case 'workspaceFolderBasename': + return paths.basename(getFolderUri().fsPath); + + case 'lineNumber': + const lineNumber = this._context.getLineNumber(); + if (lineNumber) { + return lineNumber; + } + throw new Error(localize('canNotResolveLineNumber', "'{0}' can not be resolved. Make sure to have a line selected in the active editor.", match)); + + case 'selectedText': + const selectedText = this._context.getSelectedText(); + if (selectedText) { + return selectedText; + } + throw new Error(localize('canNotResolveSelectedText', "'{0}' can not be resolved. Make sure to have some text selected in the active editor.", match)); + + case 'file': + return getFilePath(); + + case 'relativeFile': + if (folderUri) { + return paths.normalize(paths.relative(getFolderUri().fsPath, getFilePath())); + } + return getFilePath(); + + case 'fileDirname': + return paths.dirname(getFilePath()); + + case 'fileExtname': + return paths.extname(getFilePath()); + + case 'fileBasename': + return paths.basename(getFilePath()); + + case 'fileBasenameNoExtension': + const basename = paths.basename(getFilePath()); + return (basename.slice(0, basename.length - paths.extname(basename).length)); + + case 'execPath': + const ep = this._context.getExecPath(); + if (ep) { + return ep; + } + return match; + + default: + return match; + } + } + } + } + + private resolveFromMap(match: string, argument: string | undefined, commandValueMapping: IStringDictionary | undefined, prefix: string): string { + if (argument && commandValueMapping) { + const v = commandValueMapping[prefix + ':' + argument]; + if (typeof v === 'string') { + return v; + } + throw new Error(localize('noValueForCommand', "'{0}' can not be resolved because the command has no value.", match)); + } + return match; + } +} diff --git a/src/vs/workbench/services/configurationResolver/node/variableResolver.ts b/src/vs/workbench/services/configurationResolver/node/variableResolver.ts deleted file mode 100644 index 16a81003ea..0000000000 --- a/src/vs/workbench/services/configurationResolver/node/variableResolver.ts +++ /dev/null @@ -1,283 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as paths from 'vs/base/common/paths'; -import * as types from 'vs/base/common/types'; -import * as objects from 'vs/base/common/objects'; -import { IStringDictionary } from 'vs/base/common/collections'; -import { relative } from 'path'; -import { IProcessEnvironment, isWindows, isMacintosh, isLinux } from 'vs/base/common/platform'; -import { normalizeDriveLetter } from 'vs/base/common/labels'; -import { localize } from 'vs/nls'; -import { URI as uri } from 'vs/base/common/uri'; -import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; - -export interface IVariableResolveContext { - getFolderUri(folderName: string): uri | undefined; - getWorkspaceFolderCount(): number; - getConfigurationValue(folderUri: uri, section: string): string | undefined; - getExecPath(): string | undefined; - getFilePath(): string | undefined; - getSelectedText(): string | undefined; - getLineNumber(): string; -} - -export class AbstractVariableResolverService implements IConfigurationResolverService { - - static VARIABLE_REGEXP = /\$\{(.*?)\}/g; - - _serviceBrand: any; - - constructor( - private _context: IVariableResolveContext, - private _envVariables: IProcessEnvironment = process.env - ) { - if (isWindows) { - this._envVariables = Object.create(null); - Object.keys(_envVariables).forEach(key => { - this._envVariables[key.toLowerCase()] = _envVariables[key]; - }); - } - } - - public resolve(root: IWorkspaceFolder, value: string): string; - public resolve(root: IWorkspaceFolder, value: string[]): string[]; - public resolve(root: IWorkspaceFolder, value: IStringDictionary): IStringDictionary; - public resolve(root: IWorkspaceFolder, value: any): any { - return this.recursiveResolve(root ? root.uri : undefined, value); - } - - public resolveAnyBase(workspaceFolder: IWorkspaceFolder, config: any, commandValueMapping?: IStringDictionary, resolvedVariables?: Map): any { - - const result = objects.deepClone(config) as any; - - // hoist platform specific attributes to top level - if (isWindows && result.windows) { - Object.keys(result.windows).forEach(key => result[key] = result.windows[key]); - } else if (isMacintosh && result.osx) { - Object.keys(result.osx).forEach(key => result[key] = result.osx[key]); - } else if (isLinux && result.linux) { - Object.keys(result.linux).forEach(key => result[key] = result.linux[key]); - } - - // delete all platform specific sections - delete result.windows; - delete result.osx; - delete result.linux; - - // substitute all variables recursively in string values - return this.recursiveResolve(workspaceFolder ? workspaceFolder.uri : undefined, result, commandValueMapping, resolvedVariables); - } - - public resolveAny(workspaceFolder: IWorkspaceFolder, config: any, commandValueMapping?: IStringDictionary): any { - return this.resolveAnyBase(workspaceFolder, config, commandValueMapping); - } - - public resolveAnyMap(workspaceFolder: IWorkspaceFolder, config: any, commandValueMapping?: IStringDictionary): { newConfig: any, resolvedVariables: Map } { - const resolvedVariables = new Map(); - const newConfig = this.resolveAnyBase(workspaceFolder, config, commandValueMapping, resolvedVariables); - return { newConfig, resolvedVariables }; - } - - public resolveWithInteractionReplace(folder: IWorkspaceFolder, config: any): Promise { - throw new Error('resolveWithInteractionReplace not implemented.'); - } - - public resolveWithInteraction(folder: IWorkspaceFolder, config: any): Promise { - throw new Error('resolveWithInteraction not implemented.'); - } - - private recursiveResolve(folderUri: uri, value: any, commandValueMapping?: IStringDictionary, resolvedVariables?: Map): any { - if (types.isString(value)) { - const resolved = this.resolveString(folderUri, value, commandValueMapping); - if (resolvedVariables) { - resolvedVariables.set(resolved.variableName, resolved.resolvedValue); - } - return resolved.replaced; - } else if (types.isArray(value)) { - return value.map(s => this.recursiveResolve(folderUri, s, commandValueMapping, resolvedVariables)); - } else if (types.isObject(value)) { - let result: IStringDictionary | string[]> = Object.create(null); - Object.keys(value).forEach(key => { - const resolvedKey = this.resolveString(folderUri, key, commandValueMapping); - if (resolvedVariables) { - resolvedVariables.set(resolvedKey.variableName, resolvedKey.resolvedValue); - } - result[resolvedKey.replaced] = this.recursiveResolve(folderUri, value[key], commandValueMapping, resolvedVariables); - }); - return result; - } - return value; - } - - private resolveString(folderUri: uri, value: string, commandValueMapping: IStringDictionary): { replaced: string, variableName: string, resolvedValue: string } { - - const filePath = this._context.getFilePath(); - let variableName: string; - let resolvedValue: string; - const replaced = value.replace(AbstractVariableResolverService.VARIABLE_REGEXP, (match: string, variable: string) => { - - variableName = variable; - let argument: string; - const parts = variable.split(':'); - if (parts && parts.length > 1) { - variable = parts[0]; - argument = parts[1]; - } - - switch (variable) { - - case 'env': - if (argument) { - if (isWindows) { - argument = argument.toLowerCase(); - } - const env = this._envVariables[argument]; - if (types.isString(env)) { - return resolvedValue = env; - } - // For `env` we should do the same as a normal shell does - evaluates missing envs to an empty string #46436 - return resolvedValue = ''; - } - throw new Error(localize('missingEnvVarName', "'{0}' can not be resolved because no environment variable name is given.", match)); - - case 'config': - if (argument) { - const config = this._context.getConfigurationValue(folderUri, argument); - if (types.isUndefinedOrNull(config)) { - throw new Error(localize('configNotFound', "'{0}' can not be resolved because setting '{1}' not found.", match, argument)); - } - if (types.isObject(config)) { - throw new Error(localize('configNoString', "'{0}' can not be resolved because '{1}' is a structured value.", match, argument)); - } - return resolvedValue = config; - } - throw new Error(localize('missingConfigName', "'{0}' can not be resolved because no settings name is given.", match)); - - case 'command': - return resolvedValue = this.resolveFromMap(match, argument, commandValueMapping, 'command'); - case 'input': - return resolvedValue = this.resolveFromMap(match, argument, commandValueMapping, 'input'); - - default: { - - // common error handling for all variables that require an open folder and accept a folder name argument - switch (variable) { - case 'workspaceRoot': - case 'workspaceFolder': - case 'workspaceRootFolderName': - case 'workspaceFolderBasename': - case 'relativeFile': - if (argument) { - const folder = this._context.getFolderUri(argument); - if (folder) { - folderUri = folder; - } else { - throw new Error(localize('canNotFindFolder', "'{0}' can not be resolved. No such folder '{1}'.", match, argument)); - } - } - if (!folderUri) { - if (this._context.getWorkspaceFolderCount() > 1) { - throw new Error(localize('canNotResolveWorkspaceFolderMultiRoot', "'{0}' can not be resolved in a multi folder workspace. Scope this variable using ':' and a workspace folder name.", match)); - } - throw new Error(localize('canNotResolveWorkspaceFolder', "'{0}' can not be resolved. Please open a folder.", match)); - } - break; - default: - break; - } - - // common error handling for all variables that require an open file - switch (variable) { - case 'file': - case 'relativeFile': - case 'fileDirname': - case 'fileExtname': - case 'fileBasename': - case 'fileBasenameNoExtension': - if (!filePath) { - throw new Error(localize('canNotResolveFile', "'{0}' can not be resolved. Please open an editor.", match)); - } - break; - default: - break; - } - - switch (variable) { - case 'workspaceRoot': - case 'workspaceFolder': - return resolvedValue = normalizeDriveLetter(folderUri.fsPath); - - case 'cwd': - return resolvedValue = (folderUri ? normalizeDriveLetter(folderUri.fsPath) : process.cwd()); - - case 'workspaceRootFolderName': - case 'workspaceFolderBasename': - return resolvedValue = paths.basename(folderUri.fsPath); - - case 'lineNumber': - const lineNumber = this._context.getLineNumber(); - if (lineNumber) { - return resolvedValue = lineNumber; - } - throw new Error(localize('canNotResolveLineNumber', "'{0}' can not be resolved. Make sure to have a line selected in the active editor.", match)); - - case 'selectedText': - const selectedText = this._context.getSelectedText(); - if (selectedText) { - return resolvedValue = selectedText; - } - throw new Error(localize('canNotResolveSelectedText', "'{0}' can not be resolved. Make sure to have some text selected in the active editor.", match)); - - case 'file': - return resolvedValue = filePath; - - case 'relativeFile': - if (folderUri) { - return resolvedValue = paths.normalize(relative(folderUri.fsPath, filePath)); - } - return resolvedValue = filePath; - - case 'fileDirname': - return resolvedValue = paths.dirname(filePath); - - case 'fileExtname': - return resolvedValue = paths.extname(filePath); - - case 'fileBasename': - return resolvedValue = paths.basename(filePath); - - case 'fileBasenameNoExtension': - const basename = paths.basename(filePath); - return resolvedValue = (basename.slice(0, basename.length - paths.extname(basename).length)); - - case 'execPath': - const ep = this._context.getExecPath(); - if (ep) { - return resolvedValue = ep; - } - return resolvedValue = match; - - default: - return resolvedValue = match; - } - } - } - }); - return { replaced, variableName, resolvedValue }; - } - - private resolveFromMap(match: string, argument: string, commandValueMapping: IStringDictionary, prefix: string): string { - if (argument && commandValueMapping) { - const v = commandValueMapping[prefix + ':' + argument]; - if (typeof v === 'string') { - return v; - } - throw new Error(localize('noValueForCommand', "'{0}' can not be resolved because the command has no value.", match)); - } - return match; - } -} diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index f7c0f97223..c1fab09cb6 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -9,18 +9,20 @@ import * as platform from 'vs/base/common/platform'; import { IConfigurationService, getConfigurationValue, IConfigurationOverrides } from 'vs/platform/configuration/common/configuration'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; -import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/electron-browser/configurationResolverService'; +import { ConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { TestEnvironmentService, TestEditorService, TestContextService } from 'vs/workbench/test/workbenchTestServices'; +import { TestEnvironmentService, TestEditorService, TestContextService, TestWindowService } from 'vs/workbench/test/workbenchTestServices'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { Disposable } from 'vs/base/common/lifecycle'; import { IQuickInputService, IQuickPickItem, QuickPickInput, IPickOptions, Omit, IInputOptions, IQuickInputButton, IQuickPick, IInputBox, IQuickNavigateConfiguration } from 'vs/platform/quickinput/common/quickInput'; -import { CancellationToken } from 'vscode'; +import { CancellationToken } from 'vs/base/common/cancellation'; import * as Types from 'vs/base/common/types'; +import { IWindowService, IWindowConfiguration } from 'vs/platform/windows/common/windows'; suite('Configuration Resolver Service', () => { - let configurationResolverService: IConfigurationResolverService; + let configurationResolverService: IConfigurationResolverService | null; let envVariables: { [key: string]: string } = { key1: 'Value for key1', key2: 'Value for key2' }; + let windowService: IWindowService; let mockCommandService: MockCommandService; let editorService: TestEditorService; let workspace: IWorkspaceFolder; @@ -30,13 +32,14 @@ suite('Configuration Resolver Service', () => { mockCommandService = new MockCommandService(); editorService = new TestEditorService(); quickInputService = new MockQuickInputService(); + windowService = new MockWindowService(envVariables); workspace = { uri: uri.parse('file:///VSCode/workspaceLocation'), name: 'hey', index: 0, toResource: () => null }; - configurationResolverService = new ConfigurationResolverService(envVariables, editorService, TestEnvironmentService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(), quickInputService); + configurationResolverService = new ConfigurationResolverService(windowService, editorService, TestEnvironmentService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(), quickInputService); }); teardown(() => { @@ -45,46 +48,46 @@ suite('Configuration Resolver Service', () => { test('substitute one', () => { if (platform.isWindows) { - assert.strictEqual(configurationResolverService.resolve(workspace, 'abc ${workspaceFolder} xyz'), 'abc \\VSCode\\workspaceLocation xyz'); + assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${workspaceFolder} xyz'), 'abc \\VSCode\\workspaceLocation xyz'); } else { - assert.strictEqual(configurationResolverService.resolve(workspace, 'abc ${workspaceFolder} xyz'), 'abc /VSCode/workspaceLocation xyz'); + assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${workspaceFolder} xyz'), 'abc /VSCode/workspaceLocation xyz'); } }); test('workspace root folder name', () => { - assert.strictEqual(configurationResolverService.resolve(workspace, 'abc ${workspaceRootFolderName} xyz'), 'abc workspaceLocation xyz'); + assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${workspaceRootFolderName} xyz'), 'abc workspaceLocation xyz'); }); // TODO@isidor mock the editor service properly // test('current selected line number', () => { - // assert.strictEqual(configurationResolverService.resolve(workspace, 'abc ${lineNumber} xyz'), `abc ${editorService.mockLineNumber} xyz`); + // assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${lineNumber} xyz'), `abc ${editorService.mockLineNumber} xyz`); // }); // test('current selected text', () => { - // assert.strictEqual(configurationResolverService.resolve(workspace, 'abc ${selectedText} xyz'), `abc ${editorService.mockSelectedText} xyz`); + // assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${selectedText} xyz'), `abc ${editorService.mockSelectedText} xyz`); // }); test('substitute many', () => { if (platform.isWindows) { - assert.strictEqual(configurationResolverService.resolve(workspace, '${workspaceFolder} - ${workspaceFolder}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation'); + assert.strictEqual(configurationResolverService!.resolve(workspace, '${workspaceFolder} - ${workspaceFolder}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation'); } else { - assert.strictEqual(configurationResolverService.resolve(workspace, '${workspaceFolder} - ${workspaceFolder}'), '/VSCode/workspaceLocation - /VSCode/workspaceLocation'); + assert.strictEqual(configurationResolverService!.resolve(workspace, '${workspaceFolder} - ${workspaceFolder}'), '/VSCode/workspaceLocation - /VSCode/workspaceLocation'); } }); test('substitute one env variable', () => { if (platform.isWindows) { - assert.strictEqual(configurationResolverService.resolve(workspace, 'abc ${workspaceFolder} ${env:key1} xyz'), 'abc \\VSCode\\workspaceLocation Value for key1 xyz'); + assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${workspaceFolder} ${env:key1} xyz'), 'abc \\VSCode\\workspaceLocation Value for key1 xyz'); } else { - assert.strictEqual(configurationResolverService.resolve(workspace, 'abc ${workspaceFolder} ${env:key1} xyz'), 'abc /VSCode/workspaceLocation Value for key1 xyz'); + assert.strictEqual(configurationResolverService!.resolve(workspace, 'abc ${workspaceFolder} ${env:key1} xyz'), 'abc /VSCode/workspaceLocation Value for key1 xyz'); } }); test('substitute many env variable', () => { if (platform.isWindows) { - assert.strictEqual(configurationResolverService.resolve(workspace, '${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2'); + assert.strictEqual(configurationResolverService!.resolve(workspace, '${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2'); } else { - assert.strictEqual(configurationResolverService.resolve(workspace, '${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), '/VSCode/workspaceLocation - /VSCode/workspaceLocation Value for key1 - Value for key2'); + assert.strictEqual(configurationResolverService!.resolve(workspace, '${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), '/VSCode/workspaceLocation - /VSCode/workspaceLocation Value for key1 - Value for key2'); } }); @@ -93,7 +96,7 @@ suite('Configuration Resolver Service', () => { // '${workspaceRootFolderName}': '${lineNumber}', // 'hey ${env:key1} ': '${workspaceRootFolderName}' // }; - // assert.deepEqual(configurationResolverService.resolve(workspace, myObject), { + // assert.deepEqual(configurationResolverService!.resolve(workspace, myObject), { // 'workspaceLocation': `${editorService.mockLineNumber}`, // 'hey Value for key1 ': 'workspaceLocation' // }); @@ -102,15 +105,14 @@ suite('Configuration Resolver Service', () => { test('substitute one env variable using platform case sensitivity', () => { if (platform.isWindows) { - assert.strictEqual(configurationResolverService.resolve(workspace, '${env:key1} - ${env:Key1}'), 'Value for key1 - Value for key1'); + assert.strictEqual(configurationResolverService!.resolve(workspace, '${env:key1} - ${env:Key1}'), 'Value for key1 - Value for key1'); } else { - assert.strictEqual(configurationResolverService.resolve(workspace, '${env:key1} - ${env:Key1}'), 'Value for key1 - '); + assert.strictEqual(configurationResolverService!.resolve(workspace, '${env:key1} - ${env:Key1}'), 'Value for key1 - '); } }); test('substitute one configuration variable', () => { - let configurationService: IConfigurationService; - configurationService = new MockConfigurationService({ + let configurationService: IConfigurationService = new MockConfigurationService({ editor: { fontFamily: 'foo' }, @@ -121,7 +123,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new ConfigurationResolverService(windowService, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz'); }); @@ -138,7 +140,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new ConfigurationResolverService(windowService, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} xyz'), 'abc foo bar xyz'); }); @@ -155,7 +157,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new ConfigurationResolverService(windowService, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); if (platform.isWindows) { assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo \\VSCode\\workspaceLocation Value for key1 xyz'); } else { @@ -176,7 +178,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new ConfigurationResolverService(windowService, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); if (platform.isWindows) { assert.strictEqual(service.resolve(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar \\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2'); } else { @@ -210,7 +212,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new ConfigurationResolverService(windowService, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${config:editor.lineNumbers} ${config:editor.insertSpaces} xyz'), 'abc foo 123 false xyz'); }); @@ -220,7 +222,7 @@ suite('Configuration Resolver Service', () => { editor: {} }); - let service = new ConfigurationResolverService(envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new ConfigurationResolverService(windowService, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); assert.strictEqual(service.resolve(workspace, 'abc ${unknownVariable} xyz'), 'abc ${unknownVariable} xyz'); assert.strictEqual(service.resolve(workspace, 'abc ${env:unknownVariable} xyz'), 'abc xyz'); }); @@ -233,7 +235,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new ConfigurationResolverService(envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); + let service = new ConfigurationResolverService(windowService, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService, new TestContextService(), quickInputService); assert.throws(() => service.resolve(workspace, 'abc ${env} xyz')); assert.throws(() => service.resolve(workspace, 'abc ${env:} xyz')); @@ -256,7 +258,7 @@ suite('Configuration Resolver Service', () => { 'outDir': null }; - return configurationResolverService.resolveWithInteractionReplace(undefined, configuration).then(result => { + return configurationResolverService!.resolveWithInteractionReplace(undefined, configuration).then(result => { assert.deepEqual(result, { 'name': 'Attach to Process', @@ -285,7 +287,7 @@ suite('Configuration Resolver Service', () => { const commandVariables = Object.create(null); commandVariables['commandVariable1'] = 'command1'; - return configurationResolverService.resolveWithInteractionReplace(undefined, configuration, undefined, commandVariables).then(result => { + return configurationResolverService!.resolveWithInteractionReplace(undefined, configuration, undefined, commandVariables).then(result => { assert.deepEqual(result, { 'name': 'Attach to Process', @@ -318,7 +320,7 @@ suite('Configuration Resolver Service', () => { const commandVariables = Object.create(null); commandVariables['commandVariable1'] = 'command1'; - return configurationResolverService.resolveWithInteractionReplace(undefined, configuration, undefined, commandVariables).then(result => { + return configurationResolverService!.resolveWithInteractionReplace(undefined, configuration, undefined, commandVariables).then(result => { assert.deepEqual(result, { 'name': 'Attach to Process', @@ -349,7 +351,7 @@ suite('Configuration Resolver Service', () => { const commandVariables = Object.create(null); commandVariables['commandVariable1'] = 'command1'; - return configurationResolverService.resolveWithInteractionReplace(undefined, configuration, undefined, commandVariables).then(result => { + return configurationResolverService!.resolveWithInteractionReplace(undefined, configuration, undefined, commandVariables).then(result => { assert.deepEqual(result, { 'name': 'Attach to Process', @@ -374,7 +376,7 @@ suite('Configuration Resolver Service', () => { 'outDir': null }; - return configurationResolverService.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => { + return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => { assert.deepEqual(result, { 'name': 'Attach to Process', @@ -401,7 +403,7 @@ suite('Configuration Resolver Service', () => { 'outDir': null }; - return configurationResolverService.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => { + return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => { assert.deepEqual(result, { 'name': 'Attach to Process', @@ -428,7 +430,7 @@ suite('Configuration Resolver Service', () => { 'outDir': null }; - return configurationResolverService.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => { + return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => { assert.deepEqual(result, { 'name': 'Attach to Process', @@ -456,7 +458,7 @@ suite('Configuration Resolver Service', () => { 'outDir': null }; - return configurationResolverService.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => { + return configurationResolverService!.resolveWithInteractionReplace(workspace, configuration, 'tasks').then(result => { assert.deepEqual(result, { 'name': 'resolvedEnterinput3', @@ -490,15 +492,15 @@ class MockConfigurationService implements IConfigurationService { const valuePath = (value).split('.'); let object = this.configuration; while (valuePath.length && object) { - object = object[valuePath.shift()]; + object = object[valuePath.shift()!]; } return object; } - public updateValue(): Promise { return null; } + public updateValue(): Promise { return Promise.resolve(); } public getConfigurationData(): any { return null; } public onDidChangeConfiguration() { return { dispose() { } }; } - public reloadConfiguration() { return null; } + public reloadConfiguration() { return Promise.resolve(); } } class MockCommandService implements ICommandService { @@ -612,3 +614,14 @@ class MockInputsConfigurationService extends TestConfigurationService { return configuration; } } + +class MockWindowService extends TestWindowService { + + constructor(private env: platform.IProcessEnvironment) { + super(); + } + + getConfiguration(): IWindowConfiguration { + return { userEnv: this.env } as IWindowConfiguration; + } +} diff --git a/src/vs/workbench/services/contextview/electron-browser/contextmenuService.ts b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts similarity index 71% rename from src/vs/workbench/services/contextview/electron-browser/contextmenuService.ts rename to src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts index 7dc5bec5b4..e21f605df9 100644 --- a/src/vs/workbench/services/contextview/electron-browser/contextmenuService.ts +++ b/src/vs/workbench/services/contextmenu/electron-browser/contextmenuService.ts @@ -6,7 +6,7 @@ import { IAction, IActionRunner, ActionRunner } from 'vs/base/common/actions'; import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import * as dom from 'vs/base/browser/dom'; -import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { webFrame } from 'electron'; @@ -18,11 +18,55 @@ import { once } from 'vs/base/common/functional'; import { Disposable } from 'vs/base/common/lifecycle'; import { IContextMenuItem } from 'vs/base/parts/contextmenu/common/contextmenu'; import { popup } from 'vs/base/parts/contextmenu/electron-browser/contextmenu'; +import { getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { isMacintosh } from 'vs/base/common/platform'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ContextMenuService as HTMLContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; export class ContextMenuService extends Disposable implements IContextMenuService { _serviceBrand: any; + get onDidContextMenu(): Event { return this.impl.onDidContextMenu; } + + private impl: IContextMenuService; + + constructor( + @INotificationService notificationService: INotificationService, + @ITelemetryService telemetryService: ITelemetryService, + @IKeybindingService keybindingService: IKeybindingService, + @IConfigurationService configurationService: IConfigurationService, + @IEnvironmentService environmentService: IEnvironmentService, + @IContextViewService contextViewService: IContextViewService, + @IThemeService themeService: IThemeService, + @ILayoutService layoutService: ILayoutService + ) { + super(); + + // Custom context menu: Linux/Windows if custom title is enabled + if (!isMacintosh && getTitleBarStyle(configurationService, environmentService) === 'custom') { + this.impl = new HTMLContextMenuService(layoutService, telemetryService, notificationService, contextViewService, keybindingService, themeService); + } + + // Native context menu: otherwise + else { + this.impl = new NativeContextMenuService(notificationService, telemetryService, keybindingService); + } + } + + showContextMenu(delegate: IContextMenuDelegate): void { + this.impl.showContextMenu(delegate); + } +} + +class NativeContextMenuService extends Disposable implements IContextMenuService { + + _serviceBrand: any; + private _onDidContextMenu = this._register(new Emitter()); get onDidContextMenu(): Event { return this._onDidContextMenu.event; } @@ -145,3 +189,5 @@ export class ContextMenuService extends Disposable implements IContextMenuServic res.then(undefined, e => this.notificationService.error(e)); } } + +registerSingleton(IContextMenuService, ContextMenuService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts b/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts deleted file mode 100644 index da5ac6e7a1..0000000000 --- a/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts +++ /dev/null @@ -1,126 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { assign, deepClone } from 'vs/base/common/objects'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { crashReporter } from 'electron'; -import product from 'vs/platform/node/product'; -import pkg from 'vs/platform/node/package'; -import * as os from 'os'; -import { isWindows, isMacintosh, isLinux } from 'vs/base/common/platform'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IConfigurationRegistry, Extensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { Registry } from 'vs/platform/registry/common/platform'; - -export const ICrashReporterService = createDecorator('crashReporterService'); - -export const TELEMETRY_SECTION_ID = 'telemetry'; - -export interface ICrashReporterConfig { - enableCrashReporter: boolean; -} - -const configurationRegistry = Registry.as(Extensions.Configuration); -configurationRegistry.registerConfiguration({ - 'id': TELEMETRY_SECTION_ID, - 'order': 110, - title: nls.localize('telemetryConfigurationTitle', "Telemetry"), - 'type': 'object', - 'properties': { - 'telemetry.enableCrashReporter': { - 'type': 'boolean', - 'description': nls.localize('telemetry.enableCrashReporting', "Enable crash reports to be sent to a Microsoft online service. \nThis option requires restart to take effect."), - 'default': true, - 'tags': ['usesOnlineServices'] - } - } -}); - -export interface ICrashReporterService { - _serviceBrand: any; - getChildProcessStartOptions(processName: string): Electron.CrashReporterStartOptions | undefined; // TODO -} - -export const NullCrashReporterService: ICrashReporterService = { - _serviceBrand: undefined, - getChildProcessStartOptions(processName: string) { return undefined; } -}; - -export class CrashReporterService implements ICrashReporterService { - - _serviceBrand: any; - - private options: Electron.CrashReporterStartOptions; - private isEnabled: boolean; - - constructor( - @ITelemetryService private readonly telemetryService: ITelemetryService, - @IWindowsService private readonly windowsService: IWindowsService, - @IConfigurationService configurationService: IConfigurationService - ) { - const config = configurationService.getValue(TELEMETRY_SECTION_ID); - this.isEnabled = !!config.enableCrashReporter; - - if (this.isEnabled) { - this.startCrashReporter(); - } - } - - private startCrashReporter(): void { - - // base options with product info - this.options = { - companyName: product.crashReporter.companyName, - productName: product.crashReporter.productName, - submitURL: this.getSubmitURL(), - extra: { - vscode_version: pkg.version, - vscode_commit: product.commit - } - }; - - // mixin telemetry info - this.telemetryService.getTelemetryInfo() - .then(info => { - assign(this.options.extra, { - vscode_sessionId: info.sessionId - }); - - // start crash reporter right here - crashReporter.start(deepClone(this.options)); - - // start crash reporter in the main process - return this.windowsService.startCrashReporter(this.options); - }); - } - - private getSubmitURL(): string { - if (isWindows) { - return product.hockeyApp[`win32-${process.arch}`]; - } else if (isMacintosh) { - return product.hockeyApp.darwin; - } else if (isLinux) { - return product.hockeyApp[`linux-${process.arch}`]; - } - throw new Error('Unknown platform'); - } - - getChildProcessStartOptions(name: string): Electron.CrashReporterStartOptions | undefined { - - // Experimental crash reporting support for child processes on Mac only for now - if (this.isEnabled && isMacintosh) { - const childProcessOptions = deepClone(this.options); - (childProcessOptions.extra).processName = name; - childProcessOptions.crashesDirectory = os.tmpdir(); - - return childProcessOptions; - } - - return undefined; - } -} diff --git a/src/vs/workbench/services/decorations/browser/decorations.ts b/src/vs/workbench/services/decorations/browser/decorations.ts index b164e7476e..b2cd1a2153 100644 --- a/src/vs/workbench/services/decorations/browser/decorations.ts +++ b/src/vs/workbench/services/decorations/browser/decorations.ts @@ -31,7 +31,7 @@ export interface IDecoration { export interface IDecorationsProvider { readonly label: string; readonly onDidChange: Event; - provideDecorations(uri: URI, token: CancellationToken): IDecorationData | Promise | undefined; + provideDecorations(uri: URI, token: CancellationToken): IDecorationData | Promise | undefined; } export interface IResourceDecorationChangeEvent { diff --git a/src/vs/workbench/services/decorations/browser/decorationsService.ts b/src/vs/workbench/services/decorations/browser/decorationsService.ts index eefade8c25..d9c19e443f 100644 --- a/src/vs/workbench/services/decorations/browser/decorationsService.ts +++ b/src/vs/workbench/services/decorations/browser/decorationsService.ts @@ -18,6 +18,7 @@ import { isFalsyOrWhitespace } from 'vs/base/common/strings'; import { localize } from 'vs/nls'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; class DecorationRule { @@ -301,7 +302,7 @@ class DecorationProviderWrapper { const source = new CancellationTokenSource(); const dataOrThenable = this._provider.provideDecorations(uri, source.token); - if (!isThenable(dataOrThenable)) { + if (!isThenable | undefined>(dataOrThenable)) { // sync -> we have a result now return this._keepItem(uri, dataOrThenable); @@ -353,8 +354,7 @@ export class FileDecorationsService implements IDecorationsService { ); constructor( - @IThemeService themeService: IThemeService, - cleanUpCount: number = 17 + @IThemeService themeService: IThemeService ) { this._decorationStyles = new DecorationStyles(themeService); @@ -362,7 +362,7 @@ export class FileDecorationsService implements IDecorationsService { // css styles that we don't need anymore let count = 0; let reg = this.onDidChangeDecorations(() => { - if (++count % cleanUpCount === 0) { + if (++count % 17 === 0) { this._decorationStyles.cleanUp(this._data.iterator()); } }); @@ -442,3 +442,4 @@ function getColor(theme: ITheme, color: string | undefined) { return 'inherit'; } +registerSingleton(IDecorationsService, FileDecorationsService); \ No newline at end of file diff --git a/src/vs/workbench/services/dialogs/browser/fileDialogService.ts b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts new file mode 100644 index 0000000000..235f97ec38 --- /dev/null +++ b/src/vs/workbench/services/dialogs/browser/fileDialogService.ts @@ -0,0 +1,258 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { IWindowService, INativeOpenDialogOptions, OpenDialogOptions, IURIToOpen, FileFilter } from 'vs/platform/windows/common/windows'; +import { IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { URI } from 'vs/base/common/uri'; +import { Schemas } from 'vs/base/common/network'; +import * as resources from 'vs/base/common/resources'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { RemoteFileDialog } from 'vs/workbench/services/dialogs/browser/remoteFileDialog'; +import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class FileDialogService implements IFileDialogService { + + _serviceBrand: any; + + constructor( + @IWindowService private readonly windowService: IWindowService, + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IHistoryService private readonly historyService: IHistoryService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IConfigurationService private readonly configurationService: IConfigurationService + ) { } + + defaultFilePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { + + // Check for last active file first... + let candidate = this.historyService.getLastActiveFile(schemeFilter); + + // ...then for last active file root + if (!candidate) { + candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); + } else { + candidate = candidate && resources.dirname(candidate); + } + + return candidate || undefined; + } + + defaultFolderPath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { + + // Check for last active file root first... + let candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); + + // ...then for last active file + if (!candidate) { + candidate = this.historyService.getLastActiveFile(schemeFilter); + } + + return candidate && resources.dirname(candidate) || undefined; + } + + defaultWorkspacePath(schemeFilter = this.getSchemeFilterForWindow()): URI | undefined { + + // Check for current workspace config file first... + if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { + const configuration = this.contextService.getWorkspace().configuration; + if (configuration && !isUntitledWorkspace(configuration, this.environmentService)) { + return resources.dirname(configuration) || undefined; + } + } + + // ...then fallback to default folder path + return this.defaultFolderPath(schemeFilter); + } + + private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions { + return { + forceNewWindow: options.forceNewWindow, + telemetryExtraData: options.telemetryExtraData, + dialogOptions: { + defaultPath: options.defaultUri && options.defaultUri.fsPath + } + }; + } + + private shouldUseSimplified(schema: string): boolean { + return (schema !== Schemas.file) || (this.configurationService.getValue('workbench.dialogs.useSimplified') === 'true'); + } + + private ensureFileSchema(schema: string): string[] { + return schema !== Schemas.file ? [schema, Schemas.file] : [schema]; + } + + pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultFilePath(schema); + } + + if (this.shouldUseSimplified(schema)) { + const title = nls.localize('openFileOrFolder.title', 'Open File Or Folder'); + const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + return this.pickRemoteResourceAndOpen({ canSelectFiles: true, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }, !!options.forceNewWindow, true); + } + + return this.windowService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options)); + } + + pickFileAndOpen(options: IPickAndOpenOptions): Promise { + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultFilePath(schema); + } + + if (this.shouldUseSimplified(schema)) { + const title = nls.localize('openFile.title', 'Open File'); + const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + return this.pickRemoteResourceAndOpen({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }, !!options.forceNewWindow, true); + } + + return this.windowService.pickFileAndOpen(this.toNativeOpenDialogOptions(options)); + } + + pickFolderAndOpen(options: IPickAndOpenOptions): Promise { + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultFolderPath(schema); + } + + if (this.shouldUseSimplified(schema)) { + const title = nls.localize('openFolder.title', 'Open Folder'); + const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + return this.pickRemoteResourceAndOpen({ canSelectFiles: false, canSelectFolders: true, canSelectMany: false, defaultUri: options.defaultUri, title, availableFileSystems }, !!options.forceNewWindow, false); + } + + return this.windowService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options)); + } + + pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise { + const schema = this.getFileSystemSchema(options); + + if (!options.defaultUri) { + options.defaultUri = this.defaultWorkspacePath(schema); + } + + if (this.shouldUseSimplified(schema)) { + const title = nls.localize('openWorkspace.title', 'Open Workspace'); + const filters: FileFilter[] = [{ name: nls.localize('filterName.workspace', 'Workspace'), extensions: [WORKSPACE_EXTENSION] }]; + const availableFileSystems = this.ensureFileSchema(schema); // always allow file as well + return this.pickRemoteResourceAndOpen({ canSelectFiles: true, canSelectFolders: false, canSelectMany: false, defaultUri: options.defaultUri, title, filters, availableFileSystems }, !!options.forceNewWindow, false); + } + + return this.windowService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options)); + } + + private toNativeSaveDialogOptions(options: ISaveDialogOptions): Electron.SaveDialogOptions { + return { + defaultPath: options.defaultUri && options.defaultUri.fsPath, + buttonLabel: options.saveLabel, + filters: options.filters, + title: options.title + }; + } + + showSaveDialog(options: ISaveDialogOptions): Promise { + const schema = this.getFileSystemSchema(options); + if (this.shouldUseSimplified(schema)) { + if (!options.availableFileSystems) { + options.availableFileSystems = [schema]; // by default only allow saving in the own file system + } + return this.saveRemoteResource(options); + } + + return this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options)).then(result => { + if (result) { + return URI.file(result); + } + + return undefined; + }); + } + + showOpenDialog(options: IOpenDialogOptions): Promise { + const schema = this.getFileSystemSchema(options); + if (schema !== Schemas.file) { + if (!options.availableFileSystems) { + options.availableFileSystems = [schema]; // by default only allow loading in the own file system + } + return this.pickRemoteResource(options).then(urisToOpen => { + return urisToOpen && urisToOpen.map(uto => uto.uri); + }); + } + + const defaultUri = options.defaultUri; + + const newOptions: OpenDialogOptions = { + title: options.title, + defaultPath: defaultUri && defaultUri.fsPath, + buttonLabel: options.openLabel, + filters: options.filters, + properties: [] + }; + + newOptions.properties!.push('createDirectory'); + + if (options.canSelectFiles) { + newOptions.properties!.push('openFile'); + } + + if (options.canSelectFolders) { + newOptions.properties!.push('openDirectory'); + } + + if (options.canSelectMany) { + newOptions.properties!.push('multiSelections'); + } + + return this.windowService.showOpenDialog(newOptions).then(result => result ? result.map(URI.file) : undefined); + } + + private pickRemoteResourceAndOpen(options: IOpenDialogOptions, forceNewWindow: boolean, forceOpenWorkspaceAsFile: boolean) { + return this.pickRemoteResource(options).then(urisToOpen => { + if (urisToOpen) { + return this.windowService.openWindow(urisToOpen, { forceNewWindow, forceOpenWorkspaceAsFile }); + } + return undefined; + }); + } + + private pickRemoteResource(options: IOpenDialogOptions): Promise { + const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); + return remoteFileDialog.showOpenDialog(options); + } + + private saveRemoteResource(options: ISaveDialogOptions): Promise { + const remoteFileDialog = this.instantiationService.createInstance(RemoteFileDialog); + return remoteFileDialog.showSaveDialog(options); + } + + private getSchemeFilterForWindow() { + return !this.windowService.getConfiguration().remoteAuthority ? Schemas.file : REMOTE_HOST_SCHEME; + } + + private getFileSystemSchema(options: { availableFileSystems?: string[], defaultUri?: URI }): string { + return options.availableFileSystems && options.availableFileSystems[0] || options.defaultUri && options.defaultUri.scheme || this.getSchemeFilterForWindow(); + } + +} + +function isUntitledWorkspace(path: URI, environmentService: IEnvironmentService): boolean { + return resources.isEqualOrParent(path, environmentService.untitledWorkspacesHome); +} + +registerSingleton(IFileDialogService, FileDialogService, true); diff --git a/src/vs/workbench/services/dialogs/browser/media/dark/accept.svg b/src/vs/workbench/services/dialogs/browser/media/dark/accept.svg new file mode 100644 index 0000000000..c225b2f597 --- /dev/null +++ b/src/vs/workbench/services/dialogs/browser/media/dark/accept.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/services/dialogs/browser/media/dark/folder.svg b/src/vs/workbench/services/dialogs/browser/media/dark/folder.svg new file mode 100644 index 0000000000..3d64ae71db --- /dev/null +++ b/src/vs/workbench/services/dialogs/browser/media/dark/folder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/services/dialogs/browser/media/light/accept.svg b/src/vs/workbench/services/dialogs/browser/media/light/accept.svg new file mode 100644 index 0000000000..c300a55adb --- /dev/null +++ b/src/vs/workbench/services/dialogs/browser/media/light/accept.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/services/dialogs/browser/media/light/folder.svg b/src/vs/workbench/services/dialogs/browser/media/light/folder.svg new file mode 100644 index 0000000000..13b18d1801 --- /dev/null +++ b/src/vs/workbench/services/dialogs/browser/media/light/folder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts new file mode 100644 index 0000000000..9d7e0acb21 --- /dev/null +++ b/src/vs/workbench/services/dialogs/browser/remoteFileDialog.ts @@ -0,0 +1,564 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as resources from 'vs/base/common/resources'; +import * as objects from 'vs/base/common/objects'; +import { IFileService, IFileStat, FileKind } from 'vs/platform/files/common/files'; +import { IQuickInputService, IQuickPickItem, IQuickPick, IQuickInputButton } from 'vs/platform/quickinput/common/quickInput'; +import { URI } from 'vs/base/common/uri'; +import { isWindows } from 'vs/base/common/platform'; +import { ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { IWindowService, IURIToOpen, FileFilter } from 'vs/platform/windows/common/windows'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { getIconClasses } from 'vs/editor/common/services/getIconClasses'; +import { Schemas } from 'vs/base/common/network'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IRemoteEnvironmentService } from 'vs/workbench/services/remote/common/remoteEnvironmentService'; + +interface FileQuickPickItem extends IQuickPickItem { + uri: URI; + isFolder: boolean; +} + +// Reference: https://en.wikipedia.org/wiki/Filename +const INVALID_FILE_CHARS = isWindows ? /[\\/:\*\?"<>\|]/g : /[\\/]/g; +const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])$/i; + +export class RemoteFileDialog { + private acceptButton: IQuickInputButton; + private fallbackListItem: FileQuickPickItem | undefined; + private options: IOpenDialogOptions; + private currentFolder: URI; + private filePickBox: IQuickPick; + private filters: FileFilter[] | undefined; + private hidden: boolean; + private allowFileSelection: boolean; + private allowFolderSelection: boolean; + private remoteAuthority: string | undefined; + private requiresTrailing: boolean; + private userValue: string; + private scheme: string = REMOTE_HOST_SCHEME; + private shouldOverwriteFile: boolean = false; + + constructor( + @IFileService private readonly fileService: IFileService, + @IQuickInputService private readonly quickInputService: IQuickInputService, + @IWindowService private readonly windowService: IWindowService, + @ILabelService private readonly labelService: ILabelService, + @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, + @INotificationService private readonly notificationService: INotificationService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IRemoteEnvironmentService private readonly remoteEnvironmentService: IRemoteEnvironmentService, + + ) { + this.remoteAuthority = this.windowService.getConfiguration().remoteAuthority; + } + + public async showOpenDialog(options: IOpenDialogOptions = {}): Promise { + this.scheme = this.getScheme(options.defaultUri, options.availableFileSystems); + const newOptions = await this.getOptions(options); + if (!newOptions) { + return Promise.resolve(undefined); + } + this.options = newOptions; + + const openFileString = nls.localize('remoteFileDialog.localFileFallback', '(Open Local File)'); + const openFolderString = nls.localize('remoteFileDialog.localFolderFallback', '(Open Local Folder)'); + const openFileFolderString = nls.localize('remoteFileDialog.localFileFolderFallback', '(Open Local File or Folder)'); + let fallbackLabel = options.canSelectFiles ? (options.canSelectFolders ? openFileFolderString : openFileString) : openFolderString; + this.fallbackListItem = this.getFallbackFileSystem(fallbackLabel); + + return this.pickResource().then(async fileFolderUri => { + if (fileFolderUri) { + const stat = await this.fileService.resolveFile(fileFolderUri); + return [{ uri: fileFolderUri, typeHint: stat.isDirectory ? 'folder' : 'file' }]; + + } + return Promise.resolve(undefined); + }); + } + + public async showSaveDialog(options: ISaveDialogOptions): Promise { + this.scheme = this.getScheme(options.defaultUri, options.availableFileSystems); + this.requiresTrailing = true; + const newOptions = await this.getOptions(options); + if (!newOptions) { + return Promise.resolve(undefined); + } + this.options = newOptions; + this.options.canSelectFolders = true; + this.options.canSelectFiles = true; + this.fallbackListItem = this.getFallbackFileSystem(nls.localize('remoteFileDialog.localSaveFallback', '(Save Local File)')); + + return new Promise((resolve) => { + this.pickResource(true).then(folderUri => { + resolve(folderUri); + }); + }); + } + + private async getOptions(options: ISaveDialogOptions | IOpenDialogOptions): Promise { + let defaultUri = options.defaultUri; + if (!defaultUri) { + const env = await this.remoteEnvironmentService.remoteEnvironment; + if (env) { + defaultUri = env.userHome; + } else { + defaultUri = URI.from({ scheme: this.scheme, path: this.environmentService.userHome }); + } + } + if ((this.scheme !== Schemas.file) && !this.fileService.canHandleResource(defaultUri)) { + this.notificationService.info(nls.localize('remoteFileDialog.notConnectedToRemote', 'File system provider for {0} is not available.', defaultUri.toString())); + return undefined; + } + const newOptions: IOpenDialogOptions = objects.deepClone(options); + newOptions.defaultUri = defaultUri; + return newOptions; + } + + private remoteUriFrom(path: string): URI { + path = path.replace(/\\/g, '/'); + return URI.from({ scheme: this.scheme, authority: this.remoteAuthority, path }); + } + + private getScheme(defaultUri: URI | undefined, available: string[] | undefined): string { + return defaultUri ? defaultUri.scheme : (available ? available[0] : Schemas.file); + } + + private getFallbackFileSystem(label: string): FileQuickPickItem | undefined { + if (this.options && this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) { + return { label: label, uri: URI.from({ scheme: this.options.availableFileSystems[1] }), isFolder: true }; + } + return undefined; + } + + private async pickResource(isSave: boolean = false): Promise { + this.allowFolderSelection = !!this.options.canSelectFolders; + this.allowFileSelection = !!this.options.canSelectFiles; + this.hidden = false; + let homedir: URI = this.options.defaultUri ? this.options.defaultUri : this.workspaceContextService.getWorkspace().folders[0].uri; + let trailing: string | undefined; + let stat: IFileStat | undefined; + let ext: string = resources.extname(homedir); + if (this.options.defaultUri) { + try { + stat = await this.fileService.resolveFile(this.options.defaultUri); + } catch (e) { + // The file or folder doesn't exist + } + if (!stat || !stat.isDirectory) { + homedir = resources.dirname(this.options.defaultUri); + trailing = resources.basename(this.options.defaultUri); + } + // append extension + if (isSave && !ext && this.options.filters) { + for (let i = 0; i < this.options.filters.length; i++) { + if (this.options.filters[i].extensions[0] !== '*') { + ext = '.' + this.options.filters[i].extensions[0]; + trailing = trailing ? trailing + ext : ext; + break; + } + } + } + } + this.acceptButton = { iconPath: this.getDialogIcons('accept'), tooltip: this.options.title }; + + return new Promise((resolve) => { + this.filePickBox = this.quickInputService.createQuickPick(); + this.filePickBox.matchOnLabel = false; + this.filePickBox.autoFocusOnList = false; + + let isResolving = false; + let isAcceptHandled = false; + this.currentFolder = homedir; + this.filePickBox.buttons = [this.acceptButton]; + this.filePickBox.onDidTriggerButton(_ => { + // accept button + const resolveValue = this.remoteUriFrom(this.filePickBox.value); + this.validate(resolveValue).then(validated => { + if (validated) { + isResolving = true; + this.filePickBox.hide(); + resolve(resolveValue); + } + }); + }); + + this.filePickBox.title = this.options.title; + this.filePickBox.value = this.pathFromUri(this.currentFolder); + this.filePickBox.items = []; + this.filePickBox.onDidAccept(_ => { + if (isAcceptHandled || this.filePickBox.busy) { + return; + } + + isAcceptHandled = true; + isResolving = true; + this.onDidAccept().then(resolveValue => { + if (resolveValue) { + this.filePickBox.hide(); + resolve(resolveValue); + } else if (this.hidden) { + resolve(undefined); + } else { + isResolving = false; + isAcceptHandled = false; + } + }); + }); + this.filePickBox.onDidChangeActive(i => { + isAcceptHandled = false; + }); + + this.filePickBox.onDidChangeValue(async value => { + if (value !== this.userValue) { + this.filePickBox.validationMessage = undefined; + this.shouldOverwriteFile = false; + const trimmedPickBoxValue = ((this.filePickBox.value.length > 1) && this.endsWithSlash(this.filePickBox.value)) ? this.filePickBox.value.substr(0, this.filePickBox.value.length - 1) : this.filePickBox.value; + const valueUri = this.remoteUriFrom(trimmedPickBoxValue); + if (!resources.isEqual(this.currentFolder, valueUri, true)) { + await this.tryUpdateItems(value, this.remoteUriFrom(this.filePickBox.value)); + } + this.setActiveItems(value); + this.userValue = value; + } else { + this.filePickBox.activeItems = []; + } + }); + this.filePickBox.onDidHide(() => { + this.hidden = true; + if (!isResolving) { + resolve(undefined); + } + this.filePickBox.dispose(); + }); + + this.filePickBox.show(); + this.updateItems(homedir, trailing); + if (trailing) { + this.filePickBox.valueSelection = [this.filePickBox.value.length - trailing.length, this.filePickBox.value.length - ext.length]; + } else { + this.filePickBox.valueSelection = [this.filePickBox.value.length, this.filePickBox.value.length]; + } + this.userValue = this.filePickBox.value; + }); + } + + private async onDidAccept(): Promise { + // Check if Open Local has been selected + const selectedItems: ReadonlyArray = this.filePickBox.selectedItems; + if (selectedItems && (selectedItems.length > 0) && (selectedItems[0] === this.fallbackListItem)) { + if (this.options.availableFileSystems && (this.options.availableFileSystems.length > 1)) { + this.options.availableFileSystems.shift(); + } + if (this.requiresTrailing) { + return this.fileDialogService.showSaveDialog(this.options).then(result => { + return result; + }); + } else { + return this.fileDialogService.showOpenDialog(this.options).then(result => { + return result ? result[0] : undefined; + }); + } + } + + let resolveValue: URI | undefined; + let navigateValue: URI | undefined; + const trimmedPickBoxValue = ((this.filePickBox.value.length > 1) && this.endsWithSlash(this.filePickBox.value)) ? this.filePickBox.value.substr(0, this.filePickBox.value.length - 1) : this.filePickBox.value; + const inputUri = this.remoteUriFrom(trimmedPickBoxValue); + const inputUriDirname = resources.dirname(inputUri); + let stat: IFileStat | undefined; + let statDirname: IFileStat | undefined; + try { + statDirname = await this.fileService.resolveFile(inputUriDirname); + stat = await this.fileService.resolveFile(inputUri); + } catch (e) { + // do nothing + } + + // Find resolve value + if (this.filePickBox.activeItems.length === 0) { + if (!this.requiresTrailing && resources.isEqual(this.currentFolder, inputUri, true)) { + resolveValue = inputUri; + } else if (statDirname && statDirname.isDirectory) { + resolveValue = inputUri; + } else if (stat && stat.isDirectory) { + navigateValue = inputUri; + } + } else if (this.filePickBox.activeItems.length === 1) { + const item = this.filePickBox.selectedItems[0]; + if (item) { + if (!item.isFolder) { + resolveValue = item.uri; + } else { + navigateValue = item.uri; + } + } + } + + if (resolveValue) { + if (await this.validate(resolveValue)) { + return Promise.resolve(resolveValue); + } + } else if (navigateValue) { + // Try to navigate into the folder + this.updateItems(navigateValue); + } else { + // validation error. Path does not exist. + } + return Promise.resolve(undefined); + } + + private async tryUpdateItems(value: string, valueUri: URI) { + if (this.endsWithSlash(value) || (!resources.isEqual(this.currentFolder, resources.dirname(valueUri), true) && resources.isEqualOrParent(this.currentFolder, resources.dirname(valueUri), true))) { + let stat: IFileStat | undefined; + try { + stat = await this.fileService.resolveFile(valueUri); + } catch (e) { + // do nothing + } + if (stat && stat.isDirectory && (resources.basename(valueUri) !== '.')) { + this.updateItems(valueUri); + } else { + const inputUriDirname = resources.dirname(valueUri); + if (!resources.isEqual(this.currentFolder, inputUriDirname, true)) { + let statWithoutTrailing: IFileStat | undefined; + try { + statWithoutTrailing = await this.fileService.resolveFile(inputUriDirname); + } catch (e) { + // do nothing + } + if (statWithoutTrailing && statWithoutTrailing.isDirectory && (resources.basename(valueUri) !== '.')) { + this.updateItems(inputUriDirname, resources.basename(valueUri)); + } + } + } + } + } + + private setActiveItems(value: string) { + if (!this.userValue || (value !== this.userValue.substring(0, value.length))) { + const inputBasename = resources.basename(this.remoteUriFrom(value)); + let hasMatch = false; + for (let i = 0; i < this.filePickBox.items.length; i++) { + const item = this.filePickBox.items[i]; + const itemBasename = (item.label === '..') ? item.label : resources.basename(item.uri); + if ((itemBasename.length >= inputBasename.length) && (itemBasename.substr(0, inputBasename.length).toLowerCase() === inputBasename.toLowerCase())) { + this.filePickBox.activeItems = [item]; + this.filePickBox.value = this.filePickBox.value + itemBasename.substr(inputBasename.length); + this.filePickBox.valueSelection = [value.length, this.filePickBox.value.length]; + hasMatch = true; + break; + } + } + if (!hasMatch) { + this.filePickBox.activeItems = []; + } + } + } + + private async validate(uri: URI): Promise { + let stat: IFileStat | undefined; + let statDirname: IFileStat | undefined; + try { + statDirname = await this.fileService.resolveFile(resources.dirname(uri)); + stat = await this.fileService.resolveFile(uri); + } catch (e) { + // do nothing + } + + if (this.requiresTrailing) { // save + if (stat && stat.isDirectory) { + // Can't do this + this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateFolder', 'The folder already exists. Please use a new file name.'); + return Promise.resolve(false); + } else if (stat && !this.shouldOverwriteFile) { + // Replacing a file. + this.shouldOverwriteFile = true; + this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateExisting', '{0} already exists. Are you sure you want to overwrite it?', resources.basename(uri)); + return Promise.resolve(false); + } else if (!this.isValidBaseName(resources.basename(uri))) { + // Filename not allowed + this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateBadFilename', 'Please enter a valid file name.'); + return Promise.resolve(false); + } else if (!statDirname || !statDirname.isDirectory) { + // Folder to save in doesn't exist + this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateNonexistentDir', 'Please enter a path that exists.'); + return Promise.resolve(false); + } + } else { // open + if (!stat) { + // File or folder doesn't exist + this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateNonexistentDir', 'Please enter a path that exists.'); + return Promise.resolve(false); + } else if (stat.isDirectory && !this.allowFolderSelection) { + // Folder selected when folder selection not permitted + this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateFileOnly', 'Please select a file.'); + return Promise.resolve(false); + } else if (!stat.isDirectory && !this.allowFileSelection) { + // File selected when file selection not permitted + this.filePickBox.validationMessage = nls.localize('remoteFileDialog.validateFolderOnly', 'Please select a folder.'); + return Promise.resolve(false); + } + } + return Promise.resolve(true); + } + + private updateItems(newFolder: URI, trailing?: string) { + this.currentFolder = newFolder; + this.filePickBox.value = trailing ? this.pathFromUri(resources.joinPath(newFolder, trailing)) : this.pathFromUri(newFolder, true); + this.filePickBox.busy = true; + this.createItems(this.currentFolder).then(items => { + this.filePickBox.items = items; + if (this.allowFolderSelection) { + this.filePickBox.activeItems = []; + } + this.filePickBox.busy = false; + }); + } + + private pathFromUri(uri: URI, endWithSeparator: boolean = false): string { + const sep = this.labelService.getSeparator(uri.scheme, uri.authority); + let result: string; + if (sep === '/') { + result = uri.fsPath.replace(/\\/g, sep); + } else { + result = uri.fsPath.replace(/\//g, sep); + } + if (endWithSeparator && !this.endsWithSlash(result)) { + result = result + sep; + } + return result; + } + + private isValidBaseName(name: string): boolean { + if (!name || name.length === 0 || /^\s+$/.test(name)) { + return false; // require a name that is not just whitespace + } + + INVALID_FILE_CHARS.lastIndex = 0; // the holy grail of software development + if (INVALID_FILE_CHARS.test(name)) { + return false; // check for certain invalid file characters + } + + if (isWindows && WINDOWS_FORBIDDEN_NAMES.test(name)) { + return false; // check for certain invalid file names + } + + if (name === '.' || name === '..') { + return false; // check for reserved values + } + + if (isWindows && name[name.length - 1] === '.') { + return false; // Windows: file cannot end with a "." + } + + if (isWindows && name.length !== name.trim().length) { + return false; // Windows: file cannot end with a whitespace + } + + return true; + } + + private endsWithSlash(s: string) { + return /[\/\\]$/.test(s); + } + + private basenameWithTrailingSlash(fullPath: URI): string { + const child = this.pathFromUri(fullPath, true); + const parent = this.pathFromUri(resources.dirname(fullPath), true); + return child.substring(parent.length); + } + + private createBackItem(currFolder: URI): FileQuickPickItem | null { + const parentFolder = resources.dirname(currFolder)!; + if (!resources.isEqual(currFolder, parentFolder, true)) { + return { label: '..', uri: resources.dirname(currFolder), isFolder: true }; + } + return null; + } + + private async createItems(currentFolder: URI): Promise { + const result: FileQuickPickItem[] = []; + + const backDir = this.createBackItem(currentFolder); + try { + const fileNames = await this.fileService.readFolder(currentFolder); + const items = await Promise.all(fileNames.map(fileName => this.createItem(fileName, currentFolder))); + for (let item of items) { + if (item) { + result.push(item); + } + } + } catch (e) { + // ignore + console.log(e); + } + const sorted = result.sort((i1, i2) => { + if (i1.isFolder !== i2.isFolder) { + return i1.isFolder ? -1 : 1; + } + const trimmed1 = this.endsWithSlash(i1.label) ? i1.label.substr(0, i1.label.length - 1) : i1.label; + const trimmed2 = this.endsWithSlash(i2.label) ? i2.label.substr(0, i2.label.length - 1) : i2.label; + return trimmed1.localeCompare(trimmed2); + }); + + if (backDir) { + sorted.unshift(backDir); + } + + if (this.fallbackListItem) { + sorted.unshift(this.fallbackListItem); + } + return sorted; + } + + private filterFile(file: URI): boolean { + if (this.filters) { + const ext = resources.extname(file); + for (let i = 0; i < this.filters.length; i++) { + for (let j = 0; j < this.filters[i].extensions.length; j++) { + if (ext === ('.' + this.filters[i].extensions[j])) { + return true; + } + } + } + return false; + } + return true; + } + + private async createItem(filename: string, parent: URI): Promise { + let fullPath = resources.joinPath(parent, filename); + try { + const stat = await this.fileService.resolveFile(fullPath); + if (stat.isDirectory) { + filename = this.basenameWithTrailingSlash(fullPath); + return { label: filename, uri: fullPath, isFolder: true, iconClasses: getIconClasses(this.modelService, this.modeService, fullPath || undefined, FileKind.FOLDER) }; + } else if (!stat.isDirectory && this.allowFileSelection && this.filterFile(fullPath)) { + return { label: filename, uri: fullPath, isFolder: false, iconClasses: getIconClasses(this.modelService, this.modeService, fullPath || undefined) }; + } + return undefined; + } catch (e) { + return undefined; + } + } + + private getDialogIcons(name: string): { light: URI, dark: URI } { + return { + dark: URI.parse(require.toUrl(`vs/workbench/services/dialogs/browser/media/dark/${name}.svg`)), + light: URI.parse(require.toUrl(`vs/workbench/services/dialogs/browser/media/light/${name}.svg`)) + }; + } +} diff --git a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts index f3e002fbfb..2fd84d2d5d 100644 --- a/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts +++ b/src/vs/workbench/services/dialogs/electron-browser/dialogService.ts @@ -4,20 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import Severity from 'vs/base/common/severity'; import { isLinux, isWindows } from 'vs/base/common/platform'; -import { IWindowService, INativeOpenDialogOptions, OpenDialogOptions } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; -import { IDialogService, IConfirmation, IConfirmationResult, IDialogOptions, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { IDialogService, IConfirmation, IConfirmationResult, IDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { URI } from 'vs/base/common/uri'; -import { Schemas } from 'vs/base/common/network'; -import * as resources from 'vs/base/common/resources'; -import { isParent } from 'vs/platform/files/common/files'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc'; interface IMassagedMessageBoxOptions { @@ -40,8 +36,11 @@ export class DialogService implements IDialogService { constructor( @IWindowService private readonly windowService: IWindowService, - @ILogService private readonly logService: ILogService - ) { } + @ILogService private readonly logService: ILogService, + @ISharedProcessService sharedProcessService: ISharedProcessService + ) { + sharedProcessService.registerChannel('dialog', new DialogChannel(this)); + } confirm(confirmation: IConfirmation): Promise { this.logService.trace('DialogService#confirm', confirmation.message); @@ -152,159 +151,4 @@ export class DialogService implements IDialogService { } } -export class FileDialogService implements IFileDialogService { - - _serviceBrand: any; - - constructor( - @IWindowService private readonly windowService: IWindowService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IHistoryService private readonly historyService: IHistoryService, - @IEnvironmentService private readonly environmentService: IEnvironmentService - ) { } - - defaultFilePath(schemeFilter: string): URI | undefined { - - // Check for last active file first... - let candidate = this.historyService.getLastActiveFile(schemeFilter); - - // ...then for last active file root - if (!candidate) { - candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); - } - - return candidate && resources.dirname(candidate) || undefined; - } - - defaultFolderPath(schemeFilter: string): URI | undefined { - - // Check for last active file root first... - let candidate = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); - - // ...then for last active file - if (!candidate) { - candidate = this.historyService.getLastActiveFile(schemeFilter); - } - - return candidate && resources.dirname(candidate) || undefined; - } - - defaultWorkspacePath(schemeFilter: string): URI | undefined { - - // Check for current workspace config file first... - if (schemeFilter === Schemas.file && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { - const configuration = this.contextService.getWorkspace().configuration; - if (configuration && !isUntitledWorkspace(configuration.fsPath, this.environmentService)) { - return resources.dirname(configuration) || undefined; - } - } - - // ...then fallback to default folder path - return this.defaultFolderPath(schemeFilter); - } - - private toNativeOpenDialogOptions(options: IPickAndOpenOptions): INativeOpenDialogOptions { - return { - forceNewWindow: options.forceNewWindow, - telemetryExtraData: options.telemetryExtraData, - dialogOptions: { - defaultPath: options.defaultUri && options.defaultUri.fsPath - } - }; - } - - pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise { - const defaultUri = options.defaultUri; - if (!defaultUri) { - options.defaultUri = this.defaultFilePath(Schemas.file); - } - - return this.windowService.pickFileFolderAndOpen(this.toNativeOpenDialogOptions(options)); - } - - pickFileAndOpen(options: IPickAndOpenOptions): Promise { - const defaultUri = options.defaultUri; - if (!defaultUri) { - options.defaultUri = this.defaultFilePath(Schemas.file); - } - - return this.windowService.pickFileAndOpen(this.toNativeOpenDialogOptions(options)); - } - - pickFolderAndOpen(options: IPickAndOpenOptions): Promise { - const defaultUri = options.defaultUri; - if (!defaultUri) { - options.defaultUri = this.defaultFolderPath(Schemas.file); - } - - return this.windowService.pickFolderAndOpen(this.toNativeOpenDialogOptions(options)); - } - - pickWorkspaceAndOpen(options: IPickAndOpenOptions): Promise { - const defaultUri = options.defaultUri; - if (!defaultUri) { - options.defaultUri = this.defaultWorkspacePath(Schemas.file); - } - - return this.windowService.pickWorkspaceAndOpen(this.toNativeOpenDialogOptions(options)); - } - - private toNativeSaveDialogOptions(options: ISaveDialogOptions): Electron.SaveDialogOptions { - return { - defaultPath: options.defaultUri && options.defaultUri.fsPath, - buttonLabel: options.saveLabel, - filters: options.filters, - title: options.title - }; - } - - showSaveDialog(options: ISaveDialogOptions): Promise { - const defaultUri = options.defaultUri; - if (defaultUri && defaultUri.scheme !== Schemas.file) { - return Promise.reject(new Error('Not supported - Save-dialogs can only be opened on `file`-uris.')); - } - - return this.windowService.showSaveDialog(this.toNativeSaveDialogOptions(options)).then(result => { - if (result) { - return URI.file(result); - } - - return undefined; - }); - } - - showOpenDialog(options: IOpenDialogOptions): Promise { - const defaultUri = options.defaultUri; - if (defaultUri && defaultUri.scheme !== Schemas.file) { - return Promise.reject(new Error('Not supported - Open-dialogs can only be opened on `file`-uris.')); - } - - const newOptions: OpenDialogOptions = { - title: options.title, - defaultPath: defaultUri && defaultUri.fsPath, - buttonLabel: options.openLabel, - filters: options.filters, - properties: [] - }; - - newOptions.properties!.push('createDirectory'); - - if (options.canSelectFiles) { - newOptions.properties!.push('openFile'); - } - - if (options.canSelectFolders) { - newOptions.properties!.push('openDirectory'); - } - - if (options.canSelectMany) { - newOptions.properties!.push('multiSelections'); - } - - return this.windowService.showOpenDialog(newOptions).then(result => result ? result.map(URI.file) : undefined); - } -} - -function isUntitledWorkspace(path: string, environmentService: IEnvironmentService): boolean { - return isParent(path, environmentService.workspacesHome, !isLinux /* ignore case */); -} \ No newline at end of file +registerSingleton(IDialogService, DialogService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/codeEditor/browser/codeEditorService.ts b/src/vs/workbench/services/editor/browser/codeEditorService.ts similarity index 93% rename from src/vs/workbench/services/codeEditor/browser/codeEditorService.ts rename to src/vs/workbench/services/editor/browser/codeEditorService.ts index 190f668f16..2606455bc4 100644 --- a/src/vs/workbench/services/codeEditor/browser/codeEditorService.ts +++ b/src/vs/workbench/services/editor/browser/codeEditorService.ts @@ -10,6 +10,8 @@ import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TextEditorOptions } from 'vs/workbench/common/editor'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class CodeEditorService extends CodeEditorServiceImpl { @@ -72,4 +74,6 @@ export class CodeEditorService extends CodeEditorServiceImpl { return null; }); } -} \ No newline at end of file +} + +registerSingleton(ICodeEditorService, CodeEditorService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/editor/browser/editorService.ts b/src/vs/workbench/services/editor/browser/editorService.ts index 89c7986a09..11d0acf4b0 100644 --- a/src/vs/workbench/services/editor/browser/editorService.ts +++ b/src/vs/workbench/services/editor/browser/editorService.ts @@ -15,20 +15,23 @@ import { IFileService } from 'vs/platform/files/common/files'; import { Schemas } from 'vs/base/common/network'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { basename } from 'vs/base/common/paths'; +import { basename } from 'vs/base/common/resources'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { localize } from 'vs/nls'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/group/common/editorGroupsService'; -import { IResourceEditor, ACTIVE_GROUP_TYPE, SIDE_GROUP_TYPE, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder, IEditorReplacement, GroupChangeKind, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IResourceEditor, ACTIVE_GROUP_TYPE, SIDE_GROUP_TYPE, SIDE_GROUP, IResourceEditorReplacement, IOpenEditorOverrideHandler, IVisibleEditor, IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Disposable, IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { coalesce } from 'vs/base/common/arrays'; import { isCodeEditor, isDiffEditor, ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; -import { IEditorGroupView, IEditorOpeningEvent, EditorGroupsServiceImpl, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; +import { IEditorGroupView, IEditorOpeningEvent, EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; + //{{SQL CARBON EDIT}} import { convertEditorInput, getFileMode } from 'sql/parts/common/customInputConverter'; //{{SQL CARBON EDIT}} - End -import { ILabelService } from 'vs/platform/label/common/label'; type ICachedEditorInput = ResourceEditorInput | IFileEditorInput | DataUriEditorInput; @@ -40,16 +43,16 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region events - private _onDidActiveEditorChange: Emitter = this._register(new Emitter()); + private readonly _onDidActiveEditorChange: Emitter = this._register(new Emitter()); get onDidActiveEditorChange(): Event { return this._onDidActiveEditorChange.event; } - private _onDidVisibleEditorsChange: Emitter = this._register(new Emitter()); + private readonly _onDidVisibleEditorsChange: Emitter = this._register(new Emitter()); get onDidVisibleEditorsChange(): Event { return this._onDidVisibleEditorsChange.event; } - private _onDidCloseEditor: Emitter = this._register(new Emitter()); + private readonly _onDidCloseEditor: Emitter = this._register(new Emitter()); get onDidCloseEditor(): Event { return this._onDidCloseEditor.event; } - private _onDidOpenEditorFail: Emitter = this._register(new Emitter()); + private readonly _onDidOpenEditorFail: Emitter = this._register(new Emitter()); get onDidOpenEditorFail(): Event { return this._onDidOpenEditorFail.event; } //#endregion @@ -57,11 +60,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { private fileInputFactory: IFileInputFactory; private openEditorHandlers: IOpenEditorOverrideHandler[] = []; - private lastActiveEditor: IEditorInput; + private lastActiveEditor: IEditorInput | null; private lastActiveGroupId: GroupIdentifier; constructor( - @IEditorGroupsService private readonly editorGroupService: EditorGroupsServiceImpl, + @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService, @ILabelService private readonly labelService: ILabelService, @@ -149,19 +152,19 @@ export class EditorService extends Disposable implements EditorServiceImpl { for (const handler of this.openEditorHandlers) { const result = handler(event.editor, event.options, group); if (result && result.override) { - event.prevent((() => result.override)); + event.prevent((() => result.override!.then(editor => withNullAsUndefined(editor)))); break; } } } - get activeControl(): IEditor { + get activeControl(): IVisibleEditor | undefined { const activeGroup = this.editorGroupService.activeGroup; return activeGroup ? activeGroup.activeControl : undefined; } - get activeTextEditorWidget(): ICodeEditor | IDiffEditor { + get activeTextEditorWidget(): ICodeEditor | IDiffEditor | undefined { const activeControl = this.activeControl; if (activeControl) { const activeControlWidget = activeControl.getControl(); @@ -182,13 +185,13 @@ export class EditorService extends Disposable implements EditorServiceImpl { return editors; } - get activeEditor(): IEditorInput { + get activeEditor(): IEditorInput | undefined { const activeGroup = this.editorGroupService.activeGroup; - return activeGroup ? activeGroup.activeEditor : undefined; + return activeGroup ? withNullAsUndefined(activeGroup.activeEditor) : undefined; } - get visibleControls(): IEditor[] { + get visibleControls(): IVisibleEditor[] { return coalesce(this.editorGroupService.groups.map(group => group.activeControl)); } @@ -217,11 +220,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region openEditor() - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, group?: GroupIdentifier): Promise { + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IEditorInput | IResourceEditor, optionsOrGroup?: IEditorOptions | ITextEditorOptions | IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE, group?: GroupIdentifier): Promise { // Typed Editor Support if (editor instanceof EditorInput) { @@ -243,12 +246,12 @@ export class EditorService extends Disposable implements EditorServiceImpl { return Promise.resolve(null); } - protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { + protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { return group.openEditor(editor, options); } private findTargetGroup(input: IEditorInput, options?: IEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): IEditorGroup { - let targetGroup: IEditorGroup; + let targetGroup: IEditorGroup | undefined; // Group: Instance of Group if (group && typeof group !== 'number') { @@ -359,12 +362,12 @@ export class EditorService extends Disposable implements EditorServiceImpl { } // Open in targets - const result: Promise[] = []; + const result: Promise[] = []; mapGroupToEditors.forEach((editorsWithOptions, group) => { result.push(group.openEditors(editorsWithOptions)); }); - return Promise.all(result); + return Promise.all(result).then(editors => coalesce(editors)); } //#endregion @@ -379,11 +382,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { //#region getOpend() - getOpened(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier): IEditorInput { + getOpened(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier): IEditorInput | undefined { return this.doGetOpened(editor); } - private doGetOpened(editor: IEditorInput | IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier): IEditorInput { + private doGetOpened(editor: IEditorInput | IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier): IEditorInput | undefined { if (!(editor instanceof EditorInput)) { const resourceInput = editor as IResourceInput | IUntitledResourceInput; if (!resourceInput.resource) { @@ -393,7 +396,10 @@ export class EditorService extends Disposable implements EditorServiceImpl { let groups: IEditorGroup[] = []; if (typeof group === 'number') { - groups.push(this.editorGroupService.getGroup(group)); + const groupView = this.editorGroupService.getGroup(group); + if (groupView) { + groups.push(groupView); + } } else if (group) { groups.push(group); } else { @@ -419,7 +425,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { } const resourceInput = editor as IResourceInput | IUntitledResourceInput; - if (resource.toString() === resourceInput.resource.toString()) { + if (resourceInput.resource && resource.toString() === resourceInput.resource.toString()) { return editorInGroup; } } @@ -455,7 +461,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { }); const targetGroup = typeof group === 'number' ? this.editorGroupService.getGroup(group) : group; - return targetGroup.replaceEditors(typedEditors); + if (targetGroup) { + return targetGroup.replaceEditors(typedEditors); + } + + return Promise.resolve(); } //#endregion @@ -498,9 +508,10 @@ export class EditorService extends Disposable implements EditorServiceImpl { if (resourceSideBySideInput.masterResource && resourceSideBySideInput.detailResource) { const masterInput = this.createInput({ resource: resourceSideBySideInput.masterResource, forceFile: resourceSideBySideInput.forceFile }); const detailInput = this.createInput({ resource: resourceSideBySideInput.detailResource, forceFile: resourceSideBySideInput.forceFile }); + const label = resourceSideBySideInput.label || masterInput.getName() || localize('sideBySideLabels', "{0} - {1}", this.toDiffLabel(masterInput), this.toDiffLabel(detailInput)); return new SideBySideEditorInput( - resourceSideBySideInput.label || masterInput.getName(), + label, typeof resourceSideBySideInput.description === 'string' ? resourceSideBySideInput.description : masterInput.getDescription(), detailInput, masterInput @@ -514,7 +525,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { const rightInput = this.createInput({ resource: resourceDiffInput.rightResource, forceFile: resourceDiffInput.forceFile }); const label = resourceDiffInput.label || localize('compareLabels', "{0} ↔ {1}", this.toDiffLabel(leftInput), this.toDiffLabel(rightInput)); - return new DiffEditorInput(label, resourceDiffInput.description, leftInput, rightInput); + return new DiffEditorInput(label, withUndefinedAsNull(resourceDiffInput.description), leftInput, rightInput); } // Untitled file support @@ -536,7 +547,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { if (resourceInput.resource instanceof URI) { let label = resourceInput.label; if (!label && resourceInput.resource.scheme !== Schemas.data) { - label = basename(resourceInput.resource.fsPath); // derive the label from the path (but not for data URIs) + label = basename(resourceInput.resource); // derive the label from the path (but not for data URIs) } // {{SQL CARBON EDIT}} @@ -544,17 +555,24 @@ export class EditorService extends Disposable implements EditorServiceImpl { undefined, this.instantiationService); } - return null; + throw new Error('Unknown input type'); } - private createOrGet(resource: URI, instantiationService: IInstantiationService, label: string, description: string, encoding?: string, forceFile?: boolean): ICachedEditorInput { + private createOrGet(resource: URI, instantiationService: IInstantiationService, label: string | undefined, description: string | undefined, encoding: string | undefined, forceFile: boolean | undefined): ICachedEditorInput { if (EditorService.CACHE.has(resource)) { - const input = EditorService.CACHE.get(resource); + const input = EditorService.CACHE.get(resource)!; if (input instanceof ResourceEditorInput) { - input.setName(label); - input.setDescription(description); + if (label) { + input.setName(label); + } + + if (description) { + input.setDescription(description); + } } else if (!(input instanceof DataUriEditorInput)) { - input.setPreferredEncoding(encoding); + if (encoding) { + input.setPreferredEncoding(encoding); + } } return input; @@ -585,8 +603,11 @@ export class EditorService extends Disposable implements EditorServiceImpl { return input; } - private toDiffLabel(input: EditorInput): string { + private toDiffLabel(input: EditorInput): string | null { const res = input.getResource(); + if (!res) { + return null; + } // Do not try to extract any paths from simple untitled editors if (res.scheme === Schemas.untitled && !this.untitledEditorService.hasAssociatedFilePath(res)) { @@ -601,7 +622,7 @@ export class EditorService extends Disposable implements EditorServiceImpl { } export interface IEditorOpenHandler { - (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions): Promise; + (group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions): Promise; } /** @@ -612,7 +633,7 @@ export class DelegatingEditorService extends EditorService { private editorOpenHandler: IEditorOpenHandler; constructor( - @IEditorGroupsService editorGroupService: EditorGroupsServiceImpl, + @IEditorGroupsService editorGroupService: IEditorGroupsService, @IUntitledEditorService untitledEditorService: IUntitledEditorService, @IInstantiationService instantiationService: IInstantiationService, @ILabelService labelService: ILabelService, @@ -633,7 +654,7 @@ export class DelegatingEditorService extends EditorService { this.editorOpenHandler = handler; } - protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { + protected doOpenEditor(group: IEditorGroup, editor: IEditorInput, options?: IEditorOptions): Promise { if (!this.editorOpenHandler) { return super.doOpenEditor(group, editor, options); } @@ -647,3 +668,5 @@ export class DelegatingEditorService extends EditorService { }); } } + +registerSingleton(IEditorService, EditorService); diff --git a/src/vs/workbench/services/group/common/editorGroupsService.ts b/src/vs/workbench/services/editor/common/editorGroupsService.ts similarity index 88% rename from src/vs/workbench/services/group/common/editorGroupsService.ts rename to src/vs/workbench/services/editor/common/editorGroupsService.ts index 7d8bf3aa97..de20e16dad 100644 --- a/src/vs/workbench/services/group/common/editorGroupsService.ts +++ b/src/vs/workbench/services/editor/common/editorGroupsService.ts @@ -5,9 +5,12 @@ import { Event } from 'vs/base/common/event'; import { createDecorator, ServiceIdentifier, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, CloseDirection } from 'vs/workbench/common/editor'; +import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, CloseDirection, IEditorPartOptions } from 'vs/workbench/common/editor'; import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; +import { IDimension } from 'vs/editor/common/editorCommon'; +import { IDisposable } from 'vs/base/common/lifecycle'; export const IEditorGroupsService = createDecorator('editorGroupsService'); @@ -69,6 +72,10 @@ export interface EditorGroupLayout { groups: GroupLayoutArgument[]; } +export interface ICloseEditorOptions { + preserveFocus?: boolean; +} + export interface IMoveEditorOptions { index?: number; inactive?: boolean; @@ -159,6 +166,21 @@ export interface IEditorGroupsService { */ readonly onDidMoveGroup: Event; + /** + * An event for when a group gets activated. + */ + readonly onDidActivateGroup: Event; + + /** + * An event for when the group container is layed out. + */ + readonly onDidLayout: Event; + + /** + * The size of the editor groups area. + */ + readonly dimension: IDimension; + /** * An active group is the default location for new editors to open. */ @@ -180,6 +202,11 @@ export interface IEditorGroupsService { */ readonly orientation: GroupOrientation; + /** + * A promise that resolves when groups have been restored. + */ + readonly whenRestored: Promise; + /** * Get all groups that are currently visible in the editor area optionally * sorted by being most recent active or grid order. Will sort by creation @@ -190,7 +217,7 @@ export interface IEditorGroupsService { /** * Allows to convert a group identifier to a group. */ - getGroup(identifier: GroupIdentifier): IEditorGroup; + getGroup(identifier: GroupIdentifier): IEditorGroup | undefined; /** * Set a group as active. An active group is the default location for new editors to open. @@ -217,6 +244,16 @@ export interface IEditorGroupsService { */ applyLayout(layout: EditorGroupLayout): void; + /** + * Enable or disable centered editor layout. + */ + centerLayout(active: boolean): void; + + /** + * Find out if the editor layout is currently centered. + */ + isLayoutCentered(): boolean; + /** * Sets the orientation of the root group to be either vertical or horizontal. */ @@ -285,6 +322,16 @@ export interface IEditorGroupsService { * @param direction the direction of where to split to */ copyGroup(group: IEditorGroup | GroupIdentifier, location: IEditorGroup | GroupIdentifier, direction: GroupDirection): IEditorGroup; + + /** + * Access the options of the editor part. + */ + readonly partOptions: IEditorPartOptions; + + /** + * Enforce editor part options temporarily. + */ + enforcePartOptions(options: IEditorPartOptions): IDisposable; } export const enum GroupChangeKind { @@ -332,19 +379,19 @@ export interface IEditorGroup { /** * The active control is the currently visible control of the group. */ - readonly activeControl: IEditor; + readonly activeControl: IVisibleEditor | undefined; /** * The active editor is the currently visible editor of the group * within the current active control. */ - readonly activeEditor: IEditorInput; + readonly activeEditor: IEditorInput | null; /** * The editor in the group that is in preview mode if any. There can * only ever be one editor in preview mode. */ - readonly previewEditor: IEditorInput; + readonly previewEditor: IEditorInput | null; /** * The number of opend editors in this group. @@ -359,7 +406,7 @@ export interface IEditorGroup { /** * Returns the editor at a specific index of the group. */ - getEditor(index: number): IEditorInput; + getEditor(index: number): IEditorInput | null; /** * Get all editors that are currently opened in the group optionally @@ -379,7 +426,7 @@ export interface IEditorGroup { * @returns a promise that resolves around an IEditor instance unless * the call failed, or the editor was not opened as active editor. */ - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions): Promise; + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions): Promise; /** * Opens editors in this group. @@ -389,7 +436,7 @@ export interface IEditorGroup { * a group can only ever have one active editor, even if many editors are * opened, the result will only be one editor. */ - openEditors(editors: IEditorInputWithOptions[]): Promise; + openEditors(editors: IEditorInputWithOptions[]): Promise; /** * Find out if the provided editor is opened in the group. @@ -429,7 +476,7 @@ export interface IEditorGroup { * * @returns a promise when the editor is closed. */ - closeEditor(editor?: IEditorInput): Promise; + closeEditor(editor?: IEditorInput, options?: ICloseEditorOptions): Promise; /** * Closes specific editors in this group. This may trigger a confirmation dialog if @@ -437,7 +484,7 @@ export interface IEditorGroup { * * @returns a promise when all editors are closed. */ - closeEditors(editors: IEditorInput[] | ICloseEditorsFilter): Promise; + closeEditors(editors: IEditorInput[] | ICloseEditorsFilter, options?: ICloseEditorOptions): Promise; /** * Closes all editors from the group. This may trigger a confirmation dialog if diff --git a/src/vs/workbench/services/editor/common/editorService.ts b/src/vs/workbench/services/editor/common/editorService.ts index 8c1bbcdf30..01a7b8a4ab 100644 --- a/src/vs/workbench/services/editor/common/editorService.ts +++ b/src/vs/workbench/services/editor/common/editorService.ts @@ -8,7 +8,7 @@ import { IResourceInput, IEditorOptions, ITextEditorOptions } from 'vs/platform/ import { IEditorInput, IEditor, GroupIdentifier, IEditorInputWithOptions, IUntitledResourceInput, IResourceDiffInput, IResourceSideBySideInput, ITextEditor, ITextDiffEditor, ITextSideBySideEditor } from 'vs/workbench/common/editor'; import { Event } from 'vs/base/common/event'; import { IEditor as ICodeEditor } from 'vs/editor/common/editorCommon'; -import { IEditorGroup, IEditorReplacement } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroup, IEditorReplacement } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IDisposable } from 'vs/base/common/lifecycle'; export const IEditorService = createDecorator('editorService'); @@ -27,7 +27,7 @@ export const SIDE_GROUP = -2; export type SIDE_GROUP_TYPE = typeof SIDE_GROUP; export interface IOpenEditorOverrideHandler { - (editor: IEditorInput, options: IEditorOptions | ITextEditorOptions, group: IEditorGroup): IOpenEditorOverride; + (editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup): IOpenEditorOverride | undefined; } export interface IOpenEditorOverride { @@ -36,7 +36,12 @@ export interface IOpenEditorOverride { * If defined, will prevent the opening of an editor and replace the resulting * promise with the provided promise for the openEditor() call. */ - override?: Promise; + override?: Promise; +} + +export interface IVisibleEditor extends IEditor { + input: IEditorInput; + group: IEditorGroup; } export interface IEditorService { @@ -61,7 +66,7 @@ export interface IEditorService { * located in the currently active editor group. It will be `undefined` if the active * editor group has no editors open. */ - readonly activeEditor: IEditorInput; + readonly activeEditor: IEditorInput | undefined; /** * The currently active editor control or `undefined` if none. The editor control is @@ -69,7 +74,7 @@ export interface IEditorService { * * @see `IEditorService.activeEditor` */ - readonly activeControl: IEditor; + readonly activeControl: IVisibleEditor | undefined; /** * The currently active text editor widget or `undefined` if there is currently no active @@ -77,7 +82,7 @@ export interface IEditorService { * * @see `IEditorService.activeEditor` */ - readonly activeTextEditorWidget: ICodeEditor; + readonly activeTextEditorWidget: ICodeEditor | undefined; /** * All editors that are currently visible. An editor is visible when it is opened in an @@ -88,7 +93,7 @@ export interface IEditorService { /** * All editor controls that are currently visible across all editor groups. */ - readonly visibleControls: ReadonlyArray; + readonly visibleControls: ReadonlyArray; /** * All text editor widgets that are currently visible across all editor groups. A text editor @@ -114,10 +119,10 @@ export interface IEditorService { * @returns the editor that opened or NULL if the operation failed or the editor was not * opened to be active. */ - openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; - openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IEditorInput, options?: IEditorOptions | ITextEditorOptions, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceDiffInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; + openEditor(editor: IResourceSideBySideInput, group?: IEditorGroup | GroupIdentifier | SIDE_GROUP_TYPE | ACTIVE_GROUP_TYPE): Promise; /** * Open editors in an editor group. @@ -161,7 +166,7 @@ export interface IEditorService { * * @param group optional to specify a group to check for the editor */ - getOpened(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier): IEditorInput; + getOpened(editor: IResourceInput | IUntitledResourceInput, group?: IEditorGroup | GroupIdentifier): IEditorInput | undefined; /** * Allows to override the opening of editors by installing a handler that will @@ -178,5 +183,5 @@ export interface IEditorService { /** * Converts a lightweight input to a workbench editor input. */ - createInput(input: IResourceEditor): IEditorInput; + createInput(input: IResourceEditor): IEditorInput | null; } diff --git a/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts new file mode 100644 index 0000000000..eec96b9f65 --- /dev/null +++ b/src/vs/workbench/services/editor/test/browser/editorGroupsService.test.ts @@ -0,0 +1,57 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; +import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { GroupDirection, GroupsOrder, MergeGroupMode, GroupOrientation, GroupChangeKind, EditorsOrder, GroupLocation } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { EditorInput, IFileEditorInput, IEditorInputFactory, IEditorInputFactoryRegistry, Extensions as EditorExtensions, EditorOptions, CloseDirection } from 'vs/workbench/common/editor'; +import { IEditorModel } from 'vs/platform/editor/common/editor'; +import { URI } from 'vs/base/common/uri'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IEditorRegistry, Extensions, EditorDescriptor } from 'vs/workbench/browser/editor'; +import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { CancellationToken } from 'vs/base/common/cancellation'; + +export class TestEditorControl extends BaseEditor { + + constructor(@ITelemetryService telemetryService: ITelemetryService) { super('MyFileEditorForEditorGroupService', NullTelemetryService, new TestThemeService(), new TestStorageService()); } + + setInput(input: EditorInput, options: EditorOptions, token: CancellationToken): Promise { + super.setInput(input, options, token); + + return input.resolve().then(() => undefined); + } + + getId(): string { return 'MyFileEditorForEditorGroupService'; } + layout(): void { } + createEditor(): any { } +} + +export class TestEditorInput extends EditorInput implements IFileEditorInput { + + constructor(private resource: URI) { super(); } + + getTypeId() { return 'testEditorInputForEditorGroupService'; } + resolve(): Promise { return Promise.resolve(null); } + matches(other: TestEditorInput): boolean { return other && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } + setEncoding(encoding: string) { } + getEncoding(): string { return null!; } + setPreferredEncoding(encoding: string) { } + getResource(): URI { return this.resource; } + setForceOpenAsBinary(): void { } +} + +suite('Editor groups service', () => { + test('groups basics', function () { + // {{SQL CARBON EDIT}} - Remove test + assert.equal(0, 0); + }); +}); diff --git a/src/vs/workbench/services/editor/test/browser/editorService.test.ts b/src/vs/workbench/services/editor/test/browser/editorService.test.ts index db8bb95c2c..253234d0d0 100644 --- a/src/vs/workbench/services/editor/test/browser/editorService.test.ts +++ b/src/vs/workbench/services/editor/test/browser/editorService.test.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as paths from 'vs/base/common/paths'; import { IEditorModel } from 'vs/platform/editor/common/editor'; import { URI } from 'vs/base/common/uri'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; @@ -13,7 +12,7 @@ import { workbenchInstantiationService, TestStorageService } from 'vs/workbench/ import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { EditorService, DelegatingEditorService } from 'vs/workbench/services/editor/browser/editorService'; -import { IEditorGroup, IEditorGroupsService, GroupDirection } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroup, IEditorGroupsService, GroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; import { EditorPart } from 'vs/workbench/browser/parts/editor/editorPart'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; @@ -22,11 +21,12 @@ import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtil import { IEditorRegistry, EditorDescriptor, Extensions } from 'vs/workbench/browser/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { Registry } from 'vs/platform/registry/common/platform'; -import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { CancellationToken } from 'vs/base/common/cancellation'; import { timeout } from 'vs/base/common/async'; +import { toResource } from 'vs/base/test/common/utils'; // {{SQL CARBON EDIT}} - Disable editor tests /* @@ -54,7 +54,7 @@ export class TestEditorInput extends EditorInput implements IFileEditorInput { resolve(): Promise { return !this.fails ? Promise.resolve(null) : Promise.reject(new Error('fails')); } matches(other: TestEditorInput): boolean { return other && other.resource && this.resource.toString() === other.resource.toString() && other instanceof TestEditorInput; } setEncoding(encoding: string) { } - getEncoding(): string { return null; } + getEncoding(): string { return null!; } setPreferredEncoding(encoding: string) { } getResource(): URI { return this.resource; } setForceOpenAsBinary(): void { } @@ -78,7 +78,7 @@ suite('Editor service', () => { test('basics', function () { const partInstantiator = workbenchInstantiationService(); - const part = partInstantiator.createInstance(EditorPart, 'id', false); + const part = partInstantiator.createInstance(EditorPart); part.create(document.createElement('div')); part.layout(400, 300); @@ -122,7 +122,7 @@ suite('Editor service', () => { assert.equal(visibleEditorChangeEventCounter, 1); // Close input - return editor.group.closeEditor(input).then(() => { + return editor!.group!.closeEditor(input).then(() => { assert.equal(didCloseEditorListenerCounter, 1); assert.equal(activeEditorChangeEventCounter, 2); assert.equal(visibleEditorChangeEventCounter, 2); @@ -151,7 +151,7 @@ suite('Editor service', () => { test('openEditors() / replaceEditors()', function () { const partInstantiator = workbenchInstantiationService(); - const part = partInstantiator.createInstance(EditorPart, 'id', false); + const part = partInstantiator.createInstance(EditorPart); part.create(document.createElement('div')); part.layout(400, 300); @@ -182,11 +182,11 @@ suite('Editor service', () => { const service: EditorService = instantiationService.createInstance(EditorService); // Cached Input (Files) - const fileResource1 = toFileResource(this, '/foo/bar/cache1.js'); + const fileResource1 = toResource.call(this, '/foo/bar/cache1.js'); const fileInput1 = service.createInput({ resource: fileResource1 }); assert.ok(fileInput1); - const fileResource2 = toFileResource(this, '/foo/bar/cache2.js'); + const fileResource2 = toResource.call(this, '/foo/bar/cache2.js'); const fileInput2 = service.createInput({ resource: fileResource2 }); assert.ok(fileInput2); @@ -195,20 +195,20 @@ suite('Editor service', () => { const fileInput1Again = service.createInput({ resource: fileResource1 }); assert.equal(fileInput1Again, fileInput1); - fileInput1Again.dispose(); + fileInput1Again!.dispose(); - assert.ok(fileInput1.isDisposed()); + assert.ok(fileInput1!.isDisposed()); const fileInput1AgainAndAgain = service.createInput({ resource: fileResource1 }); assert.notEqual(fileInput1AgainAndAgain, fileInput1); - assert.ok(!fileInput1AgainAndAgain.isDisposed()); + assert.ok(!fileInput1AgainAndAgain!.isDisposed()); // Cached Input (Resource) - const resource1 = toResource.call(this, '/foo/bar/cache1.js'); + const resource1 = URI.from({ scheme: 'custom', path: '/foo/bar/cache1.js' }); const input1 = service.createInput({ resource: resource1 }); assert.ok(input1); - const resource2 = toResource.call(this, '/foo/bar/cache2.js'); + const resource2 = URI.from({ scheme: 'custom', path: '/foo/bar/cache2.js' }); const input2 = service.createInput({ resource: resource2 }); assert.ok(input2); @@ -217,13 +217,13 @@ suite('Editor service', () => { const input1Again = service.createInput({ resource: resource1 }); assert.equal(input1Again, input1); - input1Again.dispose(); + input1Again!.dispose(); - assert.ok(input1.isDisposed()); + assert.ok(input1!.isDisposed()); const input1AgainAndAgain = service.createInput({ resource: resource1 }); assert.notEqual(input1AgainAndAgain, input1); - assert.ok(!input1AgainAndAgain.isDisposed()); + assert.ok(!input1AgainAndAgain!.isDisposed()); }); test('createInput', function () { @@ -231,13 +231,13 @@ suite('Editor service', () => { const service: EditorService = instantiationService.createInstance(EditorService); // Untyped Input (file) - let input = service.createInput({ resource: toFileResource(this, '/index.html'), options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + let input = service.createInput({ resource: toResource.call(this, '/index.html'), options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof FileEditorInput); let contentInput = input; - assert.strictEqual(contentInput.getResource().fsPath, toFileResource(this, '/index.html').fsPath); + assert.strictEqual(contentInput.getResource().fsPath, toResource.call(this, '/index.html').fsPath); // Untyped Input (file, encoding) - input = service.createInput({ resource: toFileResource(this, '/index.html'), encoding: 'utf16le', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); + input = service.createInput({ resource: toResource.call(this, '/index.html'), encoding: 'utf16le', options: { selection: { startLineNumber: 1, startColumn: 1 } } }); assert(input instanceof FileEditorInput); contentInput = input; assert.equal(contentInput.getPreferredEncoding(), 'utf16le'); @@ -262,7 +262,7 @@ suite('Editor service', () => { class MyEditor extends BaseEditor { constructor(id: string) { - super(id, null, new TestThemeService(), new TestStorageService()); + super(id, undefined!, new TestThemeService(), new TestStorageService()); } getId(): string { @@ -292,7 +292,7 @@ suite('Editor service', () => { test('close editor does not dispose when editor opened in other group', function () { const partInstantiator = workbenchInstantiationService(); - const part = partInstantiator.createInstance(EditorPart, 'id', false); + const part = partInstantiator.createInstance(EditorPart); part.create(document.createElement('div')); part.layout(400, 300); @@ -331,7 +331,7 @@ suite('Editor service', () => { test('open to the side', function () { const partInstantiator = workbenchInstantiationService(); - const part = partInstantiator.createInstance(EditorPart, 'id', false); + const part = partInstantiator.createInstance(EditorPart); part.create(document.createElement('div')); part.layout(400, 300); @@ -349,13 +349,13 @@ suite('Editor service', () => { return service.openEditor(input1, { pinned: true, preserveFocus: true }, SIDE_GROUP).then(editor => { assert.equal(part.activeGroup, rootGroup); assert.equal(part.count, 2); - assert.equal(editor.group, part.groups[1]); + assert.equal(editor!.group, part.groups[1]); // Open to the side uses existing neighbour group if any return service.openEditor(input2, { pinned: true, preserveFocus: true }, SIDE_GROUP).then(editor => { assert.equal(part.activeGroup, rootGroup); assert.equal(part.count, 2); - assert.equal(editor.group, part.groups[1]); + assert.equal(editor!.group, part.groups[1]); }); }); }); @@ -365,7 +365,7 @@ suite('Editor service', () => { test('active editor change / visible editor change events', async function () { const partInstantiator = workbenchInstantiationService(); - const part = partInstantiator.createInstance(EditorPart, 'id', false); + const part = partInstantiator.createInstance(EditorPart); part.create(document.createElement('div')); part.layout(400, 300); @@ -405,7 +405,7 @@ suite('Editor service', () => { // 1.) open, open same, open other, close let editor = await service.openEditor(input, { pinned: true }); - const group = editor.group; + const group = editor!.group!; assertActiveEditorChangedEvent(true); assertVisibleEditorsChangedEvent(true); @@ -575,7 +575,7 @@ suite('Editor service', () => { test('openEditor returns NULL when opening fails or is inactive', async function () { const partInstantiator = workbenchInstantiationService(); - const part = partInstantiator.createInstance(EditorPart, 'id', false); + const part = partInstantiator.createInstance(EditorPart); part.create(document.createElement('div')); part.layout(400, 300); @@ -601,13 +601,3 @@ suite('Editor service', () => { }); */ }); - -/* -function toResource(path: string) { - return URI.from({ scheme: 'custom', path }); -} - -function toFileResource(self: any, path: string) { - return URI.file(paths.join('C:\\', Buffer.from(self.test.fullTitle()).toString('base64'), path)); -} -*/ \ No newline at end of file diff --git a/src/vs/platform/extensionManagement/node/multiExtensionManagement.ts b/src/vs/workbench/services/extensionManagement/node/multiExtensionManagement.ts similarity index 91% rename from src/vs/platform/extensionManagement/node/multiExtensionManagement.ts rename to src/vs/workbench/services/extensionManagement/node/multiExtensionManagement.ts index 6c8636337e..8f87e6709f 100644 --- a/src/vs/platform/extensionManagement/node/multiExtensionManagement.ts +++ b/src/vs/workbench/services/extensionManagement/node/multiExtensionManagement.ts @@ -9,17 +9,18 @@ import { IExtensionManagementServerService, IExtensionManagementServer, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { flatten } from 'vs/base/common/arrays'; -import { ExtensionType, IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { ExtensionType, IExtensionManifest, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { Disposable } from 'vs/base/common/lifecycle'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { IRemoteAuthorityResolverService, ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { getManifest } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; import { ILogService } from 'vs/platform/log/common/log'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localize } from 'vs/nls'; -import { isUIExtension } from 'vs/platform/extensions/node/extensionsUtil'; +import { isUIExtension } from 'vs/workbench/services/extensions/node/extensionsUtil'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class MultiExtensionManagementService extends Disposable implements IExtensionManagementService { @@ -36,7 +37,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IRemoteAuthorityResolverService private readonly remoteAuthorityResolverService: IRemoteAuthorityResolverService, + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, @ILogService private readonly logService: ILogService ) { super(); @@ -60,7 +61,10 @@ export class MultiExtensionManagementService extends Disposable implements IExte return Promise.reject(`Invalid location ${extension.location.toString()}`); } const syncExtensions = await this.hasToSyncExtensions(); - return syncExtensions ? this.uninstallEverywhere(extension, force) : this.uninstallInServer(extension, server, force); + if (syncExtensions || isLanguagePackExtension(extension.manifest)) { + return this.uninstallEverywhere(extension, force); + } + return this.uninstallInServer(extension, server, force); } return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.uninstall(extension, force); } @@ -133,12 +137,12 @@ export class MultiExtensionManagementService extends Disposable implements IExte async install(vsix: URI): Promise { if (this.extensionManagementServerService.remoteExtensionManagementServer) { const syncExtensions = await this.hasToSyncExtensions(); - if (syncExtensions) { + const manifest = await getManifest(vsix.fsPath); + if (syncExtensions || isLanguagePackExtension(manifest)) { // Install on both servers const [extensionIdentifier] = await Promise.all(this.servers.map(server => server.extensionManagementService.install(vsix))); return extensionIdentifier; } - const manifest = await getManifest(vsix.fsPath); if (isUIExtension(manifest, this.configurationService)) { // Install only on local server return this.extensionManagementServerService.localExtensionManagementServer.extensionManagementService.install(vsix); @@ -156,7 +160,7 @@ export class MultiExtensionManagementService extends Disposable implements IExte if (this.extensionManagementServerService.remoteExtensionManagementServer) { const [manifest, syncExtensions] = await Promise.all([this.extensionGalleryService.getManifest(gallery, CancellationToken.None), this.hasToSyncExtensions()]); if (manifest) { - if (syncExtensions) { + if (syncExtensions || isLanguagePackExtension(manifest)) { // Install on both servers return Promise.all(this.servers.map(server => server.extensionManagementService.installFromGallery(gallery))).then(() => undefined); } @@ -199,14 +203,22 @@ export class MultiExtensionManagementService extends Disposable implements IExte return this.extensionManagementServerService.getExtensionManagementServer(extension.location); } - private _remoteAuthorityResolverPromise: Promise; - private hasToSyncExtensions(): Promise { + private async hasToSyncExtensions(): Promise { if (!this.extensionManagementServerService.remoteExtensionManagementServer) { - return Promise.resolve(false); + return false; } - if (!this._remoteAuthorityResolverPromise) { - this._remoteAuthorityResolverPromise = this.remoteAuthorityResolverService.resolveAuthority(this.extensionManagementServerService.remoteExtensionManagementServer.authority); + const connection = this.remoteAgentService.getConnection(); + if (!connection) { + return false; } - return this._remoteAuthorityResolverPromise.then(({ syncExtensions }) => !!syncExtensions); + + const remoteEnv = await connection.getEnvironment(); + if (!remoteEnv) { + return false; + } + + return remoteEnv.syncExtensions; } -} \ No newline at end of file +} + +registerSingleton(IExtensionManagementService, MultiExtensionManagementService); \ No newline at end of file diff --git a/src/vs/workbench/services/extensions/common/extensions.ts b/src/vs/workbench/services/extensions/common/extensions.ts index 1016372af0..f3fc22b90a 100644 --- a/src/vs/workbench/services/extensions/common/extensions.ts +++ b/src/vs/workbench/services/extensions/common/extensions.ts @@ -8,18 +8,9 @@ import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { ExtensionIdentifier, IExtensionManifest, IExtension, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -export interface IExtensionDescription extends IExtensionManifest { - readonly identifier: ExtensionIdentifier; - readonly uuid?: string; - readonly isBuiltin: boolean; - readonly isUnderDevelopment: boolean; - readonly extensionLocation: URI; - enableProposedApi?: boolean; -} - export const nullExtensionDescription = Object.freeze({ identifier: new ExtensionIdentifier('nullExtensionDescription'), name: 'Null Extension Description', @@ -42,10 +33,15 @@ export interface IMessage { export interface IExtensionsStatus { messages: IMessage[]; - activationTimes: ActivationTimes; + activationTimes: ActivationTimes | undefined; runtimeErrors: Error[]; } +export type ExtensionActivationError = string | MissingDependencyError; +export class MissingDependencyError { + constructor(readonly dependency: string) { } +} + /** * e.g. * ``` @@ -89,7 +85,7 @@ export interface IExtensionHostProfile { /** * Extension id or one of the four known program states. */ -export type ProfileSegmentId = string | 'idle' | 'program' | 'gc' | 'self' | null; +export type ProfileSegmentId = string | 'idle' | 'program' | 'gc' | 'self'; export class ActivationTimes { constructor( @@ -259,3 +255,27 @@ export function toExtension(extensionDescription: IExtensionDescription): IExten location: extensionDescription.extensionLocation, }; } + + +export class NullExtensionService implements IExtensionService { + _serviceBrand: any; + onDidRegisterExtensions: Event = Event.None; + onDidChangeExtensionsStatus: Event = Event.None; + onDidChangeExtensions: Event = Event.None; + onWillActivateByEvent: Event = Event.None; + onDidChangeResponsiveChange: Event = Event.None; + activateByEvent(_activationEvent: string): Promise { return Promise.resolve(undefined); } + whenInstalledExtensionsRegistered(): Promise { return Promise.resolve(true); } + getExtensions(): Promise { return Promise.resolve([]); } + getExtension() { return Promise.resolve(undefined); } + readExtensionPointContributions(_extPoint: IExtensionPoint): Promise[]> { return Promise.resolve(Object.create(null)); } + getExtensionsStatus(): { [id: string]: IExtensionsStatus; } { return Object.create(null); } + canProfileExtensionHost(): boolean { return false; } + getInspectPort(): number { return 0; } + startExtensionHostProfile(): Promise { return Promise.resolve(Object.create(null)); } + restartExtensionHost(): void { } + startExtensionHost(): void { } + stopExtensionHost(): void { } + canAddExtension(): boolean { return false; } + canRemoveExtension(): boolean { return false; } +} \ No newline at end of file diff --git a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts index fd9d018dcf..9e2a180d90 100644 --- a/src/vs/workbench/services/extensions/common/extensionsRegistry.ts +++ b/src/vs/workbench/services/extensions/common/extensionsRegistry.ts @@ -10,11 +10,12 @@ import Severity from 'vs/base/common/severity'; import { EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IExtensionDescription, IMessage } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { IMessage } from 'vs/workbench/services/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; const hasOwnProperty = Object.hasOwnProperty; const schemaRegistry = Registry.as(Extensions.JSONContribution); +export type ExtensionKind = 'workspace' | 'ui' | undefined; export class ExtensionMessageCollector { @@ -67,6 +68,7 @@ export interface IExtensionPointHandler { export interface IExtensionPoint { name: string; setHandler(handler: IExtensionPointHandler): void; + defaultExtensionKind: ExtensionKind; } export class ExtensionPointUserDelta { @@ -105,18 +107,16 @@ export class ExtensionPointUserDelta { export class ExtensionPoint implements IExtensionPoint { public readonly name: string; - public readonly isDynamic: boolean; + public readonly defaultExtensionKind: ExtensionKind; private _handler: IExtensionPointHandler | null; - private _handlerCalled: boolean; private _users: IExtensionPointUser[] | null; private _delta: ExtensionPointUserDelta | null; - constructor(name: string, isDynamic: boolean) { + constructor(name: string, defaultExtensionKind: ExtensionKind) { this.name = name; - this.isDynamic = isDynamic; + this.defaultExtensionKind = defaultExtensionKind; this._handler = null; - this._handlerCalled = false; this._users = null; this._delta = null; } @@ -140,12 +140,7 @@ export class ExtensionPoint implements IExtensionPoint { return; } - if (this._handlerCalled && !this.isDynamic) { - throw new Error('The extension point is not dynamic!'); - } - try { - this._handlerCalled = true; this._handler(this._users, this._delta); } catch (err) { onUnexpectedError(err); @@ -367,10 +362,10 @@ export const schema = { }; export interface IExtensionPointDescriptor { - isDynamic?: boolean; extensionPoint: string; deps?: IExtensionPoint[]; jsonSchema: IJSONSchema; + defaultExtensionKind?: ExtensionKind; } export class ExtensionsRegistryImpl { @@ -385,7 +380,7 @@ export class ExtensionsRegistryImpl { if (hasOwnProperty.call(this._extensionPoints, desc.extensionPoint)) { throw new Error('Duplicate extension point: ' + desc.extensionPoint); } - let result = new ExtensionPoint(desc.extensionPoint, desc.isDynamic || false); + let result = new ExtensionPoint(desc.extensionPoint, desc.defaultExtensionKind); this._extensionPoints[desc.extensionPoint] = result; schema.properties['contributes'].properties[desc.extensionPoint] = desc.jsonSchema; diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index c5107c4ffb..f1a641782f 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -5,23 +5,22 @@ import * as nls from 'vs/nls'; import * as os from 'os'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import * as errors from 'vs/base/common/errors'; import { Schemas } from 'vs/base/common/network'; import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; -import { fsPath } from 'vs/base/common/resources'; +import { originalFSPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE, ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import pkg from 'vs/platform/node/package'; -import product from 'vs/platform/node/product'; +import { BUILTIN_MANIFEST_CACHE_FILE, MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE, ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/node/product'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IWindowService } from 'vs/platform/windows/common/windows'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionScanner, ExtensionScannerInput, IExtensionReference, IExtensionResolver, ILog, IRelaxedExtensionDescription, Translations } from 'vs/workbench/services/extensions/node/extensionPoints'; interface IExtensionCacheData { @@ -71,7 +70,7 @@ export class CachedExtensionScanner { const version = pkg.version; const commit = product.commit; const devMode = !!process.env['VSCODE_DEV']; - const locale = platform.locale; + const locale = platform.language; const input = new ExtensionScannerInput(version, commit, locale, devMode, path, isBuiltin, false, translations); return ExtensionScanner.scanSingleExtension(input, log); } @@ -250,7 +249,7 @@ export class CachedExtensionScanner { const version = pkg.version; const commit = product.commit; const devMode = !!process.env['VSCODE_DEV']; - const locale = platform.locale; + const locale = platform.language; const builtinExtensions = this._scanExtensionsWithCache( windowService, @@ -297,7 +296,7 @@ export class CachedExtensionScanner { let developedExtensions: Promise = Promise.resolve([]); if (environmentService.isExtensionDevelopment && environmentService.extensionDevelopmentLocationURI && environmentService.extensionDevelopmentLocationURI.scheme === Schemas.file) { developedExtensions = ExtensionScanner.scanOneOrMultipleExtensions( - new ExtensionScannerInput(version, commit, locale, devMode, fsPath(environmentService.extensionDevelopmentLocationURI), false, true, translations), log + new ExtensionScannerInput(version, commit, locale, devMode, originalFSPath(environmentService.extensionDevelopmentLocationURI), false, true, translations), log ); } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 2a8dec6e62..c8932a3f91 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -21,26 +21,26 @@ import { IRemoteConsoleLog, log, parse } from 'vs/base/node/console'; import { findFreePort, randomPort } from 'vs/base/node/ports'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc'; import { Protocol, generateRandomPipeName, BufferedProtocol } from 'vs/base/parts/ipc/node/ipc.net'; -import { IBroadcast, IBroadcastService } from 'vs/platform/broadcast/electron-browser/broadcastService'; +import { IBroadcast, IBroadcastService } from 'vs/workbench/services/broadcast/electron-browser/broadcastService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { EXTENSION_ATTACH_BROADCAST_CHANNEL, EXTENSION_CLOSE_EXTHOST_BROADCAST_CHANNEL, EXTENSION_LOG_BROADCAST_CHANNEL, EXTENSION_RELOAD_BROADCAST_CHANNEL, EXTENSION_TERMINATE_BROADCAST_CHANNEL } from 'vs/platform/extensions/common/extensionHost'; import { ILabelService } from 'vs/platform/label/common/label'; import { ILifecycleService, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IInitData } from 'vs/workbench/api/node/extHost.protocol'; -import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/common/extensionHostProtocol'; -import { ICrashReporterService } from 'vs/workbench/services/crashReporter/electron-browser/crashReporterService'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/node/extensionHostProtocol'; +import { withNullAsUndefined } from 'vs/base/common/types'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export interface IExtensionHostStarter { - readonly onCrashed: Event<[number, string]>; - start(): Promise; - getInspectPort(): number; + readonly onCrashed: Event<[number, string | null]>; + start(): Promise | null; + getInspectPort(): number | undefined; dispose(): void; } @@ -57,7 +57,7 @@ export function parseExtensionDevOptions(environmentService: IEnvironmentService const debugOk = !extDevLoc || extDevLoc.scheme === Schemas.file; let isExtensionDevDebug = debugOk && typeof environmentService.debugExtensionHost.port === 'number'; let isExtensionDevDebugBrk = debugOk && !!environmentService.debugExtensionHost.break; - let isExtensionDevTestFromCli = isExtensionDevHost && !!environmentService.extensionTestsPath && !environmentService.debugExtensionHost.break; + let isExtensionDevTestFromCli = isExtensionDevHost && !!environmentService.extensionTestsLocationURI && !environmentService.debugExtensionHost.break; return { isExtensionDevHost, isExtensionDevDebug, @@ -79,15 +79,15 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { private readonly _isExtensionDevTestFromCli: boolean; // State - private _lastExtensionHostError: string; + private _lastExtensionHostError: string | null; private _terminating: boolean; // Resources, in order they get acquired/created when .start() is called: - private _namedPipeServer: Server; + private _namedPipeServer: Server | null; private _inspectPort: number; - private _extensionHostProcess: ChildProcess; - private _extensionHostConnection: Socket; - private _messageProtocol: Promise; + private _extensionHostProcess: ChildProcess | null; + private _extensionHostConnection: Socket | null; + private _messageProtocol: Promise | null; constructor( private readonly _autoStart: boolean, @@ -101,7 +101,6 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IEnvironmentService private readonly _environmentService: IEnvironmentService, @ITelemetryService private readonly _telemetryService: ITelemetryService, - @ICrashReporterService private readonly _crashReporterService: ICrashReporterService, @ILogService private readonly _logService: ILogService, @ILabelService private readonly _labelService: ILabelService ) { @@ -159,7 +158,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { } } - public start(): Promise { + public start(): Promise | null { if (this._terminating) { // .terminate() was called return null; @@ -185,7 +184,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { // We detach because we have noticed that when the renderer exits, its child processes // (i.e. extension host) are taken down in a brutal fashion by the OS detached: !!isWindows, - execArgv: undefined, + execArgv: undefined as string[] | undefined, silent: true }; @@ -201,7 +200,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { } } - const crashReporterOptions = this._crashReporterService.getChildProcessStartOptions('extensionHost'); + const crashReporterOptions = undefined; // TODO@electron pass this in as options to the extension host after verifying this actually works if (crashReporterOptions) { opts.env.CRASH_REPORTER_START_OPTIONS = JSON.stringify(crashReporterOptions); } @@ -229,9 +228,14 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { // Print out extension host output onDebouncedOutput(output => { - const inspectorUrlMatch = !this._environmentService.isBuilt && output.data && output.data.match(/ws:\/\/([^\s]+)/); + const inspectorUrlMatch = output.data && output.data.match(/ws:\/\/([^\s]+:(\d+)\/[^\s]+)/); if (inspectorUrlMatch) { - console.log(`%c[Extension Host] %cdebugger inspector at chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=${inspectorUrlMatch[1]}`, 'color: blue', 'color: black'); + if (!this._environmentService.isBuilt) { + console.log(`%c[Extension Host] %cdebugger inspector at chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=${inspectorUrlMatch[1]}`, 'color: blue', 'color: black'); + } + if (!this._inspectPort) { + this._inspectPort = Number(inspectorUrlMatch[2]); + } } else { console.group('Extension Host'); console.log(output.data, ...output.format); @@ -301,7 +305,9 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { this._namedPipeServer = createServer(); this._namedPipeServer.on('error', reject); this._namedPipeServer.listen(pipeName, () => { - this._namedPipeServer.removeListener('error', reject); + if (this._namedPipeServer) { + this._namedPipeServer.removeListener('error', reject); + } resolve(pipeName); }); }); @@ -342,15 +348,19 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { // Wait for the extension host to connect to our named pipe // and wrap the socket in the message passing protocol let handle = setTimeout(() => { - this._namedPipeServer.close(); - this._namedPipeServer = null; + if (this._namedPipeServer) { + this._namedPipeServer.close(); + this._namedPipeServer = null; + } reject('timeout'); }, 60 * 1000); - this._namedPipeServer.on('connection', socket => { + this._namedPipeServer!.on('connection', socket => { clearTimeout(handle); - this._namedPipeServer.close(); - this._namedPipeServer = null; + if (this._namedPipeServer) { + this._namedPipeServer.close(); + this._namedPipeServer = null; + } this._extensionHostConnection = socket; // using a buffered message protocol here because between now @@ -426,16 +436,17 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined, appSettingsHome: this._environmentService.appSettingsHome ? URI.file(this._environmentService.appSettingsHome) : undefined, extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI, - extensionTestsPath: this._environmentService.extensionTestsPath, - globalStorageHome: URI.file(this._environmentService.globalStorageHome) + extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI, + globalStorageHome: URI.file(this._environmentService.globalStorageHome), + userHome: URI.file(this._environmentService.userHome) }, - workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : { - configuration: workspace.configuration, - folders: workspace.folders, + workspace: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? undefined : { + configuration: withNullAsUndefined(workspace.configuration), id: workspace.id, name: this._labelService.getWorkspaceLabel(workspace) }, resolvedExtensions: [], + hostExtensions: [], extensions: extensionDescriptions, telemetryInfo, logLevel: this._logService.getLevel(), @@ -503,6 +514,18 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { } } + public enableInspector(): Promise { + if (this._inspectPort) { + return Promise.resolve(); + } + // send SIGUSR1 and wait a little the actual port is read from the process stdout which we + // scan here: https://github.com/Microsoft/vscode/blob/67ffab8dcd1a6752d8b62bcd13d7020101eef568/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts#L225-L240 + if (this._extensionHostProcess) { + this._extensionHostProcess.kill('SIGUSR1'); + } + return timeout(1000); + } + public getInspectPort(): number { return this._inspectPort; } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHostProcessManager.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHostProcessManager.ts index 6c98d3152a..14c771a059 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHostProcessManager.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHostProcessManager.ts @@ -12,13 +12,13 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ExtHostCustomersRegistry } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/node/extHost.protocol'; -import { ProfileSession, IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { ProfileSession } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionHostStarter } from 'vs/workbench/services/extensions/electron-browser/extensionHost'; import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler'; import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier'; import { IRPCProtocolLogger, RPCProtocol, RequestInitiator, ResponsiveState } from 'vs/workbench/services/extensions/node/rpcProtocol'; import { ResolvedAuthority } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; @@ -36,7 +36,7 @@ const NO_OP_VOID_PROMISE = Promise.resolve(undefined); export class ExtensionHostProcessManager extends Disposable { - public readonly onDidCrash: Event<[number, string]>; + public readonly onDidCrash: Event<[number, string | null]>; private readonly _onDidChangeResponsiveState: Emitter = this._register(new Emitter()); public readonly onDidChangeResponsiveState: Event = this._onDidChangeResponsiveState.event; @@ -45,13 +45,13 @@ export class ExtensionHostProcessManager extends Disposable { * A map of already activated events to speed things up if the same activation event is triggered multiple times. */ private readonly _extensionHostProcessFinishedActivateEvents: { [activationEvent: string]: boolean; }; - private _extensionHostProcessRPCProtocol: RPCProtocol; + private _extensionHostProcessRPCProtocol: RPCProtocol | null; private readonly _extensionHostProcessCustomers: IDisposable[]; private readonly _extensionHostProcessWorker: IExtensionHostStarter; /** * winjs believes a proxy is a promise because it has a `then` method, so wrap the result in an object. */ - private _extensionHostProcessProxy: Promise<{ value: ExtHostExtensionServiceShape; }>; + private _extensionHostProcessProxy: Promise<{ value: ExtHostExtensionServiceShape; } | null> | null; constructor( extensionHostProcessWorker: IExtensionHostStarter, @@ -67,7 +67,7 @@ export class ExtensionHostProcessManager extends Disposable { this._extensionHostProcessWorker = extensionHostProcessWorker; this.onDidCrash = this._extensionHostProcessWorker.onCrashed; - this._extensionHostProcessProxy = this._extensionHostProcessWorker.start().then( + this._extensionHostProcessProxy = this._extensionHostProcessWorker.start()!.then( (protocol) => { return { value: this._createExtensionHostCustomers(protocol) }; }, @@ -105,16 +105,14 @@ export class ExtensionHostProcessManager extends Disposable { super.dispose(); } - // {{SQL CARBON EDIT}} - Add new getExtensionHostProcessWorker method - public getExtenstionHostProcessWorker(): IExtensionHostStarter { - return this._extensionHostProcessWorker; - } - // {{SQL CARBON EDIT}} - End - - private async measure(): Promise { - const latency = await this._measureLatency(); - const down = await this._measureDown(); - const up = await this._measureUp(); + private async measure(): Promise { + const proxy = await this._getExtensionHostProcessProxy(); + if (!proxy) { + return null; + } + const latency = await this._measureLatency(proxy); + const down = await this._measureDown(proxy); + const up = await this._measureUp(proxy); return { remoteAuthority: this._remoteAuthority, latency, @@ -123,10 +121,20 @@ export class ExtensionHostProcessManager extends Disposable { }; } - private async _measureLatency(): Promise { + private async _getExtensionHostProcessProxy(): Promise { + if (!this._extensionHostProcessProxy) { + return null; + } + const p = await this._extensionHostProcessProxy; + if (!p) { + return null; + } + return p.value; + } + + private async _measureLatency(proxy: ExtHostExtensionServiceShape): Promise { const COUNT = 10; - const { value: proxy } = await this._extensionHostProcessProxy; let sum = 0; for (let i = 0; i < COUNT; i++) { const sw = StopWatch.create(true); @@ -141,10 +149,9 @@ export class ExtensionHostProcessManager extends Disposable { return (byteCount * 1000 * 8) / elapsedMillis; } - private async _measureUp(): Promise { + private async _measureUp(proxy: ExtHostExtensionServiceShape): Promise { const SIZE = 10 * 1024 * 1024; // 10MB - const { value: proxy } = await this._extensionHostProcessProxy; let b = Buffer.alloc(SIZE, Math.random() % 256); const sw = StopWatch.create(true); await proxy.$test_up(b); @@ -152,10 +159,9 @@ export class ExtensionHostProcessManager extends Disposable { return ExtensionHostProcessManager._convert(SIZE, sw.elapsed()); } - private async _measureDown(): Promise { + private async _measureDown(proxy: ExtHostExtensionServiceShape): Promise { const SIZE = 10 * 1024 * 1024; // 10MB - const { value: proxy } = await this._extensionHostProcessProxy; const sw = StopWatch.create(true); await proxy.$test_down(SIZE); sw.stop(); @@ -177,9 +183,9 @@ export class ExtensionHostProcessManager extends Disposable { this._register(this._extensionHostProcessRPCProtocol.onDidChangeResponsiveState((responsiveState: ResponsiveState) => this._onDidChangeResponsiveState.fire(responsiveState))); const extHostContext: IExtHostContext = { remoteAuthority: this._remoteAuthority, - getProxy: (identifier: ProxyIdentifier): T => this._extensionHostProcessRPCProtocol.getProxy(identifier), - set: (identifier: ProxyIdentifier, instance: R): R => this._extensionHostProcessRPCProtocol.set(identifier, instance), - assertRegistered: (identifiers: ProxyIdentifier[]): void => this._extensionHostProcessRPCProtocol.assertRegistered(identifiers), + getProxy: (identifier: ProxyIdentifier): T => this._extensionHostProcessRPCProtocol!.getProxy(identifier), + set: (identifier: ProxyIdentifier, instance: R): R => this._extensionHostProcessRPCProtocol!.set(identifier, instance), + assertRegistered: (identifiers: ProxyIdentifier[]): void => this._extensionHostProcessRPCProtocol!.assertRegistered(identifiers), }; // Named customers @@ -205,10 +211,12 @@ export class ExtensionHostProcessManager extends Disposable { return this._extensionHostProcessRPCProtocol.getProxy(ExtHostContext.ExtHostExtensionService); } - public activate(extension: ExtensionIdentifier, activationEvent: string): Promise { - return this._extensionHostProcessProxy.then((proxy) => { - return proxy.value.$activate(extension, activationEvent); - }); + public async activate(extension: ExtensionIdentifier, activationEvent: string): Promise { + const proxy = await this._getExtensionHostProcessProxy(); + if (!proxy) { + return false; + } + return proxy.$activate(extension, activationEvent); } public activateByEvent(activationEvent: string): Promise { @@ -247,7 +255,7 @@ export class ExtensionHostProcessManager extends Disposable { return 0; } - public resolveAuthority(remoteAuthority: string): Promise { + public async resolveAuthority(remoteAuthority: string): Promise { const authorityPlusIndex = remoteAuthority.indexOf('+'); if (authorityPlusIndex === -1) { // This authority does not need to be resolved, simply parse the port number @@ -255,19 +263,30 @@ export class ExtensionHostProcessManager extends Disposable { return Promise.resolve({ authority: remoteAuthority, host: pieces[0], - port: parseInt(pieces[1], 10), - syncExtensions: false + port: parseInt(pieces[1], 10) }); } - return this._extensionHostProcessProxy.then(proxy => proxy.value.$resolveAuthority(remoteAuthority)); + const proxy = await this._getExtensionHostProcessProxy(); + if (!proxy) { + throw new Error(`Cannot resolve authority`); + } + return proxy.$resolveAuthority(remoteAuthority); } - public start(enabledExtensionIds: ExtensionIdentifier[]): Promise { - return this._extensionHostProcessProxy.then(proxy => proxy.value.$startExtensionHost(enabledExtensionIds)); + public async start(enabledExtensionIds: ExtensionIdentifier[]): Promise { + const proxy = await this._getExtensionHostProcessProxy(); + if (!proxy) { + return; + } + return proxy.$startExtensionHost(enabledExtensionIds); } - public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { - return this._extensionHostProcessProxy.then(proxy => proxy.value.$deltaExtensions(toAdd, toRemove)); + public async deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): Promise { + const proxy = await this._getExtensionHostProcessProxy(); + if (!proxy) { + return; + } + return proxy.$deltaExtensions(toAdd, toRemove); } } @@ -335,7 +354,7 @@ interface ExtHostLatencyResult { } interface ExtHostLatencyProvider { - measure(): Promise; + measure(): Promise; } let providers: ExtHostLatencyProvider[] = []; diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts index 373be15bd1..a1145f932e 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.ts @@ -6,7 +6,8 @@ import { Profile, ProfileNode } from 'v8-inspect-profiler'; import { TernarySearchTree } from 'vs/base/common/map'; import { realpathSync } from 'vs/base/node/extfs'; -import { IExtensionDescription, IExtensionHostProfile, IExtensionService, ProfileSegmentId, ProfileSession } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionHostProfile, IExtensionService, ProfileSegmentId, ProfileSession } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; export class ExtensionHostProfiler { @@ -33,12 +34,12 @@ export class ExtensionHostProfiler { let nodes = profile.nodes; let idsToNodes = new Map(); - let idsToSegmentId = new Map(); + let idsToSegmentId = new Map(); for (let node of nodes) { idsToNodes.set(node.id, node); } - function visit(node: ProfileNode, segmentId: ProfileSegmentId) { + function visit(node: ProfileNode, segmentId: ProfileSegmentId | null) { if (!segmentId) { switch (node.callFrame.functionName) { case '(root)': diff --git a/src/vs/workbench/services/extensions/node/extensionManagementServerService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionManagementServerService.ts similarity index 77% rename from src/vs/workbench/services/extensions/node/extensionManagementServerService.ts rename to src/vs/workbench/services/extensions/electron-browser/extensionManagementServerService.ts index 9b58cb6b82..2cb6b5c0d9 100644 --- a/src/vs/workbench/services/extensions/node/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionManagementServerService.ts @@ -6,11 +6,13 @@ import { localize } from 'vs/nls'; import { Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; -import { IExtensionManagementServer, IExtensionManagementServerService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { IExtensionManagementServer, IExtensionManagementServerService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/node/extensionManagementIpc'; -import { IRemoteAgentService } from 'vs/workbench/services/remote/node/remoteAgentService'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; -import { IChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; const localExtensionManagementServerAuthority: string = 'vscode-local'; @@ -22,9 +24,11 @@ export class ExtensionManagementServerService implements IExtensionManagementSer readonly remoteExtensionManagementServer: IExtensionManagementServer | null = null; constructor( - localExtensionManagementService: IExtensionManagementService, + @ISharedProcessService sharedProcessService: ISharedProcessService, @IRemoteAgentService remoteAgentService: IRemoteAgentService ) { + const localExtensionManagementService = new ExtensionManagementChannelClient(sharedProcessService.getChannel('extensions')); + this.localExtensionManagementServer = { extensionManagementService: localExtensionManagementService, authority: localExtensionManagementServerAuthority, label: localize('local', "Local") }; const remoteAgentConnection = remoteAgentService.getConnection(); if (remoteAgentConnection) { @@ -42,4 +46,6 @@ export class ExtensionManagementServerService implements IExtensionManagementSer } return null; } -} \ No newline at end of file +} + +registerSingleton(IExtensionManagementServerService, ExtensionManagementServerService); \ No newline at end of file diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index a3c2ec2666..4f1f90b97a 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { Barrier, runWhenIdle } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; @@ -17,33 +17,34 @@ import { EnablementState, IExtensionEnablementService, IExtensionIdentifier, IEx import { BetterMergeId, areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import pkg from 'vs/platform/node/package'; -import product from 'vs/platform/node/product'; +import pkg from 'vs/platform/product/node/package'; +import product from 'vs/platform/product/node/product'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; -import { ActivationTimes, ExtensionPointContribution, IExtensionDescription, IExtensionService, IExtensionsStatus, IMessage, ProfileSession, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension } from 'vs/workbench/services/extensions/common/extensions'; +import { ActivationTimes, ExtensionPointContribution, IExtensionService, IExtensionsStatus, IMessage, ProfileSession, IWillActivateEvent, IResponsiveStateChangeEvent, toExtension } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser, schema } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry'; import { ResponsiveState } from 'vs/workbench/services/extensions/node/rpcProtocol'; import { CachedExtensionScanner, Logger } from 'vs/workbench/services/extensions/electron-browser/cachedExtensionScanner'; import { ExtensionHostProcessManager } from 'vs/workbench/services/extensions/electron-browser/extensionHostProcessManager'; -import { ExtensionIdentifier, IExtension, ExtensionType } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtension, ExtensionType, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Schemas } from 'vs/base/common/network'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; const hasOwnProperty = Object.hasOwnProperty; const NO_OP_VOID_PROMISE = Promise.resolve(undefined); schema.properties.engines.properties.vscode.default = `^${pkg.version}`; -let productAllowProposedApi: Set = null; +let productAllowProposedApi: Set | null = null; function allowProposedApiFromProduct(id: ExtensionIdentifier): boolean { // create set if needed - if (productAllowProposedApi === null) { + if (!productAllowProposedApi) { productAllowProposedApi = new Set(); if (isNonEmptyArray(product.extensionAllowedProposedApi)) { - product.extensionAllowedProposedApi.forEach((id) => productAllowProposedApi.add(ExtensionIdentifier.toKey(id))); + product.extensionAllowedProposedApi.forEach((id) => productAllowProposedApi!.add(ExtensionIdentifier.toKey(id))); } } return productAllowProposedApi.has(ExtensionIdentifier.toKey(id)); @@ -61,7 +62,7 @@ export class ExtensionService extends Disposable implements IExtensionService { public _serviceBrand: any; private readonly _extensionHostLogsLocation: URI; - private _registry: ExtensionDescriptionRegistry; + private readonly _registry: ExtensionDescriptionRegistry; private readonly _installedExtensionsReady: Barrier; private readonly _isDev: boolean; private readonly _extensionsMessages: Map; @@ -101,8 +102,8 @@ export class ExtensionService extends Disposable implements IExtensionService { @ILifecycleService private readonly _lifecycleService: ILifecycleService ) { super(); - this._extensionHostLogsLocation = URI.file(path.posix.join(this._environmentService.logsPath, `exthost${this._windowService.getCurrentWindowId()}`)); - this._registry = null; + this._extensionHostLogsLocation = URI.file(path.join(this._environmentService.logsPath, `exthost${this._windowService.getCurrentWindowId()}`)); + this._registry = new ExtensionDescriptionRegistry([]); this._installedExtensionsReady = new Barrier(); this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment; this._extensionsMessages = new Map(); @@ -167,7 +168,7 @@ export class ExtensionService extends Disposable implements IExtensionService { } while (this._deltaExtensionsQueue.length > 0) { - const item = this._deltaExtensionsQueue.shift(); + const item = this._deltaExtensionsQueue.shift()!; try { this._inHandleDeltaExtensions = true; await this._deltaExtensions(item.toAdd, item.toRemove); @@ -197,8 +198,8 @@ export class ExtensionService extends Disposable implements IExtensionService { } const extensionDescription = await this._extensionScanner.scanSingleExtension(extension.location.fsPath, extension.type === ExtensionType.System, this.createLogger()); - if (!extensionDescription || !this._usesOnlyDynamicExtensionPoints(extensionDescription)) { - // uses non-dynamic extension point + if (!extensionDescription) { + // could not scan extension... continue; } @@ -227,7 +228,11 @@ export class ExtensionService extends Disposable implements IExtensionService { } // Update the local registry - this._registry.deltaExtensions(toAdd, toRemove.map(e => e.identifier)); + const result = this._registry.deltaExtensions(toAdd, toRemove.map(e => e.identifier)); + toRemove = toRemove.concat(result.removedDueToLooping); + if (result.removedDueToLooping.length > 0) { + this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', '))); + } // Update extension points this._rehandleExtensionPoints(([]).concat(toAdd).concat(toRemove)); @@ -267,28 +272,6 @@ export class ExtensionService extends Disposable implements IExtensionService { } } - private _usesOnlyDynamicExtensionPoints(extension: IExtensionDescription): boolean { - const extensionPoints = ExtensionsRegistry.getExtensionPointsMap(); - if (extension.contributes) { - for (let extPointName in extension.contributes) { - if (hasOwnProperty.call(extension.contributes, extPointName)) { - const extPoint = extensionPoints[extPointName]; - if (extPoint) { - if (!extPoint.isDynamic) { - return false; - } - } else { - // This extension has a 3rd party (unknown) extension point - // ===> require a reload for now... - return false; - } - } - } - } - - return true; - } - public canAddExtension(extension: IExtensionDescription): boolean { if (this._windowService.getConfiguration().remoteAuthority) { return false; @@ -306,7 +289,7 @@ export class ExtensionService extends Disposable implements IExtensionService { } } - return this._usesOnlyDynamicExtensionPoints(extension); + return true; } public canRemoveExtension(extension: IExtensionDescription): boolean { @@ -333,7 +316,7 @@ export class ExtensionService extends Disposable implements IExtensionService { return false; } - return this._usesOnlyDynamicExtensionPoints(extension); + return true; } private async _activateAddedExtensionIfNeeded(extensionDescription: IExtensionDescription): Promise { @@ -371,7 +354,7 @@ export class ExtensionService extends Disposable implements IExtensionService { if (shouldActivate) { await Promise.all( - this._extensionHostProcessManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, shouldActivateReason)) + this._extensionHostProcessManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, shouldActivateReason!)) ).then(() => { }); } } @@ -453,7 +436,7 @@ export class ExtensionService extends Disposable implements IExtensionService { this._extensionHostProcessManagers.push(extHostProcessManager); } - private _onExtensionHostCrashed(code: number, signal: string): void { + private _onExtensionHostCrashed(code: number, signal: string | null): void { console.error('Extension host terminated unexpectedly. Code: ', code, ' Signal: ', signal); this._stopExtensionHostProcess(); @@ -567,9 +550,9 @@ export class ExtensionService extends Disposable implements IExtensionService { for (const extension of extensions) { const extensionKey = ExtensionIdentifier.toKey(extension.identifier); result[extension.identifier.value] = { - messages: this._extensionsMessages.get(extensionKey), + messages: this._extensionsMessages.get(extensionKey) || [], activationTimes: this._extensionHostProcessActivationTimes.get(extensionKey), - runtimeErrors: this._extensionHostExtensionRuntimeErrors.get(extensionKey), + runtimeErrors: this._extensionHostExtensionRuntimeErrors.get(extensionKey) || [], }; } } @@ -625,12 +608,15 @@ export class ExtensionService extends Disposable implements IExtensionService { const enabledExtensions = await this._getRuntimeExtensions(extensions); this._handleExtensionPoints(enabledExtensions); - extensionHost.start(enabledExtensions.map(extension => extension.identifier)); + extensionHost.start(enabledExtensions.map(extension => extension.identifier).filter(id => this._registry.containsExtension(id))); this._releaseBarrier(); } private _handleExtensionPoints(allExtensions: IExtensionDescription[]): void { - this._registry = new ExtensionDescriptionRegistry(allExtensions); + const result = this._registry.deltaExtensions(allExtensions, []); + if (result.removedDueToLooping.length > 0) { + this._logOrShowMessage(Severity.Error, nls.localize('looping', "The following extensions contain dependency loops and have been disabled: {0}", result.removedDueToLooping.map(e => `'${e.identifier.value}'`).join(', '))); + } let availableExtensions = this._registry.getAllExtensionDescriptions(); let extensionPoints = ExtensionsRegistry.getExtensionPoints(); @@ -681,7 +667,11 @@ export class ExtensionService extends Disposable implements IExtensionService { (enableProposedApiFor.length === 0 && 'enable-proposed-api' in this._environmentService.args); for (const extension of allExtensions) { - const isExtensionUnderDevelopment = this._environmentService.isExtensionDevelopment && isEqualOrParent(extension.extensionLocation, this._environmentService.extensionDevelopmentLocationURI); + const isExtensionUnderDevelopment = ( + this._environmentService.isExtensionDevelopment + && this._environmentService.extensionDevelopmentLocationURI + && isEqualOrParent(extension.extensionLocation, this._environmentService.extensionDevelopmentLocationURI) + ); // Do not disable extensions under development if (!isExtensionUnderDevelopment) { if (disabledExtensions.some(disabled => areSameExtensions(disabled, { id: extension.identifier.value }))) { @@ -743,7 +733,7 @@ export class ExtensionService extends Disposable implements IExtensionService { if (!this._extensionsMessages.has(extensionKey)) { this._extensionsMessages.set(extensionKey, []); } - this._extensionsMessages.get(extensionKey).push(msg); + this._extensionsMessages.get(extensionKey)!.push(msg); const extension = this._registry.getExtensionDescription(msg.extensionId); const strMsg = `[${msg.extensionId.value}]: ${msg.message}`; @@ -815,6 +805,16 @@ export class ExtensionService extends Disposable implements IExtensionService { } } + public async _activateById(extensionId: ExtensionIdentifier, activationEvent: string): Promise { + const results = await Promise.all( + this._extensionHostProcessManagers.map(manager => manager.activate(extensionId, activationEvent)) + ); + const activated = results.some(e => e); + if (!activated) { + throw new Error(`Unknown extension ${extensionId.value}`); + } + } + public _onWillActivateExtension(extensionId: ExtensionIdentifier): void { this._extensionHostActiveExtensions.set(ExtensionIdentifier.toKey(extensionId), extensionId); } @@ -829,21 +829,9 @@ export class ExtensionService extends Disposable implements IExtensionService { if (!this._extensionHostExtensionRuntimeErrors.has(extensionKey)) { this._extensionHostExtensionRuntimeErrors.set(extensionKey, []); } - this._extensionHostExtensionRuntimeErrors.get(extensionKey).push(err); - this._onDidChangeExtensionsStatus.fire([extensionId]); - } - - public _addMessage(extensionId: ExtensionIdentifier, severity: Severity, message: string): void { - const extensionKey = ExtensionIdentifier.toKey(extensionId); - if (!this._extensionsMessages.has(extensionKey)) { - this._extensionsMessages.set(extensionKey, []); - } - this._extensionsMessages.get(extensionKey).push({ - type: severity, - message: message, - extensionId: null, - extensionPointId: null - }); + this._extensionHostExtensionRuntimeErrors.get(extensionKey)!.push(err); this._onDidChangeExtensionsStatus.fire([extensionId]); } } + +registerSingleton(IExtensionService, ExtensionService); \ No newline at end of file diff --git a/src/vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler.ts b/src/vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler.ts index 4e0993793e..ccc7516797 100644 --- a/src/vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler.ts +++ b/src/vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler.ts @@ -17,6 +17,7 @@ import { IURLHandler, IURLService } from 'vs/platform/url/common/url'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; const FIVE_MINUTES = 5 * 60 * 1000; const THIRTY_SECONDS = 30 * 1000; @@ -90,9 +91,15 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { } if (!confirmed) { + let uriString = uri.toString(); + + if (uriString.length > 40) { + uriString = `${uriString.substring(0, 30)}...${uriString.substring(uriString.length - 5)}`; + } + const result = await this.dialogService.confirm({ message: localize('confirmUrl', "Allow an extension to open this URL?", extensionId), - detail: `${extension.displayName || extension.name} (${extensionId}) wants to open a URL:\n\n${uri.toString()}`, + detail: `${extension.displayName || extension.name} (${extensionId}) wants to open a URL:\n\n${uriString}`, primaryButton: localize('open', "&&Open"), type: 'question' }); @@ -265,3 +272,5 @@ export class ExtensionUrlHandler implements IExtensionUrlHandler, IURLHandler { this.uriBuffer.clear(); } } + +registerSingleton(IExtensionUrlHandler, ExtensionUrlHandler); \ No newline at end of file diff --git a/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts b/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts index dd8c49dca0..2f11b6102e 100644 --- a/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts +++ b/src/vs/workbench/services/extensions/node/extensionDescriptionRegistry.ts @@ -3,10 +3,15 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { Emitter } from 'vs/base/common/event'; +export class DeltaExtensionsResult { + constructor( + public readonly removedDueToLooping: IExtensionDescription[] + ) { } +} + export class ExtensionDescriptionRegistry { private readonly _onDidChange = new Emitter(); public readonly onDidChange = this._onDidChange.event; @@ -60,19 +65,120 @@ export class ExtensionDescriptionRegistry { this._onDidChange.fire(undefined); } - public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]) { - this._extensionDescriptions = this._extensionDescriptions.concat(toAdd); - const toRemoveSet = new Set(); - toRemove.forEach(extensionId => toRemoveSet.add(ExtensionIdentifier.toKey(extensionId))); - this._extensionDescriptions = this._extensionDescriptions.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier))); + public deltaExtensions(toAdd: IExtensionDescription[], toRemove: ExtensionIdentifier[]): DeltaExtensionsResult { + if (toAdd.length > 0) { + this._extensionDescriptions = this._extensionDescriptions.concat(toAdd); + } + + // Immediately remove looping extensions! + const looping = ExtensionDescriptionRegistry._findLoopingExtensions(this._extensionDescriptions); + toRemove = toRemove.concat(looping.map(ext => ext.identifier)); + + if (toRemove.length > 0) { + const toRemoveSet = new Set(); + toRemove.forEach(extensionId => toRemoveSet.add(ExtensionIdentifier.toKey(extensionId))); + this._extensionDescriptions = this._extensionDescriptions.filter(extension => !toRemoveSet.has(ExtensionIdentifier.toKey(extension.identifier))); + } + this._initialize(); this._onDidChange.fire(undefined); + return new DeltaExtensionsResult(looping); + } + + private static _findLoopingExtensions(extensionDescriptions: IExtensionDescription[]): IExtensionDescription[] { + const G = new class { + + private _arcs = new Map(); + private _nodesSet = new Set(); + private _nodesArr: string[] = []; + + addNode(id: string): void { + if (!this._nodesSet.has(id)) { + this._nodesSet.add(id); + this._nodesArr.push(id); + } + } + + addArc(from: string, to: string): void { + this.addNode(from); + this.addNode(to); + if (this._arcs.has(from)) { + this._arcs.get(from)!.push(to); + } else { + this._arcs.set(from, [to]); + } + } + + getArcs(id: string): string[] { + if (this._arcs.has(id)) { + return this._arcs.get(id)!; + } + return []; + } + + hasOnlyGoodArcs(id: string, good: Set): boolean { + const dependencies = G.getArcs(id); + for (let i = 0; i < dependencies.length; i++) { + if (!good.has(dependencies[i])) { + return false; + } + } + return true; + } + + getNodes(): string[] { + return this._nodesArr; + } + }; + + let descs = new Map(); + for (let extensionDescription of extensionDescriptions) { + const extensionId = ExtensionIdentifier.toKey(extensionDescription.identifier); + descs.set(extensionId, extensionDescription); + if (extensionDescription.extensionDependencies) { + for (let _depId of extensionDescription.extensionDependencies) { + const depId = ExtensionIdentifier.toKey(_depId); + G.addArc(extensionId, depId); + } + } + } + + // initialize with all extensions with no dependencies. + let good = new Set(); + G.getNodes().filter(id => G.getArcs(id).length === 0).forEach(id => good.add(id)); + + // all other extensions will be processed below. + let nodes = G.getNodes().filter(id => !good.has(id)); + + let madeProgress: boolean; + do { + madeProgress = false; + + // find one extension which has only good deps + for (let i = 0; i < nodes.length; i++) { + const id = nodes[i]; + + if (G.hasOnlyGoodArcs(id, good)) { + nodes.splice(i, 1); + i--; + good.add(id); + madeProgress = true; + } + } + } while (madeProgress); + + // The remaining nodes are bad and have loops + return nodes.map(id => descs.get(id)!); } public containsActivationEvent(activationEvent: string): boolean { return this._activationMap.has(activationEvent); } + public containsExtension(extensionId: ExtensionIdentifier): boolean { + return this._extensionsMap.has(ExtensionIdentifier.toKey(extensionId)); + } + public getExtensionDescriptionsForActivationEvent(activationEvent: string): IExtensionDescription[] { const extensions = this._activationMap.get(activationEvent); return extensions ? extensions.slice(0) : []; @@ -82,8 +188,8 @@ export class ExtensionDescriptionRegistry { return this._extensionsArr.slice(0); } - public getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | null { + public getExtensionDescription(extensionId: ExtensionIdentifier | string): IExtensionDescription | undefined { const extension = this._extensionsMap.get(ExtensionIdentifier.toKey(extensionId)); - return extension ? extension : null; + return extension ? extension : undefined; } } diff --git a/src/vs/workbench/services/extensions/node/extensionHostMain.ts b/src/vs/workbench/services/extensions/node/extensionHostMain.ts index 4696683ac1..cef4fcfb39 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostMain.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostMain.ts @@ -15,8 +15,8 @@ import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; // we don't (yet) throw when extensions parse // uris that have no scheme @@ -49,7 +49,6 @@ export class ExtensionHostMain { private _isTerminating: boolean; private readonly _environment: IEnvironment; private readonly _extensionService: ExtHostExtensionService; - private readonly _extHostConfiguration: ExtHostConfiguration; private readonly _extHostLogService: ExtHostLogService; private disposables: IDisposable[] = []; @@ -57,14 +56,14 @@ export class ExtensionHostMain { constructor(protocol: IMessagePassingProtocol, initData: IInitData) { this._isTerminating = false; - const uriTransformer: IURITransformer = null; + const uriTransformer: IURITransformer | null = null; const rpcProtocol = new RPCProtocol(protocol, null, uriTransformer); // ensure URIs are transformed and revived initData = this.transform(initData, rpcProtocol); this._environment = initData.environment; - const allowExit = !!this._environment.extensionTestsPath; // to support other test frameworks like Jasmin that use process.exit (https://github.com/Microsoft/vscode/issues/37708) + const allowExit = !!this._environment.extensionTestsLocationURI; // to support other test frameworks like Jasmin that use process.exit (https://github.com/Microsoft/vscode/issues/37708) patchProcess(allowExit); this._patchPatchedConsole(rpcProtocol.getProxy(MainContext.MainThreadConsole)); @@ -74,13 +73,13 @@ export class ExtensionHostMain { this.disposables.push(this._extHostLogService); this._searchRequestIdProvider = new Counter(); - const extHostWorkspace = new ExtHostWorkspace(rpcProtocol, initData.workspace, this._extHostLogService, this._searchRequestIdProvider); + const extHostWorkspace = new ExtHostWorkspace(rpcProtocol, this._extHostLogService, this._searchRequestIdProvider, initData.workspace); this._extHostLogService.info('extension host started'); this._extHostLogService.trace('initData', initData); - this._extHostConfiguration = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace); - this._extensionService = new ExtHostExtensionService(nativeExit, initData, rpcProtocol, extHostWorkspace, this._extHostConfiguration, this._extHostLogService); + const extHostConfiguraiton = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace); + this._extensionService = new ExtHostExtensionService(nativeExit, initData, rpcProtocol, extHostWorkspace, extHostConfiguraiton, this._extHostLogService); // error forwarding and stack trace scanning Error.stackTraceLimit = 100; // increase number of stack frames (from 10, https://github.com/v8/v8/wiki/Stack-Trace-API) @@ -88,7 +87,7 @@ export class ExtensionHostMain { this._extensionService.getExtensionPathIndex().then(map => { (Error).prepareStackTrace = (error: Error, stackTrace: errors.V8CallSite[]) => { let stackTraceMessage = ''; - let extension: IExtensionDescription; + let extension: IExtensionDescription | undefined; let fileName: string; for (const call of stackTrace) { stackTraceMessage += `\n\tat ${call.toString()}`; @@ -118,7 +117,7 @@ export class ExtensionHostMain { private _patchPatchedConsole(mainThreadConsole: MainThreadConsoleShape): void { // The console is already patched to use `process.send()` - const nativeProcessSend = process.send; + const nativeProcessSend = process.send!; process.send = (...args: any[]) => { if (args.length === 0 || !args[0] || args[0].type !== '__$console') { return nativeProcessSend.apply(process, args); @@ -154,7 +153,9 @@ export class ExtensionHostMain { initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot)); initData.environment.appSettingsHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appSettingsHome)); initData.environment.extensionDevelopmentLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionDevelopmentLocationURI)); + initData.environment.extensionTestsLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionTestsLocationURI)); initData.environment.globalStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.globalStorageHome)); + initData.environment.userHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.userHome)); initData.logsLocation = URI.revive(rpcProtocol.transformIncomingURIs(initData.logsLocation)); initData.workspace = rpcProtocol.transformIncomingURIs(initData.workspace); return initData; diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcess.ts b/src/vs/workbench/services/extensions/node/extensionHostProcess.ts index 551503e032..dc0e224b90 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcess.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcess.ts @@ -9,9 +9,9 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/node/ipc'; import { Protocol } from 'vs/base/parts/ipc/node/ipc.net'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { IInitData } from 'vs/workbench/api/node/extHost.protocol'; -import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/common/extensionHostProtocol'; +import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/node/extensionHostProtocol'; import { exit, ExtensionHostMain } from 'vs/workbench/services/extensions/node/extensionHostMain'; // With Electron 2.x and node.js 8.x the "natives" module @@ -45,7 +45,7 @@ let onTerminate = function () { function createExtHostProtocol(): Promise { - const pipeName = process.env.VSCODE_IPC_HOOK_EXTHOST; + const pipeName = process.env.VSCODE_IPC_HOOK_EXTHOST!; return new Promise((resolve, reject) => { @@ -107,9 +107,14 @@ function connectToRenderer(protocol: IMessagePassingProtocol): Promise { const idx = unhandledPromises.indexOf(promise); if (idx >= 0) { - unhandledPromises.splice(idx, 1); - console.warn('rejected promise not handled within 1 second'); - onUnexpectedError(reason); + promise.catch(e => { + unhandledPromises.splice(idx, 1); + console.warn(`rejected promise not handled within 1 second: ${e}`); + if (e.stack) { + console.warn(`stack trace: ${e.stack}`); + } + onUnexpectedError(reason); + }); } }, 1000); }); diff --git a/src/vs/workbench/common/extensionHostProtocol.ts b/src/vs/workbench/services/extensions/node/extensionHostProtocol.ts similarity index 100% rename from src/vs/workbench/common/extensionHostProtocol.ts rename to src/vs/workbench/services/extensions/node/extensionHostProtocol.ts diff --git a/src/vs/workbench/services/extensions/node/extensionPoints.ts b/src/vs/workbench/services/extensions/node/extensionPoints.ts index 6b8ed3e97f..8c8b0aaa11 100644 --- a/src/vs/workbench/services/extensions/node/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/node/extensionPoints.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as semver from 'semver'; import * as json from 'vs/base/common/json'; import * as arrays from 'vs/base/common/arrays'; @@ -14,8 +14,7 @@ import { URI } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; import { getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { isValidExtensionVersion } from 'vs/platform/extensions/node/extensionValidator'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import { ExtensionIdentifier, ExtensionIdentifierWithVersion } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, ExtensionIdentifierWithVersion, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; const MANIFEST_FILE = 'package.json'; @@ -451,7 +450,7 @@ export class ExtensionScannerInput { constructor( public readonly ourVersion: string, - public readonly commit: string | undefined, + public readonly commit: string | null | undefined, public readonly locale: string | undefined, public readonly devMode: boolean, public readonly absoluteFolderPath: string, diff --git a/src/vs/workbench/services/extensions/node/extensionsUtil.ts b/src/vs/workbench/services/extensions/node/extensionsUtil.ts new file mode 100644 index 0000000000..935cdaceca --- /dev/null +++ b/src/vs/workbench/services/extensions/node/extensionsUtil.ts @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; +import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { isUIExtension as _isUIExtension } from 'vs/platform/extensions/node/extensionsUtil'; + +export function isUIExtension(manifest: IExtensionManifest, configurationService: IConfigurationService): boolean { + const uiExtensionPoints = ExtensionsRegistry.getExtensionPoints().filter(e => e.defaultExtensionKind !== 'workspace').map(e => e.name); + return _isUIExtension(manifest, uiExtensionPoints, configurationService); +} diff --git a/src/vs/workbench/services/extensions/node/proxyResolver.ts b/src/vs/workbench/services/extensions/node/proxyResolver.ts index 29d01ef24d..8cc472baa8 100644 --- a/src/vs/workbench/services/extensions/node/proxyResolver.ts +++ b/src/vs/workbench/services/extensions/node/proxyResolver.ts @@ -5,10 +5,15 @@ import * as http from 'http'; import * as https from 'https'; +import * as tls from 'tls'; import * as nodeurl from 'url'; +import * as os from 'os'; +import * as fs from 'fs'; +import * as cp from 'child_process'; import { assign } from 'vs/base/common/objects'; -import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; +import { endsWith } from 'vs/base/common/strings'; +import { IExtHostWorkspaceProvider } from 'vs/workbench/api/node/extHostWorkspace'; import { ExtHostConfigProvider } from 'vs/workbench/api/node/extHostConfiguration'; import { ProxyAgent } from 'vscode-proxy-agent'; import { MainThreadTelemetryShape } from 'vs/workbench/api/node/extHost.protocol'; @@ -16,6 +21,7 @@ import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; import { URI } from 'vs/base/common/uri'; +import { promisify } from 'util'; interface ConnectionResult { proxy: string; @@ -25,34 +31,37 @@ interface ConnectionResult { } export function connectProxyResolver( - extHostWorkspace: ExtHostWorkspace, + extHostWorkspace: IExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider, extensionService: ExtHostExtensionService, extHostLogService: ExtHostLogService, mainThreadTelemetry: MainThreadTelemetryShape ) { - const agents = createProxyAgents(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry); - const lookup = createPatchedModules(configProvider, agents); + const resolveProxy = setupProxyResolution(extHostWorkspace, configProvider, extHostLogService, mainThreadTelemetry); + const lookup = createPatchedModules(configProvider, resolveProxy); return configureModuleLoading(extensionService, lookup); } const maxCacheEntries = 5000; // Cache can grow twice that much due to 'oldCache'. -function createProxyAgents( - extHostWorkspace: ExtHostWorkspace, +function setupProxyResolution( + extHostWorkspace: IExtHostWorkspaceProvider, configProvider: ExtHostConfigProvider, extHostLogService: ExtHostLogService, mainThreadTelemetry: MainThreadTelemetryShape ) { + const env = process.env; + let settingsProxy = proxyFromConfigURL(configProvider.getConfiguration('http') .get('proxy')); configProvider.onDidChangeConfiguration(e => { settingsProxy = proxyFromConfigURL(configProvider.getConfiguration('http') .get('proxy')); }); - const env = process.env; let envProxy = proxyFromConfigURL(env.https_proxy || env.HTTPS_PROXY || env.http_proxy || env.HTTP_PROXY); // Not standardized. + let envNoProxy = noProxyFromEnv(env.no_proxy || env.NO_PROXY); // Not standardized. + let cacheRolls = 0; let oldCache = new Map(); let cache = new Map(); @@ -90,6 +99,7 @@ function createProxyAgents( let envCount = 0; let settingsCount = 0; let localhostCount = 0; + let envNoProxyCount = 0; let results: ConnectionResult[] = []; function logEvent() { timeout = undefined; @@ -104,19 +114,32 @@ function createProxyAgents( "envCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "settingsCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "localhostCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, + "envNoProxyCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "results": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" } } */ - mainThreadTelemetry.$publicLog('resolveProxy', { count, duration, errorCount, cacheCount, cacheSize: cache.size, cacheRolls, envCount, settingsCount, localhostCount, results }); - count = duration = errorCount = cacheCount = envCount = settingsCount = localhostCount = 0; + mainThreadTelemetry.$publicLog('resolveProxy', { count, duration, errorCount, cacheCount, cacheSize: cache.size, cacheRolls, envCount, settingsCount, localhostCount, envNoProxyCount, results }); + count = duration = errorCount = cacheCount = envCount = settingsCount = localhostCount = envNoProxyCount = 0; results = []; } - function resolveProxy(req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) { + function resolveProxy(flags: { useProxySettings: boolean, useSystemCertificates: boolean }, req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) { if (!timeout) { timeout = setTimeout(logEvent, 10 * 60 * 1000); } + useSystemCertificates(extHostLogService, flags.useSystemCertificates, opts, () => { + useProxySettings(flags.useProxySettings, req, opts, url, callback); + }); + } + + function useProxySettings(useProxySettings: boolean, req: http.ClientRequest, opts: http.RequestOptions, url: string, callback: (proxy?: string) => void) { + + if (!useProxySettings) { + callback('DIRECT'); + return; + } + const parsedUrl = nodeurl.parse(url); // Coming from Node's URL, sticking with that. const hostname = parsedUrl.hostname; @@ -127,6 +150,13 @@ function createProxyAgents( return; } + if (envNoProxy(hostname, String(parsedUrl.port || (opts.agent).defaultPort))) { + envNoProxyCount++; + callback('DIRECT'); + extHostLogService.trace('ProxyResolver#resolveProxy envNoProxy', url, 'DIRECT'); + return; + } + if (settingsProxy) { settingsCount++; callback(settingsProxy); @@ -168,11 +198,7 @@ function createProxyAgents( }); } - const httpAgent: http.Agent = new ProxyAgent({ resolveProxy }); - (httpAgent).defaultPort = 80; - const httpsAgent: http.Agent = new ProxyAgent({ resolveProxy }); - (httpsAgent).defaultPort = 443; - return { http: httpAgent, https: httpsAgent }; + return resolveProxy; } function collectResult(results: ConnectionResult[], resolveProxy: string, connection: string, req: http.ClientRequest) { @@ -189,7 +215,7 @@ function collectResult(results: ConnectionResult[], resolveProxy: string, connec }); } -function findOrCreateResult(results: ConnectionResult[], proxy: string, connection: string, code: string): ConnectionResult | undefined { +function findOrCreateResult(results: ConnectionResult[], proxy: string, connection: string, code: string): ConnectionResult { for (const result of results) { if (result.proxy === proxy && result.connection === connection && result.code === code) { return result; @@ -200,7 +226,7 @@ function findOrCreateResult(results: ConnectionResult[], proxy: string, connecti return result; } -function proxyFromConfigURL(configURL: string) { +function proxyFromConfigURL(configURL: string | undefined) { const url = (configURL || '').trim(); const i = url.indexOf('://'); if (i === -1) { @@ -218,35 +244,70 @@ function proxyFromConfigURL(configURL: string) { return undefined; } -function createPatchedModules(configProvider: ExtHostConfigProvider, agents: { http: http.Agent; https: http.Agent; }) { - const setting = { +function noProxyFromEnv(envValue?: string) { + const value = (envValue || '') + .trim() + .toLowerCase(); + + if (value === '*') { + return () => true; + } + + const filters = value + .split(',') + .map(s => s.trim().split(':', 2)) + .map(([name, port]) => ({ name, port })) + .filter(filter => !!filter.name) + .map(({ name, port }) => { + const domain = name[0] === '.' ? name : `.${name}`; + return { domain, port }; + }); + if (!filters.length) { + return () => false; + } + return (hostname: string, port: string) => filters.some(({ domain, port: filterPort }) => { + return endsWith(`.${hostname.toLowerCase()}`, domain) && (!filterPort || port === filterPort); + }); +} + +function createPatchedModules(configProvider: ExtHostConfigProvider, resolveProxy: ReturnType) { + const proxySetting = { config: configProvider.getConfiguration('http') .get('proxySupport') || 'off' }; configProvider.onDidChangeConfiguration(e => { - setting.config = configProvider.getConfiguration('http') + proxySetting.config = configProvider.getConfiguration('http') .get('proxySupport') || 'off'; }); + const certSetting = { + config: !!configProvider.getConfiguration('http') + .get('systemCertificates') + }; + configProvider.onDidChangeConfiguration(e => { + certSetting.config = !!configProvider.getConfiguration('http') + .get('systemCertificates'); + }); return { http: { - off: assign({}, http, patches(http, agents.http, agents.https, { config: 'off' }, true)), - on: assign({}, http, patches(http, agents.http, agents.https, { config: 'on' }, true)), - override: assign({}, http, patches(http, agents.http, agents.https, { config: 'override' }, true)), - onRequest: assign({}, http, patches(http, agents.http, agents.https, setting, true)), - default: assign(http, patches(http, agents.http, agents.https, setting, false)) // run last + off: assign({}, http, patches(http, resolveProxy, { config: 'off' }, certSetting, true)), + on: assign({}, http, patches(http, resolveProxy, { config: 'on' }, certSetting, true)), + override: assign({}, http, patches(http, resolveProxy, { config: 'override' }, certSetting, true)), + onRequest: assign({}, http, patches(http, resolveProxy, proxySetting, certSetting, true)), + default: assign(http, patches(http, resolveProxy, proxySetting, certSetting, false)) // run last }, https: { - off: assign({}, https, patches(https, agents.https, agents.http, { config: 'off' }, true)), - on: assign({}, https, patches(https, agents.https, agents.http, { config: 'on' }, true)), - override: assign({}, https, patches(https, agents.https, agents.http, { config: 'override' }, true)), - onRequest: assign({}, https, patches(https, agents.https, agents.http, setting, true)), - default: assign(https, patches(https, agents.https, agents.http, setting, false)) // run last - } + off: assign({}, https, patches(https, resolveProxy, { config: 'off' }, certSetting, true)), + on: assign({}, https, patches(https, resolveProxy, { config: 'on' }, certSetting, true)), + override: assign({}, https, patches(https, resolveProxy, { config: 'override' }, certSetting, true)), + onRequest: assign({}, https, patches(https, resolveProxy, proxySetting, certSetting, true)), + default: assign(https, patches(https, resolveProxy, proxySetting, certSetting, false)) // run last + }, + tls: assign(tls, tlsPatches(tls)) }; } -function patches(originals: typeof http | typeof https, agent: http.Agent, otherAgent: http.Agent, setting: { config: string; }, onRequest: boolean) { +function patches(originals: typeof http | typeof https, resolveProxy: ReturnType, proxySetting: { config: string }, certSetting: { config: boolean }, onRequest: boolean) { return { get: patch(originals.get), request: patch(originals.request) @@ -265,12 +326,15 @@ function patches(originals: typeof http | typeof https, agent: http.Agent, other } options = options || {}; - const config = onRequest && ((options)._vscodeProxySupport || /* LS */ (options)._vscodeSystemProxy) || setting.config; - if (config === 'off') { + if (options.socketPath) { return original.apply(null, arguments as unknown as any[]); } - if (!options.socketPath && (config === 'override' || config === 'on' && !options.agent) && options.agent !== agent && options.agent !== otherAgent) { + const config = onRequest && ((options)._vscodeProxySupport || /* LS */ (options)._vscodeSystemProxy) || proxySetting.config; + const useProxySettings = (config === 'override' || config === 'on' && !options.agent) && !(options.agent instanceof ProxyAgent); + const useSystemCertificates = certSetting.config && originals === https && !(options as https.RequestOptions).ca; + + if (useProxySettings || useSystemCertificates) { if (url) { const parsed = typeof url === 'string' ? new nodeurl.URL(url) : url; const urlOptions = { @@ -286,7 +350,11 @@ function patches(originals: typeof http | typeof https, agent: http.Agent, other } else { options = { ...options }; } - options.agent = agent; + options.agent = new ProxyAgent({ + resolveProxy: resolveProxy.bind(undefined, { useProxySettings, useSystemCertificates }), + defaultPort: originals === https ? 443 : 80, + originalAgent: options.agent + }); return original(options, callback); } @@ -296,12 +364,35 @@ function patches(originals: typeof http | typeof https, agent: http.Agent, other } } +function tlsPatches(originals: typeof tls) { + return { + createSecureContext: patch(originals.createSecureContext) + }; + + function patch(original: typeof tls.createSecureContext): typeof tls.createSecureContext { + return function (details: tls.SecureContextOptions): ReturnType { + const context = original.apply(null, arguments as unknown as any[]); + const certs = (details as any)._vscodeAdditionalCaCerts; + if (certs) { + for (const cert of certs) { + context.context.addCACert(cert); + } + } + return context; + }; + } +} + function configureModuleLoading(extensionService: ExtHostExtensionService, lookup: ReturnType): Promise { return extensionService.getExtensionPathIndex() .then(extensionPaths => { const node_module = require.__$__nodeRequire('module'); const original = node_module._load; node_module._load = function load(request: string, parent: any, isMain: any) { + if (request === 'tls') { + return lookup.tls; + } + if (request !== 'http' && request !== 'https') { return original.apply(this, arguments); } @@ -314,4 +405,120 @@ function configureModuleLoading(extensionService: ExtHostExtensionService, looku return modules.default; }; }); -} \ No newline at end of file +} + +function useSystemCertificates(extHostLogService: ExtHostLogService, useSystemCertificates: boolean, opts: http.RequestOptions, callback: () => void) { + if (useSystemCertificates) { + getCaCertificates(extHostLogService) + .then(caCertificates => { + if (caCertificates) { + if (caCertificates.append) { + (opts as any)._vscodeAdditionalCaCerts = caCertificates.certs; + } else { + (opts as https.RequestOptions).ca = caCertificates.certs; + } + } + callback(); + }) + .catch(err => { + extHostLogService.error('ProxyResolver#useSystemCertificates', toErrorMessage(err)); + }); + } else { + callback(); + } +} + +let _caCertificates: ReturnType | Promise; +async function getCaCertificates(extHostLogService: ExtHostLogService) { + if (!_caCertificates) { + _caCertificates = readCaCertificates() + .then(res => res && res.certs.length ? res : undefined) + .catch(err => { + extHostLogService.error('ProxyResolver#getCertificates', toErrorMessage(err)); + return undefined; + }); + } + return _caCertificates; +} + +async function readCaCertificates() { + if (process.platform === 'win32') { + return readWindowsCaCertificates(); + } + if (process.platform === 'darwin') { + return readMacCaCertificates(); + } + if (process.platform === 'linux') { + return readLinuxCaCertificates(); + } + return undefined; +} + +function readWindowsCaCertificates() { + const winCA = require.__$__nodeRequire('win-ca-lib'); + + let ders = []; + const store = winCA(); + try { + let der; + while (der = store.next()) { + ders.push(der); + } + } finally { + store.done(); + } + + const seen = {}; + const certs = ders.map(derToPem) + .filter(pem => !seen[pem] && (seen[pem] = true)); + return { + certs, + append: true + }; +} + +async function readMacCaCertificates() { + const stdout = (await promisify(cp.execFile)('/usr/bin/security', ['find-certificate', '-a', '-p'], { encoding: 'utf8' })).stdout; + const seen = {}; + const certs = stdout.split(/(?=-----BEGIN CERTIFICATE-----)/g) + .filter(pem => !!pem.length && !seen[pem] && (seen[pem] = true)); + return { + certs, + append: true + }; +} + +const linuxCaCertificatePaths = [ + '/etc/ssl/certs/ca-certificates.crt', + '/etc/ssl/certs/ca-bundle.crt', +]; + +async function readLinuxCaCertificates() { + for (const certPath of linuxCaCertificatePaths) { + try { + const content = await promisify(fs.readFile)(certPath, { encoding: 'utf8' }); + const seen = {}; + const certs = content.split(/(?=-----BEGIN CERTIFICATE-----)/g) + .filter(pem => !!pem.length && !seen[pem] && (seen[pem] = true)); + return { + certs, + append: false + }; + } catch (err) { + if (err.code !== 'ENOENT') { + throw err; + } + } + } + return undefined; +} + +function derToPem(blob) { + const lines = ['-----BEGIN CERTIFICATE-----']; + const der = blob.toString('base64'); + for (let i = 0; i < der.length; i += 64) { + lines.push(der.substr(i, 64)); + } + lines.push('-----END CERTIFICATE-----', ''); + return lines.join(os.EOL); +} diff --git a/src/vs/workbench/services/files/electron-browser/encoding.ts b/src/vs/workbench/services/files/node/encoding.ts similarity index 87% rename from src/vs/workbench/services/files/electron-browser/encoding.ts rename to src/vs/workbench/services/files/node/encoding.ts index 02c41890a9..1c12c3b80b 100644 --- a/src/vs/workbench/services/files/electron-browser/encoding.ts +++ b/src/vs/workbench/services/files/node/encoding.ts @@ -6,9 +6,9 @@ import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces'; import * as encoding from 'vs/base/node/encoding'; import { URI as uri } from 'vs/base/common/uri'; -import { IResolveContentOptions, isParent, IResourceEncodings } from 'vs/platform/files/common/files'; +import { IResolveContentOptions, isParent, IResourceEncodings, IResourceEncoding } from 'vs/platform/files/common/files'; import { isLinux } from 'vs/base/common/platform'; -import { extname } from 'path'; +import { extname } from 'vs/base/common/path'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -49,7 +49,7 @@ export class ResourceEncodings extends Disposable implements IResourceEncodings })); } - getReadEncoding(resource: uri, options: IResolveContentOptions, detected: encoding.IDetectedEncodingResult): string { + getReadEncoding(resource: uri, options: IResolveContentOptions | undefined, detected: encoding.IDetectedEncodingResult): string { let preferredEncoding: string | undefined; // Encoding passed in as option @@ -78,8 +78,13 @@ export class ResourceEncodings extends Disposable implements IResourceEncodings return this.getEncodingForResource(resource, preferredEncoding); } - getWriteEncoding(resource: uri, preferredEncoding?: string): string { - return this.getEncodingForResource(resource, preferredEncoding); + getWriteEncoding(resource: uri, preferredEncoding?: string): IResourceEncoding { + const resourceEncoding = this.getEncodingForResource(resource, preferredEncoding); + + return { + encoding: resourceEncoding, + hasBOM: resourceEncoding === encoding.UTF16be || resourceEncoding === encoding.UTF16le || resourceEncoding === encoding.UTF8_with_bom // enforce BOM for certain encodings + }; } private getEncodingForResource(resource: uri, preferredEncoding?: string): string { diff --git a/src/vs/workbench/services/files/electron-browser/fileService.ts b/src/vs/workbench/services/files/node/fileService.ts similarity index 93% rename from src/vs/workbench/services/files/electron-browser/fileService.ts rename to src/vs/workbench/services/files/node/fileService.ts index 9662ae8961..907d034cde 100644 --- a/src/vs/workbench/services/files/electron-browser/fileService.ts +++ b/src/vs/workbench/services/files/node/fileService.ts @@ -3,14 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as paths from 'path'; +import * as paths from 'vs/base/common/path'; import * as fs from 'fs'; import * as os from 'os'; import * as crypto from 'crypto'; import * as assert from 'assert'; import { isParent, FileOperation, FileOperationEvent, IContent, IFileService, IResolveFileOptions, IResolveFileResult, IResolveContentOptions, IFileStat, IStreamContent, FileOperationError, FileOperationResult, IUpdateContentOptions, FileChangeType, FileChangesEvent, ICreateFileOptions, IContentData, ITextSnapshot, IFilesConfiguration, IFileSystemProviderRegistrationEvent, IFileSystemProvider } from 'vs/platform/files/common/files'; -import { MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/platform/files/node/files'; -import { isEqualOrParent } from 'vs/base/common/paths'; +import { MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/platform/files/node/fileConstants'; +import { isEqualOrParent } from 'vs/base/common/extpath'; import { ResourceMap } from 'vs/base/common/map'; import * as arrays from 'vs/base/common/arrays'; import * as objects from 'vs/base/common/objects'; @@ -22,7 +22,7 @@ import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import * as pfs from 'vs/base/node/pfs'; -import * as encoding from 'vs/base/node/encoding'; +import { detectEncodingFromBuffer, decodeStream, detectEncodingByBOM, UTF8 } from 'vs/base/node/encoding'; import * as flow from 'vs/base/node/flow'; import { FileWatcher as UnixWatcherService } from 'vs/workbench/services/files/node/watcher/unix/watcherService'; import { FileWatcher as WindowsWatcherService } from 'vs/workbench/services/files/node/watcher/win32/watcherService'; @@ -39,9 +39,9 @@ import { Schemas } from 'vs/base/common/network'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { onUnexpectedError } from 'vs/base/common/errors'; -import product from 'vs/platform/node/product'; -import { IEncodingOverride, ResourceEncodings } from 'vs/workbench/services/files/electron-browser/encoding'; -import { createReadableOfSnapshot } from 'vs/workbench/services/files/electron-browser/streams'; +import product from 'vs/platform/product/node/product'; +import { IEncodingOverride, ResourceEncodings } from 'vs/workbench/services/files/node/encoding'; +import { createReadableOfSnapshot } from 'vs/workbench/services/files/node/streams'; export interface IFileServiceTestOptions { disableWatcher?: boolean; @@ -70,7 +70,7 @@ export class FileService extends Disposable implements IFileService { protected readonly _onDidChangeFileSystemProviderRegistrations = this._register(new Emitter()); get onDidChangeFileSystemProviderRegistrations(): Event { return this._onDidChangeFileSystemProviderRegistrations.event; } - private activeWorkspaceFileChangeWatcher: IDisposable; + private activeWorkspaceFileChangeWatcher: IDisposable | null; private activeFileChangesWatchers: ResourceMap<{ unwatch: Function, count: number }>; private fileChangesWatchDelayer: ThrottledDelayer; private undeliveredRawFileChangesEvents: IRawFileChange[]; @@ -262,7 +262,7 @@ export class FileService extends Disposable implements IFileService { )); } - const result: IStreamContent = { + const result: Partial = { resource: undefined, name: undefined, mtime: undefined, @@ -311,7 +311,7 @@ export class FileService extends Disposable implements IFileService { // Return early if file is too large to load if (typeof stat.size === 'number') { - if (stat.size > Math.max(this.environmentService.args['max-memory'] * 1024 * 1024 || 0, MAX_HEAP_SIZE)) { + if (stat.size > Math.max(typeof this.environmentService.args['max-memory'] === 'string' ? parseInt(this.environmentService.args['max-memory']) * 1024 * 1024 || 0 : 0, MAX_HEAP_SIZE)) { return onStatError(new FileOperationError( nls.localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart VS Code and allow it to use more memory"), FileOperationResult.FILE_EXCEED_MEMORY_LIMIT @@ -391,17 +391,17 @@ export class FileService extends Disposable implements IFileService { }); } - private fillInContents(content: IStreamContent, resource: uri, options: IResolveContentOptions, token: CancellationToken): Promise { + private fillInContents(content: Partial, resource: uri, options: IResolveContentOptions | undefined, token: CancellationToken): Promise { return this.resolveFileData(resource, options, token).then(data => { content.encoding = data.encoding; content.value = data.stream; }); } - private resolveFileData(resource: uri, options: IResolveContentOptions, token: CancellationToken): Promise { + private resolveFileData(resource: uri, options: IResolveContentOptions | undefined, token: CancellationToken): Promise { const chunkBuffer = Buffer.allocUnsafe(64 * 1024); - const result: IContentData = { + const result: Partial = { encoding: undefined, stream: undefined }; @@ -473,7 +473,7 @@ export class FileService extends Disposable implements IFileService { } }; - let currentPosition: number = (options && options.position) || null; + let currentPosition: number | null = (options && options.position) || null; const readChunk = () => { fs.read(fd, chunkBuffer, 0, chunkBuffer.length, currentPosition, (err, bytesRead) => { @@ -485,7 +485,7 @@ export class FileService extends Disposable implements IFileService { currentPosition += bytesRead; } - if (totalBytesRead > Math.max(this.environmentService.args['max-memory'] * 1024 * 1024 || 0, MAX_HEAP_SIZE)) { + if (totalBytesRead > Math.max(typeof this.environmentService.args['max-memory'] === 'number' ? parseInt(this.environmentService.args['max-memory']) * 1024 * 1024 || 0 : 0, MAX_HEAP_SIZE)) { finish(new FileOperationError( nls.localize('fileTooLargeForHeapError', "To open a file of this size, you need to restart VS Code and allow it to use more memory"), FileOperationResult.FILE_EXCEED_MEMORY_LIMIT @@ -510,7 +510,7 @@ export class FileService extends Disposable implements IFileService { } else { // when receiving the first chunk of data we need to create the // decoding stream which is then used to drive the string stream. - Promise.resolve(encoding.detectEncodingFromBuffer( + Promise.resolve(detectEncodingFromBuffer( { buffer: chunkBuffer, bytesRead }, (options && options.autoGuessEncoding) || this.textResourceConfigurationService.getValue(resource, 'files.autoGuessEncoding') )).then(detected => { @@ -524,8 +524,8 @@ export class FileService extends Disposable implements IFileService { } else { result.encoding = this._encoding.getReadEncoding(resource, options, detected); - result.stream = decoder = encoding.decodeStream(result.encoding); - resolve(result); + result.stream = decoder = decodeStream(result.encoding); + resolve(result as IContentData); handleChunk(bytesRead); } }).then(undefined, err => { @@ -555,7 +555,7 @@ export class FileService extends Disposable implements IFileService { // 1.) check file for writing return this.checkFileBeforeWriting(absolutePath, options).then(exists => { - let createParentsPromise: Promise; + let createParentsPromise: Promise; if (exists) { createParentsPromise = Promise.resolve(); } else { @@ -564,20 +564,20 @@ export class FileService extends Disposable implements IFileService { // 2.) create parents as needed return createParentsPromise.then(() => { - const encodingToWrite = this._encoding.getWriteEncoding(resource, options.encoding); + const { encoding, hasBOM } = this._encoding.getWriteEncoding(resource, options.encoding); let addBomPromise: Promise = Promise.resolve(false); - // UTF_16 BE and LE as well as UTF_8 with BOM always have a BOM - if (encodingToWrite === encoding.UTF16be || encodingToWrite === encoding.UTF16le || encodingToWrite === encoding.UTF8_with_bom) { - addBomPromise = Promise.resolve(true); + // Some encodings come with a BOM automatically + if (hasBOM) { + addBomPromise = Promise.resolve(hasBOM); } // Existing UTF-8 file: check for options regarding BOM - else if (exists && encodingToWrite === encoding.UTF8) { + else if (exists && encoding === UTF8) { if (options.overwriteEncoding) { addBomPromise = Promise.resolve(false); // if we are to overwrite the encoding, we do not preserve it if found } else { - addBomPromise = encoding.detectEncodingByBOM(absolutePath).then(enc => enc === encoding.UTF8); // otherwise preserve it if found + addBomPromise = detectEncodingByBOM(absolutePath).then(enc => enc === UTF8); // otherwise preserve it if found } } @@ -586,7 +586,7 @@ export class FileService extends Disposable implements IFileService { // 4.) set contents and resolve if (!exists || !isWindows) { - return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite); + return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encoding); } // On Windows and if the file exists, we use a different strategy of saving the file @@ -599,7 +599,7 @@ export class FileService extends Disposable implements IFileService { return pfs.truncate(absolutePath, 0).then(() => { // 5.) set contents (with r+ mode) and resolve - return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite, { flag: 'r+' }).then(undefined, error => { + return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encoding, { flag: 'r+' }).then(undefined, error => { if (this.environmentService.verbose) { console.error(`Truncate succeeded, but save failed (${error}), retrying after 100ms`); } @@ -608,7 +608,7 @@ export class FileService extends Disposable implements IFileService { // In that case, the file is now entirely empty and the contents are gone. This can happen if an external file watcher is // installed that reacts on the truncate and keeps the file busy right after. Our workaround is to retry to save after a // short timeout, assuming that the file is free to write then. - return timeout(100).then(() => this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite, { flag: 'r+' })); + return timeout(100).then(() => this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encoding, { flag: 'r+' })); }); }, error => { if (this.environmentService.verbose) { @@ -617,7 +617,7 @@ export class FileService extends Disposable implements IFileService { // we heard from users that fs.truncate() fails (https://github.com/Microsoft/vscode/issues/59561) // in that case we simply save the file without truncating first (same as macOS and Linux) - return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite); + return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encoding); }); } }); @@ -636,9 +636,10 @@ export class FileService extends Disposable implements IFileService { } private doSetContentsAndResolve(resource: uri, absolutePath: string, value: string | ITextSnapshot, addBOM: boolean, encodingToWrite: string, options?: { mode?: number; flag?: string; }): Promise { + // Configure encoding related options as needed const writeFileOptions: extfs.IWriteFileOptions = options ? options : Object.create(null); - if (addBOM || encodingToWrite !== encoding.UTF8) { + if (addBOM || encodingToWrite !== UTF8) { writeFileOptions.encoding = { charset: encodingToWrite, addBOM @@ -667,7 +668,7 @@ export class FileService extends Disposable implements IFileService { return this.checkFileBeforeWriting(absolutePath, options, options.overwriteReadonly /* ignore readonly if we overwrite readonly, this is handled via sudo later */).then(exists => { const writeOptions: IUpdateContentOptions = objects.assign(Object.create(null), options); writeOptions.writeElevated = false; - writeOptions.encoding = this._encoding.getWriteEncoding(resource, options.encoding); + writeOptions.encoding = this._encoding.getWriteEncoding(resource, options.encoding).encoding; // 2.) write to a temporary file to be able to copy over later const tmpPath = paths.join(os.tmpdir(), `code-elevated-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 6)}`); @@ -839,11 +840,11 @@ export class FileService extends Disposable implements IFileService { } moveFile(source: uri, target: uri, overwrite?: boolean): Promise { - return this.moveOrCopyFile(source, target, false, overwrite); + return this.moveOrCopyFile(source, target, false, !!overwrite); } copyFile(source: uri, target: uri, overwrite?: boolean): Promise { - return this.moveOrCopyFile(source, target, true, overwrite); + return this.moveOrCopyFile(source, target, true, !!overwrite); } private moveOrCopyFile(source: uri, target: uri, keepCopy: boolean, overwrite: boolean): Promise { @@ -915,7 +916,7 @@ export class FileService extends Disposable implements IFileService { return this.doMoveItemToTrash(resource); } - return this.doDelete(resource, options && options.recursive); + return this.doDelete(resource, !!(options && options.recursive)); } private doMoveItemToTrash(resource: uri): Promise { @@ -1142,7 +1143,7 @@ export class StatResolver { this.etag = etag(size, mtime); } - resolve(options: IResolveFileOptions): Promise { + resolve(options: IResolveFileOptions | undefined): Promise { // General Data const fileStat: IFileStat = { @@ -1168,16 +1169,18 @@ export class StatResolver { let absoluteTargetPaths: string[] | null = null; if (options && options.resolveTo) { absoluteTargetPaths = []; - options.resolveTo.forEach(resource => { + for (const resource of options.resolveTo) { absoluteTargetPaths.push(resource.fsPath); - }); + } } return new Promise(resolve => { // Load children - this.resolveChildren(this.resource.fsPath, absoluteTargetPaths, options && options.resolveSingleChildDescendants, children => { - children = arrays.coalesce(children); // we don't want those null children (could be permission denied when reading a child) + this.resolveChildren(this.resource.fsPath, absoluteTargetPaths, !!(options && options.resolveSingleChildDescendants), children => { + if (children) { + children = arrays.coalesce(children); // we don't want those null children (could be permission denied when reading a child) + } fileStat.children = children || []; resolve(fileStat); @@ -1186,7 +1189,7 @@ export class StatResolver { } } - private resolveChildren(absolutePath: string, absoluteTargetPaths: string[], resolveSingleChildDescendants: boolean, callback: (children: IFileStat[]) => void): void { + private resolveChildren(absolutePath: string, absoluteTargetPaths: string[] | null, resolveSingleChildDescendants: boolean, callback: (children: IFileStat[] | null) => void): void { extfs.readdir(absolutePath, (error: Error, files: string[]) => { if (error) { if (this.errorLogger) { @@ -1197,7 +1200,7 @@ export class StatResolver { } // for each file in the folder - flow.parallel(files, (file: string, clb: (error: Error, children: IFileStat) => void) => { + flow.parallel(files, (file: string, clb: (error: Error | null, children: IFileStat | null) => void) => { const fileResource = uri.file(paths.resolve(absolutePath, file)); let fileStat: fs.Stats; let isSymbolicLink = false; @@ -1257,7 +1260,9 @@ export class StatResolver { // Continue resolving children based on condition if (resolveFolderChildren) { $this.resolveChildren(fileResource.fsPath, absoluteTargetPaths, resolveSingleChildDescendants, children => { - children = arrays.coalesce(children); // we don't want those null children + if (children) { + children = arrays.coalesce(children); // we don't want those null children + } childStat.children = children || []; clb(null, childStat); diff --git a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts b/src/vs/workbench/services/files/node/remoteFileService.ts similarity index 91% rename from src/vs/workbench/services/files/electron-browser/remoteFileService.ts rename to src/vs/workbench/services/files/node/remoteFileService.ts index e1168ffe2d..d8dba50b47 100644 --- a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts +++ b/src/vs/workbench/services/files/node/remoteFileService.ts @@ -14,14 +14,15 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { FileChangesEvent, FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileWriteOptions, FileSystemProviderCapabilities, IContent, ICreateFileOptions, IFileStat, IFileSystemProvider, IFilesConfiguration, IResolveContentOptions, IResolveFileOptions, IResolveFileResult, IStat, IStreamContent, ITextSnapshot, IUpdateContentOptions, StringSnapshot, IWatchOptions, FileType } from 'vs/platform/files/common/files'; +import { FileChangesEvent, FileOperation, FileOperationError, FileOperationEvent, FileOperationResult, FileWriteOptions, FileSystemProviderCapabilities, IContent, ICreateFileOptions, IFileStat, IFileSystemProvider, IFilesConfiguration, IResolveContentOptions, IResolveFileOptions, IResolveFileResult, IStat, IStreamContent, ITextSnapshot, IUpdateContentOptions, StringSnapshot, IWatchOptions, FileType, IFileService } from 'vs/platform/files/common/files'; import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { FileService } from 'vs/workbench/services/files/electron-browser/fileService'; -import { createReadableOfProvider, createReadableOfSnapshot, createWritableOfProvider } from 'vs/workbench/services/files/electron-browser/streams'; +import { FileService } from 'vs/workbench/services/files/node/fileService'; +import { createReadableOfProvider, createReadableOfSnapshot, createWritableOfProvider } from 'vs/workbench/services/files/node/streams'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; class TypeOnlyStat implements IStat { @@ -70,7 +71,7 @@ function toIFileStat(provider: IFileSystemProvider, tuple: [URI, IStat], recurse return Promise.resolve(fileStat); } -export function toDeepIFileStat(provider: IFileSystemProvider, tuple: [URI, IStat], to: URI[]): Promise { +export function toDeepIFileStat(provider: IFileSystemProvider, tuple: [URI, IStat], to?: URI[]): Promise { const trie = TernarySearchTree.forPaths(); trie.set(tuple[0].toString(), true); @@ -213,7 +214,7 @@ export class RemoteFileService extends FileService { return resource.scheme === Schemas.file || this._provider.has(resource.scheme); } - private _tryParseFileOperationResult(err: any): FileOperationResult { + private _tryParseFileOperationResult(err: any): FileOperationResult | undefined { if (!(err instanceof Error)) { return undefined; } @@ -221,27 +222,20 @@ export class RemoteFileService extends FileService { if (!match) { return undefined; } - let res: FileOperationResult; switch (match[1]) { case 'EntryNotFound': - res = FileOperationResult.FILE_NOT_FOUND; - break; + return FileOperationResult.FILE_NOT_FOUND; case 'EntryIsADirectory': - res = FileOperationResult.FILE_IS_DIRECTORY; - break; + return FileOperationResult.FILE_IS_DIRECTORY; case 'NoPermissions': - res = FileOperationResult.FILE_PERMISSION_DENIED; - break; + return FileOperationResult.FILE_PERMISSION_DENIED; case 'EntryExists': - res = FileOperationResult.FILE_MOVE_CONFLICT; - break; + return FileOperationResult.FILE_MOVE_CONFLICT; case 'EntryNotADirectory': default: // todo - res = undefined; - break; + return undefined; } - return res; } // --- stat @@ -288,7 +282,7 @@ export class RemoteFileService extends FileService { FileOperationResult.FILE_NOT_FOUND ); } else { - return data[0].stat; + return data[0].stat!; } }); } @@ -298,7 +292,7 @@ export class RemoteFileService extends FileService { // soft-groupBy, keep order, don't rearrange/merge groups let groups: Array = []; - let group: typeof toResolve; + let group: typeof toResolve | undefined; for (const request of toResolve) { if (!group || group[0].resource.scheme !== request.resource.scheme) { group = []; @@ -326,7 +320,7 @@ export class RemoteFileService extends FileService { return toDeepIFileStat(provider, [item.resource, stat], item.options && item.options.resolveTo).then(fileStat => { result[idx] = { stat: fileStat, success: true }; }); - }, err => { + }, _err => { result[idx] = { stat: undefined, success: false }; }); }); @@ -386,7 +380,7 @@ export class RemoteFileService extends FileService { return toDecodeStream(readable, decodeStreamOpts).then(data => { if (options.acceptTextOnly && data.detected.seemsBinary) { - return Promise.reject(new FileOperationError( + return Promise.reject(new FileOperationError( localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), FileOperationResult.FILE_IS_BINARY, options @@ -446,8 +440,8 @@ export class RemoteFileService extends FileService { return this._withProvider(resource).then(RemoteFileService._throwIfFileSystemIsReadonly).then(provider => { return RemoteFileService._mkdirp(provider, resources.dirname(resource)).then(() => { - const encoding = this.encoding.getWriteEncoding(resource); - return this._writeFile(provider, resource, new StringSnapshot(content), encoding, { create: true, overwrite: Boolean(options && options.overwrite) }); + const { encoding } = this.encoding.getWriteEncoding(resource); + return this._writeFile(provider, resource, new StringSnapshot(content || ''), encoding, { create: true, overwrite: Boolean(options && options.overwrite) }); }); }).then(fileStat => { @@ -456,7 +450,7 @@ export class RemoteFileService extends FileService { }, err => { const message = localize('err.create', "Failed to create file {0}", resource.toString(false)); const result = this._tryParseFileOperationResult(err); - throw new FileOperationError(message, result, options); + throw new FileOperationError(message, result || -1, options); }); } } @@ -474,10 +468,10 @@ export class RemoteFileService extends FileService { } } - private _writeFile(provider: IFileSystemProvider, resource: URI, snapshot: ITextSnapshot, preferredEncoding: string, options: FileWriteOptions): Promise { + private _writeFile(provider: IFileSystemProvider, resource: URI, snapshot: ITextSnapshot, preferredEncoding: string | undefined = undefined, options: FileWriteOptions): Promise { const readable = createReadableOfSnapshot(snapshot); - const encoding = this.encoding.getWriteEncoding(resource, preferredEncoding); - const encoder = encodeStream(encoding); + const { encoding, hasBOM } = this.encoding.getWriteEncoding(resource, preferredEncoding); + const encoder = encodeStream(encoding, { addBOM: hasBOM }); const target = createWritableOfProvider(provider, resource, options); return new Promise((resolve, reject) => { readable.pipe(encoder).pipe(target); @@ -512,7 +506,7 @@ export class RemoteFileService extends FileService { return super.del(resource, options); } else { return this._withProvider(resource).then(RemoteFileService._throwIfFileSystemIsReadonly).then(provider => { - return provider.delete(resource, { recursive: options && options.recursive }).then(() => { + return provider.delete(resource, { recursive: !!(options && options.recursive) }).then(() => { this._onAfterOperation.fire(new FileOperationEvent(resource, FileOperation.DELETE)); }); }); @@ -556,11 +550,11 @@ export class RemoteFileService extends FileService { } } - private _doMoveWithInScheme(source: URI, target: URI, overwrite?: boolean): Promise { + private _doMoveWithInScheme(source: URI, target: URI, overwrite: boolean = false): Promise { const prepare = overwrite - ? Promise.resolve(this.del(target, { recursive: true }).then(undefined, err => { /*ignore*/ })) - : Promise.resolve(null); + ? Promise.resolve(this.del(target, { recursive: true }).catch(_err => { /*ignore*/ })) + : Promise.resolve(); return prepare.then(() => this._withProvider(source)).then(RemoteFileService._throwIfFileSystemIsReadonly).then(provider => { return RemoteFileService._mkdirp(provider, resources.dirname(target)).then(() => { @@ -600,7 +594,7 @@ export class RemoteFileService extends FileService { if (source.scheme === target.scheme && (provider.capabilities & FileSystemProviderCapabilities.FileFolderCopy)) { // good: provider supports copy withing scheme - return provider.copy(source, target, { overwrite }).then(() => { + return provider.copy!(source, target, { overwrite: !!overwrite }).then(() => { return this.resolveFile(target); }).then(fileStat => { this._onAfterOperation.fire(new FileOperationEvent(source, FileOperation.COPY, fileStat)); @@ -615,19 +609,19 @@ export class RemoteFileService extends FileService { } const prepare = overwrite - ? Promise.resolve(this.del(target, { recursive: true }).then(undefined, err => { /*ignore*/ })) - : Promise.resolve(null); + ? Promise.resolve(this.del(target, { recursive: true }).catch(_err => { /*ignore*/ })) + : Promise.resolve(); + // todo@ben, can only copy text files + // https://github.com/Microsoft/vscode/issues/41543 return prepare.then(() => { - // todo@ben, can only copy text files - // https://github.com/Microsoft/vscode/issues/41543 return this.resolveContent(source, { acceptTextOnly: true }).then(content => { return this._withProvider(target).then(provider => { return this._writeFile( provider, target, new StringSnapshot(content.value), content.encoding, - { create: true, overwrite } + { create: true, overwrite: !!overwrite } ).then(fileStat => { this._onAfterOperation.fire(new FileOperationEvent(source, FileOperation.COPY, fileStat)); return fileStat; @@ -650,15 +644,11 @@ export class RemoteFileService extends FileService { private _activeWatches = new Map, count: number }>(); - watchFileChanges(resource: URI, opts?: IWatchOptions): void { + watchFileChanges(resource: URI, opts: IWatchOptions = { recursive: false, excludes: [] }): void { if (resource.scheme === Schemas.file) { return super.watchFileChanges(resource); } - if (!opts) { - opts = { recursive: false, excludes: [] }; - } - const key = resource.toString(); const entry = this._activeWatches.get(key); if (entry) { @@ -670,7 +660,7 @@ export class RemoteFileService extends FileService { count: 1, unwatch: this._withProvider(resource).then(provider => { return provider.watch(resource, opts); - }, err => { + }, _err => { return { dispose() { } }; }) }); @@ -687,3 +677,5 @@ export class RemoteFileService extends FileService { } } } + +registerSingleton(IFileService, RemoteFileService); \ No newline at end of file diff --git a/src/vs/workbench/services/files/electron-browser/streams.ts b/src/vs/workbench/services/files/node/streams.ts similarity index 81% rename from src/vs/workbench/services/files/electron-browser/streams.ts rename to src/vs/workbench/services/files/node/streams.ts index d36f1c3662..ed5e82917e 100644 --- a/src/vs/workbench/services/files/electron-browser/streams.ts +++ b/src/vs/workbench/services/files/node/streams.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Readable, Writable } from 'stream'; +import { Readable, Writable, WritableOptions } from 'stream'; import { UTF8 } from 'vs/base/node/encoding'; import { URI } from 'vs/base/common/uri'; import { IFileSystemProvider, ITextSnapshot, FileSystemProviderCapabilities, FileWriteOptions } from 'vs/platform/files/common/files'; @@ -22,7 +22,7 @@ export function createWritableOfProvider(provider: IFileSystemProvider, resource function createSimpleWritable(provider: IFileSystemProvider, resource: URI, opts: FileWriteOptions): Writable { return new class extends Writable { _chunks: Buffer[] = []; - constructor(opts?) { + constructor(opts?: WritableOptions) { super(opts); } _write(chunk: Buffer, encoding: string, callback: Function) { @@ -31,7 +31,7 @@ function createSimpleWritable(provider: IFileSystemProvider, resource: URI, opts } end() { // todo@joh - end might have another chunk... - provider.writeFile(resource, Buffer.concat(this._chunks), opts).then(_ => { + provider.writeFile!(resource, Buffer.concat(this._chunks), opts).then(_ => { super.end(); }, err => { this.emit('error', err); @@ -44,15 +44,15 @@ function createWritable(provider: IFileSystemProvider, resource: URI, opts: File return new class extends Writable { _fd: number; _pos: number = 0; - constructor(opts?) { + constructor(opts?: WritableOptions) { super(opts); } - async _write(chunk: Buffer, encoding, callback: Function) { + async _write(chunk: Buffer, encoding: string, callback: Function) { try { if (typeof this._fd !== 'number') { - this._fd = await provider.open(resource, { create: true }); + this._fd = await provider.open!(resource, { create: true }); } - let bytesWritten = await provider.write(this._fd, this._pos, chunk, 0, chunk.length); + let bytesWritten = await provider.write!(this._fd, this._pos, chunk, 0, chunk.length); this._pos += bytesWritten; callback(); } catch (err) { @@ -61,9 +61,9 @@ function createWritable(provider: IFileSystemProvider, resource: URI, opts: File } _final(callback: (err?: any) => any) { if (typeof this._fd !== 'number') { - provider.open(resource, { create: true }).then(fd => provider.close(fd)).finally(callback); + provider.open!(resource, { create: true }).then(fd => provider.close!(fd)).finally(callback); } else { - provider.close(this._fd).finally(callback); + provider.close!(this._fd).finally(callback); } } }; @@ -85,20 +85,20 @@ function createReadable(provider: IFileSystemProvider, resource: URI, position: _pos: number = position; _reading: boolean = false; - async _read(size?: number) { + async _read(size: number = 2 ** 10) { if (this._reading) { return; } this._reading = true; try { if (typeof this._fd !== 'number') { - this._fd = await provider.open(resource, { create: false }); + this._fd = await provider.open!(resource, { create: false }); } while (this._reading) { let buffer = Buffer.allocUnsafe(size); - let bytesRead = await provider.read(this._fd, this._pos, buffer, 0, buffer.length); + let bytesRead = await provider.read!(this._fd, this._pos, buffer, 0, buffer.length); if (bytesRead === 0) { - await provider.close(this._fd); + await provider.close!(this._fd); this._reading = false; this.push(null); } else { @@ -113,7 +113,7 @@ function createReadable(provider: IFileSystemProvider, resource: URI, position: } _destroy(_err: any, callback: (err?: any) => any) { if (typeof this._fd === 'number') { - provider.close(this._fd).then(callback, callback); + provider.close!(this._fd).then(callback, callback); } } }; @@ -126,7 +126,7 @@ function createSimpleReadable(provider: IFileSystemProvider, resource: URI, posi if (this._readOperation) { return; } - this._readOperation = provider.readFile(resource).then(data => { + this._readOperation = provider.readFile!(resource).then(data => { this.push(data.slice(position)); this.push(null); }, err => { @@ -141,7 +141,7 @@ export function createReadableOfSnapshot(snapshot: ITextSnapshot): Readable { return new Readable({ read: function () { try { - let chunk: string; + let chunk: string | null = null; let canPush = true; // Push all chunks as long as we can push and as long as diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts index 7ba38adece..51e019d3f5 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/nsfwWatcherService.ts @@ -4,8 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as glob from 'vs/base/common/glob'; -import * as paths from 'vs/base/common/paths'; -import * as path from 'path'; +import * as extpath from 'vs/base/common/extpath'; +import * as path from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import * as watcher from 'vs/workbench/services/files/node/watcher/common'; import * as nsfw from 'vscode-nsfw'; @@ -233,7 +233,7 @@ export class NsfwWatcherService implements IWatcherService { */ protected _normalizeRoots(roots: IWatcherRequest[]): IWatcherRequest[] { return roots.filter(r => roots.every(other => { - return !(r.basePath.length > other.basePath.length && paths.isEqualOrParent(r.basePath, other.basePath)); + return !(r.basePath.length > other.basePath.length && extpath.isEqualOrParent(r.basePath, other.basePath)); })); } diff --git a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts index d2bd22ac55..6abd23979b 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherIpc.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { IWatcherRequest, IWatcherService, IWatcherOptions, IWatchError } from './watcher'; import { Event } from 'vs/base/common/event'; import { IRawFileChange } from 'vs/workbench/services/files/node/watcher/common'; diff --git a/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts b/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts index 2d811a9ec3..1f99ef66f1 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts @@ -7,7 +7,7 @@ import * as chokidar from 'vscode-chokidar'; import * as fs from 'fs'; import * as gracefulFs from 'graceful-fs'; gracefulFs.gracefulify(fs); -import * as paths from 'vs/base/common/paths'; +import * as extpath from 'vs/base/common/extpath'; import * as glob from 'vs/base/common/glob'; import { FileChangeType } from 'vs/platform/files/common/files'; import { ThrottledDelayer } from 'vs/base/common/async'; @@ -242,7 +242,7 @@ export class ChokidarWatcherService implements IWatcherService { }); } - return Promise.resolve(); + return Promise.resolve(undefined); }); } }); @@ -284,7 +284,7 @@ function isIgnored(path: string, requests: ExtendedWatcherRequest[]): boolean { if (request.basePath === path) { return false; } - if (paths.isEqualOrParent(path, request.basePath)) { + if (extpath.isEqualOrParent(path, request.basePath)) { if (!request.parsedPattern) { if (request.ignored && request.ignored.length > 0) { let pattern = `{${request.ignored.join(',')}}`; @@ -313,7 +313,7 @@ export function normalizeRoots(requests: IWatcherRequest[]): { [basePath: string for (let request of requests) { let basePath = request.basePath; let ignored = (request.ignored || []).sort(); - if (prevRequest && (paths.isEqualOrParent(basePath, prevRequest.basePath))) { + if (prevRequest && (extpath.isEqualOrParent(basePath, prevRequest.basePath))) { if (!isEqualIgnore(ignored, prevRequest.ignored)) { result[prevRequest.basePath].push({ basePath, ignored }); } diff --git a/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts b/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts index a7c48d446b..13baa2de03 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/test/chockidarWatcherService.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as os from 'os'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as pfs from 'vs/base/node/pfs'; import { normalizeRoots, ChokidarWatcherService } from '../chokidarWatcherService'; diff --git a/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts b/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts index d2bd22ac55..6abd23979b 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherIpc.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { IWatcherRequest, IWatcherService, IWatcherOptions, IWatchError } from './watcher'; import { Event } from 'vs/base/common/event'; import { IRawFileChange } from 'vs/workbench/services/files/node/watcher/common'; diff --git a/src/vs/workbench/services/files/node/watcher/win32/watcherService.ts b/src/vs/workbench/services/files/node/watcher/win32/watcherService.ts index 7a4593506e..dd28b94387 100644 --- a/src/vs/workbench/services/files/node/watcher/win32/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/win32/watcherService.ts @@ -7,9 +7,8 @@ import { IRawFileChange, toFileChangesEvent } from 'vs/workbench/services/files/ import { OutOfProcessWin32FolderWatcher } from 'vs/workbench/services/files/node/watcher/win32/csharpWatcherService'; import { FileChangesEvent } from 'vs/platform/files/common/files'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { normalize } from 'path'; +import { normalize, posix } from 'vs/base/common/path'; import { rtrim, endsWith } from 'vs/base/common/strings'; -import { sep } from 'vs/base/common/paths'; import { Schemas } from 'vs/base/common/network'; export class FileWatcher { @@ -30,12 +29,12 @@ export class FileWatcher { } let basePath: string = normalize(this.contextService.getWorkspace().folders[0].uri.fsPath); - if (basePath && basePath.indexOf('\\\\') === 0 && endsWith(basePath, sep)) { + if (basePath && basePath.indexOf('\\\\') === 0 && endsWith(basePath, posix.sep)) { // for some weird reason, node adds a trailing slash to UNC paths // we never ever want trailing slashes as our base path unless // someone opens root ("/"). // See also https://github.com/nodejs/io.js/issues/1765 - basePath = rtrim(basePath, sep); + basePath = rtrim(basePath, posix.sep); } const watcher = new OutOfProcessWin32FolderWatcher( diff --git a/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts b/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts index 0fd9439c58..282697b74c 100644 --- a/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts +++ b/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts @@ -4,22 +4,23 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as os from 'os'; import * as assert from 'assert'; -import { FileService } from 'vs/workbench/services/files/electron-browser/fileService'; +import { FileService } from 'vs/workbench/services/files/node/fileService'; import { FileOperation, FileOperationEvent, FileChangesEvent, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files'; import { URI as uri } from 'vs/base/common/uri'; import * as uuid from 'vs/base/common/uuid'; import * as pfs from 'vs/base/node/pfs'; import * as encodingLib from 'vs/base/node/encoding'; import * as utils from 'vs/workbench/services/files/test/electron-browser/utils'; -import { TestEnvironmentService, TestContextService, TestTextResourceConfigurationService, getRandomTestPath, TestLifecycleService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { TestEnvironmentService, TestContextService, TestTextResourceConfigurationService, TestLifecycleService, TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { getRandomTestPath } from 'vs/base/test/node/testUtils'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; import { Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { IEncodingOverride } from 'vs/workbench/services/files/electron-browser/encoding'; +import { IEncodingOverride } from 'vs/workbench/services/files/node/encoding'; import { getPathFromAmdModule } from 'vs/base/common/amd'; suite('FileService', () => { @@ -517,14 +518,14 @@ suite('FileService', () => { { resource: uri.file(testDir), options: { resolveTo: [uri.file(path.join(testDir, 'deep'))] } }, { resource: uri.file(path.join(testDir, 'deep')) } ]).then(res => { - const r1 = res[0].stat; + const r1 = res[0].stat!; assert.equal(r1.children!.length, 8); const deep = utils.getByName(r1, 'deep')!; assert.equal(deep.children!.length, 4); - const r2 = res[1].stat; + const r2 = res[1].stat!; assert.equal(r2.children!.length, 4); assert.equal(r2.name, 'deep'); }); @@ -807,27 +808,27 @@ suite('FileService', () => { }, 100); }); - test('watchFileChanges - support atomic save', function (done) { - const toWatch = uri.file(path.join(testDir, 'index.html')); + // test('watchFileChanges - support atomic save', function (done) { + // const toWatch = uri.file(path.join(testDir, 'index.html')); - service.watchFileChanges(toWatch); + // service.watchFileChanges(toWatch); - service.onFileChanges((e: FileChangesEvent) => { - assert.ok(e); + // service.onFileChanges((e: FileChangesEvent) => { + // assert.ok(e); - service.unwatchFileChanges(toWatch); - done(); - }); + // service.unwatchFileChanges(toWatch); + // done(); + // }); - setTimeout(() => { - // Simulate atomic save by deleting the file, creating it under different name - // and then replacing the previously deleted file with those contents - const renamed = `${toWatch.fsPath}.bak`; - fs.unlinkSync(toWatch.fsPath); - fs.writeFileSync(renamed, 'Changes'); - fs.renameSync(renamed, toWatch.fsPath); - }, 100); - }); + // setTimeout(() => { + // // Simulate atomic save by deleting the file, creating it under different name + // // and then replacing the previously deleted file with those contents + // const renamed = `${toWatch.fsPath}.bak`; + // fs.unlinkSync(toWatch.fsPath); + // fs.writeFileSync(renamed, 'Changes'); + // fs.renameSync(renamed, toWatch.fsPath); + // }, 100); + // }); test('options - encoding override (parent)', function () { diff --git a/src/vs/workbench/services/files/test/electron-browser/resolver.test.ts b/src/vs/workbench/services/files/test/electron-browser/resolver.test.ts index 92cbf06f19..568352593b 100644 --- a/src/vs/workbench/services/files/test/electron-browser/resolver.test.ts +++ b/src/vs/workbench/services/files/test/electron-browser/resolver.test.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as fs from 'fs'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as assert from 'assert'; -import { StatResolver } from 'vs/workbench/services/files/electron-browser/fileService'; +import { StatResolver } from 'vs/workbench/services/files/node/fileService'; import { URI as uri } from 'vs/base/common/uri'; import { isLinux } from 'vs/base/common/platform'; import * as utils from 'vs/workbench/services/files/test/electron-browser/utils'; @@ -32,13 +32,13 @@ suite('Stat Resolver', () => { test('resolve file', function () { let resolver = create('/index.html'); - return resolver.resolve(null).then(result => { + return resolver.resolve(undefined).then(result => { assert.ok(!result.isDirectory); assert.equal(result.name, 'index.html'); assert.ok(!!result.etag); resolver = create('examples'); - return resolver.resolve(null).then(result => { + return resolver.resolve(undefined).then(result => { assert.ok(result.isDirectory); }); }); @@ -49,20 +49,20 @@ suite('Stat Resolver', () => { let resolver = create('/'); - return resolver.resolve(null).then(result => { + return resolver.resolve(undefined).then(result => { assert.ok(result); assert.ok(result.children); - assert.ok(result.children.length > 0); - assert.ok(result.isDirectory); - assert.equal(result.children.length, testsElements.length); + assert.ok(result.children!.length > 0); + assert.ok(result!.isDirectory); + assert.equal(result.children!.length, testsElements.length); - assert.ok(result.children.every((entry) => { + assert.ok(result.children!.every((entry) => { return testsElements.some((name) => { return path.basename(entry.resource.fsPath) === name; }); })); - result.children.forEach((value) => { + result.children!.forEach((value) => { assert.ok(path.basename(value.resource.fsPath)); if (['examples', 'other'].indexOf(path.basename(value.resource.fsPath)) >= 0) { assert.ok(value.isDirectory); @@ -85,20 +85,20 @@ suite('Stat Resolver', () => { return resolver.resolve({ resolveTo: [toResource('other/deep')] }).then(result => { assert.ok(result); assert.ok(result.children); - assert.ok(result.children.length > 0); + assert.ok(result.children!.length > 0); assert.ok(result.isDirectory); - let children = result.children; + const children = result.children!; assert.equal(children.length, 4); - let other = utils.getByName(result, 'other'); + const other = utils.getByName(result, 'other'); assert.ok(other); - assert.ok(other.children.length > 0); + assert.ok(other!.children!.length > 0); - let deep = utils.getByName(other, 'deep'); + const deep = utils.getByName(other!, 'deep'); assert.ok(deep); - assert.ok(deep.children.length > 0); - assert.equal(deep.children.length, 4); + assert.ok(deep!.children!.length > 0); + assert.equal(deep!.children!.length, 4); }); }); @@ -108,24 +108,24 @@ suite('Stat Resolver', () => { return resolver.resolve({ resolveTo: [toResource('other/Deep')] }).then(result => { assert.ok(result); assert.ok(result.children); - assert.ok(result.children.length > 0); + assert.ok(result.children!.length > 0); assert.ok(result.isDirectory); - let children = result.children; - assert.equal(children.length, 4); + const children = result.children; + assert.equal(children!.length, 4); - let other = utils.getByName(result, 'other'); + const other = utils.getByName(result, 'other'); assert.ok(other); - assert.ok(other.children.length > 0); + assert.ok(other!.children!.length > 0); - let deep = utils.getByName(other, 'deep'); + const deep = utils.getByName(other!, 'deep'); if (isLinux) { // Linux has case sensitive file system assert.ok(deep); - assert.ok(!deep.children); // not resolved because we got instructed to resolve other/Deep with capital D + assert.ok(!deep!.children); // not resolved because we got instructed to resolve other/Deep with capital D } else { assert.ok(deep); - assert.ok(deep.children.length > 0); - assert.equal(deep.children.length, 4); + assert.ok(deep!.children!.length > 0); + assert.equal(deep!.children!.length, 4); } }); }); @@ -136,25 +136,25 @@ suite('Stat Resolver', () => { return resolver.resolve({ resolveTo: [toResource('other/deep'), toResource('examples')] }).then(result => { assert.ok(result); assert.ok(result.children); - assert.ok(result.children.length > 0); + assert.ok(result.children!.length > 0); assert.ok(result.isDirectory); - let children = result.children; + const children = result.children!; assert.equal(children.length, 4); - let other = utils.getByName(result, 'other'); + const other = utils.getByName(result, 'other'); assert.ok(other); - assert.ok(other.children.length > 0); + assert.ok(other!.children!.length > 0); - let deep = utils.getByName(other, 'deep'); + const deep = utils.getByName(other!, 'deep'); assert.ok(deep); - assert.ok(deep.children.length > 0); - assert.equal(deep.children.length, 4); + assert.ok(deep!.children!.length > 0); + assert.equal(deep!.children!.length, 4); - let examples = utils.getByName(result, 'examples'); + const examples = utils.getByName(result, 'examples'); assert.ok(examples); - assert.ok(examples.children.length > 0); - assert.equal(examples.children.length, 4); + assert.ok(examples!.children!.length > 0); + assert.equal(examples!.children!.length, 4); }); }); @@ -164,16 +164,16 @@ suite('Stat Resolver', () => { return resolver.resolve({ resolveSingleChildDescendants: true }).then(result => { assert.ok(result); assert.ok(result.children); - assert.ok(result.children.length > 0); + assert.ok(result.children!.length > 0); assert.ok(result.isDirectory); - let children = result.children; + const children = result.children!; assert.equal(children.length, 1); let deep = utils.getByName(result, 'deep'); assert.ok(deep); - assert.ok(deep.children.length > 0); - assert.equal(deep.children.length, 4); + assert.ok(deep!.children!.length > 0); + assert.equal(deep!.children!.length, 4); }); }); }); diff --git a/src/vs/workbench/services/group/test/browser/editorGroupsService.test.ts b/src/vs/workbench/services/group/test/browser/editorGroupsService.test.ts deleted file mode 100644 index fce010507d..0000000000 --- a/src/vs/workbench/services/group/test/browser/editorGroupsService.test.ts +++ /dev/null @@ -1,13 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; - -suite('Editor groups service', () => { - test('groups basics', function () { - // {{SQL CARBON EDIT}} - Remove test - assert.equal(0, 0); - }); -}); \ No newline at end of file diff --git a/src/vs/workbench/services/hash/common/hashService.ts b/src/vs/workbench/services/hash/common/hashService.ts index 08a1757d01..4a9eafba6d 100644 --- a/src/vs/workbench/services/hash/common/hashService.ts +++ b/src/vs/workbench/services/hash/common/hashService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export const IHashService = createDecorator('hashService'); @@ -13,5 +14,19 @@ export interface IHashService { /** * Produce a SHA1 hash of the provided content. */ - createSHA1(content: string): string; -} \ No newline at end of file + createSHA1(content: string): Thenable; +} + +export class HashService implements IHashService { + + _serviceBrand: any; + + createSHA1(content: string): Thenable { + return crypto.subtle.digest('SHA-1', new TextEncoder().encode(content)).then(buffer => { + // https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest#Converting_a_digest_to_a_hex_string + return Array.prototype.map.call(new Uint8Array(buffer), value => `00${value.toString(16)}`.slice(-2)).join(''); + }); + } +} + +registerSingleton(IHashService, HashService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/hash/node/hashService.ts b/src/vs/workbench/services/hash/node/hashService.ts index b1021f4364..b4f65bab00 100644 --- a/src/vs/workbench/services/hash/node/hashService.ts +++ b/src/vs/workbench/services/hash/node/hashService.ts @@ -5,12 +5,15 @@ import { createHash } from 'crypto'; import { IHashService } from 'vs/workbench/services/hash/common/hashService'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class HashService implements IHashService { _serviceBrand: any; - public createSHA1(content: string): string { - return createHash('sha1').update(content).digest('hex'); + createSHA1(content: string): Promise { + return Promise.resolve(createHash('sha1').update(content).digest('hex')); } -} \ No newline at end of file +} + +registerSingleton(IHashService, HashService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/hash/test/hashService.test.ts b/src/vs/workbench/services/hash/test/hashService.test.ts new file mode 100644 index 0000000000..ae522e8d07 --- /dev/null +++ b/src/vs/workbench/services/hash/test/hashService.test.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { HashService } from 'vs/workbench/services/hash/common/hashService'; + +suite('Hash Service', () => { + + test('computeSHA1Hash', async () => { + const service = new HashService(); + + assert.equal(await service.createSHA1(''), 'da39a3ee5e6b4b0d3255bfef95601890afd80709'); + assert.equal(await service.createSHA1('hello world'), '2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'); + assert.equal(await service.createSHA1('da39a3ee5e6b4b0d3255bfef95601890afd80709'), '10a34637ad661d98ba3344717656fcc76209c2f8'); + assert.equal(await service.createSHA1('2aae6c35c94fcfb415dbe95f408b9ce91ee846ed'), 'd6b0d82cea4269b51572b8fab43adcee9fc3cf9a'); + assert.equal(await service.createSHA1('öäü_?ß()<>ÖÄÜ'), 'b64beaeff9e317b0193c8e40a2431b210388eba9'); + }); +}); diff --git a/src/vs/workbench/services/history/electron-browser/history.ts b/src/vs/workbench/services/history/browser/history.ts similarity index 96% rename from src/vs/workbench/services/history/electron-browser/history.ts rename to src/vs/workbench/services/history/browser/history.ts index 3499ec3562..24b77dadf6 100644 --- a/src/vs/workbench/services/history/electron-browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -18,18 +18,20 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { Registry } from 'vs/platform/registry/common/platform'; import { Event } from 'vs/base/common/event'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService, IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { getCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { getExcludes, ISearchConfiguration } from 'vs/platform/search/common/search'; +import { getExcludes, ISearchConfiguration } from 'vs/workbench/services/search/common/search'; import { IExpression } from 'vs/base/common/glob'; import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ResourceGlobMatcher } from 'vs/workbench/electron-browser/resources'; +import { ResourceGlobMatcher } from 'vs/workbench/common/resources'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; -import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { coalesce } from 'vs/base/common/arrays'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { withNullAsUndefined } from 'vs/base/common/types'; /** * Stores the selection & view state of an editor and allows to compare it to other selection states. @@ -137,7 +139,7 @@ export class HistoryService extends Disposable implements IHistoryService { @IFileService private readonly fileService: IFileService, @IWindowsService private readonly windowService: IWindowsService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IPartService private readonly partService: IPartService, + @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IContextKeyService private readonly contextKeyService: IContextKeyService ) { super(); @@ -217,7 +219,7 @@ export class HistoryService extends Disposable implements IHistoryService { // Track the last edit location by tracking model content change events // Use a debouncer to make sure to capture the correct cursor position // after the model content has changed. - this.activeEditorListeners.push(Event.debounce(activeTextEditorWidget.onDidChangeModelContent, (last, event) => event, 0)((event => this.rememberLastEditLocation(activeEditor, activeTextEditorWidget)))); + this.activeEditorListeners.push(Event.debounce(activeTextEditorWidget.onDidChangeModelContent, (last, event) => event, 0)((event => this.rememberLastEditLocation(activeEditor!, activeTextEditorWidget)))); } } @@ -281,7 +283,17 @@ export class HistoryService extends Disposable implements IHistoryService { } if (lastClosedFile) { - this.editorService.openEditor({ resource: lastClosedFile.resource, options: { pinned: true, index: lastClosedFile.index } }); + this.editorService.openEditor({ resource: lastClosedFile.resource, options: { pinned: true, index: lastClosedFile.index } }).then(editor => { + + // Fix for https://github.com/Microsoft/vscode/issues/67882 + // If opening of the editor fails, make sure to try the next one + // but make sure to remove this one from the list to prevent + // endless loops. + if (!editor) { + this.recentlyClosedFiles.pop(); + this.reopenLastClosedEditor(); + } + }); } } @@ -410,7 +422,7 @@ export class HistoryService extends Disposable implements IHistoryService { this.doNavigate(this.stack[this.index], !acrossEditors).finally(() => this.navigatingInStack = false); } - private doNavigate(location: IStackEntry, withSelection: boolean): Promise { + private doNavigate(location: IStackEntry, withSelection: boolean): Promise { const options: ITextEditorOptions = { revealIfOpened: true // support to navigate across editor groups }; @@ -793,7 +805,7 @@ export class HistoryService extends Disposable implements IHistoryService { return false; } - if (this.partService.isRestored() && !this.fileService.canHandleResource(inputResource)) { + if (this.layoutService.isRestored() && !this.fileService.canHandleResource(inputResource)) { return false; // make sure to only check this when workbench has restored (for https://github.com/Microsoft/vscode/issues/48275) } @@ -889,7 +901,7 @@ export class HistoryService extends Disposable implements IHistoryService { this.onEditorDispose(input, () => this.removeFromHistory(input), this.editorHistoryListeners); } - return input; + return withNullAsUndefined(input); } } @@ -961,3 +973,5 @@ export class HistoryService extends Disposable implements IHistoryService { return undefined; } } + +registerSingleton(IHistoryService, HistoryService); \ No newline at end of file diff --git a/src/vs/platform/integrity/common/integrity.ts b/src/vs/workbench/services/integrity/common/integrity.ts similarity index 100% rename from src/vs/platform/integrity/common/integrity.ts rename to src/vs/workbench/services/integrity/common/integrity.ts diff --git a/src/vs/platform/integrity/node/integrityServiceImpl.ts b/src/vs/workbench/services/integrity/node/integrityService.ts similarity index 94% rename from src/vs/platform/integrity/node/integrityServiceImpl.ts rename to src/vs/workbench/services/integrity/node/integrityService.ts index 59ef5780f8..3971a51959 100644 --- a/src/vs/platform/integrity/node/integrityServiceImpl.ts +++ b/src/vs/workbench/services/integrity/node/integrityService.ts @@ -8,11 +8,12 @@ import * as crypto from 'crypto'; import * as fs from 'fs'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; -import { ChecksumPair, IIntegrityService, IntegrityTestResult } from 'vs/platform/integrity/common/integrity'; +import { ChecksumPair, IIntegrityService, IntegrityTestResult } from 'vs/workbench/services/integrity/common/integrity'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import product from 'vs/platform/node/product'; +import product from 'vs/platform/product/node/product'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; interface IStorageData { dontShowPrompt: boolean; @@ -161,3 +162,5 @@ export class IntegrityServiceImpl implements IIntegrityService { }; } } + +registerSingleton(IIntegrityService, IntegrityServiceImpl, true); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts index 755cf54e4f..5b8b4ff54c 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingEditing.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingEditing.ts @@ -15,7 +15,7 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { ITextModel } from 'vs/editor/common/model'; -import { ITextEditorModel, ITextModelService } from 'vs/editor/common/services/resolverService'; +import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -24,7 +24,7 @@ import { ServiceIdentifier, createDecorator } from 'vs/platform/instantiation/co import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; - +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export const IKeybindingEditingService = createDecorator('keybindingEditingService'); @@ -32,7 +32,7 @@ export interface IKeybindingEditingService { _serviceBrand: ServiceIdentifier; - editKeybinding(key: string, keybindingItem: ResolvedKeybindingItem): Promise; + editKeybinding(keybindingItem: ResolvedKeybindingItem, key: string, when: string | undefined): Promise; removeKeybinding(keybindingItem: ResolvedKeybindingItem): Promise; @@ -57,8 +57,8 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding this.queue = new Queue(); } - editKeybinding(key: string, keybindingItem: ResolvedKeybindingItem): Promise { - return this.queue.queue(() => this.doEditKeybinding(key, keybindingItem)); // queue up writes to prevent race conditions + editKeybinding(keybindingItem: ResolvedKeybindingItem, key: string, when: string | undefined): Promise { + return this.queue.queue(() => this.doEditKeybinding(keybindingItem, key, when)); // queue up writes to prevent race conditions } resetKeybinding(keybindingItem: ResolvedKeybindingItem): Promise { @@ -69,13 +69,13 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding return this.queue.queue(() => this.doRemoveKeybinding(keybindingItem)); // queue up writes to prevent race conditions } - private doEditKeybinding(key: string, keybindingItem: ResolvedKeybindingItem): Promise { + private doEditKeybinding(keybindingItem: ResolvedKeybindingItem, key: string, when: string | undefined): Promise { return this.resolveAndValidate() .then(reference => { const model = reference.object.textEditorModel; const userKeybindingEntries = json.parse(model.getValue()); const userKeybindingEntryIndex = this.findUserKeybindingEntryIndex(keybindingItem, userKeybindingEntries); - this.updateKeybinding(key, keybindingItem, model, userKeybindingEntryIndex); + this.updateKeybinding(keybindingItem, key, when, model, userKeybindingEntryIndex); if (keybindingItem.isDefault && keybindingItem.resolvedKeybinding) { this.removeDefaultKeybinding(keybindingItem, model); } @@ -112,15 +112,19 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding return this.textFileService.save(this.resource); } - private updateKeybinding(newKey: string, keybindingItem: ResolvedKeybindingItem, model: ITextModel, userKeybindingEntryIndex: number): void { + private updateKeybinding(keybindingItem: ResolvedKeybindingItem, newKey: string, when: string | undefined, model: ITextModel, userKeybindingEntryIndex: number): void { const { tabSize, insertSpaces } = model.getOptions(); const eol = model.getEOL(); if (userKeybindingEntryIndex !== -1) { // Update the keybinding with new key this.applyEditsToBuffer(setProperty(model.getValue(), [userKeybindingEntryIndex, 'key'], newKey, { tabSize, insertSpaces, eol })[0], model); + const edits = setProperty(model.getValue(), [userKeybindingEntryIndex, 'when'], when, { tabSize, insertSpaces, eol }); + if (edits.length > 0) { + this.applyEditsToBuffer(edits[0], model); + } } else { // Add the new keybinding with new key - this.applyEditsToBuffer(setProperty(model.getValue(), [-1], this.asObject(newKey, keybindingItem.command, keybindingItem.when, false), { tabSize, insertSpaces, eol })[0], model); + this.applyEditsToBuffer(setProperty(model.getValue(), [-1], this.asObject(newKey, keybindingItem.command, when, false), { tabSize, insertSpaces, eol })[0], model); } } @@ -137,7 +141,10 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding private removeDefaultKeybinding(keybindingItem: ResolvedKeybindingItem, model: ITextModel): void { const { tabSize, insertSpaces } = model.getOptions(); const eol = model.getEOL(); - this.applyEditsToBuffer(setProperty(model.getValue(), [-1], this.asObject(keybindingItem.resolvedKeybinding.getUserSettingsLabel(), keybindingItem.command, keybindingItem.when, true), { tabSize, insertSpaces, eol })[0], model); + const key = keybindingItem.resolvedKeybinding ? keybindingItem.resolvedKeybinding.getUserSettingsLabel() : null; + if (key) { + this.applyEditsToBuffer(setProperty(model.getValue(), [-1], this.asObject(key, keybindingItem.command, keybindingItem.when ? keybindingItem.when.serialize() : undefined, true), { tabSize, insertSpaces, eol })[0], model); + } } private removeUnassignedDefaultKeybinding(keybindingItem: ResolvedKeybindingItem, model: ITextModel): void { @@ -158,7 +165,8 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding return index; } if (keybinding.when && keybindingItem.when) { - if (ContextKeyExpr.deserialize(keybinding.when).serialize() === keybindingItem.when.serialize()) { + const contextKeyExpr = ContextKeyExpr.deserialize(keybinding.when); + if (contextKeyExpr && contextKeyExpr.serialize() === keybindingItem.when.serialize()) { return index; } } @@ -177,11 +185,13 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding return indices; } - private asObject(key: string, command: string, when: ContextKeyExpr, negate: boolean): any { + private asObject(key: string, command: string | null, when: string | undefined, negate: boolean): any { const object = { key }; - object['command'] = negate ? `-${command}` : command; + if (command) { + object['command'] = negate ? `-${command}` : command; + } if (when) { - object['when'] = when.serialize(); + object['when'] = when; } return object; } @@ -197,7 +207,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding } - private resolveModelReference(): Promise> { + private resolveModelReference(): Promise> { return this.fileService.existsFile(this.resource) .then(exists => { const EOL = this.configurationService.getValue('files', { overrideIdentifier: 'json' })['eol']; @@ -206,7 +216,7 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding }); } - private resolveAndValidate(): Promise> { + private resolveAndValidate(): Promise> { // Target cannot be dirty if not writing into buffer if (this.textFileService.isDirty(this.resource)) { @@ -220,11 +230,11 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding if (model.getValue()) { const parsed = this.parse(model); if (parsed.parseErrors.length) { - return Promise.reject(new Error(localize('parseErrors', "Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again."))); + return Promise.reject(new Error(localize('parseErrors', "Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again."))); } if (parsed.result) { if (!isArray(parsed.result)) { - return Promise.reject(new Error(localize('errorInvalidConfiguration', "Unable to write to the keybindings configuration file. It has an object which is not of type Array. Please open the file to clean up and try again."))); + return Promise.reject(new Error(localize('errorInvalidConfiguration', "Unable to write to the keybindings configuration file. It has an object which is not of type Array. Please open the file to clean up and try again."))); } } else { const content = EOL + '[]'; @@ -248,3 +258,5 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding return '// ' + localize('emptyKeybindingsHeader', "Place your key bindings in this file to override the defaults") + EOL + '[]'; } } + +registerSingleton(IKeybindingEditingService, KeybindingsEditingService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/common/keybindingIO.ts b/src/vs/workbench/services/keybinding/common/keybindingIO.ts index 7182d24b96..56de649039 100644 --- a/src/vs/workbench/services/keybinding/common/keybindingIO.ts +++ b/src/vs/workbench/services/keybinding/common/keybindingIO.ts @@ -5,15 +5,13 @@ import { SimpleKeybinding } from 'vs/base/common/keyCodes'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; -import { OperatingSystem } from 'vs/base/common/platform'; import { ScanCodeBinding } from 'vs/base/common/scanCode'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; export interface IUserKeybindingItem { - firstPart: SimpleKeybinding | ScanCodeBinding | null; - chordPart: SimpleKeybinding | ScanCodeBinding | null; + parts: (SimpleKeybinding | ScanCodeBinding)[]; command: string | null; commandArgs?: any; when: ContextKeyExpr | null; @@ -21,7 +19,7 @@ export interface IUserKeybindingItem { export class KeybindingIO { - public static writeKeybindingItem(out: OutputBuilder, item: ResolvedKeybindingItem, OS: OperatingSystem): void { + public static writeKeybindingItem(out: OutputBuilder, item: ResolvedKeybindingItem): void { if (!item.resolvedKeybinding) { return; } @@ -41,14 +39,13 @@ export class KeybindingIO { out.write('}'); } - public static readUserKeybindingItem(input: IUserFriendlyKeybinding, OS: OperatingSystem): IUserKeybindingItem { - const [firstPart, chordPart] = (typeof input.key === 'string' ? KeybindingParser.parseUserBinding(input.key) : [null, null]); + public static readUserKeybindingItem(input: IUserFriendlyKeybinding): IUserKeybindingItem { + const parts = (typeof input.key === 'string' ? KeybindingParser.parseUserBinding(input.key) : []); const when = (typeof input.when === 'string' ? ContextKeyExpr.deserialize(input.when) : null); const command = (typeof input.command === 'string' ? input.command : null); const commandArgs = (typeof input.args !== 'undefined' ? input.args : undefined); return { - firstPart: firstPart, - chordPart: chordPart, + parts: parts, command: command, commandArgs: commandArgs, when: when diff --git a/src/vs/workbench/services/keybinding/common/keyboardMapper.ts b/src/vs/workbench/services/keybinding/common/keyboardMapper.ts index 158658f7bb..be3b660b60 100644 --- a/src/vs/workbench/services/keybinding/common/keyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/keyboardMapper.ts @@ -11,7 +11,7 @@ export interface IKeyboardMapper { dumpDebugInfo(): string; resolveKeybinding(keybinding: Keybinding): ResolvedKeybinding[]; resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): ResolvedKeybinding; - resolveUserBinding(firstPart: SimpleKeybinding | ScanCodeBinding | null, chordPart: SimpleKeybinding | ScanCodeBinding | null): ResolvedKeybinding[]; + resolveUserBinding(firstPart: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[]; } export class CachedKeyboardMapper implements IKeyboardMapper { @@ -43,7 +43,7 @@ export class CachedKeyboardMapper implements IKeyboardMapper { return this._actual.resolveKeyboardEvent(keyboardEvent); } - public resolveUserBinding(firstPart: SimpleKeybinding | ScanCodeBinding, chordPart: SimpleKeybinding | ScanCodeBinding): ResolvedKeybinding[] { - return this._actual.resolveUserBinding(firstPart, chordPart); + public resolveUserBinding(parts: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[] { + return this._actual.resolveUserBinding(parts); } } diff --git a/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts index 803045b62d..b54f5be867 100644 --- a/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/macLinuxFallbackKeyboardMapper.ts @@ -9,6 +9,7 @@ import { IMMUTABLE_CODE_TO_KEY_CODE, ScanCode, ScanCodeBinding } from 'vs/base/c import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; +import { removeElementsAfterNulls } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; /** * A keyboard mapper to be used when reading the keymap from the OS fails. @@ -40,7 +41,7 @@ export class MacLinuxFallbackKeyboardMapper implements IKeyboardMapper { keyboardEvent.metaKey, keyboardEvent.keyCode ); - return new USLayoutResolvedKeybinding(keybinding, this._OS); + return new USLayoutResolvedKeybinding(keybinding.toChord(), this._OS); } private _scanCodeToKeyCode(scanCode: ScanCode): KeyCode { @@ -117,14 +118,10 @@ export class MacLinuxFallbackKeyboardMapper implements IKeyboardMapper { return new SimpleKeybinding(binding.ctrlKey, binding.shiftKey, binding.altKey, binding.metaKey, keyCode); } - public resolveUserBinding(firstPart: SimpleKeybinding | ScanCodeBinding | null, chordPart: SimpleKeybinding | ScanCodeBinding | null): ResolvedKeybinding[] { - const _firstPart = this._resolveSimpleUserBinding(firstPart); - const _chordPart = this._resolveSimpleUserBinding(chordPart); - if (_firstPart && _chordPart) { - return [new USLayoutResolvedKeybinding(new ChordKeybinding(_firstPart, _chordPart), this._OS)]; - } - if (_firstPart) { - return [new USLayoutResolvedKeybinding(_firstPart, this._OS)]; + public resolveUserBinding(input: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[] { + const parts: SimpleKeybinding[] = removeElementsAfterNulls(input.map(keybinding => this._resolveSimpleUserBinding(keybinding))); + if (parts.length > 0) { + return [new USLayoutResolvedKeybinding(new ChordKeybinding(parts), this._OS)]; } return []; } diff --git a/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts index 4d52c9ee1c..61f0de8b21 100644 --- a/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/macLinuxKeyboardMapper.ts @@ -4,12 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import { CharCode } from 'vs/base/common/charCode'; -import { KeyCode, KeyCodeUtils, Keybinding, KeybindingType, ResolvedKeybinding, ResolvedKeybindingPart, SimpleKeybinding } from 'vs/base/common/keyCodes'; -import { AriaLabelProvider, ElectronAcceleratorLabelProvider, UILabelProvider, UserSettingsLabelProvider } from 'vs/base/common/keybindingLabels'; +import { KeyCode, KeyCodeUtils, Keybinding, ResolvedKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes'; import { OperatingSystem } from 'vs/base/common/platform'; import { IMMUTABLE_CODE_TO_KEY_CODE, IMMUTABLE_KEY_CODE_TO_CODE, ScanCode, ScanCodeBinding, ScanCodeUtils } from 'vs/base/common/scanCode'; import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; +import { BaseResolvedKeybinding } from 'vs/platform/keybinding/common/baseResolvedKeybinding'; export interface IMacLinuxKeyMapping { value: string; @@ -63,53 +63,32 @@ export function macLinuxKeyboardMappingEquals(a: IMacLinuxKeyboardMapping | null */ const CHAR_CODE_TO_KEY_CODE: ({ keyCode: KeyCode; shiftKey: boolean } | null)[] = []; -export class NativeResolvedKeybinding extends ResolvedKeybinding { +export class NativeResolvedKeybinding extends BaseResolvedKeybinding { private readonly _mapper: MacLinuxKeyboardMapper; - private readonly _OS: OperatingSystem; - private readonly _firstPart: ScanCodeBinding; - private readonly _chordPart: ScanCodeBinding | null; - constructor(mapper: MacLinuxKeyboardMapper, OS: OperatingSystem, firstPart: ScanCodeBinding, chordPart: ScanCodeBinding | null) { - super(); - if (!firstPart) { - throw new Error(`Invalid USLayoutResolvedKeybinding`); - } + constructor(mapper: MacLinuxKeyboardMapper, os: OperatingSystem, parts: ScanCodeBinding[]) { + super(os, parts); this._mapper = mapper; - this._OS = OS; - this._firstPart = firstPart; - this._chordPart = chordPart; } - public getLabel(): string | null { - let firstPart = this._mapper.getUILabelForScanCodeBinding(this._firstPart); - let chordPart = this._mapper.getUILabelForScanCodeBinding(this._chordPart); - return UILabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, this._OS); + protected _getLabel(keybinding: ScanCodeBinding): string | null { + return this._mapper.getUILabelForScanCodeBinding(keybinding); } - public getAriaLabel(): string | null { - let firstPart = this._mapper.getAriaLabelForScanCodeBinding(this._firstPart); - let chordPart = this._mapper.getAriaLabelForScanCodeBinding(this._chordPart); - return AriaLabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, this._OS); + protected _getAriaLabel(keybinding: ScanCodeBinding): string | null { + return this._mapper.getAriaLabelForScanCodeBinding(keybinding); } - public getElectronAccelerator(): string | null { - if (this._chordPart !== null) { - // Electron cannot handle chords - return null; - } - - let firstPart = this._mapper.getElectronAcceleratorLabelForScanCodeBinding(this._firstPart); - return ElectronAcceleratorLabelProvider.toLabel(this._firstPart, firstPart, null, null, this._OS); + protected _getElectronAccelerator(keybinding: ScanCodeBinding): string | null { + return this._mapper.getElectronAcceleratorLabelForScanCodeBinding(keybinding); } - public getUserSettingsLabel(): string | null { - let firstPart = this._mapper.getUserSettingsLabelForScanCodeBinding(this._firstPart); - let chordPart = this._mapper.getUserSettingsLabelForScanCodeBinding(this._chordPart); - return UserSettingsLabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, this._OS); + protected _getUserSettingsLabel(keybinding: ScanCodeBinding): string | null { + return this._mapper.getUserSettingsLabelForScanCodeBinding(keybinding); } - private _isWYSIWYG(binding: ScanCodeBinding | null): boolean { + protected _isWYSIWYG(binding: ScanCodeBinding | null): boolean { if (!binding) { return true; } @@ -128,36 +107,8 @@ export class NativeResolvedKeybinding extends ResolvedKeybinding { return (a.toLowerCase() === b.toLowerCase()); } - public isWYSIWYG(): boolean { - return (this._isWYSIWYG(this._firstPart) && this._isWYSIWYG(this._chordPart)); - } - - public isChord(): boolean { - return (this._chordPart ? true : false); - } - - public getParts(): [ResolvedKeybindingPart, ResolvedKeybindingPart | null] { - return [ - this._toResolvedKeybindingPart(this._firstPart), - this._chordPart ? this._toResolvedKeybindingPart(this._chordPart) : null - ]; - } - - private _toResolvedKeybindingPart(binding: ScanCodeBinding): ResolvedKeybindingPart { - return new ResolvedKeybindingPart( - binding.ctrlKey, - binding.shiftKey, - binding.altKey, - binding.metaKey, - this._mapper.getUILabelForScanCodeBinding(binding), - this._mapper.getAriaLabelForScanCodeBinding(binding) - ); - } - - public getDispatchParts(): [string | null, string | null] { - let firstPart = this._firstPart ? this._mapper.getDispatchStrForScanCodeBinding(this._firstPart) : null; - let chordPart = this._chordPart ? this._mapper.getDispatchStrForScanCodeBinding(this._chordPart) : null; - return [firstPart, chordPart]; + protected _getDispatchPart(keybinding: ScanCodeBinding): string | null { + return this._mapper.getDispatchStrForScanCodeBinding(keybinding); } } @@ -774,6 +725,7 @@ export class MacLinuxKeyboardMapper implements IKeyboardMapper { const hwAltKey = (mod & 0b100) ? true : false; const scanCodeCombo = new ScanCodeCombo(hwCtrlKey, hwShiftKey, hwAltKey, scanCode); const resolvedKb = this.resolveKeyboardEvent({ + _standardKeyboardEventBrand: true, ctrlKey: scanCodeCombo.ctrlKey, shiftKey: scanCodeCombo.shiftKey, altKey: scanCodeCombo.altKey, @@ -1012,31 +964,33 @@ export class MacLinuxKeyboardMapper implements IKeyboardMapper { } public resolveKeybinding(keybinding: Keybinding): NativeResolvedKeybinding[] { - let result: NativeResolvedKeybinding[] = [], resultLen = 0; + let chordParts: ScanCodeBinding[][] = []; + for (let part of keybinding.parts) { + chordParts.push(this.simpleKeybindingToScanCodeBinding(part)); + } + return this._toResolvedKeybinding(chordParts); + } - if (keybinding.type === KeybindingType.Chord) { - const firstParts = this.simpleKeybindingToScanCodeBinding(keybinding.firstPart); - const chordParts = this.simpleKeybindingToScanCodeBinding(keybinding.chordPart); + private _toResolvedKeybinding(chordParts: ScanCodeBinding[][]): NativeResolvedKeybinding[] { + if (chordParts.length === 0) { + return []; + } + let result: NativeResolvedKeybinding[] = []; + this._generateResolvedKeybindings(chordParts, 0, [], result); + return result; + } - for (let i = 0, len = firstParts.length; i < len; i++) { - const firstPart = firstParts[i]; - for (let j = 0, lenJ = chordParts.length; j < lenJ; j++) { - const chordPart = chordParts[j]; - - result[resultLen++] = new NativeResolvedKeybinding(this, this._OS, firstPart, chordPart); - } - } - } else { - const firstParts = this.simpleKeybindingToScanCodeBinding(keybinding); - - for (let i = 0, len = firstParts.length; i < len; i++) { - const firstPart = firstParts[i]; - - result[resultLen++] = new NativeResolvedKeybinding(this, this._OS, firstPart, null); + private _generateResolvedKeybindings(chordParts: ScanCodeBinding[][], currentIndex: number, previousParts: ScanCodeBinding[], result: NativeResolvedKeybinding[]) { + const chordPart = chordParts[currentIndex]; + const isFinalIndex = currentIndex === chordParts.length - 1; + for (let i = 0, len = chordPart.length; i < len; i++) { + let chords = [...previousParts, chordPart[i]]; + if (isFinalIndex) { + result.push(new NativeResolvedKeybinding(this, this._OS, chords)); + } else { + this._generateResolvedKeybindings(chordParts, currentIndex + 1, chords, result); } } - - return result; } public resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): NativeResolvedKeybinding { @@ -1094,7 +1048,7 @@ export class MacLinuxKeyboardMapper implements IKeyboardMapper { } const keypress = new ScanCodeBinding(keyboardEvent.ctrlKey, keyboardEvent.shiftKey, keyboardEvent.altKey, keyboardEvent.metaKey, code); - return new NativeResolvedKeybinding(this, this._OS, keypress, null); + return new NativeResolvedKeybinding(this, this._OS, [keypress]); } private _resolveSimpleUserBinding(binding: SimpleKeybinding | ScanCodeBinding | null): ScanCodeBinding[] { @@ -1107,24 +1061,9 @@ export class MacLinuxKeyboardMapper implements IKeyboardMapper { return this.simpleKeybindingToScanCodeBinding(binding); } - public resolveUserBinding(_firstPart: SimpleKeybinding | ScanCodeBinding | null, _chordPart: SimpleKeybinding | ScanCodeBinding | null): ResolvedKeybinding[] { - const firstParts = this._resolveSimpleUserBinding(_firstPart); - const chordParts = this._resolveSimpleUserBinding(_chordPart); - - let result: NativeResolvedKeybinding[] = [], resultLen = 0; - for (let i = 0, len = firstParts.length; i < len; i++) { - const firstPart = firstParts[i]; - if (_chordPart) { - for (let j = 0, lenJ = chordParts.length; j < lenJ; j++) { - const chordPart = chordParts[j]; - - result[resultLen++] = new NativeResolvedKeybinding(this, this._OS, firstPart, chordPart); - } - } else { - result[resultLen++] = new NativeResolvedKeybinding(this, this._OS, firstPart, null); - } - } - return result; + public resolveUserBinding(input: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[] { + const parts: ScanCodeBinding[][] = input.map(keybinding => this._resolveSimpleUserBinding(keybinding)); + return this._toResolvedKeybinding(parts); } private static _charCodeToKb(charCode: number): { keyCode: KeyCode; shiftKey: boolean } | null { diff --git a/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts b/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts index 181606f49d..5781621052 100644 --- a/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts +++ b/src/vs/workbench/services/keybinding/common/windowsKeyboardMapper.ts @@ -4,12 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { CharCode } from 'vs/base/common/charCode'; -import { KeyCode, KeyCodeUtils, Keybinding, KeybindingType, ResolvedKeybinding, ResolvedKeybindingPart, SimpleKeybinding } from 'vs/base/common/keyCodes'; -import { AriaLabelProvider, ElectronAcceleratorLabelProvider, UILabelProvider, UserSettingsLabelProvider } from 'vs/base/common/keybindingLabels'; +import { KeyCode, KeyCodeUtils, Keybinding, ResolvedKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes'; +import { UILabelProvider } from 'vs/base/common/keybindingLabels'; import { OperatingSystem } from 'vs/base/common/platform'; import { IMMUTABLE_CODE_TO_KEY_CODE, ScanCode, ScanCodeBinding, ScanCodeUtils } from 'vs/base/common/scanCode'; import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { IKeyboardMapper } from 'vs/workbench/services/keybinding/common/keyboardMapper'; +import { BaseResolvedKeybinding } from 'vs/platform/keybinding/common/baseResolvedKeybinding'; +import { removeElementsAfterNulls } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; export interface IWindowsKeyMapping { vkey: string; @@ -76,42 +78,23 @@ export interface IScanCodeMapping { withShiftAltGr: string; } -export class WindowsNativeResolvedKeybinding extends ResolvedKeybinding { +export class WindowsNativeResolvedKeybinding extends BaseResolvedKeybinding { private readonly _mapper: WindowsKeyboardMapper; - private readonly _firstPart: SimpleKeybinding; - private readonly _chordPart: SimpleKeybinding | null; - constructor(mapper: WindowsKeyboardMapper, firstPart: SimpleKeybinding, chordPart: SimpleKeybinding | null) { - super(); - if (!firstPart) { - throw new Error(`Invalid WindowsNativeResolvedKeybinding firstPart`); - } + constructor(mapper: WindowsKeyboardMapper, parts: SimpleKeybinding[]) { + super(OperatingSystem.Windows, parts); this._mapper = mapper; - this._firstPart = firstPart; - this._chordPart = chordPart; } - private _getUILabelForKeybinding(keybinding: SimpleKeybinding | null): string | null { - if (!keybinding) { - return null; - } + protected _getLabel(keybinding: SimpleKeybinding): string | null { if (keybinding.isDuplicateModifierCase()) { return ''; } return this._mapper.getUILabelForKeyCode(keybinding.keyCode); } - public getLabel(): string | null { - let firstPart = this._getUILabelForKeybinding(this._firstPart); - let chordPart = this._getUILabelForKeybinding(this._chordPart); - return UILabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, OperatingSystem.Windows); - } - - private _getUSLabelForKeybinding(keybinding: SimpleKeybinding | null): string | null { - if (!keybinding) { - return null; - } + private _getUSLabelForKeybinding(keybinding: SimpleKeybinding): string | null { if (keybinding.isDuplicateModifierCase()) { return ''; } @@ -119,27 +102,16 @@ export class WindowsNativeResolvedKeybinding extends ResolvedKeybinding { } public getUSLabel(): string | null { - let firstPart = this._getUSLabelForKeybinding(this._firstPart); - let chordPart = this._getUSLabelForKeybinding(this._chordPart); - return UILabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, OperatingSystem.Windows); + return UILabelProvider.toLabel(this._os, this._parts, (keybinding) => this._getUSLabelForKeybinding(keybinding)); } - private _getAriaLabelForKeybinding(keybinding: SimpleKeybinding | null): string | null { - if (!keybinding) { - return null; - } + protected _getAriaLabel(keybinding: SimpleKeybinding): string | null { if (keybinding.isDuplicateModifierCase()) { return ''; } return this._mapper.getAriaLabelForKeyCode(keybinding.keyCode); } - public getAriaLabel(): string | null { - let firstPart = this._getAriaLabelForKeybinding(this._firstPart); - let chordPart = this._getAriaLabelForKeybinding(this._chordPart); - return AriaLabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, OperatingSystem.Windows); - } - private _keyCodeToElectronAccelerator(keyCode: KeyCode): string | null { if (keyCode >= KeyCode.NUMPAD_0 && keyCode <= KeyCode.NUMPAD_DIVIDE) { // Electron cannot handle numpad keys @@ -161,54 +133,26 @@ export class WindowsNativeResolvedKeybinding extends ResolvedKeybinding { return KeyCodeUtils.toString(keyCode); } - private _getElectronAcceleratorLabelForKeybinding(keybinding: SimpleKeybinding | null): string | null { - if (!keybinding) { - return null; - } + protected _getElectronAccelerator(keybinding: SimpleKeybinding): string | null { if (keybinding.isDuplicateModifierCase()) { return null; } return this._keyCodeToElectronAccelerator(keybinding.keyCode); } - public getElectronAccelerator(): string | null { - if (this._chordPart !== null) { - // Electron cannot handle chords - return null; - } - - let firstPart = this._getElectronAcceleratorLabelForKeybinding(this._firstPart); - return ElectronAcceleratorLabelProvider.toLabel(this._firstPart, firstPart, null, null, OperatingSystem.Windows); - } - - private _getUserSettingsLabelForKeybinding(keybinding: SimpleKeybinding | null): string | null { - if (!keybinding) { - return null; - } + protected _getUserSettingsLabel(keybinding: SimpleKeybinding): string | null { if (keybinding.isDuplicateModifierCase()) { return ''; } - return this._mapper.getUserSettingsLabelForKeyCode(keybinding.keyCode); - } - - public getUserSettingsLabel(): string | null { - let firstPart = this._getUserSettingsLabelForKeybinding(this._firstPart); - let chordPart = this._getUserSettingsLabelForKeybinding(this._chordPart); - let result = UserSettingsLabelProvider.toLabel(this._firstPart, firstPart, this._chordPart, chordPart, OperatingSystem.Windows); + const result = this._mapper.getUserSettingsLabelForKeyCode(keybinding.keyCode); return (result ? result.toLowerCase() : result); } - public isWYSIWYG(): boolean { - if (this._firstPart && !this._isWYSIWYG(this._firstPart.keyCode)) { - return false; - } - if (this._chordPart && !this._isWYSIWYG(this._chordPart.keyCode)) { - return false; - } - return true; + protected _isWYSIWYG(keybinding: SimpleKeybinding): boolean { + return this.__isWYSIWYG(keybinding.keyCode); } - private _isWYSIWYG(keyCode: KeyCode): boolean { + private __isWYSIWYG(keyCode: KeyCode): boolean { if ( keyCode === KeyCode.LeftArrow || keyCode === KeyCode.UpArrow @@ -222,35 +166,7 @@ export class WindowsNativeResolvedKeybinding extends ResolvedKeybinding { return (ariaLabel === userSettingsLabel); } - public isChord(): boolean { - return (this._chordPart ? true : false); - } - - public getParts(): [ResolvedKeybindingPart, ResolvedKeybindingPart | null] { - return [ - this._toResolvedKeybindingPart(this._firstPart), - this._chordPart ? this._toResolvedKeybindingPart(this._chordPart) : null - ]; - } - - private _toResolvedKeybindingPart(keybinding: SimpleKeybinding): ResolvedKeybindingPart { - return new ResolvedKeybindingPart( - keybinding.ctrlKey, - keybinding.shiftKey, - keybinding.altKey, - keybinding.metaKey, - this._getUILabelForKeybinding(keybinding), - this._getAriaLabelForKeybinding(keybinding) - ); - } - - public getDispatchParts(): [string | null, string | null] { - let firstPart = this._firstPart ? this._getDispatchStr(this._firstPart) : null; - let chordPart = this._chordPart ? this._getDispatchStr(this._chordPart) : null; - return [firstPart, chordPart]; - } - - private _getDispatchStr(keybinding: SimpleKeybinding): string | null { + protected _getDispatchPart(keybinding: SimpleKeybinding): string | null { if (keybinding.isModifierKey()) { return null; } @@ -468,7 +384,7 @@ export class WindowsKeyboardMapper implements IKeyboardMapper { const scanCodeBinding = new ScanCodeBinding(ctrlKey, shiftKey, altKey, false, scanCode); const kb = this._resolveSimpleUserBinding(scanCodeBinding); const strKeyCode = (kb ? KeyCodeUtils.toString(kb.keyCode) : null); - const resolvedKb = (kb ? new WindowsNativeResolvedKeybinding(this, kb, null) : null); + const resolvedKb = (kb ? new WindowsNativeResolvedKeybinding(this, [kb]) : null); const outScanCode = `${ctrlKey ? 'Ctrl+' : ''}${shiftKey ? 'Shift+' : ''}${altKey ? 'Alt+' : ''}${strCode}`; const ariaLabel = (resolvedKb ? resolvedKb.getAriaLabel() : null); @@ -517,24 +433,19 @@ export class WindowsKeyboardMapper implements IKeyboardMapper { } public resolveKeybinding(keybinding: Keybinding): WindowsNativeResolvedKeybinding[] { - if (keybinding.type === KeybindingType.Chord) { - const firstPartKeyCode = keybinding.firstPart.keyCode; - const chordPartKeyCode = keybinding.chordPart.keyCode; - if (!this._keyCodeExists[firstPartKeyCode] || !this._keyCodeExists[chordPartKeyCode]) { + const parts = keybinding.parts; + for (let i = 0, len = parts.length; i < len; i++) { + const part = parts[i]; + if (!this._keyCodeExists[part.keyCode]) { return []; } - return [new WindowsNativeResolvedKeybinding(this, keybinding.firstPart, keybinding.chordPart)]; - } else { - if (!this._keyCodeExists[keybinding.keyCode]) { - return []; - } - return [new WindowsNativeResolvedKeybinding(this, keybinding, null)]; } + return [new WindowsNativeResolvedKeybinding(this, parts)]; } public resolveKeyboardEvent(keyboardEvent: IKeyboardEvent): WindowsNativeResolvedKeybinding { const keybinding = new SimpleKeybinding(keyboardEvent.ctrlKey, keyboardEvent.shiftKey, keyboardEvent.altKey, keyboardEvent.metaKey, keyboardEvent.keyCode); - return new WindowsNativeResolvedKeybinding(this, keybinding, null); + return new WindowsNativeResolvedKeybinding(this, [keybinding]); } private _resolveSimpleUserBinding(binding: SimpleKeybinding | ScanCodeBinding | null): SimpleKeybinding | null { @@ -554,14 +465,10 @@ export class WindowsKeyboardMapper implements IKeyboardMapper { return new SimpleKeybinding(binding.ctrlKey, binding.shiftKey, binding.altKey, binding.metaKey, keyCode); } - public resolveUserBinding(firstPart: SimpleKeybinding | ScanCodeBinding | null, chordPart: SimpleKeybinding | ScanCodeBinding | null): ResolvedKeybinding[] { - const _firstPart = this._resolveSimpleUserBinding(firstPart); - const _chordPart = this._resolveSimpleUserBinding(chordPart); - if (_firstPart && _chordPart) { - return [new WindowsNativeResolvedKeybinding(this, _firstPart, _chordPart)]; - } - if (_firstPart) { - return [new WindowsNativeResolvedKeybinding(this, _firstPart, null)]; + public resolveUserBinding(input: (SimpleKeybinding | ScanCodeBinding)[]): ResolvedKeybinding[] { + const parts: SimpleKeybinding[] = removeElementsAfterNulls(input.map(keybinding => this._resolveSimpleUserBinding(keybinding))); + if (parts.length > 0) { + return [new WindowsNativeResolvedKeybinding(this, parts)]; } return []; } diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts index c923d274e8..517743fb3c 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts @@ -15,14 +15,14 @@ import { Keybinding, ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; import { OS, OperatingSystem } from 'vs/base/common/platform'; import { ConfigWatcher } from 'vs/base/node/config'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { Extensions as ConfigExtensions, IConfigurationNode, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry'; import { AbstractKeybindingService } from 'vs/platform/keybinding/common/abstractKeybindingService'; -import { IKeybindingEvent, IKeyboardEvent, IUserFriendlyKeybinding, KeybindingSource } from 'vs/platform/keybinding/common/keybinding'; +import { IKeybindingEvent, IKeyboardEvent, IUserFriendlyKeybinding, KeybindingSource, IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; import { IKeybindingItem, IKeybindingRule2, KeybindingWeight, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; @@ -38,6 +38,9 @@ import { MacLinuxFallbackKeyboardMapper } from 'vs/workbench/services/keybinding import { IMacLinuxKeyboardMapping, MacLinuxKeyboardMapper, macLinuxKeyboardMappingEquals } from 'vs/workbench/services/keybinding/common/macLinuxKeyboardMapper'; import { IWindowsKeyboardMapping, WindowsKeyboardMapper, windowsKeyboardMappingEquals } from 'vs/workbench/services/keybinding/common/windowsKeyboardMapper'; import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { MenuRegistry } from 'vs/platform/actions/common/actions'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class KeyboardMapperFactory { public static readonly INSTANCE = new KeyboardMapperFactory(); @@ -235,7 +238,6 @@ let keybindingType: IJSONSchema = { }; const keybindingsExtPoint = ExtensionsRegistry.registerExtensionPoint({ - isDynamic: true, extensionPoint: 'keybindings', jsonSchema: { description: nls.localize('vscode.extension.contributes.keybindings', "Contributes keybindings."), @@ -268,7 +270,6 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { private userKeybindings: ConfigWatcher; constructor( - windowElement: Window, @IContextKeyService contextKeyService: IContextKeyService, @ICommandService commandService: ICommandService, @ITelemetryService telemetryService: ITelemetryService, @@ -276,10 +277,13 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { @IEnvironmentService environmentService: IEnvironmentService, @IStatusbarService statusBarService: IStatusbarService, @IConfigurationService configurationService: IConfigurationService, - @IWindowService private readonly windowService: IWindowService + @IWindowService private readonly windowService: IWindowService, + @IExtensionService extensionService: IExtensionService ) { super(contextKeyService, commandService, telemetryService, notificationService, statusBarService); + updateSchema(); + let dispatchConfig = getDispatchConfig(configurationService); configurationService.onDidChangeConfiguration((e) => { let newDispatchConfig = getDispatchConfig(configurationService); @@ -314,12 +318,15 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { this.updateResolver({ source: KeybindingSource.Default }); }); + updateSchema(); + this._register(extensionService.onDidRegisterExtensions(() => updateSchema())); + this._register(this.userKeybindings.onDidUpdateConfiguration(event => this.updateResolver({ source: KeybindingSource.User, keybindings: event.config }))); - this._register(dom.addDisposableListener(windowElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { + this._register(dom.addDisposableListener(window, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { let keyEvent = new StandardKeyboardEvent(e); let shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target); if (shouldPreventDefault) { @@ -339,7 +346,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { }); } - public dumpDebugInfo(): string { + public _dumpDebugInfo(): string { const layoutInfo = JSON.stringify(KeyboardMapperFactory.INSTANCE.getCurrentKeyboardLayout(), null, '\t'); const mapperInfo = this._keyboardMapper.dumpDebugInfo(); const rawMapping = JSON.stringify(KeyboardMapperFactory.INSTANCE.getRawKeyboardMapping(), null, '\t'); @@ -405,13 +412,12 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { let result: ResolvedKeybindingItem[] = [], resultLen = 0; for (const item of items) { const when = (item.when ? item.when.normalize() : null); - const firstPart = item.firstPart; - const chordPart = item.chordPart; - if (!firstPart) { + const parts = item.parts; + if (parts.length === 0) { // This might be a removal keybinding item in user settings => accept it result[resultLen++] = new ResolvedKeybindingItem(null, item.command, item.commandArgs, when, isDefault); } else { - const resolvedKeybindings = this._keyboardMapper.resolveUserBinding(firstPart, chordPart); + const resolvedKeybindings = this._keyboardMapper.resolveUserBinding(parts); for (const resolvedKeybinding of resolvedKeybindings) { result[resultLen++] = new ResolvedKeybindingItem(resolvedKeybinding, item.command, item.commandArgs, when, isDefault); } @@ -436,7 +442,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { }); } - return extraUserKeybindings.map((k) => KeybindingIO.readUserKeybindingItem(k, OS)); + return extraUserKeybindings.map((k) => KeybindingIO.readUserKeybindingItem(k)); } public resolveKeybinding(kb: Keybinding): ResolvedKeybinding[] { @@ -448,8 +454,8 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { } public resolveUserBinding(userBinding: string): ResolvedKeybinding[] { - const [firstPart, chordPart] = KeybindingParser.parseUserBinding(userBinding); - return this._keyboardMapper.resolveUserBinding(firstPart, chordPart); + const parts = KeybindingParser.parseUserBinding(userBinding); + return this._keyboardMapper.resolveUserBinding(parts); } private _handleKeybindingsExtensionPointUser(isBuiltin: boolean, keybindings: ContributedKeyBinding | ContributedKeyBinding[], collector: ExtensionMessageCollector, result: IKeybindingRule2[]): void { @@ -529,7 +535,7 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { let lastIndex = defaultKeybindings.length - 1; defaultKeybindings.forEach((k, index) => { - KeybindingIO.writeKeybindingItem(out, k, OS); + KeybindingIO.writeKeybindingItem(out, k); if (index !== lastIndex) { out.writeLine(','); } else { @@ -569,10 +575,31 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { } let schemaId = 'vscode://schemas/keybindings'; +let commandsSchemas: IJSONSchema[] = []; +let commandsEnum: string[] = []; +let commandsEnumDescriptions: (string | null | undefined)[] = []; let schema: IJSONSchema = { 'id': schemaId, 'type': 'array', 'title': nls.localize('keybindings.json.title', "Keybindings configuration"), + 'definitions': { + 'editorGroupsSchema': { + 'type': 'array', + 'items': { + 'type': 'object', + 'properties': { + 'groups': { + '$ref': '#/definitions/editorGroupsSchema', + 'default': [{}, {}] + }, + 'size': { + 'type': 'number', + 'default': 0.5 + } + } + } + } + }, 'items': { 'required': ['key'], 'type': 'object', @@ -584,6 +611,8 @@ let schema: IJSONSchema = { }, 'command': { 'type': 'string', + 'enum': commandsEnum, + 'enumDescriptions': commandsEnumDescriptions, 'description': nls.localize('keybindings.json.command', "Name of the command to execute"), }, 'when': { @@ -593,13 +622,71 @@ let schema: IJSONSchema = { 'args': { 'description': nls.localize('keybindings.json.args', "Arguments to pass to the command to execute.") } - } + }, + 'allOf': commandsSchemas } }; let schemaRegistry = Registry.as(Extensions.JSONContribution); schemaRegistry.registerSchema(schemaId, schema); +function updateSchema() { + commandsSchemas.length = 0; + commandsEnum.length = 0; + commandsEnumDescriptions.length = 0; + + const knownCommands = new Set(); + const addKnownCommand = (commandId: string, description?: string | null) => { + if (!/^_/.test(commandId)) { + if (!knownCommands.has(commandId)) { + knownCommands.add(commandId); + + commandsEnum.push(commandId); + commandsEnumDescriptions.push(description); + + // Also add the negative form for keybinding removal + commandsEnum.push(`-${commandId}`); + commandsEnumDescriptions.push(description); + } + } + }; + + const allCommands = CommandsRegistry.getCommands(); + for (let commandId in allCommands) { + const commandDescription = allCommands[commandId].description; + + addKnownCommand(commandId, commandDescription && commandDescription.description); + + if (!commandDescription || !commandDescription.args || commandDescription.args.length !== 1 || !commandDescription.args[0].schema) { + continue; + } + + const argsSchema = commandDescription.args[0].schema; + const argsRequired = Array.isArray(argsSchema.required) && argsSchema.required.length > 0; + const addition = { + 'if': { + 'properties': { + 'command': { 'const': commandId } + } + }, + 'then': { + 'required': ([]).concat(argsRequired ? ['args'] : []), + 'properties': { + 'args': argsSchema + } + } + }; + + commandsSchemas.push(addition); + } + + const menuCommands = MenuRegistry.getCommands(); + for (let commandId in menuCommands) { + addKnownCommand(commandId); + } + +} + const configurationRegistry = Registry.as(ConfigExtensions.Configuration); const keyboardConfiguration: IConfigurationNode = { 'id': 'keyboard', @@ -625,3 +712,5 @@ const keyboardConfiguration: IConfigurationNode = { }; configurationRegistry.registerConfiguration(keyboardConfiguration); + +registerSingleton(IKeybindingService, WorkbenchKeybindingService); \ No newline at end of file diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts index da6fa25d65..a97f2ab9c0 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keybindingEditing.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as fs from 'fs'; import * as os from 'os'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as json from 'vs/base/common/json'; import { ChordKeybinding, KeyCode, SimpleKeybinding } from 'vs/base/common/keyCodes'; import { OS } from 'vs/base/common/platform'; @@ -38,14 +38,13 @@ import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtil import { IWorkspaceContextService, Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { FileService } from 'vs/workbench/services/files/electron-browser/fileService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; -import { IHashService } from 'vs/workbench/services/hash/common/hashService'; +import { FileService } from 'vs/workbench/services/files/node/fileService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { TestBackupFileService, TestContextService, TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestHashService, TestLifecycleService, TestLogService, TestStorageService, TestTextFileService, TestTextResourceConfigurationService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; +import { TestBackupFileService, TestContextService, TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestLifecycleService, TestLogService, TestStorageService, TestTextFileService, TestTextResourceConfigurationService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; interface Modifiers { metaKey?: boolean; @@ -76,7 +75,6 @@ suite('KeybindingsEditing', () => { const lifecycleService = new TestLifecycleService(); instantiationService.stub(ILifecycleService, lifecycleService); instantiationService.stub(IContextKeyService, instantiationService.createInstance(MockContextKeyService)); - instantiationService.stub(IHashService, new TestHashService()); instantiationService.stub(IEditorGroupsService, new TestEditorGroupsService()); instantiationService.stub(IEditorService, new TestEditorService()); instantiationService.stub(ITelemetryService, NullTelemetryService); @@ -120,28 +118,28 @@ suite('KeybindingsEditing', () => { test('errors cases - parse errors', () => { fs.writeFileSync(keybindingsFile, ',,,,,,,,,,,,,,'); - return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } })) + return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined) .then(() => assert.fail('Should fail with parse errors'), error => assert.equal(error.message, 'Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again.')); }); test('errors cases - parse errors 2', () => { fs.writeFileSync(keybindingsFile, '[{"key": }]'); - return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } })) + return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined) .then(() => assert.fail('Should fail with parse errors'), error => assert.equal(error.message, 'Unable to write to the keybindings configuration file. Please open it to correct errors/warnings in the file and try again.')); }); test('errors cases - dirty', () => { instantiationService.stub(ITextFileService, 'isDirty', true); - return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } })) + return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined) .then(() => assert.fail('Should fail with dirty error'), error => assert.equal(error.message, 'Unable to write because the keybindings configuration file is dirty. Please save it first and then try again.')); }); test('errors cases - did not find an array', () => { fs.writeFileSync(keybindingsFile, '{"key": "alt+c", "command": "hello"}'); - return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } })) + return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape } }), 'alt+c', undefined) .then(() => assert.fail('Should fail with dirty error'), error => assert.equal(error.message, 'Unable to write to the keybindings configuration file. It has an object which is not of type Array. Please open the file to clean up and try again.')); }); @@ -149,7 +147,7 @@ suite('KeybindingsEditing', () => { test('edit a default keybinding to an empty file', () => { fs.writeFileSync(keybindingsFile, ''); const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }]; - return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' })) + return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined) .then(() => assert.deepEqual(getUserKeybindings(), expected)); }); @@ -159,41 +157,41 @@ suite('KeybindingsEditing', () => { testObject = instantiationService.createInstance(KeybindingsEditingService); const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }]; - return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' })) + return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined) .then(() => assert.deepEqual(getUserKeybindings(), expected)); }); test('edit a default keybinding to an empty array', () => { writeToKeybindingsFile(); const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }]; - return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' })) + return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined) .then(() => assert.deepEqual(getUserKeybindings(), expected)); }); test('edit a default keybinding in an existing array', () => { writeToKeybindingsFile({ command: 'b', key: 'shift+c' }); const expected: IUserFriendlyKeybinding[] = [{ key: 'shift+c', command: 'b' }, { key: 'alt+c', command: 'a' }, { key: 'escape', command: '-a' }]; - return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' })) + return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'a' }), 'alt+c', undefined) .then(() => assert.deepEqual(getUserKeybindings(), expected)); }); test('add a new default keybinding', () => { const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'a' }]; - return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ command: 'a' })) + return testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a' }), 'alt+c', undefined) .then(() => assert.deepEqual(getUserKeybindings(), expected)); }); test('edit an user keybinding', () => { writeToKeybindingsFile({ key: 'escape', command: 'b' }); const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'b' }]; - return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'b', isDefault: false })) + return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'b', isDefault: false }), 'alt+c', undefined) .then(() => assert.deepEqual(getUserKeybindings(), expected)); }); test('edit an user keybinding with more than one element', () => { writeToKeybindingsFile({ key: 'escape', command: 'b' }, { key: 'alt+shift+g', command: 'c' }); const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: 'b' }, { key: 'alt+shift+g', command: 'c' }]; - return testObject.editKeybinding('alt+c', aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'b', isDefault: false })) + return testObject.editKeybinding(aResolvedKeybindingItem({ firstPart: { keyCode: KeyCode.Escape }, command: 'b', isDefault: false }), 'alt+c', undefined) .then(() => assert.deepEqual(getUserKeybindings(), expected)); }); @@ -232,7 +230,35 @@ suite('KeybindingsEditing', () => { test('add a new keybinding to unassigned keybinding', () => { writeToKeybindingsFile({ key: 'alt+c', command: '-a' }); const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }, { key: 'shift+alt+c', command: 'a' }]; - return testObject.editKeybinding('shift+alt+c', aResolvedKeybindingItem({ command: 'a', isDefault: false })) + return testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', undefined) + .then(() => assert.deepEqual(getUserKeybindings(), expected)); + }); + + test('add when expression', () => { + writeToKeybindingsFile({ key: 'alt+c', command: '-a' }); + const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus' }]; + return testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', 'editorTextFocus') + .then(() => assert.deepEqual(getUserKeybindings(), expected)); + }); + + test('update command and when expression', () => { + writeToKeybindingsFile({ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }); + const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus' }]; + return testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', 'editorTextFocus') + .then(() => assert.deepEqual(getUserKeybindings(), expected)); + }); + + test('update when expression', () => { + writeToKeybindingsFile({ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus && !editorReadonly' }); + const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a', when: 'editorTextFocus' }]; + return testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false, when: 'editorTextFocus && !editorReadonly' }), 'shift+alt+c', 'editorTextFocus') + .then(() => assert.deepEqual(getUserKeybindings(), expected)); + }); + + test('remove when expression', () => { + writeToKeybindingsFile({ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }); + const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a', when: 'editorTextFocus && !editorReadonly' }, { key: 'shift+alt+c', command: 'a' }]; + return testObject.editKeybinding(aResolvedKeybindingItem({ command: 'a', isDefault: false }), 'shift+alt+c', undefined) .then(() => assert.deepEqual(getUserKeybindings(), expected)); }); @@ -249,8 +275,15 @@ suite('KeybindingsEditing', () => { const { ctrlKey, shiftKey, altKey, metaKey } = part.modifiers || { ctrlKey: false, shiftKey: false, altKey: false, metaKey: false }; return new SimpleKeybinding(ctrlKey!, shiftKey!, altKey!, metaKey!, part.keyCode); }; - const keybinding = firstPart ? chordPart ? new ChordKeybinding(aSimpleKeybinding(firstPart), aSimpleKeybinding(chordPart)) : aSimpleKeybinding(firstPart) : null; - return new ResolvedKeybindingItem(keybinding ? new USLayoutResolvedKeybinding(keybinding, OS) : null, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : null, isDefault === undefined ? true : isDefault); + let parts: SimpleKeybinding[] = []; + if (firstPart) { + parts.push(aSimpleKeybinding(firstPart)); + if (chordPart) { + parts.push(aSimpleKeybinding(chordPart)); + } + } + let keybinding = parts.length > 0 ? new USLayoutResolvedKeybinding(new ChordKeybinding(parts), OS) : null; + return new ResolvedKeybindingItem(keybinding, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : null, isDefault === undefined ? true : isDefault); } }); diff --git a/src/vs/workbench/services/keybinding/test/keybindingIO.test.ts b/src/vs/workbench/services/keybinding/test/keybindingIO.test.ts index 6cd97b29b6..5646deba2e 100644 --- a/src/vs/workbench/services/keybinding/test/keybindingIO.test.ts +++ b/src/vs/workbench/services/keybinding/test/keybindingIO.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { KeyChord, KeyCode, KeyMod, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; import { KeybindingParser } from 'vs/base/common/keybindingParser'; -import { OS, OperatingSystem } from 'vs/base/common/platform'; +import { OperatingSystem } from 'vs/base/common/platform'; import { ScanCode, ScanCodeBinding } from 'vs/base/common/scanCode'; import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { USLayoutResolvedKeybinding } from 'vs/platform/keybinding/common/usLayoutResolvedKeybinding'; @@ -126,37 +126,35 @@ suite('keybindingIO', () => { test('issue #10452 - invalid command', () => { let strJSON = `[{ "key": "ctrl+k ctrl+f", "command": ["firstcommand", "seccondcommand"] }]`; let userKeybinding = JSON.parse(strJSON)[0]; - let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding, OS); + let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding); assert.equal(keybindingItem.command, null); }); test('issue #10452 - invalid when', () => { let strJSON = `[{ "key": "ctrl+k ctrl+f", "command": "firstcommand", "when": [] }]`; let userKeybinding = JSON.parse(strJSON)[0]; - let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding, OS); + let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding); assert.equal(keybindingItem.when, null); }); test('issue #10452 - invalid key', () => { let strJSON = `[{ "key": [], "command": "firstcommand" }]`; let userKeybinding = JSON.parse(strJSON)[0]; - let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding, OS); - assert.equal(keybindingItem.firstPart, null); - assert.equal(keybindingItem.chordPart, null); + let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding); + assert.deepEqual(keybindingItem.parts, []); }); test('issue #10452 - invalid key 2', () => { let strJSON = `[{ "key": "", "command": "firstcommand" }]`; let userKeybinding = JSON.parse(strJSON)[0]; - let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding, OS); - assert.equal(keybindingItem.firstPart, null); - assert.equal(keybindingItem.chordPart, null); + let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding); + assert.deepEqual(keybindingItem.parts, []); }); test('test commands args', () => { let strJSON = `[{ "key": "ctrl+k ctrl+f", "command": "firstcommand", "when": [], "args": { "text": "theText" } }]`; let userKeybinding = JSON.parse(strJSON)[0]; - let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding, OS); + let keybindingItem = KeybindingIO.readUserKeybindingItem(userKeybinding); assert.equal(keybindingItem.commandArgs.text, 'theText'); }); }); diff --git a/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts b/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts index f8681fac92..0170013ac7 100644 --- a/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts +++ b/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { Keybinding, ResolvedKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes'; import { ScanCodeBinding } from 'vs/base/common/scanCode'; @@ -19,7 +19,7 @@ export interface IResolvedKeybinding { userSettingsLabel: string | null; isWYSIWYG: boolean; isChord: boolean; - dispatchParts: [string | null, string | null]; + dispatchParts: (string | null)[]; } function toIResolvedKeybinding(kb: ResolvedKeybinding): IResolvedKeybinding { @@ -44,8 +44,8 @@ export function assertResolveKeyboardEvent(mapper: IKeyboardMapper, keyboardEven assert.deepEqual(actual, expected); } -export function assertResolveUserBinding(mapper: IKeyboardMapper, firstPart: SimpleKeybinding | ScanCodeBinding, chordPart: SimpleKeybinding | ScanCodeBinding | null, expected: IResolvedKeybinding[]): void { - let actual: IResolvedKeybinding[] = mapper.resolveUserBinding(firstPart, chordPart).map(toIResolvedKeybinding); +export function assertResolveUserBinding(mapper: IKeyboardMapper, parts: (SimpleKeybinding | ScanCodeBinding)[], expected: IResolvedKeybinding[]): void { + let actual: IResolvedKeybinding[] = mapper.resolveUserBinding(parts).map(toIResolvedKeybinding); assert.deepEqual(actual, expected); } diff --git a/src/vs/workbench/services/keybinding/test/macLinuxFallbackKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/macLinuxFallbackKeyboardMapper.test.ts index b1cf078924..eab15ef178 100644 --- a/src/vs/workbench/services/keybinding/test/macLinuxFallbackKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/macLinuxFallbackKeyboardMapper.test.ts @@ -27,7 +27,7 @@ suite('keyboardMapper - MAC fallback', () => { userSettingsLabel: 'cmd+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+Z', null], + dispatchParts: ['meta+Z'], }] ); }); @@ -51,6 +51,7 @@ suite('keyboardMapper - MAC fallback', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: false, shiftKey: false, altKey: false, @@ -65,16 +66,21 @@ suite('keyboardMapper - MAC fallback', () => { userSettingsLabel: 'cmd+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+Z', null], + dispatchParts: ['meta+Z'], } ); }); + test('resolveUserBinding empty', () => { + assertResolveUserBinding(mapper, [], []); + }); + test('resolveUserBinding Cmd+[Comma] Cmd+/', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(false, false, false, true, ScanCode.Comma), - new SimpleKeybinding(false, false, false, true, KeyCode.US_SLASH), + mapper, [ + new ScanCodeBinding(false, false, false, true, ScanCode.Comma), + new SimpleKeybinding(false, false, false, true, KeyCode.US_SLASH), + ], [{ label: '⌘, ⌘/', ariaLabel: 'Command+, Command+/', @@ -91,6 +97,7 @@ suite('keyboardMapper - MAC fallback', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: false, shiftKey: false, altKey: false, @@ -105,7 +112,7 @@ suite('keyboardMapper - MAC fallback', () => { userSettingsLabel: 'cmd+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -129,7 +136,7 @@ suite('keyboardMapper - LINUX fallback', () => { userSettingsLabel: 'ctrl+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+Z', null], + dispatchParts: ['ctrl+Z'], }] ); }); @@ -153,6 +160,7 @@ suite('keyboardMapper - LINUX fallback', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -167,16 +175,17 @@ suite('keyboardMapper - LINUX fallback', () => { userSettingsLabel: 'ctrl+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+Z', null], + dispatchParts: ['ctrl+Z'], } ); }); test('resolveUserBinding Ctrl+[Comma] Ctrl+/', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma), + new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + ], [{ label: 'Ctrl+, Ctrl+/', ariaLabel: 'Control+, Control+/', @@ -191,9 +200,9 @@ suite('keyboardMapper - LINUX fallback', () => { test('resolveUserBinding Ctrl+[Comma]', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - null, + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma), + ], [{ label: 'Ctrl+,', ariaLabel: 'Control+,', @@ -201,7 +210,7 @@ suite('keyboardMapper - LINUX fallback', () => { userSettingsLabel: 'ctrl+,', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+,', null], + dispatchParts: ['ctrl+,'], }] ); }); @@ -210,6 +219,7 @@ suite('keyboardMapper - LINUX fallback', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -224,7 +234,7 @@ suite('keyboardMapper - LINUX fallback', () => { userSettingsLabel: 'ctrl+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); diff --git a/src/vs/workbench/services/keybinding/test/macLinuxKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/macLinuxKeyboardMapper.test.ts index a84ea2a17c..ff738d1053 100644 --- a/src/vs/workbench/services/keybinding/test/macLinuxKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/macLinuxKeyboardMapper.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { KeyChord, KeyCode, KeyMod, SimpleKeybinding, createKeybinding } from 'vs/base/common/keyCodes'; +import { KeyChord, KeyCode, KeyMod, SimpleKeybinding, createKeybinding, createSimpleKeybinding } from 'vs/base/common/keyCodes'; import { UserSettingsLabelProvider } from 'vs/base/common/keybindingLabels'; import { OperatingSystem } from 'vs/base/common/platform'; import { ScanCode, ScanCodeBinding, ScanCodeUtils } from 'vs/base/common/scanCode'; @@ -65,7 +65,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+a', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+[KeyA]', null], + dispatchParts: ['meta+[KeyA]'], }] ); }); @@ -80,7 +80,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+b', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+[KeyB]', null], + dispatchParts: ['meta+[KeyB]'], }] ); }); @@ -95,7 +95,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+[KeyY]', null], + dispatchParts: ['meta+[KeyY]'], }] ); }); @@ -104,6 +104,7 @@ suite('keyboardMapper - MAC de_ch', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: false, shiftKey: false, altKey: false, @@ -118,7 +119,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+[KeyY]', null], + dispatchParts: ['meta+[KeyY]'], } ); }); @@ -133,7 +134,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'ctrl+alt+cmd+6', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+alt+meta+[Digit6]', null], + dispatchParts: ['ctrl+alt+meta+[Digit6]'], }] ); }); @@ -142,6 +143,7 @@ suite('keyboardMapper - MAC de_ch', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: false, shiftKey: false, altKey: false, @@ -156,7 +158,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+[BracketRight]', isWYSIWYG: false, isChord: false, - dispatchParts: ['meta+[BracketRight]', null], + dispatchParts: ['meta+[BracketRight]'], } ); }); @@ -171,7 +173,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'ctrl+alt+9', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+alt+[Digit9]', null], + dispatchParts: ['ctrl+alt+[Digit9]'], }] ); }); @@ -186,7 +188,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'shift+cmd+7', isWYSIWYG: true, isChord: false, - dispatchParts: ['shift+meta+[Digit7]', null], + dispatchParts: ['shift+meta+[Digit7]'], }] ); }); @@ -201,7 +203,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'shift+cmd+[Minus]', isWYSIWYG: false, isChord: false, - dispatchParts: ['shift+meta+[Minus]', null], + dispatchParts: ['shift+meta+[Minus]'], }] ); }); @@ -246,7 +248,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+down', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+[ArrowDown]', null], + dispatchParts: ['meta+[ArrowDown]'], }] ); }); @@ -261,7 +263,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+numpad0', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+[Numpad0]', null], + dispatchParts: ['meta+[Numpad0]'], }] ); }); @@ -276,7 +278,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+[Home]', null], + dispatchParts: ['meta+[Home]'], }] ); }); @@ -285,6 +287,7 @@ suite('keyboardMapper - MAC de_ch', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: false, shiftKey: false, altKey: false, @@ -299,16 +302,22 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+[Home]', null], + dispatchParts: ['meta+[Home]'], } ); }); + test('resolveUserBinding empty', () => { + assertResolveUserBinding(mapper, [], []); + }); + test('resolveUserBinding Cmd+[Comma] Cmd+/', () => { assertResolveUserBinding( mapper, - new ScanCodeBinding(false, false, false, true, ScanCode.Comma), - new SimpleKeybinding(false, false, false, true, KeyCode.US_SLASH), + [ + new ScanCodeBinding(false, false, false, true, ScanCode.Comma), + new SimpleKeybinding(false, false, false, true, KeyCode.US_SLASH), + ], [{ label: '⌘, ⇧⌘7', ariaLabel: 'Command+, Shift+Command+7', @@ -325,6 +334,7 @@ suite('keyboardMapper - MAC de_ch', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: false, shiftKey: false, altKey: false, @@ -339,7 +349,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -348,6 +358,7 @@ suite('keyboardMapper - MAC de_ch', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: false, shiftKey: false, altKey: false, @@ -362,7 +373,7 @@ suite('keyboardMapper - MAC de_ch', () => { userSettingsLabel: 'cmd+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -384,8 +395,10 @@ suite('keyboardMapper - MAC en_us', () => { test('resolveUserBinding Cmd+[Comma] Cmd+/', () => { assertResolveUserBinding( mapper, - new ScanCodeBinding(false, false, false, true, ScanCode.Comma), - new SimpleKeybinding(false, false, false, true, KeyCode.US_SLASH), + [ + new ScanCodeBinding(false, false, false, true, ScanCode.Comma), + new SimpleKeybinding(false, false, false, true, KeyCode.US_SLASH), + ], [{ label: '⌘, ⌘/', ariaLabel: 'Command+, Command+/', @@ -402,6 +415,7 @@ suite('keyboardMapper - MAC en_us', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: false, shiftKey: false, altKey: false, @@ -416,7 +430,7 @@ suite('keyboardMapper - MAC en_us', () => { userSettingsLabel: 'cmd+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -425,6 +439,7 @@ suite('keyboardMapper - MAC en_us', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: false, shiftKey: false, altKey: false, @@ -439,7 +454,7 @@ suite('keyboardMapper - MAC en_us', () => { userSettingsLabel: 'cmd+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -491,7 +506,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+a', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyA]', null], + dispatchParts: ['ctrl+[KeyA]'], }] ); }); @@ -506,7 +521,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyY]', null], + dispatchParts: ['ctrl+[KeyY]'], }] ); }); @@ -515,6 +530,7 @@ suite('keyboardMapper - LINUX de_ch', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -529,7 +545,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyY]', null], + dispatchParts: ['ctrl+[KeyY]'], } ); }); @@ -545,6 +561,7 @@ suite('keyboardMapper - LINUX de_ch', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -559,7 +576,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+[BracketRight]', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+[BracketRight]', null], + dispatchParts: ['ctrl+[BracketRight]'], } ); }); @@ -574,7 +591,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+alt+0', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+alt+[Digit0]', null], + dispatchParts: ['ctrl+alt+[Digit0]'], }, { label: 'Ctrl+Alt+$', ariaLabel: 'Control+Alt+$', @@ -582,7 +599,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+alt+[Backslash]', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+alt+[Backslash]', null], + dispatchParts: ['ctrl+alt+[Backslash]'], }] ); }); @@ -597,7 +614,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+shift+7', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+shift+[Digit7]', null], + dispatchParts: ['ctrl+shift+[Digit7]'], }] ); }); @@ -612,7 +629,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+shift+[Minus]', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+shift+[Minus]', null], + dispatchParts: ['ctrl+shift+[Minus]'], }] ); }); @@ -649,7 +666,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+down', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[ArrowDown]', null], + dispatchParts: ['ctrl+[ArrowDown]'], }] ); }); @@ -664,7 +681,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+numpad0', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Numpad0]', null], + dispatchParts: ['ctrl+[Numpad0]'], }] ); }); @@ -679,7 +696,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Home]', null], + dispatchParts: ['ctrl+[Home]'], }] ); }); @@ -688,6 +705,7 @@ suite('keyboardMapper - LINUX de_ch', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -702,7 +720,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Home]', null], + dispatchParts: ['ctrl+[Home]'], } ); }); @@ -711,6 +729,7 @@ suite('keyboardMapper - LINUX de_ch', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -725,16 +744,17 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+x', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyX]', null], + dispatchParts: ['ctrl+[KeyX]'], } ); }); test('resolveUserBinding Ctrl+[Comma] Ctrl+/', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma), + new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + ], [{ label: 'Ctrl+, Ctrl+Shift+7', ariaLabel: 'Control+, Control+Shift+7', @@ -751,6 +771,7 @@ suite('keyboardMapper - LINUX de_ch', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -765,7 +786,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -774,6 +795,7 @@ suite('keyboardMapper - LINUX de_ch', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -788,7 +810,7 @@ suite('keyboardMapper - LINUX de_ch', () => { userSettingsLabel: 'ctrl+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -821,7 +843,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+a', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyA]', null], + dispatchParts: ['ctrl+[KeyA]'], }] ); }); @@ -836,7 +858,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyZ]', null], + dispatchParts: ['ctrl+[KeyZ]'], }] ); }); @@ -845,6 +867,7 @@ suite('keyboardMapper - LINUX en_us', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -859,7 +882,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyZ]', null], + dispatchParts: ['ctrl+[KeyZ]'], } ); }); @@ -874,7 +897,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+]', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[BracketRight]', null], + dispatchParts: ['ctrl+[BracketRight]'], }] ); }); @@ -883,6 +906,7 @@ suite('keyboardMapper - LINUX en_us', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -897,7 +921,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+]', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[BracketRight]', null], + dispatchParts: ['ctrl+[BracketRight]'], } ); }); @@ -912,7 +936,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'shift+]', isWYSIWYG: true, isChord: false, - dispatchParts: ['shift+[BracketRight]', null], + dispatchParts: ['shift+[BracketRight]'], }] ); }); @@ -927,7 +951,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+/', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Slash]', null], + dispatchParts: ['ctrl+[Slash]'], }] ); }); @@ -942,7 +966,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+shift+/', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+shift+[Slash]', null], + dispatchParts: ['ctrl+shift+[Slash]'], }] ); }); @@ -987,7 +1011,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+down', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[ArrowDown]', null], + dispatchParts: ['ctrl+[ArrowDown]'], }] ); }); @@ -1002,7 +1026,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+numpad0', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Numpad0]', null], + dispatchParts: ['ctrl+[Numpad0]'], }] ); }); @@ -1017,7 +1041,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Home]', null], + dispatchParts: ['ctrl+[Home]'], }] ); }); @@ -1026,6 +1050,7 @@ suite('keyboardMapper - LINUX en_us', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -1040,7 +1065,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Home]', null], + dispatchParts: ['ctrl+[Home]'], } ); }); @@ -1055,7 +1080,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+shift+,', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+shift+[Comma]', null], + dispatchParts: ['ctrl+shift+[Comma]'], }, { label: 'Ctrl+<', ariaLabel: 'Control+<', @@ -1063,7 +1088,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+[IntlBackslash]', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+[IntlBackslash]', null], + dispatchParts: ['ctrl+[IntlBackslash]'], }] ); }); @@ -1078,7 +1103,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+enter', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Enter]', null], + dispatchParts: ['ctrl+[Enter]'], }] ); }); @@ -1087,6 +1112,7 @@ suite('keyboardMapper - LINUX en_us', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -1101,16 +1127,17 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+enter', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Enter]', null], + dispatchParts: ['ctrl+[Enter]'], } ); }); test('resolveUserBinding Ctrl+[Comma] Ctrl+/', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma), + new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + ], [{ label: 'Ctrl+, Ctrl+/', ariaLabel: 'Control+, Control+/', @@ -1125,9 +1152,9 @@ suite('keyboardMapper - LINUX en_us', () => { test('resolveUserBinding Ctrl+[Comma]', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - null, + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma) + ], [{ label: 'Ctrl+,', ariaLabel: 'Control+,', @@ -1135,7 +1162,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+,', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Comma]', null], + dispatchParts: ['ctrl+[Comma]'], }] ); }); @@ -1144,6 +1171,7 @@ suite('keyboardMapper - LINUX en_us', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -1158,7 +1186,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -1167,6 +1195,7 @@ suite('keyboardMapper - LINUX en_us', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -1181,7 +1210,7 @@ suite('keyboardMapper - LINUX en_us', () => { userSettingsLabel: 'ctrl+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -1202,6 +1231,7 @@ suite('keyboardMapper', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -1216,7 +1246,7 @@ suite('keyboardMapper', () => { userSettingsLabel: 'ctrl+`', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[Backquote]', null], + dispatchParts: ['ctrl+[Backquote]'], } ); }); @@ -1228,6 +1258,7 @@ suite('keyboardMapper', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: false, shiftKey: false, altKey: false, @@ -1242,7 +1273,7 @@ suite('keyboardMapper', () => { userSettingsLabel: userSettingsLabel, isWYSIWYG: true, isChord: false, - dispatchParts: [dispatch, null], + dispatchParts: [dispatch], } ); } @@ -1267,6 +1298,7 @@ suite('keyboardMapper', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: false, shiftKey: false, altKey: false, @@ -1281,7 +1313,7 @@ suite('keyboardMapper', () => { userSettingsLabel: userSettingsLabel, isWYSIWYG: true, isChord: false, - dispatchParts: [dispatch, null], + dispatchParts: [dispatch], } ); } @@ -1339,7 +1371,7 @@ suite('keyboardMapper - LINUX ru', () => { userSettingsLabel: 'ctrl+s', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+[KeyS]', null], + dispatchParts: ['ctrl+[KeyS]'], }] ); }); @@ -1362,6 +1394,7 @@ suite('keyboardMapper - LINUX en_uk', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: true, @@ -1376,7 +1409,7 @@ suite('keyboardMapper - LINUX en_uk', () => { userSettingsLabel: 'ctrl+alt+[Minus]', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+alt+[Minus]', null], + dispatchParts: ['ctrl+alt+[Minus]'], } ); }); @@ -1409,7 +1442,7 @@ suite('keyboardMapper - MAC zh_hant', () => { userSettingsLabel: 'cmd+c', isWYSIWYG: true, isChord: false, - dispatchParts: ['meta+[KeyC]', null], + dispatchParts: ['meta+[KeyC]'], }] ); }); @@ -1425,17 +1458,17 @@ function _assertKeybindingTranslation(mapper: MacLinuxKeyboardMapper, OS: Operat expected = []; } - const runtimeKeybinding = createKeybinding(kb, OS); + const runtimeKeybinding = createSimpleKeybinding(kb, OS); - const keybindingLabel = new USLayoutResolvedKeybinding(runtimeKeybinding!, OS).getUserSettingsLabel(); + const keybindingLabel = new USLayoutResolvedKeybinding(runtimeKeybinding.toChord(), OS).getUserSettingsLabel(); - const actualHardwareKeypresses = mapper.simpleKeybindingToScanCodeBinding(runtimeKeybinding); + const actualHardwareKeypresses = mapper.simpleKeybindingToScanCodeBinding(runtimeKeybinding); if (actualHardwareKeypresses.length === 0) { assert.deepEqual([], expected, `simpleKeybindingToHardwareKeypress -- "${keybindingLabel}" -- actual: "[]" -- expected: "${expected}"`); return; } const actual = actualHardwareKeypresses - .map(k => UserSettingsLabelProvider.toLabel(k, ScanCodeUtils.toString(k.scanCode), null, null, OS)); + .map(k => UserSettingsLabelProvider.toLabel(OS, [k], (keybinding) => ScanCodeUtils.toString(keybinding.scanCode))); assert.deepEqual(actual, expected, `simpleKeybindingToHardwareKeypress -- "${keybindingLabel}" -- actual: "${actual}" -- expected: "${expected}"`); } diff --git a/src/vs/workbench/services/keybinding/test/windowsKeyboardMapper.test.ts b/src/vs/workbench/services/keybinding/test/windowsKeyboardMapper.test.ts index 3e9e097792..edf5c3df8c 100644 --- a/src/vs/workbench/services/keybinding/test/windowsKeyboardMapper.test.ts +++ b/src/vs/workbench/services/keybinding/test/windowsKeyboardMapper.test.ts @@ -44,7 +44,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+a', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+A', null], + dispatchParts: ['ctrl+A'], }] ); }); @@ -60,7 +60,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+Z', null], + dispatchParts: ['ctrl+Z'], }] ); }); @@ -69,6 +69,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -83,7 +84,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+z', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+Z', null], + dispatchParts: ['ctrl+Z'], } ); }); @@ -99,7 +100,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+oem_6', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+]', null], + dispatchParts: ['ctrl+]'], }] ); }); @@ -108,6 +109,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -122,7 +124,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+oem_6', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+]', null], + dispatchParts: ['ctrl+]'], } ); }); @@ -138,7 +140,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'shift+oem_6', isWYSIWYG: false, isChord: false, - dispatchParts: ['shift+]', null], + dispatchParts: ['shift+]'], }] ); }); @@ -154,7 +156,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+oem_2', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+/', null], + dispatchParts: ['ctrl+/'], }] ); }); @@ -170,7 +172,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+shift+oem_2', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+shift+/', null], + dispatchParts: ['ctrl+shift+/'], }] ); }); @@ -210,7 +212,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+down', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+DownArrow', null], + dispatchParts: ['ctrl+DownArrow'], }] ); }); @@ -226,7 +228,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+numpad0', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+NumPad0', null], + dispatchParts: ['ctrl+NumPad0'], }] ); }); @@ -242,7 +244,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+Home', null], + dispatchParts: ['ctrl+Home'], }] ); }); @@ -251,6 +253,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -265,16 +268,21 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+home', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+Home', null], + dispatchParts: ['ctrl+Home'], } ); }); + test('resolveUserBinding empty', () => { + assertResolveUserBinding(mapper, [], []); + }); + test('resolveUserBinding Ctrl+[Comma] Ctrl+/', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma), + new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + ], [{ label: 'Ctrl+, Ctrl+§', ariaLabel: 'Control+, Control+§', @@ -291,6 +299,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -305,7 +314,7 @@ suite('keyboardMapper - WINDOWS de_ch', () => { userSettingsLabel: 'ctrl+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -341,9 +350,10 @@ suite('keyboardMapper - WINDOWS en_us', () => { test('resolveUserBinding Ctrl+[Comma] Ctrl+/', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma), + new SimpleKeybinding(true, false, false, false, KeyCode.US_SLASH), + ], [{ label: 'Ctrl+, Ctrl+/', ariaLabel: 'Control+, Control+/', @@ -358,9 +368,9 @@ suite('keyboardMapper - WINDOWS en_us', () => { test('resolveUserBinding Ctrl+[Comma]', () => { assertResolveUserBinding( - mapper, - new ScanCodeBinding(true, false, false, false, ScanCode.Comma), - null!, + mapper, [ + new ScanCodeBinding(true, false, false, false, ScanCode.Comma), + ], [{ label: 'Ctrl+,', ariaLabel: 'Control+,', @@ -368,7 +378,7 @@ suite('keyboardMapper - WINDOWS en_us', () => { userSettingsLabel: 'ctrl+,', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+,', null], + dispatchParts: ['ctrl+,'], }] ); }); @@ -377,6 +387,7 @@ suite('keyboardMapper - WINDOWS en_us', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -391,7 +402,7 @@ suite('keyboardMapper - WINDOWS en_us', () => { userSettingsLabel: 'ctrl+', isWYSIWYG: true, isChord: false, - dispatchParts: [null, null], + dispatchParts: [null], } ); }); @@ -413,6 +424,7 @@ suite('keyboardMapper - WINDOWS por_ptb', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -427,7 +439,7 @@ suite('keyboardMapper - WINDOWS por_ptb', () => { userSettingsLabel: 'ctrl+abnt_c1', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+ABNT_C1', null], + dispatchParts: ['ctrl+ABNT_C1'], } ); }); @@ -436,6 +448,7 @@ suite('keyboardMapper - WINDOWS por_ptb', () => { assertResolveKeyboardEvent( mapper, { + _standardKeyboardEventBrand: true, ctrlKey: true, shiftKey: false, altKey: false, @@ -450,7 +463,7 @@ suite('keyboardMapper - WINDOWS por_ptb', () => { userSettingsLabel: 'ctrl+abnt_c2', isWYSIWYG: false, isChord: false, - dispatchParts: ['ctrl+ABNT_C2', null], + dispatchParts: ['ctrl+ABNT_C2'], } ); }); @@ -514,7 +527,7 @@ suite('keyboardMapper - misc', () => { userSettingsLabel: 'ctrl+b', isWYSIWYG: true, isChord: false, - dispatchParts: ['ctrl+B', null], + dispatchParts: ['ctrl+B'], }] ); }); diff --git a/src/vs/workbench/services/label/common/labelService.ts b/src/vs/workbench/services/label/common/labelService.ts index 137a68ad15..57226ae1ae 100644 --- a/src/vs/workbench/services/label/common/labelService.ts +++ b/src/vs/workbench/services/label/common/labelService.ts @@ -11,24 +11,20 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWo import { Registry } from 'vs/platform/registry/common/platform'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace'; -import { isEqual, basenameOrAuthority, basename as resourceBasename } from 'vs/base/common/resources'; -import { isLinux, isWindows } from 'vs/base/common/platform'; +import { isEqual, basenameOrAuthority, isEqualOrParent, basename, joinPath, dirname } from 'vs/base/common/resources'; +import { isWindows } from 'vs/base/common/platform'; import { tildify, getPathLabel } from 'vs/base/common/labels'; -import { ltrim } from 'vs/base/common/strings'; +import { ltrim, endsWith } from 'vs/base/common/strings'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { isParent } from 'vs/platform/files/common/files'; -import { basename, dirname, join } from 'vs/base/common/paths'; import { Schemas } from 'vs/base/common/network'; -import { IWindowService } from 'vs/platform/windows/common/windows'; -import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting } from 'vs/platform/label/common/label'; import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { match } from 'vs/base/common/glob'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; const resourceLabelFormattersExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'resourceLabelFormatters', - isDynamic: true, jsonSchema: { description: localize('vscode.extension.contributes.resourceLabelFormatters', 'Contributes resource label formatting rules.'), type: 'array', @@ -103,7 +99,6 @@ export class LabelService implements ILabelService { constructor( @IEnvironmentService private readonly environmentService: IEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IWindowService private readonly windowService: IWindowService ) { } get onDidChangeFormatters(): Event { @@ -123,7 +118,7 @@ export class LabelService implements ILabelService { return; } - if (match(formatter.authority, resource.authority) && (!bestResult || !bestResult.authority || formatter.authority.length > bestResult.authority.length)) { + if (match(formatter.authority, resource.authority) && (!bestResult || !bestResult.authority || formatter.authority.length > bestResult.authority.length || ((formatter.authority.length === bestResult.authority.length) && formatter.priority))) { bestResult = formatter; } } @@ -132,34 +127,36 @@ export class LabelService implements ILabelService { return bestResult ? bestResult.formatting : undefined; } - getUriLabel(resource: URI, options: { relative?: boolean, noPrefix?: boolean } = {}): string { + getUriLabel(resource: URI, options: { relative?: boolean, noPrefix?: boolean, endWithSeparator?: boolean } = {}): string { const formatting = this.findFormatting(resource); if (!formatting) { return getPathLabel(resource.path, this.environmentService, options.relative ? this.contextService : undefined); } - if (options.relative) { - const baseResource = this.contextService && this.contextService.getWorkspaceFolder(resource); - if (baseResource) { - let relativeLabel: string; - if (isEqual(baseResource.uri, resource, !isLinux)) { - relativeLabel = ''; // no label if resources are identical - } else { - const baseResourceLabel = this.formatUri(baseResource.uri, formatting, options.noPrefix); - relativeLabel = ltrim(this.formatUri(resource, formatting, options.noPrefix).substring(baseResourceLabel.length), formatting.separator); - } + let label: string | undefined; + const baseResource = this.contextService && this.contextService.getWorkspaceFolder(resource); - const hasMultipleRoots = this.contextService.getWorkspace().folders.length > 1; - if (hasMultipleRoots && !options.noPrefix) { - const rootName = (baseResource && baseResource.name) ? baseResource.name : basenameOrAuthority(baseResource.uri); - relativeLabel = relativeLabel ? (rootName + ' • ' + relativeLabel) : rootName; // always show root basename if there are multiple - } - - return relativeLabel; + if (options.relative && baseResource) { + let relativeLabel: string; + if (isEqual(baseResource.uri, resource)) { + relativeLabel = ''; // no label if resources are identical + } else { + const baseResourceLabel = this.formatUri(baseResource.uri, formatting, options.noPrefix); + relativeLabel = ltrim(this.formatUri(resource, formatting, options.noPrefix).substring(baseResourceLabel.length), formatting.separator); } + + const hasMultipleRoots = this.contextService.getWorkspace().folders.length > 1; + if (hasMultipleRoots && !options.noPrefix) { + const rootName = (baseResource && baseResource.name) ? baseResource.name : basenameOrAuthority(baseResource.uri); + relativeLabel = relativeLabel ? (rootName + ' • ' + relativeLabel) : rootName; // always show root basename if there are multiple + } + + label = relativeLabel; + } else { + label = this.formatUri(resource, formatting, options.noPrefix); } - return this.formatUri(resource, formatting, options.noPrefix); + return options.endWithSeparator ? this.appendSeparatorIfMissing(label, formatting) : label; } getWorkspaceLabel(workspace: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | IWorkspace), options?: { verbose: boolean }): string { @@ -175,42 +172,35 @@ export class LabelService implements ILabelService { // Workspace: Single Folder if (isSingleFolderWorkspaceIdentifier(workspace)) { // Folder on disk - const label = options && options.verbose ? this.getUriLabel(workspace) : resourceBasename(workspace) || '/'; - if (workspace.scheme === Schemas.file) { - return label; - } - - const formatting = this.findFormatting(workspace); - const suffix = formatting && (typeof formatting.workspaceSuffix === 'string') ? formatting.workspaceSuffix : workspace.scheme; - return suffix ? `${label} (${suffix})` : label; + const label = options && options.verbose ? this.getUriLabel(workspace) : basename(workspace) || '/'; + return this.appendWorkspaceSuffix(label, workspace); } // Workspace: Untitled - if (isParent(workspace.configPath, this.environmentService.workspacesHome, !isLinux /* ignore case */)) { + if (isEqualOrParent(workspace.configPath, this.environmentService.untitledWorkspacesHome)) { return localize('untitledWorkspace', "Untitled (Workspace)"); } // Workspace: Saved const filename = basename(workspace.configPath); const workspaceName = filename.substr(0, filename.length - WORKSPACE_EXTENSION.length - 1); + let label; if (options && options.verbose) { - return localize('workspaceNameVerbose', "{0} (Workspace)", this.getUriLabel(URI.file(join(dirname(workspace.configPath), workspaceName)))); + label = localize('workspaceNameVerbose', "{0} (Workspace)", this.getUriLabel(joinPath(dirname(workspace.configPath), workspaceName))); + } else { + label = localize('workspaceName', "{0} (Workspace)", workspaceName); } - - return localize('workspaceName', "{0} (Workspace)", workspaceName); + return this.appendWorkspaceSuffix(label, workspace.configPath); } - getHostLabel(): string { - if (this.windowService) { - const authority = this.windowService.getConfiguration().remoteAuthority; - if (authority) { - const formatter = this.findFormatting(URI.from({ scheme: REMOTE_HOST_SCHEME, authority })); - if (formatter && formatter.workspaceSuffix) { - return formatter.workspaceSuffix; - } - } - } - return ''; + getSeparator(scheme: string, authority?: string): '/' | '\\' { + const formatter = this.findFormatting(URI.from({ scheme, authority })); + return formatter && formatter.separator || '/'; + } + + getHostLabel(scheme: string, authority?: string): string { + const formatter = this.findFormatting(URI.from({ scheme, authority })); + return formatter && formatter.workspaceSuffix || ''; } registerFormatter(formatter: ResourceLabelFormatter): IDisposable { @@ -249,4 +239,24 @@ export class LabelService implements ILabelService { return label.replace(sepRegexp, formatting.separator); } + + private appendSeparatorIfMissing(label: string, formatting: ResourceLabelFormatting): string { + let appendedLabel = label; + if (!endsWith(label, formatting.separator)) { + appendedLabel += formatting.separator; + } + return appendedLabel; + } + + private appendWorkspaceSuffix(label: string, uri: URI): string { + if (uri.scheme === Schemas.file) { + return label; + } + + const formatting = this.findFormatting(uri); + const suffix = formatting && (typeof formatting.workspaceSuffix === 'string') ? formatting.workspaceSuffix : uri.scheme; + return suffix ? `${label} [${suffix}]` : label; + } } + +registerSingleton(ILabelService, LabelService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/label/test/label.test.ts b/src/vs/workbench/services/label/test/label.test.ts index 3b2956a69c..82cc83707a 100644 --- a/src/vs/workbench/services/label/test/label.test.ts +++ b/src/vs/workbench/services/label/test/label.test.ts @@ -4,10 +4,10 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { TestEnvironmentService, TestContextService, TestWindowService } from 'vs/workbench/test/workbenchTestServices'; +import { TestEnvironmentService, TestContextService } from 'vs/workbench/test/workbenchTestServices'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { URI } from 'vs/base/common/uri'; -import { nativeSep } from 'vs/base/common/paths'; +import { sep } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; @@ -16,7 +16,7 @@ suite('URI Label', () => { let labelService: LabelService; setup(() => { - labelService = new LabelService(TestEnvironmentService, new TestContextService(), new TestWindowService()); + labelService = new LabelService(TestEnvironmentService, new TestContextService()); }); test('file scheme', function () { @@ -24,7 +24,7 @@ suite('URI Label', () => { scheme: 'file', formatting: { label: '${path}', - separator: nativeSep, + separator: sep, tildify: !isWindows, normalizeDriveLetter: isWindows } diff --git a/src/vs/workbench/services/part/common/partService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts similarity index 77% rename from src/vs/workbench/services/part/common/partService.ts rename to src/vs/workbench/services/layout/browser/layoutService.ts index d38d78d275..57ff084478 100644 --- a/src/vs/workbench/services/part/common/partService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -3,18 +3,21 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { ServiceIdentifier, createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { MenuBarVisibility } from 'vs/platform/windows/common/windows'; +import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { Part } from 'vs/workbench/browser/part'; + +export const IWorkbenchLayoutService = createDecorator('layoutService'); export const enum Parts { - ACTIVITYBAR_PART, - SIDEBAR_PART, - PANEL_PART, - EDITOR_PART, - STATUSBAR_PART, - TITLEBAR_PART, - MENUBAR_PART + TITLEBAR_PART = 'workbench.parts.titlebar', + ACTIVITYBAR_PART = 'workbench.parts.activitybar', + SIDEBAR_PART = 'workbench.parts.sidebar', + PANEL_PART = 'workbench.parts.panel', + EDITOR_PART = 'workbench.parts.editor', + STATUSBAR_PART = 'workbench.parts.statusbar' } export const enum Position { @@ -22,38 +25,25 @@ export const enum Position { RIGHT, BOTTOM } -export function PositionToString(position: Position): string { - switch (position) { - case Position.LEFT: return 'LEFT'; - case Position.RIGHT: return 'RIGHT'; - case Position.BOTTOM: return 'BOTTOM'; - } -} export interface ILayoutOptions { toggleMaximizedPanel?: boolean; source?: Parts; } -export interface IDimension { - readonly width: number; - readonly height: number; -} +export interface IWorkbenchLayoutService extends ILayoutService { -export const IPartService = createDecorator('partService'); - -export interface IPartService { _serviceBrand: ServiceIdentifier; /** * Emits when the visibility of the title bar changes. */ - onTitleBarVisibilityChange: Event; + readonly onTitleBarVisibilityChange: Event; /** - * Emits when the editor part's layout changes. + * Emits when the zen mode is enabled or disabled. */ - onEditorLayout: Event; + readonly onZenModeChange: Event; /** * Asks the part service if all parts have been fully restored. For editor part @@ -157,4 +147,9 @@ export interface IPartService { * Resizes currently focused part on main access */ resizePart(part: Parts, sizeChange: number): void; + + /** + * Register a part to participate in the layout. + */ + registerPart(part: Part): void; } diff --git a/src/vs/workbench/services/mode/common/workbenchModeService.ts b/src/vs/workbench/services/mode/common/workbenchModeService.ts index 21ec980025..b7e1afb606 100644 --- a/src/vs/workbench/services/mode/common/workbenchModeService.ts +++ b/src/vs/workbench/services/mode/common/workbenchModeService.ts @@ -8,13 +8,14 @@ import * as mime from 'vs/base/common/mime'; import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry'; -import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService'; +import { ILanguageExtensionPoint, IModeService } from 'vs/editor/common/services/modeService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { FILES_ASSOCIATIONS_CONFIG, IFilesConfiguration } from 'vs/platform/files/common/files'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { ExtensionMessageCollector, ExtensionsRegistry, IExtensionPoint, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export interface IRawLanguageExtensionPoint { id: string; @@ -28,7 +29,6 @@ export interface IRawLanguageExtensionPoint { } export const languagesExtPoint: IExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ - isDynamic: true, extensionPoint: 'languages', jsonSchema: { description: nls.localize('vscode.extension.contributes.languages', 'Contributes language declarations.'), @@ -231,3 +231,5 @@ function isValidLanguageExtensionPoint(value: IRawLanguageExtensionPoint, collec } return true; } + +registerSingleton(IModeService, WorkbenchModeServiceImpl); \ No newline at end of file diff --git a/src/vs/workbench/services/notification/common/notificationService.ts b/src/vs/workbench/services/notification/common/notificationService.ts index 503518c711..4621348a50 100644 --- a/src/vs/workbench/services/notification/common/notificationService.ts +++ b/src/vs/workbench/services/notification/common/notificationService.ts @@ -7,6 +7,7 @@ import { INotificationService, INotification, INotificationHandle, Severity, Not import { INotificationsModel, NotificationsModel, ChoiceAction } from 'vs/workbench/common/notifications'; import { dispose, Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class NotificationService extends Disposable implements INotificationService { @@ -103,4 +104,6 @@ export class NotificationService extends Disposable implements INotificationServ return handle; } -} \ No newline at end of file +} + +registerSingleton(INotificationService, NotificationService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/output/common/outputChannelModel.ts b/src/vs/workbench/services/output/common/outputChannelModel.ts new file mode 100644 index 0000000000..2672dc3cdd --- /dev/null +++ b/src/vs/workbench/services/output/common/outputChannelModel.ts @@ -0,0 +1,421 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import * as strings from 'vs/base/common/strings'; +import { ITextModel } from 'vs/editor/common/model'; +import { Emitter, Event } from 'vs/base/common/event'; +import { URI } from 'vs/base/common/uri'; +import { RunOnceScheduler, ThrottledDelayer } from 'vs/base/common/async'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { Disposable, toDisposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { isNumber } from 'vs/base/common/types'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Position } from 'vs/editor/common/core/position'; +import { binarySearch } from 'vs/base/common/arrays'; +import { toUint8ArrayBuffer } from 'vs/base/common/uint'; + +export interface IOutputChannelModel extends IDisposable { + readonly onDidAppendedContent: Event; + readonly onDispose: Event; + append(output: string): void; + update(): void; + loadModel(): Promise; + clear(till?: number): void; +} + +export const IOutputChannelModelService = createDecorator('outputChannelModelService'); + +export interface IOutputChannelModelService { + _serviceBrand: any; + + createOutputChannelModel(id: string, modelUri: URI, mimeType: string, file?: URI): IOutputChannelModel; + +} + +export abstract class AsbtractOutputChannelModelService { + + constructor( + @IInstantiationService protected readonly instantiationService: IInstantiationService + ) { } + + createOutputChannelModel(id: string, modelUri: URI, mimeType: string, file?: URI): IOutputChannelModel { + return file ? this.instantiationService.createInstance(FileOutputChannelModel, modelUri, mimeType, file) : this.instantiationService.createInstance(BufferredOutputChannel, modelUri, mimeType); + } + +} + +export abstract class AbstractFileOutputChannelModel extends Disposable implements IOutputChannelModel { + + protected _onDidAppendedContent = new Emitter(); + readonly onDidAppendedContent: Event = this._onDidAppendedContent.event; + + protected _onDispose = new Emitter(); + readonly onDispose: Event = this._onDispose.event; + + protected modelUpdater: RunOnceScheduler; + protected model: ITextModel | null; + + protected startOffset: number = 0; + protected endOffset: number = 0; + + constructor( + private readonly modelUri: URI, + private readonly mimeType: string, + protected readonly file: URI, + protected fileService: IFileService, + protected modelService: IModelService, + protected modeService: IModeService, + ) { + super(); + this.modelUpdater = new RunOnceScheduler(() => this.updateModel(), 300); + this._register(toDisposable(() => this.modelUpdater.cancel())); + } + + clear(till?: number): void { + if (this.modelUpdater.isScheduled()) { + this.modelUpdater.cancel(); + this.onUpdateModelCancelled(); + } + if (this.model) { + this.model.setValue(''); + } + this.endOffset = isNumber(till) ? till : this.endOffset; + this.startOffset = this.endOffset; + } + + update(): void { } + + protected createModel(content: string): ITextModel { + if (this.model) { + this.model.setValue(content); + } else { + this.model = this.modelService.createModel(content, this.modeService.create(this.mimeType), this.modelUri); + this.onModelCreated(this.model); + const disposables: IDisposable[] = []; + disposables.push(this.model.onWillDispose(() => { + this.onModelWillDispose(this.model); + this.model = null; + dispose(disposables); + })); + } + return this.model; + } + + appendToModel(content: string): void { + if (this.model && content) { + const lastLine = this.model.getLineCount(); + const lastLineMaxColumn = this.model.getLineMaxColumn(lastLine); + this.model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), content)]); + this._onDidAppendedContent.fire(); + } + } + + abstract loadModel(): Promise; + abstract append(message: string): void; + + protected onModelCreated(model: ITextModel) { } + protected onModelWillDispose(model: ITextModel | null) { } + protected onUpdateModelCancelled() { } + protected updateModel() { } + + dispose(): void { + this._onDispose.fire(); + super.dispose(); + } +} + +class OutputFileListener extends Disposable { + + private readonly _onDidContentChange = new Emitter(); + readonly onDidContentChange: Event = this._onDidContentChange.event; + + private watching: boolean = false; + private syncDelayer: ThrottledDelayer; + private etag: string | undefined; + + constructor( + private readonly file: URI, + private readonly fileService: IFileService + ) { + super(); + this.syncDelayer = new ThrottledDelayer(500); + } + + watch(eTag: string | undefined): void { + if (!this.watching) { + this.etag = eTag; + this.poll(); + this.watching = true; + } + } + + private poll(): void { + const loop = () => this.doWatch().then(() => this.poll()); + this.syncDelayer.trigger(loop); + } + + private doWatch(): Promise { + return this.fileService.resolveFile(this.file) + .then(stat => { + if (stat.etag !== this.etag) { + this.etag = stat.etag; + this._onDidContentChange.fire(stat.size); + } + }); + } + + unwatch(): void { + if (this.watching) { + this.syncDelayer.cancel(); + this.watching = false; + } + } + + dispose(): void { + this.unwatch(); + super.dispose(); + } +} + +/** + * An output channel driven by a file and does not support appending messages. + */ +class FileOutputChannelModel extends AbstractFileOutputChannelModel implements IOutputChannelModel { + + private readonly fileHandler: OutputFileListener; + + private updateInProgress: boolean = false; + private etag: string | undefined = ''; + private loadModelPromise: Promise | null = null; + + constructor( + modelUri: URI, + mimeType: string, + file: URI, + @IFileService fileService: IFileService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService + ) { + super(modelUri, mimeType, file, fileService, modelService, modeService); + + this.fileHandler = this._register(new OutputFileListener(this.file, this.fileService)); + this._register(this.fileHandler.onDidContentChange(size => this.update(size))); + this._register(toDisposable(() => this.fileHandler.unwatch())); + } + + loadModel(): Promise { + this.loadModelPromise = this.fileService.resolveContent(this.file, { position: this.startOffset, encoding: 'utf8' }) + .then(content => { + this.endOffset = this.startOffset + this.getByteLength(content.value); + this.etag = content.etag; + return this.createModel(content.value); + }); + return this.loadModelPromise; + } + + clear(till?: number): void { + const loadModelPromise: Promise = this.loadModelPromise ? this.loadModelPromise : Promise.resolve(); + loadModelPromise.then(() => { + super.clear(till); + this.update(); + }); + } + + append(message: string): void { + throw new Error('Not supported'); + } + + protected updateModel(): void { + if (this.model) { + this.fileService.resolveContent(this.file, { position: this.endOffset, encoding: 'utf8' }) + .then(content => { + this.etag = content.etag; + if (content.value) { + this.endOffset = this.endOffset + this.getByteLength(content.value); + this.appendToModel(content.value); + } + this.updateInProgress = false; + }, () => this.updateInProgress = false); + } else { + this.updateInProgress = false; + } + } + + protected onModelCreated(model: ITextModel): void { + this.fileHandler.watch(this.etag); + } + + protected onModelWillDispose(model: ITextModel | null): void { + this.fileHandler.unwatch(); + } + + protected onUpdateModelCancelled(): void { + this.updateInProgress = false; + } + + protected getByteLength(str: string): number { + if (typeof Buffer !== 'undefined') { + return Buffer.from(str).byteLength; + } + return toUint8ArrayBuffer(str).byteLength; + } + + update(size?: number): void { + if (this.model) { + if (!this.updateInProgress) { + this.updateInProgress = true; + if (isNumber(size) && this.endOffset > size) { // Reset - Content is removed + this.startOffset = this.endOffset = 0; + this.model.setValue(''); + } + this.modelUpdater.schedule(); + } + } + } +} + +class BufferredOutputChannel extends Disposable implements IOutputChannelModel { + + readonly file: URI | null = null; + scrollLock: boolean = false; + + protected _onDidAppendedContent = new Emitter(); + readonly onDidAppendedContent: Event = this._onDidAppendedContent.event; + + private readonly _onDispose = new Emitter(); + readonly onDispose: Event = this._onDispose.event; + + private modelUpdater: RunOnceScheduler; + private model: ITextModel | null; + private readonly bufferredContent: BufferedContent; + private lastReadId: number | undefined = undefined; + + constructor( + private readonly modelUri: URI, private readonly mimeType: string, + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService + ) { + super(); + + this.modelUpdater = new RunOnceScheduler(() => this.updateModel(), 300); + this._register(toDisposable(() => this.modelUpdater.cancel())); + + this.bufferredContent = new BufferedContent(); + this._register(toDisposable(() => this.bufferredContent.clear())); + } + + append(output: string) { + this.bufferredContent.append(output); + if (!this.modelUpdater.isScheduled()) { + this.modelUpdater.schedule(); + } + } + + update(): void { } + + clear(): void { + if (this.modelUpdater.isScheduled()) { + this.modelUpdater.cancel(); + } + if (this.model) { + this.model.setValue(''); + } + this.bufferredContent.clear(); + this.lastReadId = undefined; + } + + loadModel(): Promise { + const { value, id } = this.bufferredContent.getDelta(this.lastReadId); + if (this.model) { + this.model.setValue(value); + } else { + this.model = this.createModel(value); + } + this.lastReadId = id; + return Promise.resolve(this.model); + } + + private createModel(content: string): ITextModel { + const model = this.modelService.createModel(content, this.modeService.create(this.mimeType), this.modelUri); + const disposables: IDisposable[] = []; + disposables.push(model.onWillDispose(() => { + this.model = null; + dispose(disposables); + })); + return model; + } + + private updateModel(): void { + if (this.model) { + const { value, id } = this.bufferredContent.getDelta(this.lastReadId); + this.lastReadId = id; + const lastLine = this.model.getLineCount(); + const lastLineMaxColumn = this.model.getLineMaxColumn(lastLine); + this.model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), value)]); + this._onDidAppendedContent.fire(); + } + } + + dispose(): void { + this._onDispose.fire(); + super.dispose(); + } +} + +class BufferedContent { + + private static MAX_OUTPUT_LENGTH = 10000 /* Max. number of output lines to show in output */ * 100 /* Guestimated chars per line */; + + private data: string[] = []; + private dataIds: number[] = []; + private idPool = 0; + private length = 0; + + public append(content: string): void { + this.data.push(content); + this.dataIds.push(++this.idPool); + this.length += content.length; + this.trim(); + } + + public clear(): void { + this.data.length = 0; + this.dataIds.length = 0; + this.length = 0; + } + + private trim(): void { + if (this.length < BufferedContent.MAX_OUTPUT_LENGTH * 1.2) { + return; + } + + while (this.length > BufferedContent.MAX_OUTPUT_LENGTH) { + this.dataIds.shift(); + const removed = this.data.shift(); + if (removed) { + this.length -= removed.length; + } + } + } + + public getDelta(previousId?: number): { value: string, id: number } { + let idx = -1; + if (previousId !== undefined) { + idx = binarySearch(this.dataIds, previousId, (a, b) => a - b); + } + + const id = this.idPool; + if (idx >= 0) { + const value = strings.removeAnsiEscapeCodes(this.data.slice(idx + 1).join('')); + return { value, id }; + } else { + const value = strings.removeAnsiEscapeCodes(this.data.join('')); + return { value, id }; + } + } +} diff --git a/src/vs/workbench/services/output/common/outputChannelModelService.ts b/src/vs/workbench/services/output/common/outputChannelModelService.ts new file mode 100644 index 0000000000..7a90143ce0 --- /dev/null +++ b/src/vs/workbench/services/output/common/outputChannelModelService.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IOutputChannelModelService, AsbtractOutputChannelModelService } from 'vs/workbench/services/output/common/outputChannelModel'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class OutputChannelModelService extends AsbtractOutputChannelModelService implements IOutputChannelModelService { + _serviceBrand: any; +} + +registerSingleton(IOutputChannelModelService, OutputChannelModelService); diff --git a/src/vs/platform/output/node/outputAppender.ts b/src/vs/workbench/services/output/node/outputAppender.ts similarity index 100% rename from src/vs/platform/output/node/outputAppender.ts rename to src/vs/workbench/services/output/node/outputAppender.ts diff --git a/src/vs/workbench/services/output/node/outputChannelModelService.ts b/src/vs/workbench/services/output/node/outputChannelModelService.ts new file mode 100644 index 0000000000..9e98c2b4dc --- /dev/null +++ b/src/vs/workbench/services/output/node/outputChannelModelService.ts @@ -0,0 +1,192 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import * as extfs from 'vs/base/node/extfs'; +import { dirname, join } from 'vs/base/common/path'; +import { ITextModel } from 'vs/editor/common/model'; +import { URI } from 'vs/base/common/uri'; +import { ThrottledDelayer } from 'vs/base/common/async'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { toDisposable, IDisposable } from 'vs/base/common/lifecycle'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IOutputChannelModel, AbstractFileOutputChannelModel, IOutputChannelModelService, AsbtractOutputChannelModelService } from 'vs/workbench/services/output/common/outputChannelModel'; +import { OutputAppender } from 'vs/workbench/services/output/node/outputAppender'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { toLocalISOString } from 'vs/base/common/date'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +let watchingOutputDir = false; +let callbacks: ((eventType: string, fileName?: string) => void)[] = []; +function watchOutputDirectory(outputDir: string, logService: ILogService, onChange: (eventType: string, fileName: string) => void): IDisposable { + callbacks.push(onChange); + if (!watchingOutputDir) { + const watcherDisposable = extfs.watch(outputDir, (eventType, fileName) => { + for (const callback of callbacks) { + callback(eventType, fileName); + } + }, (error: string) => { + logService.error(error); + }); + watchingOutputDir = true; + return toDisposable(() => { + callbacks = []; + watcherDisposable.dispose(); + }); + } + return toDisposable(() => { }); +} + +class OutputChannelBackedByFile extends AbstractFileOutputChannelModel implements IOutputChannelModel { + + private appender: OutputAppender; + private appendedMessage: string; + private loadingFromFileInProgress: boolean; + private resettingDelayer: ThrottledDelayer; + private readonly rotatingFilePath: string; + + constructor( + id: string, + modelUri: URI, + mimeType: string, + @IWindowService windowService: IWindowService, + @IEnvironmentService environmentService: IEnvironmentService, + @IFileService fileService: IFileService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService, + @ILogService logService: ILogService + ) { + const outputDir = join(environmentService.logsPath, `output_${windowService.getCurrentWindowId()}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); + super(modelUri, mimeType, URI.file(join(outputDir, `${id}.log`)), fileService, modelService, modeService); + this.appendedMessage = ''; + this.loadingFromFileInProgress = false; + + // Use one rotating file to check for main file reset + this.appender = new OutputAppender(id, this.file.fsPath); + this.rotatingFilePath = `${id}.1.log`; + this._register(watchOutputDirectory(dirname(this.file.fsPath), logService, (eventType, file) => this.onFileChangedInOutputDirector(eventType, file))); + + this.resettingDelayer = new ThrottledDelayer(50); + } + + append(message: string): void { + // update end offset always as message is read + this.endOffset = this.endOffset + Buffer.from(message).byteLength; + if (this.loadingFromFileInProgress) { + this.appendedMessage += message; + } else { + this.write(message); + if (this.model) { + this.appendedMessage += message; + if (!this.modelUpdater.isScheduled()) { + this.modelUpdater.schedule(); + } + } + } + } + + clear(till?: number): void { + super.clear(till); + this.appendedMessage = ''; + } + + loadModel(): Promise { + this.loadingFromFileInProgress = true; + if (this.modelUpdater.isScheduled()) { + this.modelUpdater.cancel(); + } + this.appendedMessage = ''; + return this.loadFile() + .then(content => { + if (this.endOffset !== this.startOffset + Buffer.from(content).byteLength) { + // Queue content is not written into the file + // Flush it and load file again + this.flush(); + return this.loadFile(); + } + return content; + }) + .then(content => { + if (this.appendedMessage) { + this.write(this.appendedMessage); + this.appendedMessage = ''; + } + this.loadingFromFileInProgress = false; + return this.createModel(content); + }); + } + + private resetModel(): Promise { + this.startOffset = 0; + this.endOffset = 0; + if (this.model) { + return this.loadModel().then(() => undefined); + } + return Promise.resolve(undefined); + } + + private loadFile(): Promise { + return this.fileService.resolveContent(this.file, { position: this.startOffset, encoding: 'utf8' }) + .then(content => this.appendedMessage ? content.value + this.appendedMessage : content.value); + } + + protected updateModel(): void { + if (this.model && this.appendedMessage) { + this.appendToModel(this.appendedMessage); + this.appendedMessage = ''; + } + } + + private onFileChangedInOutputDirector(eventType: string, fileName?: string): void { + // Check if rotating file has changed. It changes only when the main file exceeds its limit. + if (this.rotatingFilePath === fileName) { + this.resettingDelayer.trigger(() => this.resetModel()); + } + } + + private write(content: string): void { + this.appender.append(content); + } + + private flush(): void { + this.appender.flush(); + } +} + +export class OutputChannelModelService extends AsbtractOutputChannelModelService implements IOutputChannelModelService { + + _serviceBrand: any; + + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @ILogService private readonly logService: ILogService, + @ITelemetryService private readonly telemetryService: ITelemetryService + ) { + super(instantiationService); + } + + createOutputChannelModel(id: string, modelUri: URI, mimeType: string, file?: URI): IOutputChannelModel { + if (!file) { + try { + return this.instantiationService.createInstance(OutputChannelBackedByFile, id, modelUri, mimeType); + } catch (e) { + // Do not crash if spdlog rotating logger cannot be loaded (workaround for https://github.com/Microsoft/vscode/issues/47883) + this.logService.error(e); + /* __GDPR__ + "output.channel.creation.error" : {} + */ + this.telemetryService.publicLog('output.channel.creation.error'); + } + } + return super.createOutputChannelModel(id, modelUri, mimeType, file); + } + +} + +registerSingleton(IOutputChannelModelService, OutputChannelModelService); diff --git a/src/vs/workbench/services/panel/common/panelService.ts b/src/vs/workbench/services/panel/common/panelService.ts index 393eb4aad3..dec7845307 100644 --- a/src/vs/workbench/services/panel/common/panelService.ts +++ b/src/vs/workbench/services/panel/common/panelService.ts @@ -6,6 +6,8 @@ import { Event } from 'vs/base/common/event'; import { IPanel } from 'vs/workbench/common/panel'; import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; +import { IBadge } from 'vs/workbench/services/activity/common/activity'; +import { IDisposable } from 'vs/base/common/lifecycle'; export const IPanelService = createDecorator('panelService'); @@ -25,15 +27,15 @@ export interface IPanelService { /** * Opens a panel with the given identifier and pass keyboard focus to it if specified. */ - openPanel(id: string, focus?: boolean): IPanel; + openPanel(id: string, focus?: boolean): IPanel | null; /** * Returns the current active panel or null if none */ - getActivePanel(): IPanel; + getActivePanel(): IPanel | null; /** - * * Returns all built-in panels following the default order (Problems - Output - Debug Console - Terminal) + * Returns all built-in panels following the default order (Problems - Output - Debug Console - Terminal) */ getPanels(): IPanelIdentifier[]; @@ -41,4 +43,19 @@ export interface IPanelService { * Returns pinned panels following the visual order */ getPinnedPanels(): IPanelIdentifier[]; + + /** + * Show an activity in a panel. + */ + showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable; + + /** + * Hide the currently active panel. + */ + hideActivePanel(): void; + + /** + * Get the last active panel ID. + */ + getLastActivePanelId(): string; } diff --git a/src/vs/workbench/services/preferences/browser/preferencesService.ts b/src/vs/workbench/services/preferences/browser/preferencesService.ts index 4c3c5f2f26..cdd9ea5cd6 100644 --- a/src/vs/workbench/services/preferences/browser/preferencesService.ts +++ b/src/vs/workbench/services/preferences/browser/preferencesService.ts @@ -18,7 +18,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import * as nls from 'vs/nls'; -import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; @@ -29,13 +29,13 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { EditorInput, IEditor } from 'vs/workbench/common/editor'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { GroupDirection, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { GroupDirection, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { DEFAULT_SETTINGS_EDITOR_SETTING, FOLDER_SETTINGS_PATH, getSettingsTargetName, IPreferencesEditorModel, IPreferencesService, ISetting, ISettingsEditorOptions, SettingsEditorOptions, USE_SPLIT_JSON_SETTING } from 'vs/workbench/services/preferences/common/preferences'; import { DefaultPreferencesEditorInput, KeybindingsEditorInput, PreferencesEditorInput, SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; -import { defaultKeybindingsContents, DefaultKeybindingsEditorModel, DefaultSettings, DefaultSettingsEditorModel, Settings2EditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; +import { defaultKeybindingsContents, DefaultKeybindingsEditorModel, DefaultSettings, DefaultSettingsEditorModel, Settings2EditorModel, SettingsEditorModel, WorkspaceConfigurationEditorModel, DefaultRawSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; const emptyEditableSettingsContent = '{\n}'; @@ -45,7 +45,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic private lastOpenedSettingsInput: PreferencesEditorInput | null = null; - private readonly _onDispose = new Emitter(); + private readonly _onDispose = this._register(new Emitter()); private _defaultUserSettingsUriCounter = 0; private _defaultUserSettingsContentModel: DefaultSettings; @@ -58,7 +58,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic @IEditorService private readonly editorService: IEditorService, @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, @IFileService private readonly fileService: IFileService, - @IWorkspaceConfigurationService private readonly configurationService: IWorkspaceConfigurationService, + @IConfigurationService private readonly configurationService: IConfigurationService, @INotificationService private readonly notificationService: INotificationService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -74,24 +74,24 @@ export class PreferencesService extends Disposable implements IPreferencesServic super(); // The default keybindings.json updates based on keyboard layouts, so here we make sure // if a model has been given out we update it accordingly. - keybindingService.onDidUpdateKeybindings(() => { + this._register(keybindingService.onDidUpdateKeybindings(() => { const model = modelService.getModel(this.defaultKeybindingsResource); if (!model) { // model has not been given out => nothing to do return; } modelService.updateModel(model, defaultKeybindingsContents(keybindingService)); - }); + })); } readonly defaultKeybindingsResource = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/keybindings.json' }); private readonly defaultSettingsRawResource = URI.from({ scheme: network.Schemas.vscode, authority: 'defaultsettings', path: '/defaultSettings.json' }); get userSettingsResource(): URI { - return this.getEditableSettingsURI(ConfigurationTarget.USER); + return this.getEditableSettingsURI(ConfigurationTarget.USER)!; } - get workspaceSettingsResource(): URI { + get workspaceSettingsResource(): URI | null { return this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE); } @@ -99,18 +99,18 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.instantiationService.createInstance(SettingsEditor2Input); } - getFolderSettingsResource(resource: URI): URI { + getFolderSettingsResource(resource: URI): URI | null { return this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE_FOLDER, resource); } - resolveModel(uri: URI): Promise { + resolveModel(uri: URI): Promise { if (this.isDefaultSettingsResource(uri)) { const target = this.getConfigurationTargetFromDefaultSettingsResource(uri); const languageSelection = this.modeService.create('jsonc'); const model = this._register(this.modelService.createModel('', languageSelection, uri)); - let defaultSettings: DefaultSettings; + let defaultSettings: DefaultSettings | undefined; this.configurationService.onDidChangeConfiguration(e => { if (e.source === ConfigurationTarget.DEFAULT) { const model = this.modelService.getModel(uri); @@ -134,9 +134,9 @@ export class PreferencesService extends Disposable implements IPreferencesServic } if (this.defaultSettingsRawResource.toString() === uri.toString()) { - let defaultSettings: DefaultSettings = this.getDefaultSettings(ConfigurationTarget.USER); + const defaultRawSettingsEditorModel = this.instantiationService.createInstance(DefaultRawSettingsEditorModel, this.getDefaultSettings(ConfigurationTarget.USER)); const languageSelection = this.modeService.create('jsonc'); - const model = this._register(this.modelService.createModel(defaultSettings.raw, languageSelection, uri)); + const model = this._register(this.modelService.createModel(defaultRawSettingsEditorModel.content, languageSelection, uri)); return Promise.resolve(model); } @@ -155,7 +155,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.createDefaultSettingsEditorModel(uri); } - if (this.getEditableSettingsURI(ConfigurationTarget.USER).toString() === uri.toString()) { + if (this.userSettingsResource.toString() === uri.toString()) { return this.createEditableSettingsEditorModel(ConfigurationTarget.USER, uri); } @@ -168,18 +168,18 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.createEditableSettingsEditorModel(ConfigurationTarget.WORKSPACE_FOLDER, uri); } - return Promise.resolve>(null); + return Promise.reject(`unknown resource: ${uri.toString()}`); } - openRawDefaultSettings(): Promise { + openRawDefaultSettings(): Promise { return this.editorService.openEditor({ resource: this.defaultSettingsRawResource }); } - openRawUserSettings(): Promise { + openRawUserSettings(): Promise { return this.editorService.openEditor({ resource: this.userSettingsResource }); } - openSettings(jsonEditor?: boolean): Promise { + openSettings(jsonEditor?: boolean): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -189,7 +189,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic } const editorInput = this.getActiveSettingsEditorInput() || this.lastOpenedSettingsInput; - const resource = editorInput ? editorInput.master.getResource() : this.userSettingsResource; + const resource = editorInput ? editorInput.master.getResource()! : this.userSettingsResource; const target = this.getConfigurationTargetFromSettingsResource(resource); return this.openOrSwitchSettings(target, resource); } @@ -197,10 +197,10 @@ export class PreferencesService extends Disposable implements IPreferencesServic private openSettings2(): Promise { const input = this.settingsEditor2Input; return this.editorGroupService.activeGroup.openEditor(input) - .then(() => this.editorGroupService.activeGroup.activeControl); + .then(() => this.editorGroupService.activeGroup.activeControl!); } - openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; @@ -215,9 +215,9 @@ export class PreferencesService extends Disposable implements IPreferencesServic this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; - if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) { + if (!this.workspaceSettingsResource) { this.notificationService.info(nls.localize('openFolderFirst', "Open a folder first to create workspace settings")); - return Promise.resolve(null); + return Promise.reject(null); } return jsonEditor ? @@ -225,26 +225,30 @@ export class PreferencesService extends Disposable implements IPreferencesServic this.openOrSwitchSettings2(ConfigurationTarget.WORKSPACE, undefined, options, group); } - openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { jsonEditor = typeof jsonEditor === 'undefined' ? this.configurationService.getValue('workbench.settings.editor') === 'json' : jsonEditor; - - return jsonEditor ? - this.openOrSwitchSettings(ConfigurationTarget.WORKSPACE_FOLDER, this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE_FOLDER, folder), options, group) : - this.openOrSwitchSettings2(ConfigurationTarget.WORKSPACE_FOLDER, folder, options, group); + const folderSettingsUri = this.getEditableSettingsURI(ConfigurationTarget.WORKSPACE_FOLDER, folder); + if (jsonEditor) { + if (folderSettingsUri) { + return this.openOrSwitchSettings(ConfigurationTarget.WORKSPACE_FOLDER, folderSettingsUri, options, group); + } + return Promise.reject(`Invalid folder URI - ${folder.toString()}`); + } + return this.openOrSwitchSettings2(ConfigurationTarget.WORKSPACE_FOLDER, folder, options, group); } switchSettings(target: ConfigurationTarget, resource: URI, jsonEditor?: boolean): Promise { if (!jsonEditor) { - return this.doOpenSettings2(target, resource).then(() => null); + return this.doOpenSettings2(target, resource).then(() => undefined); } const activeControl = this.editorService.activeControl; if (activeControl && activeControl.input instanceof PreferencesEditorInput) { - return this.doSwitchSettings(target, resource, activeControl.input, activeControl.group).then(() => null); + return this.doSwitchSettings(target, resource, activeControl.input, activeControl.group).then(() => undefined); } else { - return this.doOpenSettings(target, resource).then(() => null); + return this.doOpenSettings(target, resource).then(() => undefined); } } @@ -275,10 +279,10 @@ export class PreferencesService extends Disposable implements IPreferencesServic }); } - return this.editorService.openEditor(this.instantiationService.createInstance(KeybindingsEditorInput), { pinned: true, revealIfOpened: true }).then(() => null); + return this.editorService.openEditor(this.instantiationService.createInstance(KeybindingsEditorInput), { pinned: true, revealIfOpened: true }).then(() => undefined); } - openDefaultKeybindingsFile(): Promise { + openDefaultKeybindingsFile(): Promise { return this.editorService.openEditor({ resource: this.defaultKeybindingsResource, label: nls.localize('defaultKeybindings', "Default Keybindings") }); } @@ -286,11 +290,11 @@ export class PreferencesService extends Disposable implements IPreferencesServic this.openGlobalSettings(true) .then(editor => this.createPreferencesEditorModel(this.userSettingsResource) .then((settingsModel: IPreferencesEditorModel) => { - const codeEditor = getCodeEditor(editor.getControl()); + const codeEditor = editor ? getCodeEditor(editor.getControl()) : null; if (codeEditor) { this.addLanguageOverrideEntry(language, settingsModel, codeEditor) .then(position => { - if (codeEditor) { + if (codeEditor && position) { codeEditor.setPosition(position); codeEditor.revealLine(position.lineNumber); codeEditor.focus(); @@ -300,19 +304,22 @@ export class PreferencesService extends Disposable implements IPreferencesServic })); } - private openOrSwitchSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { + private openOrSwitchSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { const editorInput = this.getActiveSettingsEditorInput(group); - if (editorInput && editorInput.master.getResource().fsPath !== resource.fsPath) { - return this.doSwitchSettings(configurationTarget, resource, editorInput, group, options); + if (editorInput) { + const editorInputResource = editorInput.master.getResource(); + if (editorInputResource && editorInputResource.fsPath !== resource.fsPath) { + return this.doSwitchSettings(configurationTarget, resource, editorInput, group, options); + } } return this.doOpenSettings(configurationTarget, resource, options, group); } - private openOrSwitchSettings2(configurationTarget: ConfigurationTarget, folderUri?: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { + private openOrSwitchSettings2(configurationTarget: ConfigurationTarget, folderUri?: URI, options?: ISettingsEditorOptions, group: IEditorGroup = this.editorGroupService.activeGroup): Promise { return this.doOpenSettings2(configurationTarget, folderUri, options, group); } - private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + private doOpenSettings(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { const openSplitJSON = !!this.configurationService.getValue(USE_SPLIT_JSON_SETTING); if (openSplitJSON) { return this.doOpenSplitJSON(configurationTarget, resource, options, group); @@ -334,14 +341,14 @@ export class PreferencesService extends Disposable implements IPreferencesServic return Promise.all([ this.editorService.openEditor({ resource: this.defaultSettingsRawResource, options: { pinned: true, preserveFocus: true, revealIfOpened: true }, label: nls.localize('defaultSettings', "Default Settings"), description: '' }), this.editorService.openEditor(editableSettingsEditorInput, { pinned: true, revealIfOpened: true }, sideEditorGroup.id) - ]).then(() => null); + ]).then(([defaultEditor, editor]) => editor); } else { return this.editorService.openEditor(editableSettingsEditorInput, SettingsEditorOptions.create(options), group); } }); } - private doOpenSplitJSON(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { + private doOpenSplitJSON(configurationTarget: ConfigurationTarget, resource: URI, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise { return this.getOrCreateEditableSettingsEditorInput(configurationTarget, resource) .then(editableSettingsEditorInput => { if (!options) { @@ -361,7 +368,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.instantiationService.createInstance(Settings2EditorModel, this.getDefaultSettings(ConfigurationTarget.USER)); } - private doOpenSettings2(target: ConfigurationTarget, folderUri: URI | undefined, options?: IEditorOptions, group?: IEditorGroup): Promise { + private doOpenSettings2(target: ConfigurationTarget, folderUri: URI | undefined, options?: IEditorOptions, group?: IEditorGroup): Promise { const input = this.settingsEditor2Input; const settingsOptions: ISettingsEditorOptions = { ...options, @@ -373,7 +380,11 @@ export class PreferencesService extends Disposable implements IPreferencesServic } private doSwitchSettings(target: ConfigurationTarget, resource: URI, input: PreferencesEditorInput, group: IEditorGroup, options?: ISettingsEditorOptions): Promise { - return this.getOrCreateEditableSettingsEditorInput(target, this.getEditableSettingsURI(target, resource)) + const settingsURI = this.getEditableSettingsURI(target, resource); + if (!settingsURI) { + return Promise.reject(`Invalid settings URI - ${resource.toString()}`); + } + return this.getOrCreateEditableSettingsEditorInput(target, settingsURI) .then(toInput => { return group.openEditor(input).then(() => { const replaceWith = new PreferencesEditorInput(this.getPreferencesEditorInputName(target, resource), toInput.getDescription(), this.instantiationService.createInstance(DefaultPreferencesEditorInput, this.getDefaultSettingsResource(target)), toInput); @@ -381,10 +392,10 @@ export class PreferencesService extends Disposable implements IPreferencesServic return group.replaceEditors([{ editor: input, replacement: replaceWith, - options: SettingsEditorOptions.create(options) + options: options ? SettingsEditorOptions.create(options) : undefined }]).then(() => { this.lastOpenedSettingsInput = replaceWith; - return group.activeControl; + return group.activeControl!; }); }); }); @@ -463,7 +474,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this.textModelResolverService.createModelReference(settingsUri) .then(reference => this.instantiationService.createInstance(SettingsEditorModel, reference, configurationTarget)); } - return Promise.resolve(null); + return Promise.reject(`unknown target: ${configurationTarget} and resource: ${resource.toString()}`); } private createDefaultSettingsEditorModel(defaultSettingsUri: URI): Promise { @@ -493,7 +504,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic return this._defaultUserSettingsContentModel; } - private getEditableSettingsURI(configurationTarget: ConfigurationTarget, resource?: URI): URI { + private getEditableSettingsURI(configurationTarget: ConfigurationTarget, resource?: URI): URI | null { switch (configurationTarget) { case ConfigurationTarget.USER: return URI.file(this.environmentService.appSettingsPath); @@ -504,8 +515,10 @@ export class PreferencesService extends Disposable implements IPreferencesServic const workspace = this.contextService.getWorkspace(); return workspace.configuration || workspace.folders[0].toResource(FOLDER_SETTINGS_PATH); case ConfigurationTarget.WORKSPACE_FOLDER: - const folder = this.contextService.getWorkspaceFolder(resource); - return folder ? folder.toResource(FOLDER_SETTINGS_PATH) : null; + if (resource) { + const folder = this.contextService.getWorkspaceFolder(resource); + return folder ? folder.toResource(FOLDER_SETTINGS_PATH) : null; + } } return null; } @@ -522,7 +535,7 @@ export class PreferencesService extends Disposable implements IPreferencesServic if (Object.keys(parse(content.value)).indexOf('settings') === -1) { return this.jsonEditingService.write(resource, { key: 'settings', value: {} }, true).then(undefined, () => { }); } - return null; + return undefined; }); } return this.createIfNotExists(resource, emptyEditableSettingsContent).then(() => { }); @@ -556,29 +569,35 @@ export class PreferencesService extends Disposable implements IPreferencesServic ]; } - private addLanguageOverrideEntry(language: string, settingsModel: IPreferencesEditorModel, codeEditor: ICodeEditor): Promise { + private addLanguageOverrideEntry(language: string, settingsModel: IPreferencesEditorModel, codeEditor: ICodeEditor): Promise { const languageKey = `[${language}]`; let setting = settingsModel.getPreference(languageKey); const model = codeEditor.getModel(); - const configuration = this.configurationService.getValue<{ editor: { tabSize: number; insertSpaces: boolean } }>(); - const eol = model.getEOL(); - if (setting) { - if (setting.overrides.length) { - const lastSetting = setting.overrides[setting.overrides.length - 1]; - return Promise.resolve({ lineNumber: lastSetting.valueRange.endLineNumber, column: model.getLineMaxColumn(lastSetting.valueRange.endLineNumber) }); + if (model) { + const configuration = this.configurationService.getValue<{ editor: { tabSize: number; insertSpaces: boolean } }>(); + const eol = model.getEOL(); + if (setting) { + if (setting.overrides && setting.overrides.length) { + const lastSetting = setting.overrides[setting.overrides.length - 1]; + return Promise.resolve({ lineNumber: lastSetting.valueRange.endLineNumber, column: model.getLineMaxColumn(lastSetting.valueRange.endLineNumber) }); + } + return Promise.resolve({ lineNumber: setting.valueRange.startLineNumber, column: setting.valueRange.startColumn + 1 }); } - return Promise.resolve({ lineNumber: setting.valueRange.startLineNumber, column: setting.valueRange.startColumn + 1 }); + return this.configurationService.updateValue(languageKey, {}, ConfigurationTarget.USER) + .then(() => { + setting = settingsModel.getPreference(languageKey); + if (setting) { + let content = eol + this.spaces(2, configuration.editor) + eol + this.spaces(1, configuration.editor); + let editOperation = EditOperation.insert(new Position(setting.valueRange.endLineNumber, setting.valueRange.endColumn - 1), content); + model.pushEditOperations([], [editOperation], () => []); + let lineNumber = setting.valueRange.endLineNumber + 1; + settingsModel.dispose(); + return { lineNumber, column: model.getLineMaxColumn(lineNumber) }; + } + return null; + }); } - return this.configurationService.updateValue(languageKey, {}, ConfigurationTarget.USER) - .then(() => { - setting = settingsModel.getPreference(languageKey); - let content = eol + this.spaces(2, configuration.editor) + eol + this.spaces(1, configuration.editor); - let editOperation = EditOperation.insert(new Position(setting.valueRange.endLineNumber, setting.valueRange.endColumn - 1), content); - model.pushEditOperations([], [editOperation], () => []); - let lineNumber = setting.valueRange.endLineNumber + 1; - settingsModel.dispose(); - return { lineNumber, column: model.getLineMaxColumn(lineNumber) }; - }); + return Promise.resolve(null); } private spaces(count: number, { tabSize, insertSpaces }: { tabSize: number; insertSpaces: boolean }): string { @@ -590,3 +609,5 @@ export class PreferencesService extends Disposable implements IPreferencesServic super.dispose(); } } + +registerSingleton(IPreferencesService, PreferencesService); \ No newline at end of file diff --git a/src/vs/workbench/services/preferences/common/keybindingsEditorModel.ts b/src/vs/workbench/services/preferences/common/keybindingsEditorModel.ts index 32a31a5476..17a0bac196 100644 --- a/src/vs/workbench/services/preferences/common/keybindingsEditorModel.ts +++ b/src/vs/workbench/services/preferences/common/keybindingsEditorModel.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { distinct, coalesce } from 'vs/base/common/arrays'; import * as strings from 'vs/base/common/strings'; -import { OperatingSystem, language, LANGUAGE_DEFAULT } from 'vs/base/common/platform'; +import { OperatingSystem, Language } from 'vs/base/common/platform'; import { IMatch, IFilter, or, matchesContiguousSubString, matchesPrefix, matchesCamelCase, matchesWords } from 'vs/base/common/filters'; import { Registry } from 'vs/platform/registry/common/platform'; import { ResolvedKeybinding, ResolvedKeybindingPart } from 'vs/base/common/keyCodes'; @@ -224,7 +224,7 @@ export class KeybindingsEditorModel extends EditorModel { } private static getCommandDefaultLabel(menuCommand: ICommandAction, workbenchActionsRegistry: IWorkbenchActionRegistry): string | null { - if (language !== LANGUAGE_DEFAULT) { + if (!Language.isDefaultVariant()) { if (menuCommand && menuCommand.title && (menuCommand.title).original) { const category: string | undefined = menuCommand.category ? (menuCommand.category).original : undefined; const title = (menuCommand.title).original; diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index f5e8e09d26..ce3e6783d0 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -5,7 +5,6 @@ import { IStringDictionary } from 'vs/base/common/collections'; import { Event } from 'vs/base/common/event'; -import { join } from 'vs/base/common/paths'; import { URI } from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; import { ITextModel } from 'vs/editor/common/model'; @@ -13,11 +12,10 @@ import { localize } from 'vs/nls'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; -import { ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { EditorOptions, IEditor } from 'vs/workbench/common/editor'; -import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; export enum SettingValueType { @@ -55,7 +53,7 @@ export interface ISetting { value: any; valueRange: IRange; description: string[]; - descriptionIsMarkdown: boolean; + descriptionIsMarkdown?: boolean; descriptionRanges: IRange[]; overrides?: ISetting[]; overrideOf?: ISetting; @@ -67,12 +65,12 @@ export interface ISetting { enumDescriptions?: string[]; enumDescriptionsAreMarkdown?: boolean; tags?: string[]; - validator?: (value: any) => string; + validator?: (value: any) => string | null; } export interface IExtensionSetting extends ISetting { - extensionName: string; - extensionPublisher: string; + extensionName?: string; + extensionPublisher?: string; } export interface ISearchResult { @@ -99,7 +97,7 @@ export interface IFilterResult { export interface ISettingMatch { setting: ISetting; - matches: IRange[]; + matches: IRange[] | null; score: number; } @@ -124,7 +122,6 @@ export interface IFilterMetadata { timestamp: number; duration: number; scoredResults: IScoredResults; - extensions?: ILocalExtension[]; /** The number of requests made, since requests are split by number of filters */ requestCount?: number; @@ -135,19 +132,19 @@ export interface IFilterMetadata { export interface IPreferencesEditorModel { uri?: URI; - getPreference(key: string): T; + getPreference(key: string): T | undefined; dispose(): void; } -export type IGroupFilter = (group: ISettingsGroup) => boolean; -export type ISettingMatcher = (setting: ISetting, group: ISettingsGroup) => { matches: IRange[], score: number }; +export type IGroupFilter = (group: ISettingsGroup) => boolean | null; +export type ISettingMatcher = (setting: ISetting, group: ISettingsGroup) => { matches: IRange[], score: number } | null; export interface ISettingsEditorModel extends IPreferencesEditorModel { readonly onDidChangeGroups: Event; settingsGroups: ISettingsGroup[]; filterSettings(filter: string, groupFilter: IGroupFilter, settingMatcher: ISettingMatcher): ISettingMatch[]; findValueMatches(filter: string, setting: ISetting): IRange[]; - updateResultGroup(id: string, resultGroup: ISearchResultGroup): IFilterResult; + updateResultGroup(id: string, resultGroup: ISearchResultGroup | undefined): IFilterResult | undefined; } export interface ISettingsEditorOptions extends IEditorOptions { @@ -166,10 +163,6 @@ export class SettingsEditorOptions extends EditorOptions implements ISettingsEdi query?: string; static create(settings: ISettingsEditorOptions): SettingsEditorOptions { - if (!settings) { - return null; - } - const options = new SettingsEditorOptions(); options.target = settings.target; @@ -198,23 +191,23 @@ export interface IPreferencesService { _serviceBrand: any; userSettingsResource: URI; - workspaceSettingsResource: URI; - getFolderSettingsResource(resource: URI): URI; + workspaceSettingsResource: URI | null; + getFolderSettingsResource(resource: URI): URI | null; - resolveModel(uri: URI): Promise; + resolveModel(uri: URI): Promise; createPreferencesEditorModel(uri: URI): Promise>; createSettings2EditorModel(): Settings2EditorModel; // TODO - openRawDefaultSettings(): Promise; - openSettings(jsonEditor?: boolean): Promise; - openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; - openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; - openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; + openRawDefaultSettings(): Promise; + openSettings(jsonEditor?: boolean): Promise; + openGlobalSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; + openWorkspaceSettings(jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; + openFolderSettings(folder: URI, jsonEditor?: boolean, options?: ISettingsEditorOptions, group?: IEditorGroup): Promise; switchSettings(target: ConfigurationTarget, resource: URI, jsonEditor?: boolean): Promise; openGlobalKeybindingSettings(textual: boolean): Promise; - openDefaultKeybindingsFile(): Promise; + openDefaultKeybindingsFile(): Promise; - configureSettingsForLanguage(language: string): void; + configureSettingsForLanguage(language: string | null): void; } export function getSettingsTargetName(target: ConfigurationTarget, resource: URI, workspaceContextService: IWorkspaceContextService): string { @@ -230,6 +223,6 @@ export function getSettingsTargetName(target: ConfigurationTarget, resource: URI return ''; } -export const FOLDER_SETTINGS_PATH = join('.vscode', 'settings.json'); +export const FOLDER_SETTINGS_PATH = '.azuredatastudio/settings.json'; export const DEFAULT_SETTINGS_EDITOR_SETTING = 'workbench.settings.openDefaultSettings'; export const USE_SPLIT_JSON_SETTING = 'workbench.settings.useSplitJSON'; diff --git a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts index 9e120d5007..32d637ba5d 100644 --- a/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts +++ b/src/vs/workbench/services/preferences/common/preferencesEditorInput.ts @@ -10,7 +10,6 @@ import * as nls from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { EditorInput, SideBySideEditorInput, Verbosity } from 'vs/workbench/common/editor'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; -import { IHashService } from 'vs/workbench/services/hash/common/hashService'; import { KeybindingsEditorModel } from 'vs/workbench/services/preferences/common/keybindingsEditorModel'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { Settings2EditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; @@ -22,7 +21,7 @@ export class PreferencesEditorInput extends SideBySideEditorInput { return PreferencesEditorInput.ID; } - getTitle(verbosity: Verbosity): string { + getTitle(verbosity: Verbosity): string | null { return this.master.getTitle(verbosity); } } @@ -30,10 +29,9 @@ export class PreferencesEditorInput extends SideBySideEditorInput { export class DefaultPreferencesEditorInput extends ResourceEditorInput { static readonly ID = 'workbench.editorinputs.defaultpreferences'; constructor(defaultSettingsResource: URI, - @ITextModelService textModelResolverService: ITextModelService, - @IHashService hashService: IHashService + @ITextModelService textModelResolverService: ITextModelService ) { - super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, textModelResolverService, hashService); + super(nls.localize('settingsEditorName', "Default Settings"), '', defaultSettingsResource, textModelResolverService); } getTypeId(): string { diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index dcbcbd12bc..f6309dfa41 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -22,12 +22,16 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Registry } from 'vs/platform/registry/common/platform'; import { EditorModel } from 'vs/workbench/common/editor'; import { IFilterMetadata, IFilterResult, IGroupFilter, IKeybindingsEditorModel, ISearchResultGroup, ISetting, ISettingMatch, ISettingMatcher, ISettingsEditorModel, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; +import { withNullAsUndefined } from 'vs/base/common/types'; + +export const nullRange: IRange = { startLineNumber: -1, startColumn: -1, endLineNumber: -1, endColumn: -1 }; +export function isNullRange(range: IRange): boolean { return range.startLineNumber === -1 && range.startColumn === -1 && range.endLineNumber === -1 && range.endColumn === -1; } export abstract class AbstractSettingsModel extends EditorModel { protected _currentResultGroups = new Map(); - updateResultGroup(id: string, resultGroup: ISearchResultGroup): IFilterResult { + updateResultGroup(id: string, resultGroup: ISearchResultGroup | undefined): IFilterResult | undefined { if (resultGroup) { this._currentResultGroups.set(id, resultGroup); } else { @@ -44,9 +48,9 @@ export abstract class AbstractSettingsModel extends EditorModel { private removeDuplicateResults(): void { const settingKeys = new Set(); map.keys(this._currentResultGroups) - .sort((a, b) => this._currentResultGroups.get(a).order - this._currentResultGroups.get(b).order) + .sort((a, b) => this._currentResultGroups.get(a)!.order - this._currentResultGroups.get(b)!.order) .forEach(groupId => { - const group = this._currentResultGroups.get(groupId); + const group = this._currentResultGroups.get(groupId)!; group.result.filterMatches = group.result.filterMatches.filter(s => !settingKeys.has(s.setting.key)); group.result.filterMatches.forEach(s => settingKeys.add(s.setting.key)); }); @@ -76,7 +80,7 @@ export abstract class AbstractSettingsModel extends EditorModel { return filterMatches.sort((a, b) => b.score - a.score); } - getPreference(key: string): ISetting { + getPreference(key: string): ISetting | undefined { for (const group of this.settingsGroups) { for (const section of group.sections) { for (const setting of section.settings) { @@ -86,7 +90,8 @@ export abstract class AbstractSettingsModel extends EditorModel { } } } - return null; + + return undefined; } protected collectMetadata(groups: ISearchResultGroup[]): IStringDictionary { @@ -111,12 +116,12 @@ export abstract class AbstractSettingsModel extends EditorModel { abstract findValueMatches(filter: string, setting: ISetting): IRange[]; - protected abstract update(): IFilterResult; + protected abstract update(): IFilterResult | undefined; } export class SettingsEditorModel extends AbstractSettingsModel implements ISettingsEditorModel { - private _settingsGroups: ISettingsGroup[]; + private _settingsGroups: ISettingsGroup[] | undefined; protected settingsModel: ITextModel; private readonly _onDidChangeGroups: Emitter = this._register(new Emitter()); @@ -124,10 +129,10 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti constructor(reference: IReference, private _configurationTarget: ConfigurationTarget) { super(); - this.settingsModel = reference.object.textEditorModel; + this.settingsModel = reference.object.textEditorModel!; this._register(this.onDispose(() => reference.dispose())); this._register(this.settingsModel.onDidChangeContent(() => { - this._settingsGroups = null; + this._settingsGroups = undefined; this._onDidChangeGroups.fire(); })); } @@ -144,7 +149,7 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti if (!this._settingsGroups) { this.parse(); } - return this._settingsGroups; + return this._settingsGroups!; } get content(): string { @@ -163,10 +168,10 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti this._settingsGroups = parse(this.settingsModel, (property: string, previousParents: string[]): boolean => this.isSettingsProperty(property, previousParents)); } - protected update(): IFilterResult { + protected update(): IFilterResult | undefined { const resultGroups = map.values(this._currentResultGroups); if (!resultGroups.length) { - return null; + return undefined; } // Transform resultGroups into IFilterResult - ISetting ranges are already correct here @@ -175,11 +180,13 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti resultGroups.forEach(group => { group.result.filterMatches.forEach(filterMatch => { filteredSettings.push(filterMatch.setting); - matches.push(...filterMatch.matches); + if (filterMatch.matches) { + matches.push(...filterMatch.matches); + } }); }); - let filteredGroup: ISettingsGroup; + let filteredGroup: ISettingsGroup | undefined; const modelGroup = this.settingsGroups[0]; // Editable model has one or zero groups if (modelGroup) { filteredGroup = { @@ -216,12 +223,16 @@ export class Settings2EditorModel extends AbstractSettingsModel implements ISett ) { super(); - configurationService.onDidChangeConfiguration(e => { + this._register(configurationService.onDidChangeConfiguration(e => { if (e.source === ConfigurationTarget.DEFAULT) { this.dirty = true; this._onDidChangeGroups.fire(); } - }); + })); + this._register(Registry.as(Extensions.Configuration).onDidSchemaChange(e => { + this.dirty = true; + this._onDidChangeGroups.fire(); + })); } protected get filterGroups(): ISettingsGroup[] { @@ -268,7 +279,7 @@ function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, } if (previousParents.length === settingsPropertyIndex + 1 || (previousParents.length === settingsPropertyIndex + 2 && overrideSetting !== null)) { // settings value started - const setting = previousParents.length === settingsPropertyIndex + 1 ? settings[settings.length - 1] : overrideSetting.overrides[overrideSetting.overrides.length - 1]; + const setting = previousParents.length === settingsPropertyIndex + 1 ? settings[settings.length - 1] : overrideSetting!.overrides![overrideSetting!.overrides!.length - 1]; if (setting) { const valueStartPosition = model.getPositionAt(offset); const valueEndPosition = model.getPositionAt(offset + length); @@ -288,7 +299,7 @@ function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, } const visitor: JSONVisitor = { onObjectBegin: (offset: number, length: number) => { - if (isSettingsProperty(currentProperty, previousParents)) { + if (isSettingsProperty(currentProperty!, previousParents)) { // Settings started settingsPropertyIndex = previousParents.length; const position = model.getPositionAt(offset); @@ -323,10 +334,10 @@ function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, endColumn: 0 }, value: null, - valueRange: null, - descriptionRanges: null, + valueRange: nullRange, + descriptionRanges: [], overrides: [], - overrideOf: overrideSetting + overrideOf: withNullAsUndefined(overrideSetting) }; if (previousParents.length === settingsPropertyIndex + 1) { settings.push(setting); @@ -334,7 +345,7 @@ function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, overrideSetting = setting; } } else { - overrideSetting.overrides.push(setting); + overrideSetting!.overrides!.push(setting); } } }, @@ -342,7 +353,7 @@ function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, currentParent = previousParents.pop(); if (previousParents.length === settingsPropertyIndex + 1 || (previousParents.length === settingsPropertyIndex + 2 && overrideSetting !== null)) { // setting ended - const setting = previousParents.length === settingsPropertyIndex + 1 ? settings[settings.length - 1] : overrideSetting.overrides[overrideSetting.overrides.length - 1]; + const setting = previousParents.length === settingsPropertyIndex + 1 ? settings[settings.length - 1] : overrideSetting!.overrides![overrideSetting!.overrides!.length - 1]; if (setting) { const valueEndPosition = model.getPositionAt(offset + length); setting.valueRange = assign(setting.valueRange, { @@ -377,7 +388,7 @@ function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, currentParent = previousParents.pop(); if (previousParents.length === settingsPropertyIndex + 1 || (previousParents.length === settingsPropertyIndex + 2 && overrideSetting !== null)) { // setting value ended - const setting = previousParents.length === settingsPropertyIndex + 1 ? settings[settings.length - 1] : overrideSetting.overrides[overrideSetting.overrides.length - 1]; + const setting = previousParents.length === settingsPropertyIndex + 1 ? settings[settings.length - 1] : overrideSetting!.overrides![overrideSetting!.overrides!.length - 1]; if (setting) { const valueEndPosition = model.getPositionAt(offset + length); setting.valueRange = assign(setting.valueRange, { @@ -394,7 +405,7 @@ function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, onLiteralValue: onValue, onError: (error) => { const setting = settings[settings.length - 1]; - if (setting && (!setting.range || !setting.keyRange || !setting.valueRange)) { + if (setting && (isNullRange(setting.range) || isNullRange(setting.keyRange) || isNullRange(setting.valueRange))) { settings.pop(); } } @@ -408,8 +419,8 @@ function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, settings } ], - title: null, - titleRange: null, + title: '', + titleRange: nullRange, range }] : []; } @@ -435,8 +446,6 @@ export class WorkspaceConfigurationEditorModel extends SettingsEditorModel { export class DefaultSettings extends Disposable { - private static _RAW: string; - private _allSettingsGroups: ISettingsGroup[]; private _content: string; private _settingsByName: Map; @@ -479,15 +488,7 @@ export class DefaultSettings extends Disposable { return [mostCommonlyUsed, ...settingsGroups]; } - get raw(): string { - if (!DefaultSettings._RAW) { - DefaultSettings._RAW = this.toContent(this.getRegisteredGroups()); - } - - return DefaultSettings._RAW; - } - - private getRegisteredGroups(): ISettingsGroup[] { + getRegisteredGroups(): ISettingsGroup[] { const configurations = Registry.as(Extensions.Configuration).getConfigurations().slice(); const groups = this.removeEmptySettingsGroups(configurations.sort(this.compareConfigurationNodes) .reduce((result, config, index, array) => this.parseConfig(config, result, array), [])); @@ -524,13 +525,15 @@ export class DefaultSettings extends Disposable { description: setting.description, key: setting.key, value: setting.value, - range: null, - valueRange: null, + keyRange: nullRange, + range: nullRange, + valueRange: nullRange, overrides: [], scope: ConfigurationScope.RESOURCE, type: setting.type, enum: setting.enum, - enumDescriptions: setting.enumDescriptions + enumDescriptions: setting.enumDescriptions, + descriptionRanges: [] }; } return null; @@ -538,9 +541,9 @@ export class DefaultSettings extends Disposable { return { id: 'mostCommonlyUsed', - range: null, + range: nullRange, title: nls.localize('commonlyUsed', "Commonly Used"), - titleRange: null, + titleRange: nullRange, sections: [ { settings @@ -562,7 +565,7 @@ export class DefaultSettings extends Disposable { if (!settingsGroup) { settingsGroup = find(result, g => g.title === title); if (!settingsGroup) { - settingsGroup = { sections: [{ settings: [] }], id: config.id, title: title, titleRange: null, range: null, contributedByExtension: !!config.contributedByExtension }; + settingsGroup = { sections: [{ settings: [] }], id: config.id || '', title: title || '', titleRange: nullRange, range: nullRange, contributedByExtension: !!config.contributedByExtension }; result.push(settingsGroup); } } else { @@ -571,7 +574,7 @@ export class DefaultSettings extends Disposable { } if (config.properties) { if (!settingsGroup) { - settingsGroup = { sections: [{ settings: [] }], id: config.id, title: config.id, titleRange: null, range: null, contributedByExtension: !!config.contributedByExtension }; + settingsGroup = { sections: [{ settings: [] }], id: config.id || '', title: config.id || '', titleRange: nullRange, range: nullRange, contributedByExtension: !!config.contributedByExtension }; result.push(settingsGroup); } const configurationSettings: ISetting[] = []; @@ -615,9 +618,9 @@ export class DefaultSettings extends Disposable { value, description, descriptionIsMarkdown: !prop.description, - range: null, - keyRange: null, - valueRange: null, + range: nullRange, + keyRange: nullRange, + valueRange: nullRange, descriptionRanges: [], overrides, scope: prop.scope, @@ -640,9 +643,9 @@ export class DefaultSettings extends Disposable { value: overrideSettings[key], description: [], descriptionIsMarkdown: false, - range: null, - keyRange: null, - valueRange: null, + range: nullRange, + keyRange: nullRange, + valueRange: nullRange, descriptionRanges: [], overrides: [] })); @@ -701,7 +704,7 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements super(); this._register(defaultSettings.onDidChange(() => this._onDidChangeGroups.fire())); - this._model = reference.object.textEditorModel; + this._model = reference.object.textEditorModel!; this._register(this.onDispose(() => reference.dispose())); } @@ -722,9 +725,9 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements return this.settingsGroups.slice(1); } - protected update(): IFilterResult { + protected update(): IFilterResult | undefined { if (this._model.isDisposed()) { - return null; + return undefined; } // Grab current result groups, only render non-empty groups @@ -744,7 +747,7 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements matches, metadata } : - null; + undefined; } /** @@ -835,10 +838,10 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements overrideOf: setting.overrideOf, tags: setting.tags, deprecationMessage: setting.deprecationMessage, - keyRange: undefined, - valueRange: undefined, + keyRange: nullRange, + valueRange: nullRange, descriptionIsMarkdown: undefined, - descriptionRanges: undefined + descriptionRanges: [] }; } @@ -846,7 +849,7 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements return []; } - getPreference(key: string): ISetting { + getPreference(key: string): ISetting | undefined { for (const group of this.settingsGroups) { for (const section of group.sections) { for (const setting of section.settings) { @@ -856,15 +859,15 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements } } } - return null; + return undefined; } private getGroup(resultGroup: ISearchResultGroup): ISettingsGroup { return { id: resultGroup.id, - range: null, + range: nullRange, title: resultGroup.label, - titleRange: null, + titleRange: nullRange, sections: [ { settings: resultGroup.result.filterMatches.map(m => this.copySetting(m.setting)) @@ -889,10 +892,6 @@ class SettingsContentBuilder { this._contentByLines = []; } - private offsetIndexToIndex(offsetIdx: number): number { - return offsetIdx - this._rangeOffset; - } - pushLine(...lineText: string[]): void { this._contentByLines.push(...lineText); } @@ -901,11 +900,11 @@ class SettingsContentBuilder { this._contentByLines.push('{'); this._contentByLines.push(''); this._contentByLines.push(''); - const lastSetting = this._pushGroup(settingsGroups); + const lastSetting = this._pushGroup(settingsGroups, ' '); if (lastSetting) { // Strip the comma from the last setting - const lineIdx = this.offsetIndexToIndex(lastSetting.range.endLineNumber); + const lineIdx = lastSetting.range.endLineNumber - this._rangeOffset; const content = this._contentByLines[lineIdx - 2]; this._contentByLines[lineIdx - 2] = content.substring(0, content.length - 1); } @@ -913,8 +912,7 @@ class SettingsContentBuilder { this._contentByLines.push('}'); } - private _pushGroup(group: ISettingsGroup): ISetting { - const indent = ' '; + protected _pushGroup(group: ISettingsGroup, indent: string): ISetting | null { let lastSetting: ISetting | null = null; const groupStart = this.lineCountWithOffset + 1; for (const section of group.sections) { @@ -974,12 +972,12 @@ class SettingsContentBuilder { if (setting.enumDescriptions && setting.enumDescriptions.some(desc => !!desc)) { setting.enumDescriptions.forEach((desc, i) => { - const displayEnum = escapeInvisibleChars(String(setting.enum[i])); + const displayEnum = escapeInvisibleChars(String(setting.enum![i])); const line = desc ? `${displayEnum}: ${fixSettingLink(desc)}` : displayEnum; - this._contentByLines.push(` // - ${line}`); + this._contentByLines.push(`${indent}// - ${line}`); setting.descriptionRanges.push({ startLineNumber: this.lineCountWithOffset, startColumn: this.lastLine.indexOf(line) + 1, endLineNumber: this.lineCountWithOffset, endColumn: this.lastLine.length }); }); @@ -989,7 +987,7 @@ class SettingsContentBuilder { private pushValue(setting: ISetting, preValueConent: string, indent: string): void { const valueString = JSON.stringify(setting.value, null, indent); if (valueString && (typeof setting.value === 'object')) { - if (setting.overrides.length) { + if (setting.overrides && setting.overrides.length) { this._contentByLines.push(preValueConent + ' {'); for (const subSetting of setting.overrides) { this.pushSetting(subSetting, indent + indent); @@ -1018,7 +1016,7 @@ class SettingsContentBuilder { } } -export function createValidator(prop: IConfigurationPropertySchema): ((value: any) => string) | null { +export function createValidator(prop: IConfigurationPropertySchema): (value: any) => (string | null) { return value => { let exclusiveMax: number | undefined; let exclusiveMin: number | undefined; @@ -1052,28 +1050,28 @@ export function createValidator(prop: IConfigurationPropertySchema): ((value: an const numericValidations: Validator[] = isNumeric ? [ { enabled: exclusiveMax !== undefined && (prop.maximum === undefined || exclusiveMax <= prop.maximum), - isValid: (value => value < exclusiveMax), + isValid: (value => value < exclusiveMax!), message: nls.localize('validations.exclusiveMax', "Value must be strictly less than {0}.", exclusiveMax) }, { enabled: exclusiveMin !== undefined && (prop.minimum === undefined || exclusiveMin >= prop.minimum), - isValid: (value => value > exclusiveMin), + isValid: (value => value > exclusiveMin!), message: nls.localize('validations.exclusiveMin', "Value must be strictly greater than {0}.", exclusiveMin) }, { enabled: prop.maximum !== undefined && (exclusiveMax === undefined || exclusiveMax > prop.maximum), - isValid: (value => value <= prop.maximum), + isValid: (value => value <= prop.maximum!), message: nls.localize('validations.max', "Value must be less than or equal to {0}.", prop.maximum) }, { enabled: prop.minimum !== undefined && (exclusiveMin === undefined || exclusiveMin < prop.minimum), - isValid: (value => value >= prop.minimum), + isValid: (value => value >= prop.minimum!), message: nls.localize('validations.min', "Value must be greater than or equal to {0}.", prop.minimum) }, { enabled: prop.multipleOf !== undefined, - isValid: (value => value % prop.multipleOf === 0), + isValid: (value => value % prop.multipleOf! === 0), message: nls.localize('validations.multipleOf', "Value must be a multiple of {0}.", prop.multipleOf) }, { @@ -1086,17 +1084,17 @@ export function createValidator(prop: IConfigurationPropertySchema): ((value: an const stringValidations: Validator[] = [ { enabled: prop.maxLength !== undefined, - isValid: (value => value.length <= prop.maxLength), + isValid: (value => value.length <= prop.maxLength!), message: nls.localize('validations.maxLength', "Value must be {0} or fewer characters long.", prop.maxLength) }, { enabled: prop.minLength !== undefined, - isValid: (value => value.length >= prop.minLength), + isValid: (value => value.length >= prop.minLength!), message: nls.localize('validations.minLength', "Value must be {0} or more characters long.", prop.minLength) }, { enabled: patternRegex !== undefined, - isValid: (value => patternRegex.test(value)), + isValid: (value => patternRegex!.test(value)), message: prop.patternErrorMessage || nls.localize('validations.regex', "Value must match regex `{0}`.", prop.pattern) }, ].filter(validation => validation.enabled); @@ -1124,6 +1122,41 @@ export function createValidator(prop: IConfigurationPropertySchema): ((value: an }; } +class RawSettingsContentBuilder extends SettingsContentBuilder { + + constructor(private indent: string = '\t') { + super(0); + } + + pushGroup(settingsGroups: ISettingsGroup): void { + this._pushGroup(settingsGroups, this.indent); + } + +} + +export class DefaultRawSettingsEditorModel extends Disposable { + + private _content: string | null = null; + + constructor(private defaultSettings: DefaultSettings) { + super(); + this._register(defaultSettings.onDidChange(() => this._content = null)); + } + + get content(): string { + if (this._content === null) { + const builder = new RawSettingsContentBuilder(); + builder.pushLine('{'); + for (const settingsGroup of this.defaultSettings.getRegisteredGroups()) { + builder.pushGroup(settingsGroup); + } + builder.pushLine('}'); + this._content = builder.getContent(); + } + return this._content; + } +} + function escapeInvisibleChars(enumValue: string): string { return enumValue && enumValue .replace(/\n/g, '\\n') diff --git a/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts b/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts index fcc68214a6..8ca1b3bb52 100644 --- a/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts +++ b/src/vs/workbench/services/preferences/test/common/keybindingsEditorModel.test.ts @@ -610,8 +610,15 @@ suite('KeybindingsEditorModel test', () => { const { ctrlKey, shiftKey, altKey, metaKey } = part.modifiers || { ctrlKey: false, shiftKey: false, altKey: false, metaKey: false }; return new SimpleKeybinding(ctrlKey!, shiftKey!, altKey!, metaKey!, part.keyCode); }; - const keybinding = firstPart ? chordPart ? new ChordKeybinding(aSimpleKeybinding(firstPart), aSimpleKeybinding(chordPart)) : aSimpleKeybinding(firstPart) : null; - return new ResolvedKeybindingItem(keybinding ? new USLayoutResolvedKeybinding(keybinding, OS) : null, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : null, isDefault === undefined ? true : isDefault); + let parts: SimpleKeybinding[] = []; + if (firstPart) { + parts.push(aSimpleKeybinding(firstPart)); + if (chordPart) { + parts.push(aSimpleKeybinding(chordPart)); + } + } + let keybinding = parts.length > 0 ? new USLayoutResolvedKeybinding(new ChordKeybinding(parts), OS) : null; + return new ResolvedKeybindingItem(keybinding, command || 'some command', null, when ? ContextKeyExpr.deserialize(when) : null, isDefault === undefined ? true : isDefault); } function asResolvedKeybindingItems(keybindingEntries: IKeybindingItemEntry[], keepUnassigned: boolean = false): ResolvedKeybindingItem[] { diff --git a/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts b/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts index f6433e3039..9e83c4b145 100644 --- a/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts +++ b/src/vs/workbench/services/preferences/test/common/preferencesModel.test.ts @@ -10,7 +10,7 @@ import { IConfigurationPropertySchema } from 'vs/platform/configuration/common/c suite('Preferences Model test', () => { class Tester { - private validator: (value: any) => string; + private validator: (value: any) => string | null; constructor(private settings: IConfigurationPropertySchema) { this.validator = createValidator(settings)!; @@ -24,8 +24,12 @@ suite('Preferences Model test', () => { assert.notEqual(this.validator(input), '', `Expected ${JSON.stringify(this.settings)} to reject \`${input}\`.`); return { withMessage: - (message) => assert(this.validator(input).indexOf(message) > -1, - `Expected error of ${JSON.stringify(this.settings)} on \`${input}\` to contain ${message}. Got ${this.validator(input)}.`) + (message) => { + const actual = this.validator(input); + assert.ok(actual); + assert(actual!.indexOf(message) > -1, + `Expected error of ${JSON.stringify(this.settings)} on \`${input}\` to contain ${message}. Got ${this.validator(input)}.`); + } }; } diff --git a/src/vs/workbench/services/progress/browser/progressService2.ts b/src/vs/workbench/services/progress/browser/progressService2.ts index e4487c5e06..067c9c340e 100644 --- a/src/vs/workbench/services/progress/browser/progressService2.ts +++ b/src/vs/workbench/services/progress/browser/progressService2.ts @@ -15,6 +15,7 @@ import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/ import { INotificationService, Severity, INotificationHandle, INotificationActions } from 'vs/platform/notification/common/notification'; import { Action } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class ProgressService2 implements IProgressService2 { @@ -38,8 +39,7 @@ export class ProgressService2 implements IProgressService2 { if (viewlet) { return this._withViewletProgress(location, task); } - console.warn(`Bad progress location: ${location}`); - return undefined; + return Promise.reject(new Error(`Bad progress location: ${location}`)); } switch (location) { @@ -54,8 +54,7 @@ export class ProgressService2 implements IProgressService2 { case ProgressLocation.Extensions: return this._withViewletProgress('workbench.view.extensions', task); default: - console.warn(`Bad progress location: ${location}`); - return undefined; + return Promise.reject(new Error(`Bad progress location: ${location}`)); } } @@ -267,3 +266,5 @@ export class ProgressService2 implements IProgressService2 { return promise; } } + +registerSingleton(IProgressService2, ProgressService2, true); diff --git a/src/vs/workbench/services/progress/test/progressService.test.ts b/src/vs/workbench/services/progress/test/progressService.test.ts index a052f0a58c..a491553d18 100644 --- a/src/vs/workbench/services/progress/test/progressService.test.ts +++ b/src/vs/workbench/services/progress/test/progressService.test.ts @@ -6,88 +6,11 @@ import * as assert from 'assert'; import { IAction, IActionItem } from 'vs/base/common/actions'; import { IEditorControl } from 'vs/workbench/common/editor'; -import { Viewlet, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; -import { IPanel } from 'vs/workbench/common/panel'; import { ScopedProgressService, ScopedService } from 'vs/workbench/services/progress/browser/progressService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IViewlet } from 'vs/workbench/common/viewlet'; -import { Emitter } from 'vs/base/common/event'; - -let activeViewlet: Viewlet = {} as any; - -class TestViewletService implements IViewletService { - public _serviceBrand: any; - - onDidViewletRegisterEmitter = new Emitter(); - onDidViewletDeregisterEmitter = new Emitter(); - onDidViewletOpenEmitter = new Emitter(); - onDidViewletCloseEmitter = new Emitter(); - - onDidViewletRegister = this.onDidViewletRegisterEmitter.event; - onDidViewletDeregister = this.onDidViewletDeregisterEmitter.event; - onDidViewletOpen = this.onDidViewletOpenEmitter.event; - onDidViewletClose = this.onDidViewletCloseEmitter.event; - - public openViewlet(id: string, focus?: boolean): Promise { - return Promise.resolve(null!); - } - - public getViewlets(): ViewletDescriptor[] { - return []; - } - - public getAllViewlets(): ViewletDescriptor[] { - return []; - } - - public getActiveViewlet(): IViewlet { - return activeViewlet; - } - - public dispose() { - } - - public getDefaultViewletId(): string { - return 'workbench.view.explorer'; - } - - public getViewlet(id: string): ViewletDescriptor { - return null!; - } - - public getProgressIndicator(id: string) { - return null!; - } -} - -class TestPanelService implements IPanelService { - public _serviceBrand: any; - - onDidPanelOpen = new Emitter<{ panel: IPanel, focus: boolean }>().event; - onDidPanelClose = new Emitter().event; - - public openPanel(id: string, focus?: boolean): IPanel { - return null!; - } - - public getPanels(): any[] { - return []; - } - - public getPinnedPanels(): any[] { - return []; - } - - public getActivePanel(): IViewlet { - return activeViewlet; - } - - public setPanelEnablement(id: string, enabled: boolean): void { } - - public dispose() { - } -} +import { TestViewletService, TestPanelService } from 'vs/workbench/test/workbenchTestServices'; class TestViewlet implements IViewlet { diff --git a/src/vs/workbench/services/remote/node/remoteAgentService.ts b/src/vs/workbench/services/remote/common/remoteAgentService.ts similarity index 59% rename from src/vs/workbench/services/remote/node/remoteAgentService.ts rename to src/vs/workbench/services/remote/common/remoteAgentService.ts index 8aa1f67597..16562a3bd4 100644 --- a/src/vs/workbench/services/remote/node/remoteAgentService.ts +++ b/src/vs/workbench/services/remote/common/remoteAgentService.ts @@ -3,29 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OperatingSystem } from 'vs/base/common/platform'; -import { URI } from 'vs/base/common/uri'; -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import { RemoteAgentConnectionContext } from 'vs/platform/remote/node/remoteAgentConnection'; +import { IRemoteAgentEnvironment, RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; export const RemoteExtensionLogFileName = 'remoteagent'; export const IRemoteAgentService = createDecorator('remoteAgentService'); -export interface IRemoteAgentEnvironment { - pid: number; - appRoot: URI; - appSettingsHome: URI; - logsPath: URI; - extensionsPath: URI; - extensionHostLogsPath: URI; - globalStorageHome: URI; - extensions: IExtensionDescription[]; - os: OperatingSystem; -} - export interface IRemoteAgentService { _serviceBrand: any; @@ -38,5 +23,5 @@ export interface IRemoteAgentConnection { getEnvironment(): Promise; getChannel(channelName: string): T; - registerChannel>(channelName: string, channel: T); + registerChannel>(channelName: string, channel: T): void; } diff --git a/src/vs/workbench/services/remote/common/remoteEnvironmentService.ts b/src/vs/workbench/services/remote/common/remoteEnvironmentService.ts new file mode 100644 index 0000000000..ab6d619b4f --- /dev/null +++ b/src/vs/workbench/services/remote/common/remoteEnvironmentService.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; +import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export interface IRemoteEnvironmentService { + _serviceBrand: any; + remoteEnvironment: Promise; +} + +export const IRemoteEnvironmentService = createDecorator('remoteEnvironmentService'); + +export class RemoteEnvironmentService implements IRemoteEnvironmentService { + _serviceBrand: any; + + constructor( + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + ) { } + + get remoteEnvironment(): Promise { + const connection = this.remoteAgentService.getConnection(); + if (connection) { + return connection.getEnvironment(); + } + return Promise.resolve(null); + } +} + +registerSingleton(IRemoteEnvironmentService, RemoteEnvironmentService, true); diff --git a/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts b/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts index 62115f0df3..df68669523 100644 --- a/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts +++ b/src/vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl.ts @@ -5,15 +5,24 @@ import { localize } from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IChannel, getDelayedChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; +import { getDelayedChannel } from 'vs/base/parts/ipc/node/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.net'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { connectRemoteAgentManagement, RemoteAgentConnectionContext } from 'vs/platform/remote/node/remoteAgentConnection'; -import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; +import { connectRemoteAgentManagement } from 'vs/platform/remote/node/remoteAgentConnection'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { RemoteExtensionEnvironmentChannelClient } from 'vs/workbench/services/remote/node/remoteAgentEnvironmentChannel'; -import { IRemoteAgentConnection, IRemoteAgentEnvironment, IRemoteAgentService } from 'vs/workbench/services/remote/node/remoteAgentService'; +import { IRemoteAgentConnection, IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { DialogChannel } from 'vs/platform/dialogs/node/dialogIpc'; +import { DownloadServiceChannel } from 'vs/platform/download/node/downloadIpc'; +import { LogLevelSetterChannel } from 'vs/platform/log/node/logIpc'; +import { ILogService } from 'vs/platform/log/common/log'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IRemoteAgentEnvironment, RemoteAgentConnectionContext } from 'vs/platform/remote/common/remoteAgentEnvironment'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; export class RemoteAgentService implements IRemoteAgentService { @@ -22,13 +31,23 @@ export class RemoteAgentService implements IRemoteAgentService { private readonly _connection: IRemoteAgentConnection | null = null; constructor( - window: IWindowConfiguration, + @IWindowService windowService: IWindowService, @INotificationService notificationService: INotificationService, @IEnvironmentService environmentService: IEnvironmentService, - @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService + @IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService, + @ILifecycleService lifecycleService: ILifecycleService, + @ILogService logService: ILogService, + @IInstantiationService instantiationService: IInstantiationService ) { - if (window.remoteAuthority) { - this._connection = new RemoteAgentConnection(window.remoteAuthority, notificationService, environmentService, remoteAuthorityResolverService); + const { remoteAuthority } = windowService.getConfiguration(); + if (remoteAuthority) { + const connection = this._connection = new RemoteAgentConnection(remoteAuthority, notificationService, environmentService, remoteAuthorityResolverService); + + lifecycleService.when(LifecyclePhase.Ready).then(() => { + connection.registerChannel('dialog', instantiationService.createInstance(DialogChannel)); + connection.registerChannel('download', new DownloadServiceChannel()); + connection.registerChannel('loglevel', new LogLevelSetterChannel(logService)); + }); } } @@ -83,3 +102,5 @@ class RemoteAgentConnection extends Disposable implements IRemoteAgentConnection return this._connection; } } + +registerSingleton(IRemoteAgentService, RemoteAgentService); \ No newline at end of file diff --git a/src/vs/workbench/services/remote/node/remoteAgentEnvironmentChannel.ts b/src/vs/workbench/services/remote/node/remoteAgentEnvironmentChannel.ts index d55f3ce803..d159e505c9 100644 --- a/src/vs/workbench/services/remote/node/remoteAgentEnvironmentChannel.ts +++ b/src/vs/workbench/services/remote/node/remoteAgentEnvironmentChannel.ts @@ -3,11 +3,17 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { OperatingSystem } from 'vs/base/common/platform'; +import * as platform from 'vs/base/common/platform'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IChannel } from 'vs/base/parts/ipc/node/ipc'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import { IRemoteAgentEnvironment } from 'vs/workbench/services/remote/node/remoteAgentService'; +import { IChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; + +export interface IGetEnvironmentDataArguments { + language: string; + remoteAuthority: string; + extensionDevelopmentPath: UriComponents | undefined; +} export interface IRemoteAgentEnvironmentDTO { pid: number; @@ -17,8 +23,10 @@ export interface IRemoteAgentEnvironmentDTO { extensionsPath: UriComponents; extensionHostLogsPath: UriComponents; globalStorageHome: UriComponents; + userHome: UriComponents; extensions: IExtensionDescription[]; - os: OperatingSystem; + os: platform.OperatingSystem; + syncExtensions: boolean; } export class RemoteExtensionEnvironmentChannelClient { @@ -26,7 +34,12 @@ export class RemoteExtensionEnvironmentChannelClient { constructor(private channel: IChannel) { } getEnvironmentData(remoteAuthority: string, extensionDevelopmentPath?: URI): Promise { - return this.channel.call('getEnvironmentData', [remoteAuthority, extensionDevelopmentPath]) + const args: IGetEnvironmentDataArguments = { + language: platform.language, + remoteAuthority, + extensionDevelopmentPath + }; + return this.channel.call('getEnvironmentData', args) .then((data: IRemoteAgentEnvironmentDTO): IRemoteAgentEnvironment => { return { pid: data.pid, @@ -36,8 +49,10 @@ export class RemoteExtensionEnvironmentChannelClient { extensionsPath: URI.revive(data.extensionsPath), extensionHostLogsPath: URI.revive(data.extensionHostLogsPath), globalStorageHome: URI.revive(data.globalStorageHome), + userHome: URI.revive(data.userHome), extensions: data.extensions.map(ext => { (ext).extensionLocation = URI.revive(ext.extensionLocation); return ext; }), - os: data.os + os: data.os, + syncExtensions: data.syncExtensions }; }); } diff --git a/src/vs/platform/search/common/replace.ts b/src/vs/workbench/services/search/common/replace.ts similarity index 98% rename from src/vs/platform/search/common/replace.ts rename to src/vs/workbench/services/search/common/replace.ts index 163f4f131a..1f7b15da7b 100644 --- a/src/vs/platform/search/common/replace.ts +++ b/src/vs/workbench/services/search/common/replace.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as strings from 'vs/base/common/strings'; -import { IPatternInfo } from 'vs/platform/search/common/search'; +import { IPatternInfo } from 'vs/workbench/services/search/common/search'; import { CharCode } from 'vs/base/common/charCode'; export class ReplacePattern { diff --git a/src/vs/platform/search/common/search.ts b/src/vs/workbench/services/search/common/search.ts similarity index 64% rename from src/vs/platform/search/common/search.ts rename to src/vs/workbench/services/search/common/search.ts index 70e76e6aa0..cd7fc49094 100644 --- a/src/vs/platform/search/common/search.ts +++ b/src/vs/workbench/services/search/common/search.ts @@ -5,19 +5,27 @@ import { mapArrayOrNot } from 'vs/base/common/arrays'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; import { IDisposable } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; -import * as paths from 'vs/base/common/paths'; +import * as extpath from 'vs/base/common/extpath'; import { getNLines } from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; import { IFilesConfiguration } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; +import { Event } from 'vs/base/common/event'; +import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views'; +import { Registry } from 'vs/platform/registry/common/platform'; +export const VIEWLET_ID = 'workbench.view.search'; +export const PANEL_ID = 'workbench.view.search'; export const VIEW_ID = 'workbench.view.search'; +/** + * Search viewlet container. + */ +export const VIEW_CONTAINER: ViewContainer = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID, true); -export const ISearchHistoryService = createDecorator('searchHistoryService'); export const ISearchService = createDecorator('searchService'); /** @@ -27,38 +35,21 @@ export interface ISearchService { _serviceBrand: any; textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (result: ISearchProgressItem) => void): Promise; fileSearch(query: IFileQuery, token?: CancellationToken): Promise; - extendQuery(query: ITextQuery | IFileQuery): void; clearCache(cacheKey: string): Promise; registerSearchResultProvider(scheme: string, type: SearchProviderType, provider: ISearchResultProvider): IDisposable; } -export interface ISearchHistoryValues { - search?: string[]; - replace?: string[]; - include?: string[]; - exclude?: string[]; -} - -export interface ISearchHistoryService { - _serviceBrand: any; - onDidClearHistory: Event; - clearHistory(): void; - load(): ISearchHistoryValues; - save(history: ISearchHistoryValues): void; -} - /** * TODO@roblou - split text from file search entirely, or share code in a more natural way. */ export const enum SearchProviderType { file, - fileIndex, text } export interface ISearchResultProvider { - textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): Promise; - fileSearch(query: IFileQuery, token?: CancellationToken): Promise; + textSearch(query: ITextQuery, onProgress?: (p: ISearchProgressItem) => void, token?: CancellationToken): Promise; + fileSearch(query: IFileQuery, token?: CancellationToken): Promise; clearCache(cacheKey: string): Promise; } @@ -89,9 +80,6 @@ export interface IFileQueryProps extends ICommonQueryPr type: QueryType.File; filePattern?: string; - // TODO: Remove this! - disregardExcludeSettings?: boolean; - /** * If true no results will be returned. Instead `limitHit` will indicate if at least one result exists or not. * Currently does not work with queries including a 'siblings clause'. @@ -216,10 +204,10 @@ export interface ITextSearchStats { export interface IFileSearchStats { fromCache: boolean; - detailStats: ISearchEngineStats | ICachedSearchStats | IFileSearchProviderStats | IFileIndexProviderStats; + detailStats: ISearchEngineStats | ICachedSearchStats | IFileSearchProviderStats; resultCount: number; - type: 'fileIndexProvider' | 'fileSearchProvider' | 'searchProcess'; + type: 'fileSearchProvider' | 'searchProcess'; sortingTime?: number; } @@ -243,14 +231,6 @@ export interface IFileSearchProviderStats { postProcessTime: number; } -export interface IFileIndexProviderStats { - providerTime: number; - providerResultCount: number; - fileWalkTime: number; - directoriesWalked: number; - filesWalked: number; -} - export class FileMatch implements IFileMatch { results: ITextSearchResult[] = []; constructor(public resource: URI) { @@ -328,6 +308,7 @@ export interface ISearchConfigurationProperties { showLineNumbers: boolean; usePCRE2: boolean; actionsPosition: 'auto' | 'right'; + maintainFileSearchCache: boolean; collapseResults: 'auto' | 'alwaysCollapse' | 'alwaysExpand'; } @@ -338,9 +319,9 @@ export interface ISearchConfiguration extends IFilesConfiguration { }; } -export function getExcludes(configuration: ISearchConfiguration): glob.IExpression | undefined { +export function getExcludes(configuration: ISearchConfiguration, includeSearchExcludes = true): glob.IExpression | undefined { const fileExcludes = configuration && configuration.files && configuration.files.exclude; - const searchExcludes = configuration && configuration.search && configuration.search.exclude; + const searchExcludes = includeSearchExcludes && configuration && configuration.search && configuration.search.exclude; if (!fileExcludes && !searchExcludes) { return undefined; @@ -371,7 +352,7 @@ export function pathIncludedInQuery(queryProps: ICommonQueryProps, fsPath: if (queryProps.usingSearchPaths) { return !!queryProps.folderQueries && queryProps.folderQueries.every(fq => { const searchPath = fq.folder.fsPath; - if (paths.isEqualOrParent(fsPath, searchPath)) { + if (extpath.isEqualOrParent(fsPath, searchPath)) { return !fq.includePattern || !!glob.match(fq.includePattern, fsPath); } else { return false; @@ -410,3 +391,196 @@ export function serializeSearchError(searchError: SearchError): Error { const details = { message: searchError.message, code: searchError.code }; return new Error(JSON.stringify(details)); } +export interface ITelemetryEvent { + eventName: string; + data: ITelemetryData; +} + +export interface IRawSearchService { + fileSearch(search: IRawFileQuery): Event; + textSearch(search: IRawTextQuery): Event; + clearCache(cacheKey: string): Promise; +} + +export interface IRawFileMatch { + base?: string; + relativePath: string; + basename: string; + size?: number; +} + +export interface ISearchEngine { + search: (onResult: (matches: T) => void, onProgress: (progress: IProgress) => void, done: (error: Error | null, complete: ISearchEngineSuccess) => void) => void; + cancel: () => void; +} + +export interface ISerializedSearchSuccess { + type: 'success'; + limitHit: boolean; + stats: IFileSearchStats | ITextSearchStats | null; +} + +export interface ISearchEngineSuccess { + limitHit: boolean; + stats: ISearchEngineStats; +} + +export interface ISerializedSearchError { + type: 'error'; + error: { + message: string, + stack: string + }; +} + +export type ISerializedSearchComplete = ISerializedSearchSuccess | ISerializedSearchError; + +export function isSerializedSearchComplete(arg: ISerializedSearchProgressItem | ISerializedSearchComplete): arg is ISerializedSearchComplete { + if ((arg as any).type === 'error') { + return true; + } else if ((arg as any).type === 'success') { + return true; + } else { + return false; + } +} + +export function isSerializedSearchSuccess(arg: ISerializedSearchComplete): arg is ISerializedSearchSuccess { + return arg.type === 'success'; +} + +export function isSerializedFileMatch(arg: ISerializedSearchProgressItem): arg is ISerializedFileMatch { + return !!(arg).path; +} + +export interface ISerializedFileMatch { + path?: string; + results?: ITextSearchResult[]; + numMatches?: number; +} + +// Type of the possible values for progress calls from the engine +export type ISerializedSearchProgressItem = ISerializedFileMatch | ISerializedFileMatch[] | IProgress; +export type IFileSearchProgressItem = IRawFileMatch | IRawFileMatch[] | IProgress; + + +export class SerializableFileMatch implements ISerializedFileMatch { + path: string; + results: ITextSearchMatch[]; + + constructor(path: string) { + this.path = path; + this.results = []; + } + + addMatch(match: ITextSearchMatch): void { + this.results.push(match); + } + + serialize(): ISerializedFileMatch { + return { + path: this.path, + results: this.results, + numMatches: this.results.length + }; + } +} + +/** + * Computes the patterns that the provider handles. Discards sibling clauses and 'false' patterns + */ +export function resolvePatternsForProvider(globalPattern: glob.IExpression | undefined, folderPattern: glob.IExpression | undefined): string[] { + const merged = { + ...(globalPattern || {}), + ...(folderPattern || {}) + }; + + return Object.keys(merged) + .filter(key => { + const value = merged[key]; + return typeof value === 'boolean' && value; + }); +} + +export class QueryGlobTester { + + private _excludeExpression: glob.IExpression; + private _parsedExcludeExpression: glob.ParsedExpression; + + private _parsedIncludeExpression: glob.ParsedExpression; + + constructor(config: ISearchQuery, folderQuery: IFolderQuery) { + this._excludeExpression = { + ...(config.excludePattern || {}), + ...(folderQuery.excludePattern || {}) + }; + this._parsedExcludeExpression = glob.parse(this._excludeExpression); + + // Empty includeExpression means include nothing, so no {} shortcuts + let includeExpression: glob.IExpression | undefined = config.includePattern; + if (folderQuery.includePattern) { + if (includeExpression) { + includeExpression = { + ...includeExpression, + ...folderQuery.includePattern + }; + } else { + includeExpression = folderQuery.includePattern; + } + } + + if (includeExpression) { + this._parsedIncludeExpression = glob.parse(includeExpression); + } + } + + /** + * Guaranteed sync - siblingsFn should not return a promise. + */ + includedInQuerySync(testPath: string, basename?: string, hasSibling?: (name: string) => boolean): boolean { + if (this._parsedExcludeExpression && this._parsedExcludeExpression(testPath, basename, hasSibling)) { + return false; + } + + if (this._parsedIncludeExpression && !this._parsedIncludeExpression(testPath, basename, hasSibling)) { + return false; + } + + return true; + } + + /** + * Guaranteed async. + */ + includedInQuery(testPath: string, basename?: string, hasSibling?: (name: string) => boolean | Promise): Promise { + const excludeP = this._parsedExcludeExpression ? + Promise.resolve(this._parsedExcludeExpression(testPath, basename, hasSibling)).then(result => !!result) : + Promise.resolve(false); + + return excludeP.then(excluded => { + if (excluded) { + return false; + } + + return this._parsedIncludeExpression ? + Promise.resolve(this._parsedIncludeExpression(testPath, basename, hasSibling)).then(result => !!result) : + Promise.resolve(true); + }).then(included => { + return included; + }); + } + + hasSiblingExcludeClauses(): boolean { + return hasSiblingClauses(this._excludeExpression); + } +} + +function hasSiblingClauses(pattern: glob.IExpression): boolean { + for (const key in pattern) { + if (typeof pattern[key] !== 'boolean') { + return true; + } + } + + return false; +} diff --git a/src/vs/workbench/services/search/common/searchHelpers.ts b/src/vs/workbench/services/search/common/searchHelpers.ts index 63192f24ab..3d4c0f9d44 100644 --- a/src/vs/workbench/services/search/common/searchHelpers.ts +++ b/src/vs/workbench/services/search/common/searchHelpers.ts @@ -5,7 +5,7 @@ import { Range } from 'vs/editor/common/core/range'; import { FindMatch, ITextModel } from 'vs/editor/common/model'; -import { ITextSearchPreviewOptions, TextSearchMatch, ITextSearchResult, ITextSearchMatch, ITextQuery, ITextSearchContext } from 'vs/platform/search/common/search'; +import { ITextSearchPreviewOptions, TextSearchMatch, ITextSearchResult, ITextSearchMatch, ITextQuery, ITextSearchContext } from 'vs/workbench/services/search/common/search'; function editorMatchToTextSearchResult(matches: FindMatch[], model: ITextModel, previewOptions?: ITextSearchPreviewOptions): TextSearchMatch { const firstLine = matches[0].range.startLineNumber; diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index 19b3332ba2..ffa57cefd3 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -5,7 +5,7 @@ import * as childProcess from 'child_process'; import * as fs from 'fs'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { Readable } from 'stream'; import { NodeStringDecoder, StringDecoder } from 'string_decoder'; import * as arrays from 'vs/base/common/arrays'; @@ -13,7 +13,7 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as glob from 'vs/base/common/glob'; import * as normalization from 'vs/base/common/normalization'; import * as objects from 'vs/base/common/objects'; -import { isEqualOrParent } from 'vs/base/common/paths'; +import { isEqualOrParent } from 'vs/base/common/extpath'; import * as platform from 'vs/base/common/platform'; import { StopWatch } from 'vs/base/common/stopwatch'; import * as strings from 'vs/base/common/strings'; @@ -21,8 +21,7 @@ import * as types from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; import * as extfs from 'vs/base/node/extfs'; import * as flow from 'vs/base/node/flow'; -import { IFileQuery, IFolderQuery, IProgress, ISearchEngineStats } from 'vs/platform/search/common/search'; -import { IRawFileMatch, ISearchEngine, ISearchEngineSuccess } from 'vs/workbench/services/search/node/search'; +import { IFileQuery, IFolderQuery, IProgress, ISearchEngineStats, IRawFileMatch, ISearchEngine, ISearchEngineSuccess } from 'vs/workbench/services/search/common/search'; import { spawnRipgrepCmd } from './ripgrepFileSearch'; interface IDirectoryEntry { diff --git a/src/vs/workbench/services/search/node/fileSearchManager.ts b/src/vs/workbench/services/search/node/fileSearchManager.ts index 2847a0088b..b89155565e 100644 --- a/src/vs/workbench/services/search/node/fileSearchManager.ts +++ b/src/vs/workbench/services/search/node/fileSearchManager.ts @@ -3,15 +3,14 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as glob from 'vs/base/common/glob'; import * as resources from 'vs/base/common/resources'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI } from 'vs/base/common/uri'; -import { IFileMatch, IFileSearchProviderStats, IFolderQuery, ISearchCompleteStats, IFileQuery } from 'vs/platform/search/common/search'; -import { QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/node/search'; +import { IFileMatch, IFileSearchProviderStats, IFolderQuery, ISearchCompleteStats, IFileQuery, QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/common/search'; import * as vscode from 'vscode'; export interface IInternalFileMatch { diff --git a/src/vs/workbench/services/search/node/rawSearchService.ts b/src/vs/workbench/services/search/node/rawSearchService.ts index dc4f9cd851..2a4f5fb955 100644 --- a/src/vs/workbench/services/search/node/rawSearchService.ts +++ b/src/vs/workbench/services/search/node/rawSearchService.ts @@ -5,7 +5,7 @@ import * as fs from 'fs'; import * as gracefulFs from 'graceful-fs'; -import { join, sep } from 'path'; +import { join, sep } from 'vs/base/common/path'; import * as arrays from 'vs/base/common/arrays'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -16,11 +16,10 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import * as strings from 'vs/base/common/strings'; import { URI, UriComponents } from 'vs/base/common/uri'; import { compareItemsByScore, IItemAccessor, prepareQuery, ScorerCache } from 'vs/base/parts/quickopen/common/quickOpenScorer'; -import { MAX_FILE_SIZE } from 'vs/platform/files/node/files'; -import { ICachedSearchStats, IFileQuery, IFileSearchStats, IFolderQuery, IProgress, IRawFileQuery, IRawQuery, IRawTextQuery, ITextQuery } from 'vs/platform/search/common/search'; +import { MAX_FILE_SIZE } from 'vs/platform/files/node/fileConstants'; +import { ICachedSearchStats, IFileQuery, IFileSearchStats, IFolderQuery, IProgress, IRawFileQuery, IRawQuery, IRawTextQuery, ITextQuery, IFileSearchProgressItem, IRawFileMatch, IRawSearchService, ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess } from 'vs/workbench/services/search/common/search'; import { Engine as FileSearchEngine } from 'vs/workbench/services/search/node/fileSearch'; import { TextSearchEngineAdapter } from 'vs/workbench/services/search/node/textSearchAdapter'; -import { IFileSearchProgressItem, IRawFileMatch, IRawSearchService, ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess } from './search'; gracefulFs.gracefulify(fs); diff --git a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts index d7c361e810..44e08546e1 100644 --- a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts +++ b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import * as cp from 'child_process'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as glob from 'vs/base/common/glob'; import { normalizeNFD } from 'vs/base/common/normalization'; import * as objects from 'vs/base/common/objects'; -import * as paths from 'vs/base/common/paths'; +import * as extpath from 'vs/base/common/extpath'; import { isMacintosh as isMac } from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; -import { IFileQuery, IFolderQuery } from 'vs/platform/search/common/search'; +import { IFileQuery, IFolderQuery } from 'vs/workbench/services/search/common/search'; import { anchorGlob } from 'vs/workbench/services/search/node/ripgrepSearchUtils'; import { rgPath } from 'vscode-ripgrep'; @@ -159,7 +159,7 @@ function globExprsToRgGlobs(patterns: glob.IExpression, folder?: string, exclude * Exported for testing */ export function getAbsoluteGlob(folder: string, key: string): string { - return paths.isAbsolute(key) ? + return path.isAbsolute(key) ? key : path.join(folder, key); } @@ -170,7 +170,7 @@ function trimTrailingSlash(str: string): string { } export function fixDriveC(path: string): string { - const root = paths.getRoot(path); + const root = extpath.getRoot(path); return root.toLowerCase() === 'c:/' ? path.replace(/^c:[/\\]/i, '/') : path; diff --git a/src/vs/workbench/services/search/node/ripgrepSearchUtils.ts b/src/vs/workbench/services/search/node/ripgrepSearchUtils.ts index 771f41caff..63b14842b0 100644 --- a/src/vs/workbench/services/search/node/ripgrepSearchUtils.ts +++ b/src/vs/workbench/services/search/node/ripgrepSearchUtils.ts @@ -5,7 +5,7 @@ import { startsWith } from 'vs/base/common/strings'; import { ILogService } from 'vs/platform/log/common/log'; -import { SearchRange, TextSearchMatch } from 'vs/platform/search/common/search'; +import { SearchRange, TextSearchMatch } from 'vs/workbench/services/search/common/search'; import * as vscode from 'vscode'; import { mapArrayOrNot } from 'vs/base/common/arrays'; diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts index 40b941916e..7e01a4934a 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearchEngine.ts @@ -5,11 +5,11 @@ import * as cp from 'child_process'; import { EventEmitter } from 'events'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { NodeStringDecoder, StringDecoder } from 'string_decoder'; import { createRegExp, startsWith, startsWithUTF8BOM, stripUTF8BOM, escapeRegExpCharacters, endsWith } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; -import { IExtendedExtensionSearchOptions, SearchError, SearchErrorCode, serializeSearchError } from 'vs/platform/search/common/search'; +import { IExtendedExtensionSearchOptions, SearchError, SearchErrorCode, serializeSearchError } from 'vs/workbench/services/search/common/search'; import * as vscode from 'vscode'; import { rgPath } from 'vscode-ripgrep'; import { anchorGlob, createTextSearchResult, IOutputChannel, Maybe, Range } from './ripgrepSearchUtils'; diff --git a/src/vs/workbench/services/search/node/search.ts b/src/vs/workbench/services/search/node/search.ts deleted file mode 100644 index 2803e7c5b2..0000000000 --- a/src/vs/workbench/services/search/node/search.ts +++ /dev/null @@ -1,203 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { Event } from 'vs/base/common/event'; -import * as glob from 'vs/base/common/glob'; -import { IFileSearchStats, IFolderQuery, IProgress, IRawFileQuery, IRawTextQuery, ISearchEngineStats, ISearchQuery, ITextSearchMatch, ITextSearchStats, ITextSearchResult } from 'vs/platform/search/common/search'; -import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; - -export interface ITelemetryEvent { - eventName: string; - data: ITelemetryData; -} - -export interface IRawSearchService { - fileSearch(search: IRawFileQuery): Event; - textSearch(search: IRawTextQuery): Event; - clearCache(cacheKey: string): Promise; -} - -export interface IRawFileMatch { - base?: string; - relativePath: string; - basename: string; - size?: number; -} - -export interface ISearchEngine { - search: (onResult: (matches: T) => void, onProgress: (progress: IProgress) => void, done: (error: Error | null, complete: ISearchEngineSuccess) => void) => void; - cancel: () => void; -} - -export interface ISerializedSearchSuccess { - type: 'success'; - limitHit: boolean; - stats: IFileSearchStats | ITextSearchStats | null; -} - -export interface ISearchEngineSuccess { - limitHit: boolean; - stats: ISearchEngineStats; -} - -export interface ISerializedSearchError { - type: 'error'; - error: { - message: string, - stack: string - }; -} - -export type ISerializedSearchComplete = ISerializedSearchSuccess | ISerializedSearchError; - -export function isSerializedSearchComplete(arg: ISerializedSearchProgressItem | ISerializedSearchComplete): arg is ISerializedSearchComplete { - if ((arg as any).type === 'error') { - return true; - } else if ((arg as any).type === 'success') { - return true; - } else { - return false; - } -} - -export function isSerializedSearchSuccess(arg: ISerializedSearchComplete): arg is ISerializedSearchSuccess { - return arg.type === 'success'; -} - -export function isSerializedFileMatch(arg: ISerializedSearchProgressItem): arg is ISerializedFileMatch { - return !!(arg).path; -} - -export interface ISerializedFileMatch { - path?: string; - results?: ITextSearchResult[]; - numMatches?: number; -} - -// Type of the possible values for progress calls from the engine -export type ISerializedSearchProgressItem = ISerializedFileMatch | ISerializedFileMatch[] | IProgress; -export type IFileSearchProgressItem = IRawFileMatch | IRawFileMatch[] | IProgress; - - -export class FileMatch implements ISerializedFileMatch { - path: string; - results: ITextSearchMatch[]; - - constructor(path: string) { - this.path = path; - this.results = []; - } - - addMatch(match: ITextSearchMatch): void { - this.results.push(match); - } - - serialize(): ISerializedFileMatch { - return { - path: this.path, - results: this.results, - numMatches: this.results.length - }; - } -} - -/** - * Computes the patterns that the provider handles. Discards sibling clauses and 'false' patterns - */ -export function resolvePatternsForProvider(globalPattern: glob.IExpression | undefined, folderPattern: glob.IExpression | undefined): string[] { - const merged = { - ...(globalPattern || {}), - ...(folderPattern || {}) - }; - - return Object.keys(merged) - .filter(key => { - const value = merged[key]; - return typeof value === 'boolean' && value; - }); -} - -export class QueryGlobTester { - - private _excludeExpression: glob.IExpression; - private _parsedExcludeExpression: glob.ParsedExpression; - - private _parsedIncludeExpression: glob.ParsedExpression; - - constructor(config: ISearchQuery, folderQuery: IFolderQuery) { - this._excludeExpression = { - ...(config.excludePattern || {}), - ...(folderQuery.excludePattern || {}) - }; - this._parsedExcludeExpression = glob.parse(this._excludeExpression); - - // Empty includeExpression means include nothing, so no {} shortcuts - let includeExpression: glob.IExpression | undefined = config.includePattern; - if (folderQuery.includePattern) { - if (includeExpression) { - includeExpression = { - ...includeExpression, - ...folderQuery.includePattern - }; - } else { - includeExpression = folderQuery.includePattern; - } - } - - if (includeExpression) { - this._parsedIncludeExpression = glob.parse(includeExpression); - } - } - - /** - * Guaranteed sync - siblingsFn should not return a promise. - */ - includedInQuerySync(testPath: string, basename?: string, hasSibling?: (name: string) => boolean): boolean { - if (this._parsedExcludeExpression && this._parsedExcludeExpression(testPath, basename, hasSibling)) { - return false; - } - - if (this._parsedIncludeExpression && !this._parsedIncludeExpression(testPath, basename, hasSibling)) { - return false; - } - - return true; - } - - /** - * Guaranteed async. - */ - includedInQuery(testPath: string, basename?: string, hasSibling?: (name: string) => boolean | Promise): Promise { - const excludeP = this._parsedExcludeExpression ? - Promise.resolve(this._parsedExcludeExpression(testPath, basename, hasSibling)).then(result => !!result) : - Promise.resolve(false); - - return excludeP.then(excluded => { - if (excluded) { - return false; - } - - return this._parsedIncludeExpression ? - Promise.resolve(this._parsedIncludeExpression(testPath, basename, hasSibling)).then(result => !!result) : - Promise.resolve(true); - }).then(included => { - return included; - }); - } - - hasSiblingExcludeClauses(): boolean { - return hasSiblingClauses(this._excludeExpression); - } -} - -function hasSiblingClauses(pattern: glob.IExpression): boolean { - for (const key in pattern) { - if (typeof pattern[key] !== 'boolean') { - return true; - } - } - - return false; -} diff --git a/src/vs/workbench/services/search/node/searchIpc.ts b/src/vs/workbench/services/search/node/searchIpc.ts index 1f7707fde3..f077ebe296 100644 --- a/src/vs/workbench/services/search/node/searchIpc.ts +++ b/src/vs/workbench/services/search/node/searchIpc.ts @@ -4,9 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { Event } from 'vs/base/common/event'; -import { IChannel, IServerChannel } from 'vs/base/parts/ipc/node/ipc'; -import { IRawFileQuery, IRawTextQuery } from 'vs/platform/search/common/search'; -import { IRawSearchService, ISerializedSearchComplete, ISerializedSearchProgressItem } from './search'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { IRawFileQuery, IRawTextQuery, IRawSearchService, ISerializedSearchComplete, ISerializedSearchProgressItem } from 'vs/workbench/services/search/common/search'; export class SearchChannel implements IServerChannel { diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index 2ea28a481c..5db60a1eb5 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -11,25 +11,24 @@ import { Event } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { keys, ResourceMap, values } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; -import * as objects from 'vs/base/common/objects'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI as uri } from 'vs/base/common/uri'; import * as pfs from 'vs/base/node/pfs'; import { getNextTickChannel } from 'vs/base/parts/ipc/node/ipc'; import { Client, IIPCOptions } from 'vs/base/parts/ipc/node/ipc.cp'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IDebugParams, IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ILogService } from 'vs/platform/log/common/log'; -import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgress, ISearchComplete, ISearchConfiguration, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType } from 'vs/platform/search/common/search'; +import { deserializeSearchError, FileMatch, ICachedSearchStats, IFileMatch, IFileQuery, IFileSearchStats, IFolderQuery, IProgress, ISearchComplete, ISearchEngineStats, ISearchProgressItem, ISearchQuery, ISearchResultProvider, ISearchService, ITextQuery, pathIncludedInQuery, QueryType, SearchError, SearchErrorCode, SearchProviderType, ISearchConfiguration, IRawSearchService, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess } from 'vs/workbench/services/search/common/search'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { addContextToEditorMatches, editorMatchesToTextSearchResults } from 'vs/workbench/services/search/common/searchHelpers'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { IRawSearchService, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess } from './search'; import { SearchChannelClient } from './searchIpc'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class SearchService extends Disposable implements ISearchService { _serviceBrand: any; @@ -37,7 +36,6 @@ export class SearchService extends Disposable implements ISearchService { private diskSearch: DiskSearch; private readonly fileSearchProviders = new Map(); private readonly textSearchProviders = new Map(); - private readonly fileIndexProviders = new Map(); constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -46,12 +44,11 @@ export class SearchService extends Disposable implements ISearchService { @IEditorService private readonly editorService: IEditorService, @IEnvironmentService environmentService: IEnvironmentService, @ITelemetryService private readonly telemetryService: ITelemetryService, - @IConfigurationService private readonly configurationService: IConfigurationService, @ILogService private readonly logService: ILogService, @IExtensionService private readonly extensionService: IExtensionService ) { super(); - this.diskSearch = this.instantiationService.createInstance(DiskSearch, !environmentService.isBuilt || environmentService.verbose, /*timeout=*/undefined, environmentService.debugSearch); + this.diskSearch = this.instantiationService.createInstance(DiskSearch, !environmentService.isBuilt || environmentService.verbose, environmentService.debugSearch); } registerSearchResultProvider(scheme: string, type: SearchProviderType, provider: ISearchResultProvider): IDisposable { @@ -60,8 +57,8 @@ export class SearchService extends Disposable implements ISearchService { list = this.fileSearchProviders; } else if (type === SearchProviderType.text) { list = this.textSearchProviders; - } else if (type === SearchProviderType.fileIndex) { - list = this.fileIndexProviders; + } else { + throw new Error('Unknown SearchProviderType'); } list.set(scheme, provider); @@ -71,22 +68,6 @@ export class SearchService extends Disposable implements ISearchService { }); } - extendQuery(query: IFileQuery): void { - const configuration = this.configurationService.getValue(); - - // Configuration: File Excludes - if (!query.disregardExcludeSettings) { - const fileExcludes = objects.deepClone(configuration && configuration.files && configuration.files.exclude); - if (fileExcludes) { - if (!query.excludePattern) { - query.excludePattern = fileExcludes; - } else { - objects.mixin(query.excludePattern, fileExcludes, false /* no overwrite */); - } - } - } - } - textSearch(query: ITextQuery, token?: CancellationToken, onProgress?: (item: ISearchProgressItem) => void): Promise { // Get local results from dirty/untitled const localResults = this.getLocalResults(query); @@ -95,8 +76,6 @@ export class SearchService extends Disposable implements ISearchService { arrays.coalesce(localResults.values()).forEach(onProgress); } - this.logService.trace('SearchService#search', JSON.stringify(query)); - const onProviderProgress = progress => { if (progress.resource) { // Match @@ -121,6 +100,8 @@ export class SearchService extends Disposable implements ISearchService { } private doSearch(query: ISearchQuery, token?: CancellationToken, onProgress?: (item: ISearchProgressItem) => void): Promise { + this.logService.trace('SearchService#search', JSON.stringify(query)); + const schemesInQuery = this.getSchemesInQuery(query); const providerActivations: Promise[] = [Promise.resolve(null)]; @@ -197,7 +178,7 @@ export class SearchService extends Disposable implements ISearchService { keys(fqs).forEach(scheme => { const schemeFQs = fqs.get(scheme); const provider = query.type === QueryType.File ? - this.fileSearchProviders.get(scheme) || this.fileIndexProviders.get(scheme) : + this.fileSearchProviders.get(scheme) : this.textSearchProviders.get(scheme); if (!provider && scheme === 'file') { @@ -339,7 +320,7 @@ export class SearchService extends Disposable implements ISearchService { }); } } else if (query.type === QueryType.Text) { - let errorType: string; + let errorType: string | undefined; if (err) { errorType = err.code === SearchErrorCode.regexParseError ? 'regex' : err.code === SearchErrorCode.unknownEncoding ? 'encoding' : @@ -436,7 +417,6 @@ export class SearchService extends Disposable implements ISearchService { clearCache(cacheKey: string): Promise { const clearPs = [ this.diskSearch, - ...values(this.fileIndexProviders), ...values(this.fileSearchProviders) ].map(provider => provider && provider.clearCache(cacheKey)); @@ -446,19 +426,21 @@ export class SearchService extends Disposable implements ISearchService { } export class DiskSearch implements ISearchResultProvider { - _serviceBrand: any; - private raw: IRawSearchService; constructor( verboseLogging: boolean, - timeout: number = 60 * 60 * 1000, searchDebug: IDebugParams | undefined, - @ILogService private readonly logService: ILogService + @ILogService private readonly logService: ILogService, + @IConfigurationService private readonly configService: IConfigurationService, ) { + const timeout = this.configService.getValue().search.maintainFileSearchCache ? + Number.MAX_VALUE : + 60 * 60 * 1000; + const opts: IIPCOptions = { serverName: 'Search', - timeout: timeout, + timeout, args: ['--type=searchService'], // See https://github.com/Microsoft/vscode/issues/27665 // Pass in fresh execArgv to the forked process such that it doesn't inherit them from `process.execArgv`. @@ -600,3 +582,5 @@ export class DiskSearch implements ISearchResultProvider { return this.raw.clearCache(cacheKey); } } + +registerSingleton(ISearchService, SearchService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/search/node/textSearchAdapter.ts b/src/vs/workbench/services/search/node/textSearchAdapter.ts index 7ac9e0eefc..76d25147c7 100644 --- a/src/vs/workbench/services/search/node/textSearchAdapter.ts +++ b/src/vs/workbench/services/search/node/textSearchAdapter.ts @@ -5,10 +5,9 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import * as extfs from 'vs/base/node/extfs'; -import { IFileMatch, IProgress, ITextQuery, ITextSearchStats, ITextSearchMatch } from 'vs/platform/search/common/search'; +import { IFileMatch, IProgress, ITextQuery, ITextSearchStats, ITextSearchMatch, ISerializedFileMatch, ISerializedSearchSuccess } from 'vs/workbench/services/search/common/search'; import { RipgrepTextSearchEngine } from 'vs/workbench/services/search/node/ripgrepTextSearchEngine'; import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; -import { ISerializedFileMatch, ISerializedSearchSuccess } from './search'; export class TextSearchEngineAdapter { diff --git a/src/vs/workbench/services/search/node/textSearchManager.ts b/src/vs/workbench/services/search/node/textSearchManager.ts index 99334b2054..33b1d6ca75 100644 --- a/src/vs/workbench/services/search/node/textSearchManager.ts +++ b/src/vs/workbench/services/search/node/textSearchManager.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { mapArrayOrNot } from 'vs/base/common/arrays'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -12,8 +12,7 @@ import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { toCanonicalName } from 'vs/base/node/encoding'; import * as extfs from 'vs/base/node/extfs'; -import { IExtendedExtensionSearchOptions, IFileMatch, IFolderQuery, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchResult } from 'vs/platform/search/common/search'; -import { QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/node/search'; +import { IExtendedExtensionSearchOptions, IFileMatch, IFolderQuery, IPatternInfo, ISearchCompleteStats, ITextQuery, ITextSearchContext, ITextSearchMatch, ITextSearchResult, QueryGlobTester, resolvePatternsForProvider } from 'vs/workbench/services/search/common/search'; import * as vscode from 'vscode'; export class TextSearchManager { diff --git a/src/vs/platform/search/test/common/replace.test.ts b/src/vs/workbench/services/search/test/common/replace.test.ts similarity index 99% rename from src/vs/platform/search/test/common/replace.test.ts rename to src/vs/workbench/services/search/test/common/replace.test.ts index 050354a9df..a1dde74b95 100644 --- a/src/vs/platform/search/test/common/replace.test.ts +++ b/src/vs/workbench/services/search/test/common/replace.test.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ReplacePattern } from 'vs/platform/search/common/replace'; +import { ReplacePattern } from 'vs/workbench/services/search/common/replace'; suite('Replace Pattern test', () => { diff --git a/src/vs/platform/search/test/common/search.test.ts b/src/vs/workbench/services/search/test/common/search.test.ts similarity index 98% rename from src/vs/platform/search/test/common/search.test.ts rename to src/vs/workbench/services/search/test/common/search.test.ts index 928dd2e573..5bad58b1fc 100644 --- a/src/vs/platform/search/test/common/search.test.ts +++ b/src/vs/workbench/services/search/test/common/search.test.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { ITextSearchPreviewOptions, OneLineRange, TextSearchMatch, SearchRange } from 'vs/platform/search/common/search'; +import { ITextSearchPreviewOptions, OneLineRange, TextSearchMatch, SearchRange } from 'vs/workbench/services/search/common/search'; suite('TextSearchResult', () => { diff --git a/src/vs/workbench/services/search/test/common/searchHelpers.test.ts b/src/vs/workbench/services/search/test/common/searchHelpers.test.ts index 6c4a5e3384..7e29cf58a5 100644 --- a/src/vs/workbench/services/search/test/common/searchHelpers.test.ts +++ b/src/vs/workbench/services/search/test/common/searchHelpers.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { ITextModel, FindMatch } from 'vs/editor/common/model'; import { editorMatchesToTextSearchResults, addContextToEditorMatches } from 'vs/workbench/services/search/common/searchHelpers'; import { Range } from 'vs/editor/common/core/range'; -import { ITextQuery, QueryType, ITextSearchContext } from 'vs/platform/search/common/search'; +import { ITextQuery, QueryType, ITextSearchContext } from 'vs/workbench/services/search/common/search'; suite('SearchHelpers', () => { suite('editorMatchesToTextSearchResults', () => { diff --git a/src/vs/workbench/services/search/test/node/rawSearchService.test.ts b/src/vs/workbench/services/search/test/node/rawSearchService.test.ts index d732b1bcb9..76b2b732d6 100644 --- a/src/vs/workbench/services/search/test/node/rawSearchService.test.ts +++ b/src/vs/workbench/services/search/test/node/rawSearchService.test.ts @@ -4,14 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; -import { IFileQuery, IFileSearchStats, IFolderQuery, IProgress, ISearchEngineStats, QueryType } from 'vs/platform/search/common/search'; +import { IFileQuery, IFileSearchStats, IFolderQuery, IProgress, ISearchEngineStats, QueryType, IRawFileMatch, ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess } from 'vs/workbench/services/search/common/search'; import { SearchService as RawSearchService } from 'vs/workbench/services/search/node/rawSearchService'; -import { IRawFileMatch, ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess } from 'vs/workbench/services/search/node/search'; import { DiskSearch } from 'vs/workbench/services/search/node/searchService'; const TEST_FOLDER_QUERIES = [ diff --git a/src/vs/workbench/services/search/test/node/search.test.ts b/src/vs/workbench/services/search/test/node/search.test.ts index 4adebdb8d3..9c87d21bc2 100644 --- a/src/vs/workbench/services/search/test/node/search.test.ts +++ b/src/vs/workbench/services/search/test/node/search.test.ts @@ -4,15 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; -import { join, normalize } from 'vs/base/common/paths'; import * as platform from 'vs/base/common/platform'; import { joinPath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; -import { IFolderQuery, QueryType } from 'vs/platform/search/common/search'; +import { IFolderQuery, QueryType, IRawFileMatch } from 'vs/workbench/services/search/common/search'; import { Engine as FileSearchEngine, FileWalker } from 'vs/workbench/services/search/node/fileSearch'; -import { IRawFileMatch } from 'vs/workbench/services/search/node/search'; const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, './fixtures')); const EXAMPLES_FIXTURES = URI.file(path.join(TEST_FIXTURES, 'examples')); @@ -188,7 +186,7 @@ suite('FileSearchEngine', () => { const engine = new FileSearchEngine({ type: QueryType.File, folderQueries: ROOT_FOLDER_QUERY, - filePattern: normalize(join('examples', 'com*'), true) + filePattern: path.join('examples', 'com*') }); let count = 0; diff --git a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts index eced6317dc..fdef99b135 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -4,13 +4,12 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as glob from 'vs/base/common/glob'; import { URI } from 'vs/base/common/uri'; -import { deserializeSearchError, IFolderQuery, ISearchRange, ITextQuery, ITextSearchContext, ITextSearchMatch, QueryType, SearchErrorCode } from 'vs/platform/search/common/search'; -import { ISerializedFileMatch } from 'vs/workbench/services/search/node/search'; +import { deserializeSearchError, IFolderQuery, ISearchRange, ITextQuery, ITextSearchContext, ITextSearchMatch, QueryType, SearchErrorCode, ISerializedFileMatch } from 'vs/workbench/services/search/common/search'; import { TextSearchEngineAdapter } from 'vs/workbench/services/search/node/textSearchAdapter'; const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, './fixtures')); diff --git a/src/vs/workbench/services/search/test/node/textSearchManager.test.ts b/src/vs/workbench/services/search/test/node/textSearchManager.test.ts index 40c6315119..5fc412e22f 100644 --- a/src/vs/workbench/services/search/test/node/textSearchManager.test.ts +++ b/src/vs/workbench/services/search/test/node/textSearchManager.test.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; -import { ITextQuery, QueryType } from 'vs/platform/search/common/search'; +import { ITextQuery, QueryType } from 'vs/workbench/services/search/common/search'; import { TextSearchManager } from 'vs/workbench/services/search/node/textSearchManager'; import * as vscode from 'vscode'; diff --git a/src/vs/workbench/services/textMate/electron-browser/TMGrammars.ts b/src/vs/workbench/services/textMate/common/TMGrammars.ts similarity index 99% rename from src/vs/workbench/services/textMate/electron-browser/TMGrammars.ts rename to src/vs/workbench/services/textMate/common/TMGrammars.ts index 36e2eb8033..9ddad3b765 100644 --- a/src/vs/workbench/services/textMate/electron-browser/TMGrammars.ts +++ b/src/vs/workbench/services/textMate/common/TMGrammars.ts @@ -25,7 +25,6 @@ export interface ITMSyntaxExtensionPoint { } export const grammarsExtPoint: IExtensionPoint = ExtensionsRegistry.registerExtensionPoint({ - isDynamic: true, extensionPoint: 'grammars', deps: [languagesExtPoint], jsonSchema: { diff --git a/src/vs/workbench/services/textMate/electron-browser/TMHelper.ts b/src/vs/workbench/services/textMate/common/TMHelper.ts similarity index 92% rename from src/vs/workbench/services/textMate/electron-browser/TMHelper.ts rename to src/vs/workbench/services/textMate/common/TMHelper.ts index 206210243f..67cefc19fd 100644 --- a/src/vs/workbench/services/textMate/electron-browser/TMHelper.ts +++ b/src/vs/workbench/services/textMate/common/TMHelper.ts @@ -3,7 +3,21 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IColorTheme, ITokenColorizationSetting } from 'vs/workbench/services/themes/common/workbenchThemeService'; +export interface IColorTheme { + readonly tokenColors: ITokenColorizationRule[]; +} + +export interface ITokenColorizationRule { + name?: string; + scope?: string | string[]; + settings: ITokenColorizationSetting; +} + +export interface ITokenColorizationSetting { + foreground?: string; + background?: string; + fontStyle?: string; // italic, underline, bold +} export function findMatchingThemeRule(theme: IColorTheme, scopes: string[], onlyColorRules: boolean = true): ThemeRule | null { for (let i = scopes.length - 1; i >= 0; i--) { diff --git a/src/vs/workbench/services/textMate/common/textMateService.ts b/src/vs/workbench/services/textMate/common/textMateService.ts new file mode 100644 index 0000000000..5ba02e68b2 --- /dev/null +++ b/src/vs/workbench/services/textMate/common/textMateService.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { LanguageId } from 'vs/editor/common/modes'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const ITextMateService = createDecorator('textMateService'); + +export interface ITextMateService { + _serviceBrand: any; + + onDidEncounterLanguage: Event; + + createGrammar(modeId: string): Promise; +} + +// -------------- Types "liberated" from vscode-textmate due to usage in /common/ + +export const enum StandardTokenType { + Other = 0, + Comment = 1, + String = 2, + RegEx = 4, +} +/** + * A grammar + */ +export interface IGrammar { + /** + * Tokenize `lineText` using previous line state `prevState`. + */ + tokenizeLine(lineText: string, prevState: StackElement | null): ITokenizeLineResult; + /** + * Tokenize `lineText` using previous line state `prevState`. + * The result contains the tokens in binary format, resolved with the following information: + * - language + * - token type (regex, string, comment, other) + * - font style + * - foreground color + * - background color + * e.g. for getting the languageId: `(metadata & MetadataConsts.LANGUAGEID_MASK) >>> MetadataConsts.LANGUAGEID_OFFSET` + */ + tokenizeLine2(lineText: string, prevState: StackElement | null): ITokenizeLineResult2; +} +export interface ITokenizeLineResult { + readonly tokens: IToken[]; + /** + * The `prevState` to be passed on to the next line tokenization. + */ + readonly ruleStack: StackElement; +} +/** + * Helpers to manage the "collapsed" metadata of an entire StackElement stack. + * The following assumptions have been made: + * - languageId < 256 => needs 8 bits + * - unique color count < 512 => needs 9 bits + * + * The binary format is: + * - ------------------------------------------- + * 3322 2222 2222 1111 1111 1100 0000 0000 + * 1098 7654 3210 9876 5432 1098 7654 3210 + * - ------------------------------------------- + * xxxx xxxx xxxx xxxx xxxx xxxx xxxx xxxx + * bbbb bbbb bfff ffff ffFF FTTT LLLL LLLL + * - ------------------------------------------- + * - L = LanguageId (8 bits) + * - T = StandardTokenType (3 bits) + * - F = FontStyle (3 bits) + * - f = foreground color (9 bits) + * - b = background color (9 bits) + */ +export const enum MetadataConsts { + LANGUAGEID_MASK = 255, + TOKEN_TYPE_MASK = 1792, + FONT_STYLE_MASK = 14336, + FOREGROUND_MASK = 8372224, + BACKGROUND_MASK = 4286578688, + LANGUAGEID_OFFSET = 0, + TOKEN_TYPE_OFFSET = 8, + FONT_STYLE_OFFSET = 11, + FOREGROUND_OFFSET = 14, + BACKGROUND_OFFSET = 23, +} +export interface ITokenizeLineResult2 { + /** + * The tokens in binary format. Each token occupies two array indices. For token i: + * - at offset 2*i => startIndex + * - at offset 2*i + 1 => metadata + * + */ + readonly tokens: Uint32Array; + /** + * The `prevState` to be passed on to the next line tokenization. + */ + readonly ruleStack: StackElement; +} +export interface IToken { + startIndex: number; + readonly endIndex: number; + readonly scopes: string[]; +} +/** + * **IMPORTANT** - Immutable! + */ +export interface StackElement { + _stackElementBrand: void; + readonly depth: number; + clone(): StackElement; + equals(other: StackElement): boolean; +} +// -------------- End Types "liberated" from vscode-textmate due to usage in /common/ diff --git a/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts b/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts deleted file mode 100644 index 972d08acc6..0000000000 --- a/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts +++ /dev/null @@ -1,508 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import * as dom from 'vs/base/browser/dom'; -import { Color } from 'vs/base/common/color'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { Emitter, Event } from 'vs/base/common/event'; -import * as resources from 'vs/base/common/resources'; -import * as types from 'vs/base/common/types'; -import { URI } from 'vs/base/common/uri'; -import { TokenizationResult, TokenizationResult2 } from 'vs/editor/common/core/token'; -import { IState, ITokenizationSupport, LanguageId, TokenMetadata, TokenizationRegistry } from 'vs/editor/common/modes'; -import { nullTokenize2 } from 'vs/editor/common/modes/nullMode'; -import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { IFileService } from 'vs/platform/files/common/files'; -import { ILogService } from 'vs/platform/log/common/log'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IEmbeddedLanguagesMap, ITMSyntaxExtensionPoint, TokenTypesContribution, grammarsExtPoint } from 'vs/workbench/services/textMate/electron-browser/TMGrammars'; -import { ITextMateService } from 'vs/workbench/services/textMate/electron-browser/textMateService'; -import { ITokenColorizationRule, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { IEmbeddedLanguagesMap as IEmbeddedLanguagesMap2, IGrammar, ITokenTypeMap, Registry, StackElement, StandardTokenType } from 'vscode-textmate'; -import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; - -export class TMScopeRegistry { - - private _scopeNameToLanguageRegistration: { [scopeName: string]: TMLanguageRegistration; }; - private _encounteredLanguages: boolean[]; - - private readonly _onDidEncounterLanguage = new Emitter(); - public readonly onDidEncounterLanguage: Event = this._onDidEncounterLanguage.event; - - constructor() { - this.reset(); - } - - public reset(): void { - this._scopeNameToLanguageRegistration = Object.create(null); - this._encounteredLanguages = []; - } - - public register(scopeName: string, grammarLocation: URI, embeddedLanguages?: IEmbeddedLanguagesMap, tokenTypes?: TokenTypesContribution): void { - if (this._scopeNameToLanguageRegistration[scopeName]) { - const existingRegistration = this._scopeNameToLanguageRegistration[scopeName]; - if (!resources.isEqual(existingRegistration.grammarLocation, grammarLocation)) { - console.warn( - `Overwriting grammar scope name to file mapping for scope ${scopeName}.\n` + - `Old grammar file: ${existingRegistration.grammarLocation.toString()}.\n` + - `New grammar file: ${grammarLocation.toString()}` - ); - } - } - this._scopeNameToLanguageRegistration[scopeName] = new TMLanguageRegistration(scopeName, grammarLocation, embeddedLanguages, tokenTypes); - } - - public getLanguageRegistration(scopeName: string): TMLanguageRegistration { - return this._scopeNameToLanguageRegistration[scopeName] || null; - } - - public getGrammarLocation(scopeName: string): URI | null { - let data = this.getLanguageRegistration(scopeName); - return data ? data.grammarLocation : null; - } - - /** - * To be called when tokenization found/hit an embedded language. - */ - public onEncounteredLanguage(languageId: LanguageId): void { - if (!this._encounteredLanguages[languageId]) { - this._encounteredLanguages[languageId] = true; - this._onDidEncounterLanguage.fire(languageId); - } - } -} - -export class TMLanguageRegistration { - _topLevelScopeNameDataBrand: void; - - readonly scopeName: string; - readonly grammarLocation: URI; - readonly embeddedLanguages: IEmbeddedLanguagesMap; - readonly tokenTypes: ITokenTypeMap; - - constructor(scopeName: string, grammarLocation: URI, embeddedLanguages: IEmbeddedLanguagesMap | undefined, tokenTypes: TokenTypesContribution | undefined) { - this.scopeName = scopeName; - this.grammarLocation = grammarLocation; - - // embeddedLanguages handling - this.embeddedLanguages = Object.create(null); - - if (embeddedLanguages) { - // If embeddedLanguages are configured, fill in `this._embeddedLanguages` - let scopes = Object.keys(embeddedLanguages); - for (let i = 0, len = scopes.length; i < len; i++) { - let scope = scopes[i]; - let language = embeddedLanguages[scope]; - if (typeof language !== 'string') { - // never hurts to be too careful - continue; - } - this.embeddedLanguages[scope] = language; - } - } - - this.tokenTypes = Object.create(null); - if (tokenTypes) { - // If tokenTypes is configured, fill in `this._tokenTypes` - const scopes = Object.keys(tokenTypes); - for (const scope of scopes) { - const tokenType = tokenTypes[scope]; - switch (tokenType) { - case 'string': - this.tokenTypes[scope] = StandardTokenType.String; - break; - case 'other': - this.tokenTypes[scope] = StandardTokenType.Other; - break; - case 'comment': - this.tokenTypes[scope] = StandardTokenType.Comment; - break; - } - } - } - } -} - -interface ICreateGrammarResult { - languageId: LanguageId; - grammar: IGrammar; - initialState: StackElement; - containsEmbeddedLanguages: boolean; -} - -export class TextMateService extends Disposable implements ITextMateService { - public _serviceBrand: any; - - private readonly _onDidEncounterLanguage: Emitter = this._register(new Emitter()); - public readonly onDidEncounterLanguage: Event = this._onDidEncounterLanguage.event; - - private readonly _styleElement: HTMLStyleElement; - private readonly _createdModes: string[]; - - private _scopeRegistry: TMScopeRegistry; - private _injections: { [scopeName: string]: string[]; }; - private _injectedEmbeddedLanguages: { [scopeName: string]: IEmbeddedLanguagesMap[]; }; - private _languageToScope: Map; - private _grammarRegistry: Promise<[Registry, StackElement]> | null; - private _tokenizersRegistrations: IDisposable[]; - private _currentTokenColors: ITokenColorizationRule[] | null; - private _themeListener: IDisposable | null; - - constructor( - @IModeService private readonly _modeService: IModeService, - @IWorkbenchThemeService private readonly _themeService: IWorkbenchThemeService, - @IFileService private readonly _fileService: IFileService, - @INotificationService private readonly _notificationService: INotificationService, - @ILogService private readonly _logService: ILogService - ) { - super(); - this._styleElement = dom.createStyleSheet(); - this._styleElement.className = 'vscode-tokens-styles'; - this._createdModes = []; - this._scopeRegistry = new TMScopeRegistry(); - this._scopeRegistry.onDidEncounterLanguage((language) => this._onDidEncounterLanguage.fire(language)); - this._injections = {}; - this._injectedEmbeddedLanguages = {}; - this._languageToScope = new Map(); - this._grammarRegistry = null; - this._tokenizersRegistrations = []; - this._currentTokenColors = null; - this._themeListener = null; - - grammarsExtPoint.setHandler((extensions) => { - this._scopeRegistry.reset(); - this._injections = {}; - this._injectedEmbeddedLanguages = {}; - this._languageToScope = new Map(); - this._grammarRegistry = null; - this._tokenizersRegistrations = dispose(this._tokenizersRegistrations); - this._currentTokenColors = null; - if (this._themeListener) { - this._themeListener.dispose(); - this._themeListener = null; - } - - for (const extension of extensions) { - let grammars = extension.value; - for (const grammar of grammars) { - this._handleGrammarExtensionPointUser(extension.description.extensionLocation, grammar, extension.collector); - } - } - - for (const createMode of this._createdModes) { - this._registerDefinitionIfAvailable(createMode); - } - }); - - // Generate some color map until the grammar registry is loaded - let colorTheme = this._themeService.getColorTheme(); - let defaultForeground: Color = Color.transparent; - let defaultBackground: Color = Color.transparent; - for (let i = 0, len = colorTheme.tokenColors.length; i < len; i++) { - let rule = colorTheme.tokenColors[i]; - if (!rule.scope && rule.settings) { - if (rule.settings.foreground) { - defaultForeground = Color.fromHex(rule.settings.foreground); - } - if (rule.settings.background) { - defaultBackground = Color.fromHex(rule.settings.background); - } - } - } - TokenizationRegistry.setColorMap([null!, defaultForeground, defaultBackground]); - - this._modeService.onDidCreateMode((mode) => { - let modeId = mode.getId(); - this._createdModes.push(modeId); - this._registerDefinitionIfAvailable(modeId); - }); - } - - private _registerDefinitionIfAvailable(modeId: string): void { - if (this._languageToScope.has(modeId)) { - const promise = this._createGrammar(modeId).then((r) => { - return new TMTokenization(this._scopeRegistry, r.languageId, r.grammar, r.initialState, r.containsEmbeddedLanguages, this._notificationService); - }, e => { - onUnexpectedError(e); - return null; - }); - this._tokenizersRegistrations.push(TokenizationRegistry.registerPromise(modeId, promise)); - } - } - - private async _createGrammarRegistry(): Promise<[Registry, StackElement]> { - const { Registry, INITIAL, parseRawGrammar } = await import('vscode-textmate'); - const grammarRegistry = new Registry({ - loadGrammar: async (scopeName: string) => { - const location = this._scopeRegistry.getGrammarLocation(scopeName); - if (!location) { - this._logService.trace(`No grammar found for scope ${scopeName}`); - return null; - } - try { - const content = await this._fileService.resolveContent(location, { encoding: 'utf8' }); - return parseRawGrammar(content.value, location.path); - } catch (e) { - this._logService.error(`Unable to load and parse grammar for scope ${scopeName} from ${location}`, e); - return null; - } - }, - getInjections: (scopeName: string) => { - const scopeParts = scopeName.split('.'); - let injections: string[] = []; - for (let i = 1; i <= scopeParts.length; i++) { - const subScopeName = scopeParts.slice(0, i).join('.'); - injections = [...injections, ...this._injections[subScopeName]]; - } - return injections; - } - }); - this._updateTheme(grammarRegistry); - this._themeListener = this._themeService.onDidColorThemeChange((e) => this._updateTheme(grammarRegistry)); - return <[Registry, StackElement]>[grammarRegistry, INITIAL]; - } - - private _getOrCreateGrammarRegistry(): Promise<[Registry, StackElement]> { - if (!this._grammarRegistry) { - this._grammarRegistry = this._createGrammarRegistry(); - } - return this._grammarRegistry; - } - - private static _toColorMap(colorMap: string[]): Color[] { - let result: Color[] = [null!]; - for (let i = 1, len = colorMap.length; i < len; i++) { - result[i] = Color.fromHex(colorMap[i]); - } - return result; - } - - private _updateTheme(grammarRegistry: Registry): void { - let colorTheme = this._themeService.getColorTheme(); - if (!this.compareTokenRules(colorTheme.tokenColors)) { - return; - } - grammarRegistry.setTheme({ name: colorTheme.label, settings: colorTheme.tokenColors }); - let colorMap = TextMateService._toColorMap(grammarRegistry.getColorMap()); - let cssRules = generateTokensCSSForColorMap(colorMap); - this._styleElement.innerHTML = cssRules; - TokenizationRegistry.setColorMap(colorMap); - } - - private compareTokenRules(newRules: ITokenColorizationRule[]): boolean { - let currRules = this._currentTokenColors; - this._currentTokenColors = newRules; - if (!newRules || !currRules || newRules.length !== currRules.length) { - return true; - } - for (let i = newRules.length - 1; i >= 0; i--) { - let r1 = newRules[i]; - let r2 = currRules[i]; - if (r1.scope !== r2.scope) { - return true; - } - let s1 = r1.settings; - let s2 = r2.settings; - if (s1 && s2) { - if (s1.fontStyle !== s2.fontStyle || s1.foreground !== s2.foreground || s1.background !== s2.background) { - return true; - } - } else if (!s1 || !s2) { - return true; - } - } - return false; - } - - private _handleGrammarExtensionPointUser(extensionLocation: URI, syntax: ITMSyntaxExtensionPoint, collector: ExtensionMessageCollector): void { - if (syntax.language && ((typeof syntax.language !== 'string') || !this._modeService.isRegisteredMode(syntax.language))) { - collector.error(nls.localize('invalid.language', "Unknown language in `contributes.{0}.language`. Provided value: {1}", grammarsExtPoint.name, String(syntax.language))); - return; - } - if (!syntax.scopeName || (typeof syntax.scopeName !== 'string')) { - collector.error(nls.localize('invalid.scopeName', "Expected string in `contributes.{0}.scopeName`. Provided value: {1}", grammarsExtPoint.name, String(syntax.scopeName))); - return; - } - if (!syntax.path || (typeof syntax.path !== 'string')) { - collector.error(nls.localize('invalid.path.0', "Expected string in `contributes.{0}.path`. Provided value: {1}", grammarsExtPoint.name, String(syntax.path))); - return; - } - if (syntax.injectTo && (!Array.isArray(syntax.injectTo) || syntax.injectTo.some(scope => typeof scope !== 'string'))) { - collector.error(nls.localize('invalid.injectTo', "Invalid value in `contributes.{0}.injectTo`. Must be an array of language scope names. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.injectTo))); - return; - } - if (syntax.embeddedLanguages && !types.isObject(syntax.embeddedLanguages)) { - collector.error(nls.localize('invalid.embeddedLanguages', "Invalid value in `contributes.{0}.embeddedLanguages`. Must be an object map from scope name to language. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.embeddedLanguages))); - return; - } - - if (syntax.tokenTypes && !types.isObject(syntax.tokenTypes)) { - collector.error(nls.localize('invalid.tokenTypes', "Invalid value in `contributes.{0}.tokenTypes`. Must be an object map from scope name to token type. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.tokenTypes))); - return; - } - - const grammarLocation = resources.joinPath(extensionLocation, syntax.path); - if (!resources.isEqualOrParent(grammarLocation, extensionLocation)) { - collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", grammarsExtPoint.name, grammarLocation.path, extensionLocation.path)); - } - - this._scopeRegistry.register(syntax.scopeName, grammarLocation, syntax.embeddedLanguages, syntax.tokenTypes); - - if (syntax.injectTo) { - for (let injectScope of syntax.injectTo) { - let injections = this._injections[injectScope]; - if (!injections) { - this._injections[injectScope] = injections = []; - } - injections.push(syntax.scopeName); - } - - if (syntax.embeddedLanguages) { - for (let injectScope of syntax.injectTo) { - let injectedEmbeddedLanguages = this._injectedEmbeddedLanguages[injectScope]; - if (!injectedEmbeddedLanguages) { - this._injectedEmbeddedLanguages[injectScope] = injectedEmbeddedLanguages = []; - } - injectedEmbeddedLanguages.push(syntax.embeddedLanguages); - } - } - } - - let modeId = syntax.language; - if (modeId) { - this._languageToScope.set(modeId, syntax.scopeName); - } - } - - private _resolveEmbeddedLanguages(embeddedLanguages: IEmbeddedLanguagesMap): IEmbeddedLanguagesMap2 { - let scopes = Object.keys(embeddedLanguages); - let result: IEmbeddedLanguagesMap2 = Object.create(null); - for (let i = 0, len = scopes.length; i < len; i++) { - let scope = scopes[i]; - let language = embeddedLanguages[scope]; - let languageIdentifier = this._modeService.getLanguageIdentifier(language); - if (languageIdentifier) { - result[scope] = languageIdentifier.id; - } - } - return result; - } - - public async createGrammar(modeId: string): Promise { - const { grammar } = await this._createGrammar(modeId); - return grammar; - } - - private async _createGrammar(modeId: string): Promise { - const scopeName = this._languageToScope.get(modeId); - if (typeof scopeName !== 'string') { - // No TM grammar defined - return Promise.reject(new Error(nls.localize('no-tm-grammar', "No TM Grammar registered for this language."))); - } - const languageRegistration = this._scopeRegistry.getLanguageRegistration(scopeName); - if (!languageRegistration) { - // No TM grammar defined - return Promise.reject(new Error(nls.localize('no-tm-grammar', "No TM Grammar registered for this language."))); - } - let embeddedLanguages = this._resolveEmbeddedLanguages(languageRegistration.embeddedLanguages); - let rawInjectedEmbeddedLanguages = this._injectedEmbeddedLanguages[scopeName]; - if (rawInjectedEmbeddedLanguages) { - let injectedEmbeddedLanguages: IEmbeddedLanguagesMap2[] = rawInjectedEmbeddedLanguages.map(this._resolveEmbeddedLanguages.bind(this)); - for (const injected of injectedEmbeddedLanguages) { - for (const scope of Object.keys(injected)) { - embeddedLanguages[scope] = injected[scope]; - } - } - } - - let languageId = this._modeService.getLanguageIdentifier(modeId)!.id; - let containsEmbeddedLanguages = (Object.keys(embeddedLanguages).length > 0); - - const [grammarRegistry, initialState] = await this._getOrCreateGrammarRegistry(); - const grammar = await grammarRegistry.loadGrammarWithConfiguration(scopeName, languageId, { embeddedLanguages, tokenTypes: languageRegistration.tokenTypes }); - return { - languageId: languageId, - grammar: grammar, - initialState: initialState, - containsEmbeddedLanguages: containsEmbeddedLanguages - }; - } -} - -class TMTokenization implements ITokenizationSupport { - - private readonly _scopeRegistry: TMScopeRegistry; - private readonly _languageId: LanguageId; - private readonly _grammar: IGrammar; - private readonly _containsEmbeddedLanguages: boolean; - private readonly _seenLanguages: boolean[]; - private readonly _initialState: StackElement; - private _tokenizationWarningAlreadyShown: boolean; - - constructor(scopeRegistry: TMScopeRegistry, languageId: LanguageId, grammar: IGrammar, initialState: StackElement, containsEmbeddedLanguages: boolean, @INotificationService private readonly notificationService: INotificationService) { - this._scopeRegistry = scopeRegistry; - this._languageId = languageId; - this._grammar = grammar; - this._initialState = initialState; - this._containsEmbeddedLanguages = containsEmbeddedLanguages; - this._seenLanguages = []; - } - - public getInitialState(): IState { - return this._initialState; - } - - public tokenize(line: string, state: IState, offsetDelta: number): TokenizationResult { - throw new Error('Not supported!'); - } - - public tokenize2(line: string, state: StackElement, offsetDelta: number): TokenizationResult2 { - if (offsetDelta !== 0) { - throw new Error('Unexpected: offsetDelta should be 0.'); - } - - // Do not attempt to tokenize if a line has over 20k - if (line.length >= 20000) { - if (!this._tokenizationWarningAlreadyShown) { - this._tokenizationWarningAlreadyShown = true; - this.notificationService.warn(nls.localize('too many characters', "Tokenization is skipped for lines longer than 20k characters for performance reasons.")); - } - console.log(`Line (${line.substr(0, 15)}...): longer than 20k characters, tokenization skipped.`); - return nullTokenize2(this._languageId, line, state, offsetDelta); - } - - let textMateResult = this._grammar.tokenizeLine2(line, state); - - if (this._containsEmbeddedLanguages) { - let seenLanguages = this._seenLanguages; - let tokens = textMateResult.tokens; - - // Must check if any of the embedded languages was hit - for (let i = 0, len = (tokens.length >>> 1); i < len; i++) { - let metadata = tokens[(i << 1) + 1]; - let languageId = TokenMetadata.getLanguageId(metadata); - - if (!seenLanguages[languageId]) { - seenLanguages[languageId] = true; - this._scopeRegistry.onEncounteredLanguage(languageId); - } - } - } - - let endState: StackElement; - // try to save an object if possible - if (state.equals(textMateResult.ruleStack)) { - endState = state; - } else { - endState = textMateResult.ruleStack; - - } - - return new TokenizationResult2(textMateResult.tokens, endState); - } -} diff --git a/src/vs/workbench/services/textMate/electron-browser/textMateService.ts b/src/vs/workbench/services/textMate/electron-browser/textMateService.ts index 3c7104ec75..d5cbbcb40e 100644 --- a/src/vs/workbench/services/textMate/electron-browser/textMateService.ts +++ b/src/vs/workbench/services/textMate/electron-browser/textMateService.ts @@ -3,17 +3,513 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event } from 'vs/base/common/event'; -import { LanguageId } from 'vs/editor/common/modes'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IGrammar } from 'vscode-textmate'; +import * as nls from 'vs/nls'; +import * as dom from 'vs/base/browser/dom'; +import { Color } from 'vs/base/common/color'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { Emitter, Event } from 'vs/base/common/event'; +import * as resources from 'vs/base/common/resources'; +import * as types from 'vs/base/common/types'; +import { URI } from 'vs/base/common/uri'; +import { TokenizationResult, TokenizationResult2 } from 'vs/editor/common/core/token'; +import { IState, ITokenizationSupport, LanguageId, TokenMetadata, TokenizationRegistry } from 'vs/editor/common/modes'; +import { nullTokenize2 } from 'vs/editor/common/modes/nullMode'; +import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IFileService } from 'vs/platform/files/common/files'; +import { ILogService } from 'vs/platform/log/common/log'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { IEmbeddedLanguagesMap, ITMSyntaxExtensionPoint, TokenTypesContribution, grammarsExtPoint } from 'vs/workbench/services/textMate/common/TMGrammars'; +import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; +import { ITokenColorizationRule, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { IEmbeddedLanguagesMap as IEmbeddedLanguagesMap2, IGrammar, ITokenTypeMap, Registry, StackElement, StandardTokenType } from 'vscode-textmate'; +import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -export const ITextMateService = createDecorator('textMateService'); +export class TMScopeRegistry { -export interface ITextMateService { - _serviceBrand: any; + private _scopeNameToLanguageRegistration: { [scopeName: string]: TMLanguageRegistration; }; + private _encounteredLanguages: boolean[]; - onDidEncounterLanguage: Event; + private readonly _onDidEncounterLanguage = new Emitter(); + public readonly onDidEncounterLanguage: Event = this._onDidEncounterLanguage.event; - createGrammar(modeId: string): Promise; + constructor() { + this.reset(); + } + + public reset(): void { + this._scopeNameToLanguageRegistration = Object.create(null); + this._encounteredLanguages = []; + } + + public register(scopeName: string, grammarLocation: URI, embeddedLanguages?: IEmbeddedLanguagesMap, tokenTypes?: TokenTypesContribution): void { + if (this._scopeNameToLanguageRegistration[scopeName]) { + const existingRegistration = this._scopeNameToLanguageRegistration[scopeName]; + if (!resources.isEqual(existingRegistration.grammarLocation, grammarLocation)) { + console.warn( + `Overwriting grammar scope name to file mapping for scope ${scopeName}.\n` + + `Old grammar file: ${existingRegistration.grammarLocation.toString()}.\n` + + `New grammar file: ${grammarLocation.toString()}` + ); + } + } + this._scopeNameToLanguageRegistration[scopeName] = new TMLanguageRegistration(scopeName, grammarLocation, embeddedLanguages, tokenTypes); + } + + public getLanguageRegistration(scopeName: string): TMLanguageRegistration { + return this._scopeNameToLanguageRegistration[scopeName] || null; + } + + public getGrammarLocation(scopeName: string): URI | null { + let data = this.getLanguageRegistration(scopeName); + return data ? data.grammarLocation : null; + } + + /** + * To be called when tokenization found/hit an embedded language. + */ + public onEncounteredLanguage(languageId: LanguageId): void { + if (!this._encounteredLanguages[languageId]) { + this._encounteredLanguages[languageId] = true; + this._onDidEncounterLanguage.fire(languageId); + } + } } + +export class TMLanguageRegistration { + _topLevelScopeNameDataBrand: void; + + readonly scopeName: string; + readonly grammarLocation: URI; + readonly embeddedLanguages: IEmbeddedLanguagesMap; + readonly tokenTypes: ITokenTypeMap; + + constructor(scopeName: string, grammarLocation: URI, embeddedLanguages: IEmbeddedLanguagesMap | undefined, tokenTypes: TokenTypesContribution | undefined) { + this.scopeName = scopeName; + this.grammarLocation = grammarLocation; + + // embeddedLanguages handling + this.embeddedLanguages = Object.create(null); + + if (embeddedLanguages) { + // If embeddedLanguages are configured, fill in `this._embeddedLanguages` + let scopes = Object.keys(embeddedLanguages); + for (let i = 0, len = scopes.length; i < len; i++) { + let scope = scopes[i]; + let language = embeddedLanguages[scope]; + if (typeof language !== 'string') { + // never hurts to be too careful + continue; + } + this.embeddedLanguages[scope] = language; + } + } + + this.tokenTypes = Object.create(null); + if (tokenTypes) { + // If tokenTypes is configured, fill in `this._tokenTypes` + const scopes = Object.keys(tokenTypes); + for (const scope of scopes) { + const tokenType = tokenTypes[scope]; + switch (tokenType) { + case 'string': + this.tokenTypes[scope] = StandardTokenType.String; + break; + case 'other': + this.tokenTypes[scope] = StandardTokenType.Other; + break; + case 'comment': + this.tokenTypes[scope] = StandardTokenType.Comment; + break; + } + } + } + } +} + +interface ICreateGrammarResult { + languageId: LanguageId; + grammar: IGrammar; + initialState: StackElement; + containsEmbeddedLanguages: boolean; +} + +export class TextMateService extends Disposable implements ITextMateService { + public _serviceBrand: any; + + private readonly _onDidEncounterLanguage: Emitter = this._register(new Emitter()); + public readonly onDidEncounterLanguage: Event = this._onDidEncounterLanguage.event; + + private readonly _styleElement: HTMLStyleElement; + private readonly _createdModes: string[]; + + private _scopeRegistry: TMScopeRegistry; + private _injections: { [scopeName: string]: string[]; }; + private _injectedEmbeddedLanguages: { [scopeName: string]: IEmbeddedLanguagesMap[]; }; + private _languageToScope: Map; + private _grammarRegistry: Promise<[Registry, StackElement]> | null; + private _tokenizersRegistrations: IDisposable[]; + private _currentTokenColors: ITokenColorizationRule[] | null; + private _themeListener: IDisposable | null; + + constructor( + @IModeService private readonly _modeService: IModeService, + @IWorkbenchThemeService private readonly _themeService: IWorkbenchThemeService, + @IFileService private readonly _fileService: IFileService, + @INotificationService private readonly _notificationService: INotificationService, + @ILogService private readonly _logService: ILogService, + @IConfigurationService private readonly _configurationService: IConfigurationService + ) { + super(); + this._styleElement = dom.createStyleSheet(); + this._styleElement.className = 'vscode-tokens-styles'; + this._createdModes = []; + this._scopeRegistry = new TMScopeRegistry(); + this._scopeRegistry.onDidEncounterLanguage((language) => this._onDidEncounterLanguage.fire(language)); + this._injections = {}; + this._injectedEmbeddedLanguages = {}; + this._languageToScope = new Map(); + this._grammarRegistry = null; + this._tokenizersRegistrations = []; + this._currentTokenColors = null; + this._themeListener = null; + + grammarsExtPoint.setHandler((extensions) => { + this._scopeRegistry.reset(); + this._injections = {}; + this._injectedEmbeddedLanguages = {}; + this._languageToScope = new Map(); + this._grammarRegistry = null; + this._tokenizersRegistrations = dispose(this._tokenizersRegistrations); + this._currentTokenColors = null; + if (this._themeListener) { + this._themeListener.dispose(); + this._themeListener = null; + } + + for (const extension of extensions) { + let grammars = extension.value; + for (const grammar of grammars) { + this._handleGrammarExtensionPointUser(extension.description.extensionLocation, grammar, extension.collector); + } + } + + for (const createMode of this._createdModes) { + this._registerDefinitionIfAvailable(createMode); + } + }); + + // Generate some color map until the grammar registry is loaded + let colorTheme = this._themeService.getColorTheme(); + let defaultForeground: Color = Color.transparent; + let defaultBackground: Color = Color.transparent; + for (let i = 0, len = colorTheme.tokenColors.length; i < len; i++) { + let rule = colorTheme.tokenColors[i]; + if (!rule.scope && rule.settings) { + if (rule.settings.foreground) { + defaultForeground = Color.fromHex(rule.settings.foreground); + } + if (rule.settings.background) { + defaultBackground = Color.fromHex(rule.settings.background); + } + } + } + TokenizationRegistry.setColorMap([null!, defaultForeground, defaultBackground]); + + this._modeService.onDidCreateMode((mode) => { + let modeId = mode.getId(); + this._createdModes.push(modeId); + this._registerDefinitionIfAvailable(modeId); + }); + } + + private _registerDefinitionIfAvailable(modeId: string): void { + if (this._languageToScope.has(modeId)) { + const promise = this._createGrammar(modeId).then((r) => { + return new TMTokenization(this._scopeRegistry, r.languageId, r.grammar, r.initialState, r.containsEmbeddedLanguages, this._notificationService, this._configurationService); + }, e => { + onUnexpectedError(e); + return null; + }); + this._tokenizersRegistrations.push(TokenizationRegistry.registerPromise(modeId, promise)); + } + } + + private async _createGrammarRegistry(): Promise<[Registry, StackElement]> { + const { Registry, INITIAL, parseRawGrammar } = await import('vscode-textmate'); + const grammarRegistry = new Registry({ + loadGrammar: async (scopeName: string) => { + const location = this._scopeRegistry.getGrammarLocation(scopeName); + if (!location) { + this._logService.trace(`No grammar found for scope ${scopeName}`); + return null; + } + try { + const content = await this._fileService.resolveContent(location, { encoding: 'utf8' }); + return parseRawGrammar(content.value, location.path); + } catch (e) { + this._logService.error(`Unable to load and parse grammar for scope ${scopeName} from ${location}`, e); + return null; + } + }, + getInjections: (scopeName: string) => { + const scopeParts = scopeName.split('.'); + let injections: string[] = []; + for (let i = 1; i <= scopeParts.length; i++) { + const subScopeName = scopeParts.slice(0, i).join('.'); + injections = [...injections, ...(this._injections[subScopeName] || [])]; + } + return injections; + } + }); + this._updateTheme(grammarRegistry); + this._themeListener = this._themeService.onDidColorThemeChange((e) => this._updateTheme(grammarRegistry)); + return <[Registry, StackElement]>[grammarRegistry, INITIAL]; + } + + private _getOrCreateGrammarRegistry(): Promise<[Registry, StackElement]> { + if (!this._grammarRegistry) { + this._grammarRegistry = this._createGrammarRegistry(); + } + return this._grammarRegistry; + } + + private static _toColorMap(colorMap: string[]): Color[] { + let result: Color[] = [null!]; + for (let i = 1, len = colorMap.length; i < len; i++) { + result[i] = Color.fromHex(colorMap[i]); + } + return result; + } + + private _updateTheme(grammarRegistry: Registry): void { + let colorTheme = this._themeService.getColorTheme(); + if (!this.compareTokenRules(colorTheme.tokenColors)) { + return; + } + grammarRegistry.setTheme({ name: colorTheme.label, settings: colorTheme.tokenColors }); + let colorMap = TextMateService._toColorMap(grammarRegistry.getColorMap()); + let cssRules = generateTokensCSSForColorMap(colorMap); + this._styleElement.innerHTML = cssRules; + TokenizationRegistry.setColorMap(colorMap); + } + + private compareTokenRules(newRules: ITokenColorizationRule[]): boolean { + let currRules = this._currentTokenColors; + this._currentTokenColors = newRules; + if (!newRules || !currRules || newRules.length !== currRules.length) { + return true; + } + for (let i = newRules.length - 1; i >= 0; i--) { + let r1 = newRules[i]; + let r2 = currRules[i]; + if (r1.scope !== r2.scope) { + return true; + } + let s1 = r1.settings; + let s2 = r2.settings; + if (s1 && s2) { + if (s1.fontStyle !== s2.fontStyle || s1.foreground !== s2.foreground || s1.background !== s2.background) { + return true; + } + } else if (!s1 || !s2) { + return true; + } + } + return false; + } + + private _handleGrammarExtensionPointUser(extensionLocation: URI, syntax: ITMSyntaxExtensionPoint, collector: ExtensionMessageCollector): void { + if (syntax.language && ((typeof syntax.language !== 'string') || !this._modeService.isRegisteredMode(syntax.language))) { + collector.error(nls.localize('invalid.language', "Unknown language in `contributes.{0}.language`. Provided value: {1}", grammarsExtPoint.name, String(syntax.language))); + return; + } + if (!syntax.scopeName || (typeof syntax.scopeName !== 'string')) { + collector.error(nls.localize('invalid.scopeName', "Expected string in `contributes.{0}.scopeName`. Provided value: {1}", grammarsExtPoint.name, String(syntax.scopeName))); + return; + } + if (!syntax.path || (typeof syntax.path !== 'string')) { + collector.error(nls.localize('invalid.path.0', "Expected string in `contributes.{0}.path`. Provided value: {1}", grammarsExtPoint.name, String(syntax.path))); + return; + } + if (syntax.injectTo && (!Array.isArray(syntax.injectTo) || syntax.injectTo.some(scope => typeof scope !== 'string'))) { + collector.error(nls.localize('invalid.injectTo', "Invalid value in `contributes.{0}.injectTo`. Must be an array of language scope names. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.injectTo))); + return; + } + if (syntax.embeddedLanguages && !types.isObject(syntax.embeddedLanguages)) { + collector.error(nls.localize('invalid.embeddedLanguages', "Invalid value in `contributes.{0}.embeddedLanguages`. Must be an object map from scope name to language. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.embeddedLanguages))); + return; + } + + if (syntax.tokenTypes && !types.isObject(syntax.tokenTypes)) { + collector.error(nls.localize('invalid.tokenTypes', "Invalid value in `contributes.{0}.tokenTypes`. Must be an object map from scope name to token type. Provided value: {1}", grammarsExtPoint.name, JSON.stringify(syntax.tokenTypes))); + return; + } + + const grammarLocation = resources.joinPath(extensionLocation, syntax.path); + if (!resources.isEqualOrParent(grammarLocation, extensionLocation)) { + collector.warn(nls.localize('invalid.path.1', "Expected `contributes.{0}.path` ({1}) to be included inside extension's folder ({2}). This might make the extension non-portable.", grammarsExtPoint.name, grammarLocation.path, extensionLocation.path)); + } + + this._scopeRegistry.register(syntax.scopeName, grammarLocation, syntax.embeddedLanguages, syntax.tokenTypes); + + if (syntax.injectTo) { + for (let injectScope of syntax.injectTo) { + let injections = this._injections[injectScope]; + if (!injections) { + this._injections[injectScope] = injections = []; + } + injections.push(syntax.scopeName); + } + + if (syntax.embeddedLanguages) { + for (let injectScope of syntax.injectTo) { + let injectedEmbeddedLanguages = this._injectedEmbeddedLanguages[injectScope]; + if (!injectedEmbeddedLanguages) { + this._injectedEmbeddedLanguages[injectScope] = injectedEmbeddedLanguages = []; + } + injectedEmbeddedLanguages.push(syntax.embeddedLanguages); + } + } + } + + let modeId = syntax.language; + if (modeId) { + this._languageToScope.set(modeId, syntax.scopeName); + } + } + + private _resolveEmbeddedLanguages(embeddedLanguages: IEmbeddedLanguagesMap): IEmbeddedLanguagesMap2 { + let scopes = Object.keys(embeddedLanguages); + let result: IEmbeddedLanguagesMap2 = Object.create(null); + for (let i = 0, len = scopes.length; i < len; i++) { + let scope = scopes[i]; + let language = embeddedLanguages[scope]; + let languageIdentifier = this._modeService.getLanguageIdentifier(language); + if (languageIdentifier) { + result[scope] = languageIdentifier.id; + } + } + return result; + } + + public async createGrammar(modeId: string): Promise { + const { grammar } = await this._createGrammar(modeId); + return grammar; + } + + private async _createGrammar(modeId: string): Promise { + const scopeName = this._languageToScope.get(modeId); + if (typeof scopeName !== 'string') { + // No TM grammar defined + return Promise.reject(new Error(nls.localize('no-tm-grammar', "No TM Grammar registered for this language."))); + } + const languageRegistration = this._scopeRegistry.getLanguageRegistration(scopeName); + if (!languageRegistration) { + // No TM grammar defined + return Promise.reject(new Error(nls.localize('no-tm-grammar', "No TM Grammar registered for this language."))); + } + let embeddedLanguages = this._resolveEmbeddedLanguages(languageRegistration.embeddedLanguages); + let rawInjectedEmbeddedLanguages = this._injectedEmbeddedLanguages[scopeName]; + if (rawInjectedEmbeddedLanguages) { + let injectedEmbeddedLanguages: IEmbeddedLanguagesMap2[] = rawInjectedEmbeddedLanguages.map(this._resolveEmbeddedLanguages.bind(this)); + for (const injected of injectedEmbeddedLanguages) { + for (const scope of Object.keys(injected)) { + embeddedLanguages[scope] = injected[scope]; + } + } + } + + let languageId = this._modeService.getLanguageIdentifier(modeId)!.id; + let containsEmbeddedLanguages = (Object.keys(embeddedLanguages).length > 0); + + const [grammarRegistry, initialState] = await this._getOrCreateGrammarRegistry(); + const grammar = await grammarRegistry.loadGrammarWithConfiguration(scopeName, languageId, { embeddedLanguages, tokenTypes: languageRegistration.tokenTypes }); + return { + languageId: languageId, + grammar: grammar, + initialState: initialState, + containsEmbeddedLanguages: containsEmbeddedLanguages + }; + } +} + +class TMTokenization implements ITokenizationSupport { + + private readonly _scopeRegistry: TMScopeRegistry; + private readonly _languageId: LanguageId; + private readonly _grammar: IGrammar; + private readonly _containsEmbeddedLanguages: boolean; + private readonly _seenLanguages: boolean[]; + private readonly _initialState: StackElement; + private _maxTokenizationLineLength: number; + private _tokenizationWarningAlreadyShown: boolean; + + constructor(scopeRegistry: TMScopeRegistry, languageId: LanguageId, grammar: IGrammar, initialState: StackElement, containsEmbeddedLanguages: boolean, @INotificationService private readonly notificationService: INotificationService, @IConfigurationService readonly configurationService: IConfigurationService) { + this._scopeRegistry = scopeRegistry; + this._languageId = languageId; + this._grammar = grammar; + this._initialState = initialState; + this._containsEmbeddedLanguages = containsEmbeddedLanguages; + this._seenLanguages = []; + this._maxTokenizationLineLength = configurationService.getValue('editor.maxTokenizationLineLength'); + } + + public getInitialState(): IState { + return this._initialState; + } + + public tokenize(line: string, state: IState, offsetDelta: number): TokenizationResult { + throw new Error('Not supported!'); + } + + public tokenize2(line: string, state: StackElement, offsetDelta: number): TokenizationResult2 { + if (offsetDelta !== 0) { + throw new Error('Unexpected: offsetDelta should be 0.'); + } + + // Do not attempt to tokenize if a line is too long + if (line.length >= this._maxTokenizationLineLength) { + if (!this._tokenizationWarningAlreadyShown) { + this._tokenizationWarningAlreadyShown = true; + this.notificationService.warn(nls.localize('too many characters', "Tokenization is skipped for long lines for performance reasons. The length of a long line can be configured via `editor.maxTokenizationLineLength`.")); + } + console.log(`Line (${line.substr(0, 15)}...): longer than ${this._maxTokenizationLineLength} characters, tokenization skipped.`); + return nullTokenize2(this._languageId, line, state, offsetDelta); + } + + let textMateResult = this._grammar.tokenizeLine2(line, state); + + if (this._containsEmbeddedLanguages) { + let seenLanguages = this._seenLanguages; + let tokens = textMateResult.tokens; + + // Must check if any of the embedded languages was hit + for (let i = 0, len = (tokens.length >>> 1); i < len; i++) { + let metadata = tokens[(i << 1) + 1]; + let languageId = TokenMetadata.getLanguageId(metadata); + + if (!seenLanguages[languageId]) { + seenLanguages[languageId] = true; + this._scopeRegistry.onEncounteredLanguage(languageId); + } + } + } + + let endState: StackElement; + // try to save an object if possible + if (state.equals(textMateResult.ruleStack)) { + endState = state; + } else { + endState = textMateResult.ruleStack; + + } + + return new TokenizationResult2(textMateResult.tokens, endState); + } +} + +registerSingleton(ITextMateService, TextMateService); \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 1d43e20a7c..744974a7fb 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -3,7 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as path from 'vs/base/common/paths'; +import { join } from 'vs/base/common/path'; import * as nls from 'vs/nls'; import { Event, Emitter } from 'vs/base/common/event'; import { guessMimeTypes } from 'vs/base/common/mime'; @@ -12,7 +12,7 @@ import { URI } from 'vs/base/common/uri'; import { isUndefinedOrNull } from 'vs/base/common/types'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, IRawTextContent, ILoadOptions, LoadReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveOptions, ISaveErrorHandler, ISaveParticipant, StateChange, SaveReason, IRawTextContent, ILoadOptions, LoadReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { EncodingMode } from 'vs/workbench/common/editor'; import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -29,7 +29,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { isLinux } from 'vs/base/common/platform'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; -import { isEqual, isEqualOrParent } from 'vs/base/common/resources'; +import { isEqual, isEqualOrParent, extname, basename } from 'vs/base/common/resources'; import { onUnexpectedError } from 'vs/base/common/errors'; /** @@ -45,8 +45,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private static saveErrorHandler: ISaveErrorHandler; static setSaveErrorHandler(handler: ISaveErrorHandler): void { TextFileEditorModel.saveErrorHandler = handler; } - private static saveParticipant: ISaveParticipant; - static setSaveParticipant(handler: ISaveParticipant): void { TextFileEditorModel.saveParticipant = handler; } + private static saveParticipant: ISaveParticipant | null; + static setSaveParticipant(handler: ISaveParticipant | null): void { TextFileEditorModel.saveParticipant = handler; } private readonly _onDidContentChange: Emitter = this._register(new Emitter()); get onDidContentChange(): Event { return this._onDidContentChange.event; } @@ -62,15 +62,15 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private bufferSavedVersionId: number; private lastResolvedDiskStat: IFileStat; private blockModelContentChange: boolean; - private autoSaveAfterMillies: number; + private autoSaveAfterMillies?: number; private autoSaveAfterMilliesEnabled: boolean; - private autoSaveDisposable: IDisposable; + private autoSaveDisposable?: IDisposable; private contentChangeEventScheduler: RunOnceScheduler; private orphanedChangeEventScheduler: RunOnceScheduler; private saveSequentializer: SaveSequentializer; private disposed: boolean; private lastSaveAttemptTime: number; - private createTextEditorModelPromise: Promise; + private createTextEditorModelPromise: Promise | null; private inConflictMode: boolean; private inOrphanMode: boolean; private inErrorMode: boolean; @@ -129,7 +129,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private onFileChanges(e: FileChangesEvent): void { let fileEventImpactsModel = false; - let newInOrphanModeGuess: boolean; + let newInOrphanModeGuess: boolean | undefined; // If we are currently orphaned, we check if the model file was added back if (this.inOrphanMode) { @@ -215,7 +215,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Unset flags const undo = this.setDirty(false); - let loadPromise: Promise; + let loadPromise: Promise; if (soft) { loadPromise = Promise.resolve(); } else { @@ -235,7 +235,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil }); } - load(options?: ILoadOptions): Promise { + load(options?: ILoadOptions): Promise { this.logService.trace('load() - enter', this.resource); // It is very important to not reload the model when the model is dirty. @@ -268,11 +268,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (!!backup) { const content: IRawTextContent = { resource: this.resource, - name: path.basename(this.resource.fsPath), + name: basename(this.resource), mtime: Date.now(), etag: undefined, value: createTextBufferFactory(''), /* will be filled later from backup */ - encoding: this.fileService.encoding.getWriteEncoding(this.resource, this.preferredEncoding), + encoding: this.fileService.encoding.getWriteEncoding(this.resource, this.preferredEncoding).encoding, isReadonly: false }; @@ -289,7 +289,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil const allowBinary = this.isResolved() /* always allow if we resolved previously */ || (options && options.allowBinary); // Decide on etag - let etag: string; + let etag: string | undefined; if (forceReadFromDisk) { etag = undefined; // reset ETag if we enforce to read from disk } else if (this.lastResolvedDiskStat) { @@ -435,7 +435,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.updateSavedVersionId(); } - private doCreateTextModel(resource: URI, value: ITextBufferFactory, backup: URI): Promise { + private doCreateTextModel(resource: URI, value: ITextBufferFactory, backup: URI | undefined): Promise { this.logService.trace('load() - created text editor model', this.resource); this.createTextEditorModelPromise = this.doLoadBackup(backup).then(backupContent => { @@ -443,7 +443,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Create model const hasBackupContent = !!backupContent; - this.createTextEditorModel(hasBackupContent ? backupContent : value, resource); + this.createTextEditorModel(backupContent ? backupContent : value, resource); // We restored a backup so we have to set the model as being dirty // We also want to trigger auto save if it is enabled to simulate the exact same behaviour @@ -480,18 +480,20 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // where `value` was captured in the content change listener closure scope. // Content Change - this._register(this.textEditorModel.onDidChangeContent(() => this.onModelContentChanged())); + if (this.textEditorModel) { + this._register(this.textEditorModel.onDidChangeContent(() => this.onModelContentChanged())); + } } - private doLoadBackup(backup: URI): Promise { + private doLoadBackup(backup: URI | undefined): Promise { if (!backup) { return Promise.resolve(null); } - return this.backupFileService.resolveBackupContent(backup).then(backupContent => backupContent, error => null /* ignore errors */); + return this.backupFileService.resolveBackupContent(backup).then(backupContent => backupContent || null, error => null /* ignore errors */); } - protected getOrCreateMode(modeService: IModeService, preferredModeIds: string, firstLineText?: string): ILanguageSelection { + protected getOrCreateMode(modeService: IModeService, preferredModeIds: string | undefined, firstLineText?: string): ILanguageSelection { return modeService.createByFilepathOrFirstLine(this.resource.fsPath, firstLineText); } @@ -511,7 +513,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // In this case we clear the dirty flag and emit a SAVED event to indicate this state. // Note: we currently only do this check when auto-save is turned off because there you see // a dirty indicator that you want to get rid of when undoing to the saved version. - if (!this.autoSaveAfterMilliesEnabled && this.textEditorModel.getAlternativeVersionId() === this.bufferSavedVersionId) { + if (!this.autoSaveAfterMilliesEnabled && this.textEditorModel && this.textEditorModel.getAlternativeVersionId() === this.bufferSavedVersionId) { this.logService.trace('onModelContentChanged() - model content changed back to last saved version', this.resource); // Clear flags @@ -609,7 +611,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil if (this.saveSequentializer.hasPendingSave(versionId)) { this.logService.trace(`doSave(${versionId}) - exit - found a pending save for versionId ${versionId}`, this.resource); - return this.saveSequentializer.pendingSave; + return this.saveSequentializer.pendingSave || Promise.resolve(undefined); } // Return early if not dirty (unless forced) or version changed meanwhile @@ -642,7 +644,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Push all edit operations to the undo stack so that the user has a chance to // Ctrl+Z back to the saved version. We only do this when auto-save is turned off - if (!this.autoSaveAfterMilliesEnabled) { + if (!this.autoSaveAfterMilliesEnabled && this.textEditorModel) { this.textEditorModel.pushStackElement(); } @@ -659,7 +661,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil }; this.blockModelContentChange = true; - saveParticipantPromise = TextFileEditorModel.saveParticipant.participate(this, { reason: options.reason }).then(onCompleteOrError, onCompleteOrError); + saveParticipantPromise = TextFileEditorModel.saveParticipant.participate(this as IResolvedTextFileEditorModel, { reason: options.reason }).then(onCompleteOrError, onCompleteOrError); } // mark the save participant as current pending save operation @@ -698,7 +700,11 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Save to Disk // mark the save operation as currently pending with the versionId (it might have changed from a save participant triggering) this.logService.trace(`doSave(${versionId}) - before updateContent()`, this.resource); - return this.saveSequentializer.setPending(newVersionId, this.fileService.updateContent(this.lastResolvedDiskStat.resource, this.createSnapshot(), { + const snapshot = this.createSnapshot(); + if (!snapshot) { + throw new Error('Invalid snapshot'); + } + return this.saveSequentializer.setPending(newVersionId, this.fileService.updateContent(this.lastResolvedDiskStat.resource, snapshot, { overwriteReadonly: options.overwriteReadonly, overwriteEncoding: options.overwriteEncoding, mtime: this.lastResolvedDiskStat.mtime, @@ -708,26 +714,6 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil }).then(stat => { this.logService.trace(`doSave(${versionId}) - after updateContent()`, this.resource); - // Telemetry - const settingsType = this.getTypeIfSettings(); - if (settingsType) { - /* __GDPR__ - "settingsWritten" : { - "settingsType": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('settingsWritten', { settingsType }); // Do not log write to user settings.json and .vscode folder as a filePUT event as it ruins our JSON usage data - } else { - /* __GDPR__ - "filePUT" : { - "${include}": [ - "${FileTelemetryData}" - ] - } - */ - this.telemetryService.publicLog('filePUT', this.getTelemetryData(options.reason)); - } - // Update dirty state unless model has changed meanwhile if (versionId === this.versionId) { this.logService.trace(`doSave(${versionId}) - setting dirty to false because versionId did not change`, this.resource); @@ -744,6 +730,33 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Emit File Saved Event this._onDidStateChange.fire(StateChange.SAVED); + + // Telemetry + let telemetryPromise: Thenable; + const settingsType = this.getTypeIfSettings(); + if (settingsType) { + /* __GDPR__ + "settingsWritten" : { + "settingsType": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('settingsWritten', { settingsType }); // Do not log write to user settings.json and .vscode folder as a filePUT event as it ruins our JSON usage data + + telemetryPromise = Promise.resolve(); + } else { + telemetryPromise = this.getTelemetryData(options.reason).then(data => { + /* __GDPR__ + "filePUT" : { + "${include}": [ + "${FileTelemetryData}" + ] + } + */ + this.telemetryService.publicLog('filePUT', data); + }); + } + + return telemetryPromise; }, error => { if (!error) { error = new Error('Unknown Save Error'); // TODO@remote we should never get null as error (https://github.com/Microsoft/vscode/issues/55051) @@ -769,7 +782,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } private getTypeIfSettings(): string { - if (path.extname(this.resource.fsPath) !== '.json') { + if (extname(this.resource) !== '.json') { return ''; } @@ -784,12 +797,12 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } // Check for locale file - if (isEqual(this.resource, URI.file(path.join(this.environmentService.appSettingsHome, 'locale.json')), !isLinux)) { + if (isEqual(this.resource, URI.file(join(this.environmentService.appSettingsHome, 'locale.json')), !isLinux)) { return 'locale'; } // Check for snippets - if (isEqualOrParent(this.resource, URI.file(path.join(this.environmentService.appSettingsHome, 'snippets')))) { + if (isEqualOrParent(this.resource, URI.file(join(this.environmentService.appSettingsHome, 'snippets')))) { return 'snippets'; } @@ -798,7 +811,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil for (const folder of folders) { // {{SQL CARBON EDIT}} if (isEqualOrParent(this.resource, folder.toResource('.azuredatastudio'))) { - const filename = path.basename(this.resource.fsPath); + const filename = basename(this.resource); if (TextFileEditorModel.WHITELIST_WORKSPACE_JSON.indexOf(filename) > -1) { // {{SQL CARBON EDIT}} return `.azuredatastudio/${filename}`; @@ -809,34 +822,40 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return ''; } - private getTelemetryData(reason: number): Object { - const ext = path.extname(this.resource.fsPath); - const fileName = path.basename(this.resource.fsPath); - const telemetryData = { - mimeType: guessMimeTypes(this.resource.fsPath).join(', '), - ext, - path: this.hashService.createSHA1(this.resource.fsPath), - reason - }; + private getTelemetryData(reason: number | undefined): Thenable { + return this.hashService.createSHA1(this.resource.fsPath).then(hashedPath => { + const ext = extname(this.resource); + const fileName = basename(this.resource); + const telemetryData = { + mimeType: guessMimeTypes(this.resource.fsPath).join(', '), + ext, + path: hashedPath, + reason + }; - if (ext === '.json' && TextFileEditorModel.WHITELIST_JSON.indexOf(fileName) > -1) { - telemetryData['whitelistedjson'] = fileName; - } - - /* __GDPR__FRAGMENT__ - "FileTelemetryData" : { - "mimeType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "ext": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "path": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "reason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, - "whitelistedjson": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + if (ext === '.json' && TextFileEditorModel.WHITELIST_JSON.indexOf(fileName) > -1) { + telemetryData['whitelistedjson'] = fileName; } - */ - return telemetryData; + + /* __GDPR__FRAGMENT__ + "FileTelemetryData" : { + "mimeType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "ext": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "path": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "reason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "whitelistedjson": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + return telemetryData; + }); } private doTouch(versionId: number): Promise { - return this.saveSequentializer.setPending(versionId, this.fileService.updateContent(this.lastResolvedDiskStat.resource, this.createSnapshot(), { + const snapshot = this.createSnapshot(); + if (!snapshot) { + throw new Error('invalid snapshot'); + } + return this.saveSequentializer.setPending(versionId, this.fileService.updateContent(this.lastResolvedDiskStat.resource, snapshot, { mtime: this.lastResolvedDiskStat.mtime, encoding: this.getEncoding(), etag: this.lastResolvedDiskStat.etag @@ -916,8 +935,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil return this.lastSaveAttemptTime; } - getETag(): string { - return this.lastResolvedDiskStat ? this.lastResolvedDiskStat.etag : null; + getETag(): string | null { + return this.lastResolvedDiskStat ? this.lastResolvedDiskStat.etag || null : null; } hasState(state: ModelState): boolean { @@ -1006,7 +1025,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil } isReadonly(): boolean { - return this.lastResolvedDiskStat && this.lastResolvedDiskStat.isReadonly; + return !!(this.lastResolvedDiskStat && this.lastResolvedDiskStat.isReadonly); } isDisposed(): boolean { @@ -1048,8 +1067,8 @@ interface ISaveOperation { } export class SaveSequentializer { - private _pendingSave: IPendingSave; - private _nextSave: ISaveOperation; + private _pendingSave?: IPendingSave; + private _nextSave?: ISaveOperation; hasPendingSave(versionId?: number): boolean { if (!this._pendingSave) { @@ -1063,7 +1082,7 @@ export class SaveSequentializer { return !!this._pendingSave; } - get pendingSave(): Promise { + get pendingSave(): Promise | undefined { return this._pendingSave ? this._pendingSave.promise : undefined; } @@ -1112,8 +1131,8 @@ export class SaveSequentializer { this._nextSave = { run, promise, - promiseResolve: promiseResolve, - promiseReject: promiseReject + promiseResolve: promiseResolve!, + promiseReject: promiseReject! }; } @@ -1131,6 +1150,6 @@ class DefaultSaveErrorHandler implements ISaveErrorHandler { constructor(@INotificationService private readonly notificationService: INotificationService) { } onSaveError(error: any, model: TextFileEditorModel): void { - this.notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", path.basename(model.getResource().fsPath), toErrorMessage(error, false))); + this.notificationService.error(nls.localize('genericSaveError', "Failed to save '{0}': {1}", basename(model.getResource()), toErrorMessage(error, false))); } } diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 44039481d2..f643ee058e 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -117,7 +117,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE return 250; } - get(resource: URI): ITextFileEditorModel { + get(resource: URI): ITextFileEditorModel | undefined { return this.mapResourceToModel.get(resource); } @@ -153,12 +153,12 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Model does not exist else { - model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined); + const newModel = model = this.instantiationService.createInstance(TextFileEditorModel, resource, options ? options.encoding : undefined); modelPromise = model.load(options); // Install state change listener this.mapResourceToStateChangeListener.set(resource, model.onDidStateChange(state => { - const event = new TextFileModelChangeEvent(model, state); + const event = new TextFileModelChangeEvent(newModel, state); switch (state) { case StateChange.DIRTY: this._onModelDirty.fire(event); @@ -183,7 +183,7 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE // Install model content change listener this.mapResourceToModelContentChangeListener.set(resource, model.onDidContentChange(e => { - this._onModelContentChanged.fire(new TextFileModelChangeEvent(model, e)); + this._onModelContentChanged.fire(new TextFileModelChangeEvent(newModel, e)); })); } @@ -207,7 +207,9 @@ export class TextFileEditorModelManager extends Disposable implements ITextFileE }, error => { // Free resources of this invalid model - model.dispose(); + if (model) { + model.dispose(); + } // Remove from pending loads this.mapResourceToPendingModelLoaders.delete(resource); diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index e0ccbd4342..eb6ebba5c0 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -5,7 +5,6 @@ import * as nls from 'vs/nls'; import { URI } from 'vs/base/common/uri'; -import * as paths from 'vs/base/common/paths'; import * as errors from 'vs/base/common/errors'; import * as objects from 'vs/base/common/objects'; import { Event, Emitter } from 'vs/base/common/event'; @@ -28,10 +27,17 @@ import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { createTextBufferFactoryFromSnapshot } from 'vs/editor/common/model/textModel'; +import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { isEqualOrParent, isEqual, joinPath, dirname } from 'vs/base/common/resources'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { isEqualOrParent, isEqual, joinPath, dirname, extname, basename } from 'vs/base/common/resources'; +import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts'; +import { getConfirmMessage, IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { coalesce } from 'vs/base/common/arrays'; +import { trim } from 'vs/base/common/strings'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export interface IBackupResult { didBackup: boolean; @@ -42,7 +48,7 @@ export interface IBackupResult { * * It also adds diagnostics and logging around file system operations. */ -export abstract class TextFileService extends Disposable implements ITextFileService { +export class TextFileService extends Disposable implements ITextFileService { _serviceBrand: any; @@ -57,34 +63,38 @@ export abstract class TextFileService extends Disposable implements ITextFileSer private _models: TextFileEditorModelManager; private currentFilesAssociationConfig: { [key: string]: string; }; - private configuredAutoSaveDelay: number; + private configuredAutoSaveDelay?: number; private configuredAutoSaveOnFocusChange: boolean; private configuredAutoSaveOnWindowChange: boolean; private configuredHotExit: string; private autoSaveContext: IContextKey; constructor( - private lifecycleService: ILifecycleService, - private contextService: IWorkspaceContextService, - private configurationService: IConfigurationService, - protected fileService: IFileService, - private untitledEditorService: IUntitledEditorService, - private instantiationService: IInstantiationService, - private notificationService: INotificationService, - protected environmentService: IEnvironmentService, - private backupFileService: IBackupFileService, - private windowsService: IWindowsService, - protected windowService: IWindowService, - private historyService: IHistoryService, - contextKeyService: IContextKeyService, - private modelService: IModelService + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IFileService protected readonly fileService: IFileService, + @IUntitledEditorService private readonly untitledEditorService: IUntitledEditorService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IModeService private readonly modeService: IModeService, + @IModelService private readonly modelService: IModelService, + @IWindowService private readonly windowService: IWindowService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @INotificationService private readonly notificationService: INotificationService, + @IBackupFileService private readonly backupFileService: IBackupFileService, + @IWindowsService private readonly windowsService: IWindowsService, + @IHistoryService private readonly historyService: IHistoryService, + @IContextKeyService contextKeyService: IContextKeyService, + @IDialogService private readonly dialogService: IDialogService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IEditorService private readonly editorService: IEditorService ) { super(); - this._models = this.instantiationService.createInstance(TextFileEditorModelManager); + this._models = instantiationService.createInstance(TextFileEditorModelManager); this.autoSaveContext = AutoSaveContext.bindTo(contextKeyService); - const configuration = this.configurationService.getValue(); + const configuration = configurationService.getValue(); this.currentFilesAssociationConfig = configuration && configuration.files && configuration.files.associations; this.onFilesConfigurationChange(configuration); @@ -96,11 +106,124 @@ export abstract class TextFileService extends Disposable implements ITextFileSer return this._models; } - abstract resolveTextContent(resource: URI, options?: IResolveContentOptions): Promise; + resolveTextContent(resource: URI, options?: IResolveContentOptions): Promise { + return this.fileService.resolveStreamContent(resource, options).then(streamContent => { + return createTextBufferFactoryFromStream(streamContent.value).then(res => { + const r: IRawTextContent = { + resource: streamContent.resource, + name: streamContent.name, + mtime: streamContent.mtime, + etag: streamContent.etag, + encoding: streamContent.encoding, + isReadonly: streamContent.isReadonly, + value: res + }; + return r; + }); + }); + } - abstract promptForPath(resource: URI, defaultPath: URI): Promise; + promptForPath(resource: URI, defaultUri: URI): Promise { - abstract confirmSave(resources?: URI[]): Promise; + // Help user to find a name for the file by opening it first + return this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true, } }).then(() => { + return this.fileDialogService.showSaveDialog(this.getSaveDialogOptions(defaultUri)); + }); + } + + private getSaveDialogOptions(defaultUri: URI): ISaveDialogOptions { + const options: ISaveDialogOptions = { + defaultUri, + title: nls.localize('saveAsTitle', "Save As") + }; + + // Filters are only enabled on Windows where they work properly + if (!platform.isWindows) { + return options; + } + + interface IFilter { name: string; extensions: string[]; } + + // Build the file filter by using our known languages + const ext: string | undefined = defaultUri ? extname(defaultUri) : undefined; + let matchingFilter: IFilter | undefined; + const filters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => { + const extensions = this.modeService.getExtensions(languageName); + if (!extensions || !extensions.length) { + return null; + } + + const filter: IFilter = { name: languageName, extensions: extensions.slice(0, 10).map(e => trim(e, '.')) }; + + if (ext && extensions.indexOf(ext) >= 0) { + matchingFilter = filter; + + return null; // matching filter will be added last to the top + } + + return filter; + })); + + // Filters are a bit weird on Windows, based on having a match or not: + // Match: we put the matching filter first so that it shows up selected and the all files last + // No match: we put the all files filter first + const allFilesFilter = { name: nls.localize('allFiles', "All Files"), extensions: ['*'] }; + if (matchingFilter) { + filters.unshift(matchingFilter); + filters.unshift(allFilesFilter); + } else { + filters.unshift(allFilesFilter); + } + + // Allow to save file without extension + filters.push({ name: nls.localize('noExt', "No Extension"), extensions: [''] }); + + options.filters = filters; + + return options; + } + + confirmSave(resources?: URI[]): Promise { + if (this.environmentService.isExtensionDevelopment) { + return Promise.resolve(ConfirmResult.DONT_SAVE); // no veto when we are in extension dev mode because we cannot assum we run interactive (e.g. tests) + } + + const resourcesToConfirm = this.getDirty(resources); + if (resourcesToConfirm.length === 0) { + return Promise.resolve(ConfirmResult.DONT_SAVE); + } + + const message = resourcesToConfirm.length === 1 ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", basename(resourcesToConfirm[0])) + : getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm); + + const buttons: string[] = [ + resourcesToConfirm.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), + nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), + nls.localize('cancel', "Cancel") + ]; + + return this.dialogService.show(Severity.Warning, message, buttons, { + cancelId: 2, + detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.") + }).then(index => { + switch (index) { + case 0: return ConfirmResult.SAVE; + case 1: return ConfirmResult.DONT_SAVE; + default: return ConfirmResult.CANCEL; + } + }); + } + + confirmOverwrite(resource: URI): Promise { + const confirm: IConfirmation = { + message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), + detail: nls.localize('irreversible', "A file or folder with the same name already exists in the folder {0}. Replacing it will overwrite its current contents.", basename(dirname(resource))), + primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), + type: 'warning' + }; + + return this.dialogService.confirm(confirm).then(result => result.confirmed); + } private registerListeners(): void { @@ -133,7 +256,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer return this.handleDirtyBeforeShutdown(remainingDirty, reason); } - return undefined; + return false; }); } @@ -176,7 +299,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer // closed is the only VS Code window open, except for on Mac where hot exit is only // ever activated when quit is requested. - let doBackup: boolean; + let doBackup: boolean | undefined; switch (reason) { case ShutdownReason.CLOSE: if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { @@ -221,7 +344,10 @@ export abstract class TextFileService extends Disposable implements ITextFileSer const untitledToBackup: URI[] = []; dirtyToBackup.forEach(s => { if (this.fileService.canHandleResource(s)) { - filesToBackup.push(textFileEditorModelManager.get(s)); + const model = textFileEditorModelManager.get(s); + if (model) { + filesToBackup.push(model); + } } else if (s.scheme === Schemas.untitled) { untitledToBackup.push(s); } @@ -231,9 +357,16 @@ export abstract class TextFileService extends Disposable implements ITextFileSer } private doBackupAll(dirtyFileModels: ITextFileEditorModel[], untitledResources: URI[]): Promise { + const promises = dirtyFileModels.map(model => { + const snapshot = model.createSnapshot(); + if (snapshot) { + return this.backupFileService.backupResource(model.getResource(), snapshot, model.getVersionId()); + } + return Promise.resolve(); + }); // Handle file resources first - return Promise.all(dirtyFileModels.map(model => this.backupFileService.backupResource(model.getResource(), model.createSnapshot(), model.getVersionId()))).then(results => { + return Promise.all(promises).then(results => { // Handle untitled resources const untitledModelPromises = untitledResources @@ -242,7 +375,11 @@ export abstract class TextFileService extends Disposable implements ITextFileSer return Promise.all(untitledModelPromises).then(untitledModels => { const untitledBackupPromises = untitledModels.map(model => { - return this.backupFileService.backupResource(model.getResource(), model.createSnapshot(), model.getVersionId()); + const snapshot = model.createSnapshot(); + if (snapshot) { + return this.backupFileService.backupResource(model.getResource(), snapshot, model.getVersionId()); + } + return Promise.resolve(); }); return Promise.all(untitledBackupPromises).then(() => undefined); @@ -279,7 +416,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer return true; // veto } - return undefined; + return false; }); } @@ -392,7 +529,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer } } - return this.saveAll([resource], options).then(result => result.results.length === 1 && result.results[0].success); + return this.saveAll([resource], options).then(result => result.results.length === 1 && !!result.results[0].success); } saveAll(includeUntitled?: boolean, options?: ISaveOptions): Promise; @@ -434,7 +571,7 @@ export abstract class TextFileService extends Disposable implements ITextFileSer // Untitled with associated file path don't need to prompt if (this.untitledEditorService.hasAssociatedFilePath(untitled)) { - targetUri = untitled.with({ scheme: Schemas.file }); + targetUri = this.untitledToAssociatedFileResource(untitled); } // Otherwise ask user @@ -471,6 +608,12 @@ export abstract class TextFileService extends Disposable implements ITextFileSer }); } + private untitledToAssociatedFileResource(untitled: URI): URI { + const authority = this.windowService.getConfiguration().remoteAuthority; + + return authority ? untitled.with({ scheme: REMOTE_HOST_SCHEME, authority }) : untitled.with({ scheme: Schemas.file }); + } + private doSaveAllFiles(resources?: URI[], options: ISaveOptions = Object.create(null)): Promise { const dirtyFileModels = this.getDirtyFileModels(Array.isArray(resources) ? resources : undefined /* Save All */) .filter(model => { @@ -491,7 +634,10 @@ export abstract class TextFileService extends Disposable implements ITextFileSer return Promise.all(dirtyFileModels.map(model => { return model.save(options).then(() => { if (!model.isDirty()) { - mapResourceToResult.get(model.getResource()).success = true; + const result = mapResourceToResult.get(model.getResource()); + if (result) { + result.success = true; + } } }); })).then(r => ({ results: mapResourceToResult.values() })); @@ -518,10 +664,10 @@ export abstract class TextFileService extends Disposable implements ITextFileSer return this.getFileModels(arg1).filter(model => model.isDirty()); } - saveAs(resource: URI, target?: URI, options?: ISaveOptions): Promise { + saveAs(resource: URI, target?: URI, options?: ISaveOptions): Promise { // Get to target resource - let targetPromise: Promise; + let targetPromise: Promise; if (target) { targetPromise = Promise.resolve(target); } else { @@ -533,9 +679,9 @@ export abstract class TextFileService extends Disposable implements ITextFileSer targetPromise = this.promptForPath(resource, dialogPath); } - return targetPromise.then(target => { + return targetPromise.then(target => { if (!target) { - return null; // user canceled + return undefined; // user canceled } // Just save if target is same as models own resource @@ -548,17 +694,17 @@ export abstract class TextFileService extends Disposable implements ITextFileSer }); } - private doSaveAs(resource: URI, target?: URI, options?: ISaveOptions): Promise { + private doSaveAs(resource: URI, target: URI, options?: ISaveOptions): Promise { // Retrieve text model from provided resource if any - let modelPromise: Promise = Promise.resolve(null); + let modelPromise: Promise = Promise.resolve(undefined); if (this.fileService.canHandleResource(resource)) { modelPromise = Promise.resolve(this._models.get(resource)); } else if (resource.scheme === Schemas.untitled && this.untitledEditorService.exists(resource)) { modelPromise = this.untitledEditorService.loadOrCreate({ resource }); } - return modelPromise.then(model => { + return modelPromise.then(model => { // We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before) if (model) { @@ -566,8 +712,13 @@ export abstract class TextFileService extends Disposable implements ITextFileSer } // Otherwise we can only copy - return this.fileService.copyFile(resource, target); - }).then(() => { + return this.fileService.copyFile(resource, target).then(() => true); + }).then(result => { + + // Return early if the operation was not running + if (!result) { + return target; + } // Revert the source return this.revert(resource).then(() => { @@ -578,30 +729,61 @@ export abstract class TextFileService extends Disposable implements ITextFileSer }); } - private doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI, options?: ISaveOptions): Promise { + private doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI, options?: ISaveOptions): Promise { let targetModelResolver: Promise; + let targetExists: boolean = false; // Prefer an existing model if it is already loaded for the given target resource const targetModel = this.models.get(target); if (targetModel && targetModel.isResolved()) { targetModelResolver = Promise.resolve(targetModel); + targetExists = true; } // Otherwise create the target file empty if it does not exist already and resolve it from there else { - targetModelResolver = this.fileService.resolveFile(target).then(stat => stat, () => null).then(stat => stat || this.fileService.updateContent(target, '')).then(stat => { - return this.models.loadOrCreate(target); - }); + targetModelResolver = this.fileService.existsFile(target).then(exists => { + targetExists = exists; + + // create target model adhoc if file does not exist yet + if (!targetExists) { + return this.fileService.updateContent(target, ''); + } + + return undefined; + }).then(() => this.models.loadOrCreate(target)); } return targetModelResolver.then(targetModel => { - // take over encoding and model value from source model - targetModel.updatePreferredEncoding(sourceModel.getEncoding()); - this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot())); + // Confirm to overwrite if we have an untitled file with associated file where + // the file actually exists on disk and we are instructed to save to that file + // path. This can happen if the file was created after the untitled file was opened. + // See https://github.com/Microsoft/vscode/issues/67946 + let confirmWrite: Promise; + if (sourceModel instanceof UntitledEditorModel && sourceModel.hasAssociatedFilePath && targetExists && isEqual(target, this.untitledToAssociatedFileResource(sourceModel.getResource()))) { + confirmWrite = this.confirmOverwrite(target); + } else { + confirmWrite = Promise.resolve(true); + } - // save model - return targetModel.save(options); + return confirmWrite.then(write => { + if (!write) { + return false; + } + + // take over encoding and model value from source model + targetModel.updatePreferredEncoding(sourceModel.getEncoding()); + if (targetModel.textEditorModel) { + const snapshot = sourceModel.createSnapshot(); + if (snapshot) { + this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(snapshot)); + } + } + + // save model + return targetModel.save(options).then(() => true); + }); }, error => { // binary model: delete the file and run the operation again @@ -615,8 +797,8 @@ export abstract class TextFileService extends Disposable implements ITextFileSer private suggestFileName(untitledResource: URI): URI { const untitledFileName = this.untitledEditorService.suggestFileName(untitledResource); - - const schemeFilter = Schemas.file; + const remoteAuthority = this.windowService.getConfiguration().remoteAuthority; + const schemeFilter = remoteAuthority ? REMOTE_HOST_SCHEME : Schemas.file; const lastActiveFile = this.historyService.getLastActiveFile(schemeFilter); if (lastActiveFile) { @@ -629,11 +811,11 @@ export abstract class TextFileService extends Disposable implements ITextFileSer return joinPath(lastActiveFolder, untitledFileName); } - return URI.file(untitledFileName); + return schemeFilter === Schemas.file ? URI.file(untitledFileName) : URI.from({ scheme: schemeFilter, authority: remoteAuthority, path: '/' + untitledFileName }); } revert(resource: URI, options?: IRevertOptions): Promise { - return this.revertAll([resource], options).then(result => result.results.length === 1 && result.results[0].success); + return this.revertAll([resource], options).then(result => result.results.length === 1 && !!result.results[0].success); } revertAll(resources?: URI[], options?: IRevertOptions): Promise { @@ -662,13 +844,19 @@ export abstract class TextFileService extends Disposable implements ITextFileSer return Promise.all(fileModels.map(model => { return model.revert(options && options.soft).then(() => { if (!model.isDirty()) { - mapResourceToResult.get(model.getResource()).success = true; + const result = mapResourceToResult.get(model.getResource()); + if (result) { + result.success = true; + } } }, error => { // FileNotFound means the file got deleted meanwhile, so still record as successful revert if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { - mapResourceToResult.get(model.getResource()).success = true; + const result = mapResourceToResult.get(model.getResource()); + if (result) { + result.success = true; + } } // Otherwise bubble up the error @@ -747,14 +935,18 @@ export abstract class TextFileService extends Disposable implements ITextFileSer // Otherwise a parent folder of the source is being moved, so we need // to compute the target resource based on that else { - targetModelResource = sourceModelResource.with({ path: paths.join(target.path, sourceModelResource.path.substr(source.path.length + 1)) }); + targetModelResource = sourceModelResource.with({ path: joinPath(target, sourceModelResource.path.substr(source.path.length + 1)).path }); } // Remember as dirty target model to load after the operation dirtyTargetModels.push(targetModelResource); // Backup dirty source model to the target resource it will become later - return this.backupFileService.backupResource(targetModelResource, sourceModel.createSnapshot(), sourceModel.getVersionId()); + const snapshot = sourceModel.createSnapshot(); + if (snapshot) { + return this.backupFileService.backupResource(targetModelResource, snapshot, sourceModel.getVersionId()); + } + return Promise.resolve(); })); } else { handleDirtySourceModels = Promise.resolve(); @@ -818,3 +1010,5 @@ export abstract class TextFileService extends Disposable implements ITextFileSer super.dispose(); } } + +registerSingleton(ITextFileService, TextFileService); \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index 52f5d40e19..2cc26a0dbd 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -10,7 +10,7 @@ import { IEncodingSupport, ConfirmResult, IRevertOptions } from 'vs/workbench/co import { IBaseStat, IResolveContentOptions, ITextSnapshot } from 'vs/platform/files/common/files'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; -import { ITextBufferFactory } from 'vs/editor/common/model'; +import { ITextBufferFactory, ITextModel } from 'vs/editor/common/model'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; /** @@ -29,7 +29,7 @@ export interface ISaveParticipant { /** * Participate in a save of a model. Allows to change the model before it is being saved to disk. */ - participate(model: ITextFileEditorModel, env: { reason: SaveReason }): Promise; + participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }): Promise; } /** @@ -101,7 +101,7 @@ export interface IResult { } export interface IAutoSaveConfiguration { - autoSaveDelay: number; + autoSaveDelay?: number; autoSaveFocusChange: boolean; autoSaveApplicationChange: boolean; } @@ -189,7 +189,7 @@ export interface ITextFileEditorModelManager { onModelsSaved: Event; onModelsReverted: Event; - get(resource: URI): ITextFileEditorModel; + get(resource: URI): ITextFileEditorModel | undefined; getAll(resource?: URI): ITextFileEditorModel[]; @@ -236,7 +236,7 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport hasState(state: ModelState): boolean; - getETag(): string; + getETag(): string | null; updatePreferredEncoding(encoding: string): void; @@ -246,7 +246,7 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport revert(soft?: boolean): Promise; - createSnapshot(): ITextSnapshot; + createSnapshot(): ITextSnapshot | null; isDirty(): boolean; @@ -255,6 +255,12 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport isDisposed(): boolean; } +export interface IResolvedTextFileEditorModel extends ITextFileEditorModel { + readonly textEditorModel: ITextModel; + + createSnapshot(): ITextSnapshot; +} + export interface IWillMoveEvent { oldResource: URI; @@ -313,9 +319,9 @@ export interface ITextFileService extends IDisposable { * @param resource the resource to save as. * @param targetResource the optional target to save to. * @param options optional save options - * @return true if the file was saved. + * @return Path of the saved resource. */ - saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): Promise; + saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): Promise; /** * Saves the set of resources and returns a promise with the operation result. diff --git a/src/vs/workbench/services/textfile/electron-browser/textFileService.ts b/src/vs/workbench/services/textfile/electron-browser/textFileService.ts deleted file mode 100644 index b574e9cb62..0000000000 --- a/src/vs/workbench/services/textfile/electron-browser/textFileService.ts +++ /dev/null @@ -1,165 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the Source EULA. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import * as paths from 'vs/base/common/paths'; -import * as strings from 'vs/base/common/strings'; -import { isWindows } from 'vs/base/common/platform'; -import { URI } from 'vs/base/common/uri'; -import { ConfirmResult } from 'vs/workbench/common/editor'; -import { TextFileService as AbstractTextFileService } from 'vs/workbench/services/textfile/common/textFileService'; -import { IRawTextContent } from 'vs/workbench/services/textfile/common/textfiles'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { IFileService, IResolveContentOptions } from 'vs/platform/files/common/files'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { getConfirmMessage, IDialogService, ISaveDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { coalesce } from 'vs/base/common/arrays'; - -export class TextFileService extends AbstractTextFileService { - - constructor( - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IFileService fileService: IFileService, - @IUntitledEditorService untitledEditorService: IUntitledEditorService, - @ILifecycleService lifecycleService: ILifecycleService, - @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService, - @IModeService private readonly modeService: IModeService, - @IModelService modelService: IModelService, - @IWindowService windowService: IWindowService, - @IEnvironmentService environmentService: IEnvironmentService, - @INotificationService notificationService: INotificationService, - @IBackupFileService backupFileService: IBackupFileService, - @IWindowsService windowsService: IWindowsService, - @IHistoryService historyService: IHistoryService, - @IContextKeyService contextKeyService: IContextKeyService, - @IDialogService private readonly dialogService: IDialogService, - @IFileDialogService private readonly fileDialogService: IFileDialogService, - @IEditorService private readonly editorService: IEditorService - ) { - super(lifecycleService, contextService, configurationService, fileService, untitledEditorService, instantiationService, notificationService, environmentService, backupFileService, windowsService, windowService, historyService, contextKeyService, modelService); - } - - resolveTextContent(resource: URI, options?: IResolveContentOptions): Promise { - return this.fileService.resolveStreamContent(resource, options).then(streamContent => { - return createTextBufferFactoryFromStream(streamContent.value).then(res => { - const r: IRawTextContent = { - resource: streamContent.resource, - name: streamContent.name, - mtime: streamContent.mtime, - etag: streamContent.etag, - encoding: streamContent.encoding, - isReadonly: streamContent.isReadonly, - value: res - }; - return r; - }); - }); - } - - confirmSave(resources?: URI[]): Promise { - if (this.environmentService.isExtensionDevelopment) { - return Promise.resolve(ConfirmResult.DONT_SAVE); // no veto when we are in extension dev mode because we cannot assum we run interactive (e.g. tests) - } - - const resourcesToConfirm = this.getDirty(resources); - if (resourcesToConfirm.length === 0) { - return Promise.resolve(ConfirmResult.DONT_SAVE); - } - - const message = resourcesToConfirm.length === 1 ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", paths.basename(resourcesToConfirm[0].fsPath)) - : getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm); - - const buttons: string[] = [ - resourcesToConfirm.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), - nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), - nls.localize('cancel', "Cancel") - ]; - - return this.dialogService.show(Severity.Warning, message, buttons, { - cancelId: 2, - detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.") - }).then(index => { - switch (index) { - case 0: return ConfirmResult.SAVE; - case 1: return ConfirmResult.DONT_SAVE; - default: return ConfirmResult.CANCEL; - } - }); - } - - promptForPath(resource: URI, defaultUri: URI): Promise { - - // Help user to find a name for the file by opening it first - return this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true, } }).then(() => { - return this.fileDialogService.showSaveDialog(this.getSaveDialogOptions(defaultUri)); - }); - } - - private getSaveDialogOptions(defaultUri: URI): ISaveDialogOptions { - const options: ISaveDialogOptions = { - defaultUri, - title: nls.localize('saveAsTitle', "Save As") - }; - - // Filters are only enabled on Windows where they work properly - if (!isWindows) { - return options; - } - - interface IFilter { name: string; extensions: string[]; } - - // Build the file filter by using our known languages - const ext: string = defaultUri ? paths.extname(defaultUri.path) : undefined; - let matchingFilter: IFilter; - const filters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => { - const extensions = this.modeService.getExtensions(languageName); - if (!extensions || !extensions.length) { - return null; - } - - const filter: IFilter = { name: languageName, extensions: extensions.slice(0, 10).map(e => strings.trim(e, '.')) }; - - if (ext && extensions.indexOf(ext) >= 0) { - matchingFilter = filter; - - return null; // matching filter will be added last to the top - } - - return filter; - })); - - // Filters are a bit weird on Windows, based on having a match or not: - // Match: we put the matching filter first so that it shows up selected and the all files last - // No match: we put the all files filter first - const allFilesFilter = { name: nls.localize('allFiles', "All Files"), extensions: ['*'] }; - if (matchingFilter) { - filters.unshift(matchingFilter); - filters.unshift(allFilesFilter); - } else { - filters.unshift(allFilesFilter); - } - - // Allow to save file without extension - filters.push({ name: nls.localize('noExt', "No Extension"), extensions: [''] }); - - options.filters = filters; - - return options; - } -} diff --git a/src/vs/workbench/services/textfile/electron-browser/textResourcePropertiesService.ts b/src/vs/workbench/services/textfile/node/textResourcePropertiesService.ts similarity index 84% rename from src/vs/workbench/services/textfile/electron-browser/textResourcePropertiesService.ts rename to src/vs/workbench/services/textfile/node/textResourcePropertiesService.ts index 6e2b94fc11..5354b31d27 100644 --- a/src/vs/workbench/services/textfile/electron-browser/textResourcePropertiesService.ts +++ b/src/vs/workbench/services/textfile/node/textResourcePropertiesService.ts @@ -7,10 +7,12 @@ import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { OperatingSystem, OS } from 'vs/base/common/platform'; -import { IRemoteAgentService, IRemoteAgentEnvironment } from 'vs/workbench/services/remote/node/remoteAgentService'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { Schemas } from 'vs/base/common/network'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IWindowService } from 'vs/platform/windows/common/windows'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; export class TextResourcePropertiesService implements ITextResourcePropertiesService { @@ -45,11 +47,12 @@ export class TextResourcePropertiesService implements ITextResourcePropertiesSer if (remoteAuthority) { if (resource.scheme !== Schemas.file) { const osCacheKey = `resource.authority.os.${remoteAuthority}`; - os = this.remoteEnvironment ? this.remoteEnvironment.os : /* Get it from cache */ this.storageService.getInteger(osCacheKey, StorageScope.WORKSPACE, OS); + os = this.remoteEnvironment ? this.remoteEnvironment.os : /* Get it from cache */ this.storageService.getNumber(osCacheKey, StorageScope.WORKSPACE, OS); this.storageService.store(osCacheKey, os, StorageScope.WORKSPACE); } } return os; } +} -} \ No newline at end of file +registerSingleton(ITextResourcePropertiesService, TextResourcePropertiesService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts index 43c67bc25b..8798b36e43 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModel.test.ts @@ -48,7 +48,7 @@ suite('Files - TextFileEditorModel', () => { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); return model.load().then(() => { - model.textEditorModel.setValue('bar'); + model.textEditorModel!.setValue('bar'); assert.ok(getLastModifiedTime(model) <= Date.now()); return model.save().then(() => { @@ -90,7 +90,7 @@ suite('Files - TextFileEditorModel', () => { const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); return model.load().then(() => { - model.textEditorModel.dispose(); + model.textEditorModel!.dispose(); assert.ok(model.isDisposed()); }); @@ -117,7 +117,7 @@ suite('Files - TextFileEditorModel', () => { const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/index_async.txt'), 'utf8'); return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model.textEditorModel!.setValue('foo'); assert.ok(model.isDirty()); assert.ok(model.hasState(ModelState.DIRTY)); @@ -141,13 +141,13 @@ suite('Files - TextFileEditorModel', () => { }); return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model.textEditorModel!.setValue('foo'); assert.ok(model.isDirty()); return model.revert().then(() => { assert.ok(!model.isDirty()); - assert.equal(model.textEditorModel.getValue(), 'Hello Html'); + assert.equal(model.textEditorModel!.getValue(), 'Hello Html'); assert.equal(eventCounter, 1); model.dispose(); @@ -167,13 +167,13 @@ suite('Files - TextFileEditorModel', () => { }); return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model.textEditorModel!.setValue('foo'); assert.ok(model.isDirty()); return model.revert(true /* soft revert */).then(() => { assert.ok(!model.isDirty()); - assert.equal(model.textEditorModel.getValue(), 'foo'); + assert.equal(model.textEditorModel!.getValue(), 'foo'); assert.equal(eventCounter, 1); model.dispose(); @@ -186,7 +186,7 @@ suite('Files - TextFileEditorModel', () => { return model.load().then(() => { accessor.fileService.setContent('Hello Change'); return model.load().then(() => { - model.textEditorModel.undo(); + model.textEditorModel!.undo(); assert.ok(model.isDirty()); }); @@ -227,7 +227,7 @@ suite('Files - TextFileEditorModel', () => { return input1.resolve().then((model1: TextFileEditorModel) => { return input2.resolve().then((model2: TextFileEditorModel) => { - model1.textEditorModel.setValue('foo'); + model1.textEditorModel!.setValue('foo'); const m1Mtime = model1.getStat().mtime; const m2Mtime = model2.getStat().mtime; @@ -238,7 +238,7 @@ suite('Files - TextFileEditorModel', () => { assert.ok(accessor.textFileService.isDirty(toResource.call(this, '/path/index_async2.txt'))); assert.ok(!accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt'))); - model2.textEditorModel.setValue('foo'); + model2.textEditorModel!.setValue('foo'); assert.ok(accessor.textFileService.isDirty(toResource.call(this, '/path/index_async.txt'))); return timeout(10).then(() => { @@ -264,7 +264,7 @@ suite('Files - TextFileEditorModel', () => { model.onDidStateChange(e => { if (e === StateChange.SAVED) { - assert.equal(snapshotToString(model.createSnapshot()), 'bar'); + assert.equal(snapshotToString(model.createSnapshot()!), 'bar'); assert.ok(!model.isDirty()); eventCounter++; } @@ -281,7 +281,7 @@ suite('Files - TextFileEditorModel', () => { }); return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model.textEditorModel!.setValue('foo'); return model.save().then(() => { model.dispose(); @@ -302,7 +302,7 @@ suite('Files - TextFileEditorModel', () => { }); return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model.textEditorModel!.setValue('foo'); const now = Date.now(); return model.save().then(() => { @@ -322,7 +322,7 @@ suite('Files - TextFileEditorModel', () => { }); return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model.textEditorModel!.setValue('foo'); return model.save().then(() => { model.dispose(); }); @@ -337,7 +337,7 @@ suite('Files - TextFileEditorModel', () => { assert.ok(!sequentializer.pendingSave); // pending removes itself after done - return sequentializer.setPending(1, Promise.resolve(null)).then(() => { + return sequentializer.setPending(1, Promise.resolve()).then(() => { assert.ok(!sequentializer.hasPendingSave()); assert.ok(!sequentializer.hasPendingSave(1)); assert.ok(!sequentializer.pendingSave); @@ -361,11 +361,11 @@ suite('Files - TextFileEditorModel', () => { const sequentializer = new SaveSequentializer(); let pendingDone = false; - sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return null; })); + sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return; })); // next finishes instantly let nextDone = false; - const res = sequentializer.setNext(() => Promise.resolve(null).then(() => { nextDone = true; return null; })); + const res = sequentializer.setNext(() => Promise.resolve(null).then(() => { nextDone = true; return; })); return res.then(() => { assert.ok(pendingDone); @@ -377,11 +377,11 @@ suite('Files - TextFileEditorModel', () => { const sequentializer = new SaveSequentializer(); let pendingDone = false; - sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return null; })); + sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return; })); // next finishes after timeout let nextDone = false; - const res = sequentializer.setNext(() => timeout(1).then(() => { nextDone = true; return null; })); + const res = sequentializer.setNext(() => timeout(1).then(() => { nextDone = true; return; })); return res.then(() => { assert.ok(pendingDone); @@ -393,17 +393,17 @@ suite('Files - TextFileEditorModel', () => { const sequentializer = new SaveSequentializer(); let pendingDone = false; - sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return null; })); + sequentializer.setPending(1, timeout(1).then(() => { pendingDone = true; return; })); // next finishes after timeout let firstDone = false; - let firstRes = sequentializer.setNext(() => timeout(2).then(() => { firstDone = true; return null; })); + let firstRes = sequentializer.setNext(() => timeout(2).then(() => { firstDone = true; return; })); let secondDone = false; - let secondRes = sequentializer.setNext(() => timeout(3).then(() => { secondDone = true; return null; })); + let secondRes = sequentializer.setNext(() => timeout(3).then(() => { secondDone = true; return; })); let thirdDone = false; - let thirdRes = sequentializer.setNext(() => timeout(4).then(() => { thirdDone = true; return null; })); + let thirdRes = sequentializer.setNext(() => timeout(4).then(() => { thirdDone = true; return; })); return Promise.all([firstRes, secondRes, thirdRes]).then(() => { assert.ok(pendingDone); diff --git a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts index bee6100e1d..a8fa8ec7f0 100644 --- a/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileEditorModelManager.test.ts @@ -7,12 +7,12 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { join } from 'vs/base/common/paths'; import { workbenchInstantiationService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { IFileService, FileChangesEvent, FileChangeType } from 'vs/platform/files/common/files'; import { IModelService } from 'vs/editor/common/services/modelService'; import { timeout } from 'vs/base/common/async'; +import { toResource } from 'vs/base/test/common/utils'; export class TestTextFileEditorModelManager extends TextFileEditorModelManager { @@ -29,10 +29,6 @@ class ServiceAccessor { } } -function toResource(path: string): URI { - return URI.file(join('C:\\', path)); -} - suite('Files - TextFileEditorModelManager', () => { let instantiationService: IInstantiationService; @@ -46,9 +42,9 @@ suite('Files - TextFileEditorModelManager', () => { test('add, remove, clear, get, getAll', function () { const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource('/path/random1.txt'), 'utf8'); - const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource('/path/random2.txt'), 'utf8'); - const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource('/path/random3.txt'), 'utf8'); + const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8'); + const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8'); + const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8'); manager.add(URI.file('/test.html'), model1); manager.add(URI.file('/some/other.html'), model2); @@ -126,9 +122,9 @@ suite('Files - TextFileEditorModelManager', () => { test('removed from cache when model disposed', function () { const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource('/path/random1.txt'), 'utf8'); - const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource('/path/random2.txt'), 'utf8'); - const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource('/path/random3.txt'), 'utf8'); + const model1: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random1.txt'), 'utf8'); + const model2: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random2.txt'), 'utf8'); + const model3: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/random3.txt'), 'utf8'); manager.add(URI.file('/test.html'), model1); manager.add(URI.file('/some/other.html'), model2); @@ -143,14 +139,14 @@ suite('Files - TextFileEditorModelManager', () => { model3.dispose(); }); - test('events', () => { + test('events', function () { TextFileEditorModel.DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = 0; TextFileEditorModel.DEFAULT_ORPHANED_CHANGE_BUFFER_DELAY = 0; const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - const resource1 = toResource('/path/index.txt'); - const resource2 = toResource('/path/other.txt'); + const resource1 = toResource.call(this, '/path/index.txt'); + const resource2 = toResource.call(this, '/path/other.txt'); let dirtyCounter = 0; let revertedCounter = 0; @@ -198,11 +194,11 @@ suite('Files - TextFileEditorModelManager', () => { accessor.fileService.fireFileChanges(new FileChangesEvent([{ resource: resource1, type: FileChangeType.ADDED }])); return manager.loadOrCreate(resource2, { encoding: 'utf8' }).then(model2 => { - model1.textEditorModel.setValue('changed'); + model1.textEditorModel!.setValue('changed'); model1.updatePreferredEncoding('utf16'); return model1.revert().then(() => { - model1.textEditorModel.setValue('changed again'); + model1.textEditorModel!.setValue('changed again'); return model1.save().then(() => { model1.dispose(); @@ -235,8 +231,8 @@ suite('Files - TextFileEditorModelManager', () => { test('events debounced', function () { const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - const resource1 = toResource('/path/index.txt'); - const resource2 = toResource('/path/other.txt'); + const resource1 = toResource.call(this, '/path/index.txt'); + const resource2 = toResource.call(this, '/path/other.txt'); let dirtyCounter = 0; let revertedCounter = 0; @@ -261,11 +257,11 @@ suite('Files - TextFileEditorModelManager', () => { return manager.loadOrCreate(resource1, { encoding: 'utf8' }).then(model1 => { return manager.loadOrCreate(resource2, { encoding: 'utf8' }).then(model2 => { - model1.textEditorModel.setValue('changed'); + model1.textEditorModel!.setValue('changed'); model1.updatePreferredEncoding('utf16'); return model1.revert().then(() => { - model1.textEditorModel.setValue('changed again'); + model1.textEditorModel!.setValue('changed again'); return model1.save().then(() => { model1.dispose(); @@ -293,7 +289,7 @@ suite('Files - TextFileEditorModelManager', () => { test('disposing model takes it out of the manager', function () { const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - const resource = toResource('/path/index_something.txt'); + const resource = toResource.call(this, '/path/index_something.txt'); return manager.loadOrCreate(resource, { encoding: 'utf8' }).then(model => { model.dispose(); @@ -308,10 +304,10 @@ suite('Files - TextFileEditorModelManager', () => { test('dispose prevents dirty model from getting disposed', function () { const manager: TestTextFileEditorModelManager = instantiationService.createInstance(TestTextFileEditorModelManager); - const resource = toResource('/path/index_something.txt'); + const resource = toResource.call(this, '/path/index_something.txt'); return manager.loadOrCreate(resource, { encoding: 'utf8' }).then(model => { - model.textEditorModel.setValue('make dirty'); + model.textEditorModel!.setValue('make dirty'); manager.disposeModel(model as TextFileEditorModel); assert.ok(!model.isDisposed()); diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 8b79bd95c8..5f429e039a 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -58,7 +58,9 @@ suite('Files - TextFileService', () => { }); teardown(() => { - model.dispose(); + if (model) { + model.dispose(); + } (accessor.textFileService.models).clear(); (accessor.textFileService.models).dispose(); accessor.untitledEditorService.revertAll(); @@ -89,7 +91,7 @@ suite('Files - TextFileService', () => { service.setConfirmResult(ConfirmResult.CANCEL); return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model.textEditorModel!.setValue('foo'); assert.equal(service.getDirty().length, 1); @@ -109,7 +111,7 @@ suite('Files - TextFileService', () => { service.onFilesConfigurationChange({ files: { hotExit: 'off' } }); return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model.textEditorModel!.setValue('foo'); assert.equal(service.getDirty().length, 1); @@ -140,7 +142,7 @@ suite('Files - TextFileService', () => { service.onFilesConfigurationChange({ files: { hotExit: 'off' } }); return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model.textEditorModel!.setValue('foo'); assert.equal(service.getDirty().length, 1); @@ -161,7 +163,7 @@ suite('Files - TextFileService', () => { const service = accessor.textFileService; return model.load().then(() => { assert.ok(!service.isDirty(model.getResource())); - model.textEditorModel.setValue('foo'); + model.textEditorModel!.setValue('foo'); assert.ok(service.isDirty(model.getResource())); assert.equal(service.getDirty().length, 1); @@ -171,7 +173,7 @@ suite('Files - TextFileService', () => { return untitled.resolve().then((model: UntitledEditorModel) => { assert.ok(!service.isDirty(untitled.getResource())); assert.equal(service.getDirty().length, 1); - model.textEditorModel.setValue('changed'); + model.textEditorModel!.setValue('changed'); assert.ok(service.isDirty(untitled.getResource())); assert.equal(service.getDirty().length, 2); @@ -187,7 +189,7 @@ suite('Files - TextFileService', () => { const service = accessor.textFileService; return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model.textEditorModel!.setValue('foo'); assert.ok(service.isDirty(model.getResource())); @@ -212,7 +214,7 @@ suite('Files - TextFileService', () => { sinon.stub(accessor.modelService, 'updateModel', () => { }); return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model.textEditorModel!.setValue('foo'); return accessor.textFileService.saveAll(true).then(res => { assert.ok(loadOrCreateStub.calledOnce); @@ -233,7 +235,7 @@ suite('Files - TextFileService', () => { const service = accessor.textFileService; return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model.textEditorModel!.setValue('foo'); assert.ok(service.isDirty(model.getResource())); @@ -254,12 +256,12 @@ suite('Files - TextFileService', () => { service.setPromptPath(model.getResource()); return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model.textEditorModel!.setValue('foo'); assert.ok(service.isDirty(model.getResource())); return service.saveAs(model.getResource()).then(res => { - assert.equal(res.toString(), model.getResource().toString()); + assert.equal(res!.toString(), model.getResource().toString()); assert.ok(!service.isDirty(model.getResource())); }); }); @@ -273,7 +275,7 @@ suite('Files - TextFileService', () => { service.setPromptPath(model.getResource()); return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model!.textEditorModel!.setValue('foo'); assert.ok(service.isDirty(model.getResource())); @@ -291,7 +293,7 @@ suite('Files - TextFileService', () => { const service = accessor.textFileService; return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model!.textEditorModel!.setValue('foo'); assert.ok(service.isDirty(model.getResource())); @@ -310,7 +312,7 @@ suite('Files - TextFileService', () => { const service = accessor.textFileService; return sourceModel.load().then(() => { - sourceModel.textEditorModel.setValue('foo'); + sourceModel.textEditorModel!.setValue('foo'); assert.ok(service.isDirty(sourceModel.getResource())); @@ -447,7 +449,7 @@ suite('Files - TextFileService', () => { service.setConfirmResult(ConfirmResult.CANCEL); return model.load().then(() => { - model.textEditorModel.setValue('foo'); + model.textEditorModel!.setValue('foo'); assert.equal(service.getDirty().length, 1); diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index a18a8e8ab9..7029dafaa1 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -12,10 +12,11 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ResourceEditorModel } from 'vs/workbench/common/editor/resourceEditorModel'; import { ITextFileService, LoadReason } from 'vs/workbench/services/textfile/common/textfiles'; import * as network from 'vs/base/common/network'; -import { ITextModelService, ITextModelContentProvider, ITextEditorModel } from 'vs/editor/common/services/resolverService'; +import { ITextModelService, ITextModelContentProvider, ITextEditorModel, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { IFileService } from 'vs/platform/files/common/files'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; class ResourceModelCollection extends ReferenceCollection> { @@ -103,11 +104,11 @@ class ResourceModelCollection extends ReferenceCollection { const resource = URI.parse(key); const providers = this.providers[resource.scheme] || []; - const factories = providers.map(p => () => Promise.resolve(p.provideTextContent(resource))); + const factories = providers.map(p => () => Promise.resolve(p.provideTextContent(resource))); return first(factories).then(model => { if (!model) { - return Promise.reject(new Error('resource is not available')); + return Promise.reject(new Error('resource is not available')); } return model; @@ -129,15 +130,15 @@ export class TextModelResolverService implements ITextModelService { this.resourceModelCollection = instantiationService.createInstance(ResourceModelCollection); } - createModelReference(resource: URI): Promise> { + createModelReference(resource: URI): Promise> { return this._createModelReference(resource); } - private _createModelReference(resource: URI): Promise> { + private _createModelReference(resource: URI): Promise> { // Untitled Schema: go through cached input if (resource.scheme === network.Schemas.untitled) { - return this.untitledEditorService.loadOrCreate({ resource }).then(model => new ImmortalReference(model)); + return this.untitledEditorService.loadOrCreate({ resource }).then(model => new ImmortalReference(model as IResolvedTextEditorModel)); } // InMemory Schema: go through model service cache @@ -148,7 +149,7 @@ export class TextModelResolverService implements ITextModelService { return Promise.reject(new Error('Cant resolve inmemory resource')); } - return Promise.resolve(new ImmortalReference(this.instantiationService.createInstance(ResourceEditorModel, resource))); + return Promise.resolve(new ImmortalReference(this.instantiationService.createInstance(ResourceEditorModel, resource) as IResolvedTextEditorModel)); } const ref = this.resourceModelCollection.acquire(resource.toString()); @@ -171,3 +172,5 @@ export class TextModelResolverService implements ITextModelService { return this.resourceModelCollection.hasTextModelContentProvider(scheme); } } + +registerSingleton(ITextModelService, TextModelResolverService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts index 7eccfc779b..3a3bc30d74 100644 --- a/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts +++ b/src/vs/workbench/services/textmodelResolver/test/textModelResolverService.test.ts @@ -72,7 +72,7 @@ suite('Workbench - TextModelResolverService', () => { return input.resolve().then(async model => { assert.ok(model); - assert.equal(snapshotToString((model as ResourceEditorModel).createSnapshot()), 'Hello Test'); + assert.equal(snapshotToString((model as ResourceEditorModel).createSnapshot()!), 'Hello Test'); let disposed = false; let disposedPromise = new Promise(resolve => { diff --git a/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts b/src/vs/workbench/services/themes/browser/colorThemeData.ts similarity index 85% rename from src/vs/workbench/services/themes/electron-browser/colorThemeData.ts rename to src/vs/workbench/services/themes/browser/colorThemeData.ts index b3d00ffbae..2df353155f 100644 --- a/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts +++ b/src/vs/workbench/services/themes/browser/colorThemeData.ts @@ -3,11 +3,11 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as Paths from 'vs/base/common/paths'; +import { basename } from 'vs/base/common/path'; import * as Json from 'vs/base/common/json'; import { Color } from 'vs/base/common/color'; import { ExtensionData, ITokenColorCustomizations, ITokenColorizationRule, IColorTheme, IColorMap, IThemeExtensionPoint, VS_LIGHT_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { convertSettings } from 'vs/workbench/services/themes/electron-browser/themeCompatibility'; +import { convertSettings } from 'vs/workbench/services/themes/browser/themeCompatibility'; import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import * as objects from 'vs/base/common/objects'; @@ -15,10 +15,12 @@ import * as resources from 'vs/base/common/resources'; import { Extensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; import { ThemeType } from 'vs/platform/theme/common/themeService'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IColorCustomizations } from 'vs/workbench/services/themes/electron-browser/workbenchThemeService'; +import { IColorCustomizations } from 'vs/workbench/services/themes/browser/workbenchThemeService'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; +import { parse as parsePList } from 'vs/workbench/services/themes/common/plistParser'; +import { startsWith } from 'vs/base/common/strings'; let colorRegistry = Registry.as(Extensions.ColorContribution); @@ -34,17 +36,26 @@ const tokenGroupToScopesMap: { [setting: string]: string[] } = { export class ColorThemeData implements IColorTheme { - private constructor() { - } - id: string; label: string; settingsId: string; description?: string; isLoaded: boolean; location?: URI; - watch: boolean; - extensionData: ExtensionData; + watch?: boolean; + extensionData?: ExtensionData; + + private themeTokenColors: ITokenColorizationRule[] = []; + private customTokenColors: ITokenColorizationRule[] = []; + private colorMap: IColorMap = {}; + private customColorMap: IColorMap = {}; + + private constructor(id: string, label: string, settingsId: string) { + this.id = id; + this.label = label; + this.settingsId = settingsId; + this.isLoaded = false; + } get tokenColors(): ITokenColorizationRule[] { // Add the custom colors after the theme colors @@ -52,13 +63,10 @@ export class ColorThemeData implements IColorTheme { return this.themeTokenColors.concat(this.customTokenColors); } - private themeTokenColors: ITokenColorizationRule[] = []; - private customTokenColors: ITokenColorizationRule[] = []; - private colorMap: IColorMap = {}; - private customColorMap: IColorMap = {}; - public getColor(colorId: ColorIdentifier, useDefault?: boolean): Color | null { - let color: Color | null = this.customColorMap[colorId]; + + public getColor(colorId: ColorIdentifier, useDefault?: boolean): Color | undefined { + let color: Color | undefined = this.customColorMap[colorId]; if (color) { return color; } @@ -69,7 +77,7 @@ export class ColorThemeData implements IColorTheme { return color; } - public getDefault(colorId: ColorIdentifier): Color | null { + public getDefault(colorId: ColorIdentifier): Color | undefined { return colorRegistry.resolveDefaultColor(colorId, this); } @@ -210,10 +218,7 @@ export class ColorThemeData implements IColorTheme { // constructors static createUnloadedTheme(id: string): ColorThemeData { - let themeData = new ColorThemeData(); - themeData.id = id; - themeData.label = ''; - themeData.settingsId = '__' + id; + let themeData = new ColorThemeData(id, '', '__' + id); themeData.isLoaded = false; themeData.themeTokenColors = [{ settings: {} }]; themeData.watch = false; @@ -221,10 +226,7 @@ export class ColorThemeData implements IColorTheme { } static createLoadedEmptyTheme(id: string, settingsId: string): ColorThemeData { - let themeData = new ColorThemeData(); - themeData.id = id; - themeData.label = ''; - themeData.settingsId = settingsId; + let themeData = new ColorThemeData(id, '', settingsId); themeData.isLoaded = true; themeData.themeTokenColors = [{ settings: {} }]; themeData.watch = false; @@ -234,7 +236,7 @@ export class ColorThemeData implements IColorTheme { static fromStorageData(input: string): ColorThemeData | undefined { try { let data = JSON.parse(input); - let theme = new ColorThemeData(); + let theme = new ColorThemeData('', '', ''); for (let key in data) { switch (key) { case 'colorMap': @@ -249,6 +251,9 @@ export class ColorThemeData implements IColorTheme { break; } } + if (!theme.id || !theme.settingsId) { + return undefined; + } return theme; } catch (e) { return undefined; @@ -256,13 +261,12 @@ export class ColorThemeData implements IColorTheme { } static fromExtensionTheme(theme: IThemeExtensionPoint, colorThemeLocation: URI, extensionData: ExtensionData): ColorThemeData { - let baseTheme: string = theme['uiTheme'] || 'vs-dark'; - - let themeSelector = toCSSSelector(extensionData.extensionId + '-' + Paths.normalize(theme.path)); - let themeData = new ColorThemeData(); - themeData.id = `${baseTheme} ${themeSelector}`; - themeData.label = theme.label || Paths.basename(theme.path); - themeData.settingsId = theme.id || themeData.label; + const baseTheme: string = theme['uiTheme'] || 'vs-dark'; + const themeSelector = toCSSSelector(extensionData.extensionId, theme.path); + const id = `${baseTheme} ${themeSelector}`; + const label = theme.label || basename(theme.path); + const settingsId = theme.id || label; + const themeData = new ColorThemeData(id, label, settingsId); themeData.description = theme.description; themeData.watch = theme._watch === true; themeData.location = colorThemeLocation; @@ -272,9 +276,13 @@ export class ColorThemeData implements IColorTheme { } } +function toCSSSelector(extensionId: string, path: string) { + if (startsWith(path, './')) { + path = path.substr(2); + } + let str = `${extensionId}-${path}`; - -function toCSSSelector(str: string) { + //remove all characters that are not allowed in css str = str.replace(/[^_\-a-zA-Z0-9]/g, '-'); if (str.charAt(0).match(/[0-9\-]/)) { str = '_' + str; @@ -283,7 +291,7 @@ function toCSSSelector(str: string) { } function _loadColorTheme(fileService: IFileService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): Promise { - if (Paths.extname(themeLocation.path) === '.json') { + if (resources.extname(themeLocation) === '.json') { return fileService.resolveContent(themeLocation, { encoding: 'utf8' }).then(content => { let errors: Json.ParseError[] = []; let contentValue = Json.parse(content.value.toString(), errors); @@ -292,7 +300,7 @@ function _loadColorTheme(fileService: IFileService, themeLocation: URI, resultRu } let includeCompletes: Promise = Promise.resolve(null); if (contentValue.include) { - includeCompletes = _loadColorTheme(fileService, resources.joinPath(resources.dirname(themeLocation)!, contentValue.include), resultRules, resultColors); + includeCompletes = _loadColorTheme(fileService, resources.joinPath(resources.dirname(themeLocation), contentValue.include), resultRules, resultColors); } return includeCompletes.then(_ => { if (Array.isArray(contentValue.settings)) { @@ -318,7 +326,7 @@ function _loadColorTheme(fileService: IFileService, themeLocation: URI, resultRu resultRules.push(...tokenColors); return null; } else if (typeof tokenColors === 'string') { - return _loadSyntaxTokens(fileService, resources.joinPath(resources.dirname(themeLocation)!, tokenColors), resultRules, {}); + return _loadSyntaxTokens(fileService, resources.joinPath(resources.dirname(themeLocation), tokenColors), resultRules, {}); } else { return Promise.reject(new Error(nls.localize({ key: 'error.invalidformat.tokenColors', comment: ['{0} will be replaced by a path. Values in quotes should not be translated.'] }, "Problem parsing color theme file: {0}. Property 'tokenColors' should be either an array specifying colors or a path to a TextMate theme file", themeLocation.toString()))); } @@ -331,29 +339,19 @@ function _loadColorTheme(fileService: IFileService, themeLocation: URI, resultRu } } -let pListParser: Promise<{ parse(content: string) }>; -function getPListParser() { - if (!pListParser) { - pListParser = import('fast-plist'); - } - return pListParser; -} - function _loadSyntaxTokens(fileService: IFileService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): Promise { return fileService.resolveContent(themeLocation, { encoding: 'utf8' }).then(content => { - return getPListParser().then(parser => { - try { - let contentValue = parser.parse(content.value.toString()); - let settings: ITokenColorizationRule[] = contentValue.settings; - if (!Array.isArray(settings)) { - return Promise.reject(new Error(nls.localize('error.plist.invalidformat', "Problem parsing tmTheme file: {0}. 'settings' is not array."))); - } - convertSettings(settings, resultRules, resultColors); - return Promise.resolve(null); - } catch (e) { - return Promise.reject(new Error(nls.localize('error.cannotparse', "Problems parsing tmTheme file: {0}", e.message))); + try { + let contentValue = parsePList(content.value.toString()); + let settings: ITokenColorizationRule[] = contentValue.settings; + if (!Array.isArray(settings)) { + return Promise.reject(new Error(nls.localize('error.plist.invalidformat', "Problem parsing tmTheme file: {0}. 'settings' is not array."))); } - }); + convertSettings(settings, resultRules, resultColors); + return Promise.resolve(null); + } catch (e) { + return Promise.reject(new Error(nls.localize('error.cannotparse', "Problems parsing tmTheme file: {0}", e.message))); + } }, error => { return Promise.reject(new Error(nls.localize('error.cannotload', "Problems loading tmTheme file {0}: {1}", themeLocation.toString(), error.message))); }); diff --git a/src/vs/workbench/services/themes/electron-browser/colorThemeStore.ts b/src/vs/workbench/services/themes/browser/colorThemeStore.ts similarity index 90% rename from src/vs/workbench/services/themes/electron-browser/colorThemeStore.ts rename to src/vs/workbench/services/themes/browser/colorThemeStore.ts index 68c16b17b3..8ed91ed6d5 100644 --- a/src/vs/workbench/services/themes/electron-browser/colorThemeStore.ts +++ b/src/vs/workbench/services/themes/browser/colorThemeStore.ts @@ -8,15 +8,14 @@ import * as nls from 'vs/nls'; import * as types from 'vs/base/common/types'; import * as resources from 'vs/base/common/resources'; import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; -import { IColorTheme, ExtensionData, IThemeExtensionPoint, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { ColorThemeData } from 'vs/workbench/services/themes/electron-browser/colorThemeData'; +import { ExtensionData, IThemeExtensionPoint, VS_LIGHT_THEME, VS_DARK_THEME, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; +import { ColorThemeData } from 'vs/workbench/services/themes/browser/colorThemeData'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; const themesExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'themes', - isDynamic: true, jsonSchema: { description: nls.localize('vscode.extension.contributes.themes', 'Contributes textmate color themes.'), type: 'array', @@ -155,7 +154,17 @@ export class ColorThemeStore { }); } - public getColorThemes(): Promise { + public findThemeDataByParentLocation(parentLocation: URI | undefined): Promise { + if (parentLocation) { + return this.getColorThemes().then(allThemes => { + return allThemes.filter(t => t.location && resources.isEqualOrParent(t.location, parentLocation)); + }); + } + return Promise.resolve([]); + + } + + public getColorThemes(): Promise { return this.extensionService.whenInstalledExtensionsRegistered().then(_ => { return this.extensionsColorThemes; }); diff --git a/src/vs/workbench/services/themes/electron-browser/themeCompatibility.ts b/src/vs/workbench/services/themes/browser/themeCompatibility.ts similarity index 86% rename from src/vs/workbench/services/themes/electron-browser/themeCompatibility.ts rename to src/vs/workbench/services/themes/browser/themeCompatibility.ts index 128cf23e6f..4dd11378a5 100644 --- a/src/vs/workbench/services/themes/electron-browser/themeCompatibility.ts +++ b/src/vs/workbench/services/themes/browser/themeCompatibility.ts @@ -8,8 +8,6 @@ import { Color } from 'vs/base/common/color'; import * as colorRegistry from 'vs/platform/theme/common/colorRegistry'; import * as editorColorRegistry from 'vs/editor/common/view/editorColorRegistry'; -import * as wordHighlighter from 'vs/editor/contrib/wordHighlighter/wordHighlighter'; -import { peekViewEditorMatchHighlight, peekViewResultsMatchHighlight } from 'vs/editor/contrib/referenceSearch/referencesWidget'; const settingToColorIdMapping: { [settingId: string]: string[] } = {}; function addSettingMapping(settingId: string, colorId: string) { @@ -56,11 +54,11 @@ addSettingMapping('selectionHighlightColor', colorRegistry.editorSelectionHighli addSettingMapping('findMatchHighlight', colorRegistry.editorFindMatchHighlight); addSettingMapping('currentFindMatchHighlight', colorRegistry.editorFindMatch); addSettingMapping('hoverHighlight', colorRegistry.editorHoverHighlight); -addSettingMapping('wordHighlight', wordHighlighter.editorWordHighlight); -addSettingMapping('wordHighlightStrong', wordHighlighter.editorWordHighlightStrong); +addSettingMapping('wordHighlight', 'editor.wordHighlightBackground'); // inlined to avoid editor/contrib dependenies +addSettingMapping('wordHighlightStrong', 'editor.wordHighlightStrongBackground'); addSettingMapping('findRangeHighlight', colorRegistry.editorFindRangeHighlight); -addSettingMapping('findMatchHighlight', peekViewResultsMatchHighlight); -addSettingMapping('referenceHighlight', peekViewEditorMatchHighlight); +addSettingMapping('findMatchHighlight', 'peekViewResult.matchHighlightBackground'); +addSettingMapping('referenceHighlight', 'peekViewEditor.matchHighlightBackground'); addSettingMapping('lineHighlight', editorColorRegistry.editorLineHighlight); addSettingMapping('rangeHighlight', editorColorRegistry.editorRangeHighlight); addSettingMapping('caret', editorColorRegistry.editorCursorForeground); diff --git a/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts similarity index 93% rename from src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts rename to src/vs/workbench/services/themes/browser/workbenchThemeService.ts index 8eaff85f7b..b3b1457d5d 100644 --- a/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/browser/workbenchThemeService.ts @@ -13,15 +13,14 @@ import { Registry } from 'vs/platform/registry/common/platform'; import * as errors from 'vs/base/common/errors'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions, IConfigurationPropertySchema, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ColorThemeData } from './colorThemeData'; import { ITheme, Extensions as ThemingExtensions, IThemingRegistry } from 'vs/platform/theme/common/themeService'; import { Event, Emitter } from 'vs/base/common/event'; import { registerFileIconThemeSchemas } from 'vs/workbench/services/themes/common/fileIconThemeSchema'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { ColorThemeStore } from 'vs/workbench/services/themes/electron-browser/colorThemeStore'; -import { FileIconThemeStore } from 'vs/workbench/services/themes/electron-browser/fileIconThemeStore'; -import { FileIconThemeData } from 'vs/workbench/services/themes/electron-browser/fileIconThemeData'; +import { ColorThemeStore } from 'vs/workbench/services/themes/browser/colorThemeStore'; +import { FileIconThemeStore } from 'vs/workbench/services/themes/common/fileIconThemeStore'; +import { FileIconThemeData } from 'vs/workbench/services/themes/common/fileIconThemeData'; import { IWindowService } from 'vs/platform/windows/common/windows'; import { removeClasses, addClasses } from 'vs/base/browser/dom'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -31,6 +30,7 @@ import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { textmateColorsSchemaId, registerColorThemeSchemas, textmateColorSettingsSchemaId } from 'vs/workbench/services/themes/common/colorThemeSchema'; import { workbenchColorsSchemaId } from 'vs/platform/theme/common/colorRegistry'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; // implementation // {{SQL CARBON EDIT}} @@ -72,8 +72,6 @@ export interface IColorCustomizations { export class WorkbenchThemeService implements IWorkbenchThemeService { _serviceBrand: any; - private fileService: IFileService; - private colorThemeStore: ColorThemeStore; private currentColorTheme: ColorThemeData; private container: HTMLElement; @@ -86,7 +84,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private watchedIconThemeLocation: URI | undefined; private themingParticipantChangeListener: IDisposable; - private _configurationWriter: ConfigurationWriter; private get colorCustomizations(): IColorCustomizations { return this.configurationService.getValue(CUSTOM_WORKBENCH_COLORS_SETTING) || {}; @@ -97,21 +94,20 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } constructor( - container: HTMLElement, @IExtensionService extensionService: IExtensionService, @IStorageService private readonly storageService: IStorageService, @IConfigurationService private readonly configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IWindowService private readonly windowService: IWindowService, - @IInstantiationService private readonly instantiationService: IInstantiationService, - @IEnvironmentService private readonly environmentService: IEnvironmentService + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IFileService private readonly fileService: IFileService ) { - this.container = container; + this.container = document.body; this.colorThemeStore = new ColorThemeStore(extensionService, ColorThemeData.createLoadedEmptyTheme(DEFAULT_THEME_ID, DEFAULT_THEME_SETTING_VALUE)); this.onFileIconThemeChange = new Emitter(); this.iconThemeStore = new FileIconThemeStore(extensionService); - this.onColorThemeChange = new Emitter(); + this.onColorThemeChange = new Emitter({ leakWarningThreshold: 400 }); this.currentIconTheme = FileIconThemeData.createUnloadedTheme(''); @@ -209,10 +205,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { } } }); - } - - acquireFileService(fileService: IFileService): void { - this.fileService = fileService; this.fileService.onFileChanges(async e => { if (this.watchedColorThemeLocation && this.currentColorTheme && e.contains(this.watchedColorThemeLocation, FileChangeType.UPDATED)) { @@ -259,10 +251,22 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { return Promise.all([ this.colorThemeStore.findThemeDataBySettingsId(colorThemeSetting, DEFAULT_THEME_ID).then(theme => { - return this.setColorTheme(theme && theme.id, undefined); + return this.colorThemeStore.findThemeDataByParentLocation(this.environmentService.extensionDevelopmentLocationURI).then(devThemes => { + if (devThemes.length) { + return this.setColorTheme(devThemes[0].id, ConfigurationTarget.MEMORY); + } else { + return this.setColorTheme(theme && theme.id, undefined); + } + }); }), this.iconThemeStore.findThemeBySettingsId(iconThemeSetting).then(theme => { - return this.setFileIconTheme(theme && theme.id, undefined); + return this.iconThemeStore.findThemeDataByParentLocation(this.environmentService.extensionDevelopmentLocationURI).then(devThemes => { + if (devThemes.length) { + return this.setFileIconTheme(devThemes[0].id, ConfigurationTarget.MEMORY); + } else { + return this.setFileIconTheme(theme && theme.id, undefined); + } + }); }), ]); } @@ -410,14 +414,16 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.onColorThemeChange.fire(this.currentColorTheme); // remember theme data for a quick restore - this.storageService.store(PERSISTED_THEME_STORAGE_KEY, newTheme.toStorageData(), StorageScope.GLOBAL); + if (newTheme.isLoaded) { + this.storageService.store(PERSISTED_THEME_STORAGE_KEY, newTheme.toStorageData(), StorageScope.GLOBAL); + } return this.writeColorThemeConfiguration(settingsTarget); } private writeColorThemeConfiguration(settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { if (!types.isUndefinedOrNull(settingsTarget)) { - return this.configurationWriter.writeConfiguration(COLOR_THEME_SETTING, this.currentColorTheme.settingsId, settingsTarget).then(_ => this.currentColorTheme); + return this.writeConfiguration(COLOR_THEME_SETTING, this.currentColorTheme.settingsId, settingsTarget).then(_ => this.currentColorTheme); } return Promise.resolve(this.currentColorTheme); } @@ -469,7 +475,9 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { this.doSetFileIconTheme(newIconTheme); // remember theme data for a quick restore - this.storageService.store(PERSISTED_ICON_THEME_STORAGE_KEY, newIconTheme.toStorageData(), StorageScope.GLOBAL); + if (newIconTheme.isLoaded) { + this.storageService.store(PERSISTED_ICON_THEME_STORAGE_KEY, newIconTheme.toStorageData(), StorageScope.GLOBAL); + } return this.writeFileIconConfiguration(settingsTarget); }; @@ -524,17 +532,38 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private writeFileIconConfiguration(settingsTarget: ConfigurationTarget | undefined | 'auto'): Promise { if (!types.isUndefinedOrNull(settingsTarget)) { - return this.configurationWriter.writeConfiguration(ICON_THEME_SETTING, this.currentIconTheme.settingsId, settingsTarget).then(_ => this.currentIconTheme); + return this.writeConfiguration(ICON_THEME_SETTING, this.currentIconTheme.settingsId, settingsTarget).then(_ => this.currentIconTheme); } return Promise.resolve(this.currentIconTheme); } - private get configurationWriter(): ConfigurationWriter { - // separate out the ConfigurationWriter to avoid a dependency of the IConfigurationEditingService - if (!this._configurationWriter) { - this._configurationWriter = this.instantiationService.createInstance(ConfigurationWriter); + public writeConfiguration(key: string, value: any, settingsTarget: ConfigurationTarget | 'auto'): Promise { + let settings = this.configurationService.inspect(key); + if (settingsTarget === 'auto') { + if (!types.isUndefined(settings.workspaceFolder)) { + settingsTarget = ConfigurationTarget.WORKSPACE_FOLDER; + } else if (!types.isUndefined(settings.workspace)) { + settingsTarget = ConfigurationTarget.WORKSPACE; + } else { + settingsTarget = ConfigurationTarget.USER; + } } - return this._configurationWriter; + + if (settingsTarget === ConfigurationTarget.USER) { + if (value === settings.user) { + return Promise.resolve(undefined); // nothing to do + } else if (value === settings.default) { + if (types.isUndefined(settings.user)) { + return Promise.resolve(undefined); // nothing to do + } + value = undefined; // remove configuration from user settings + } + } else if (settingsTarget === ConfigurationTarget.WORKSPACE || settingsTarget === ConfigurationTarget.WORKSPACE_FOLDER) { + if (value === settings.value) { + return Promise.resolve(undefined); // nothing to do + } + } + return this.configurationService.updateValue(key, value, settingsTarget); } private getBaseThemeFromContainer() { @@ -571,40 +600,6 @@ function _applyRules(styleSheetContent: string, rulesClassName: string) { registerColorThemeSchemas(); registerFileIconThemeSchemas(); -class ConfigurationWriter { - constructor(@IConfigurationService private readonly configurationService: IConfigurationService) { - } - - public writeConfiguration(key: string, value: any, settingsTarget: ConfigurationTarget | 'auto'): Promise { - let settings = this.configurationService.inspect(key); - if (settingsTarget === 'auto') { - if (!types.isUndefined(settings.workspaceFolder)) { - settingsTarget = ConfigurationTarget.WORKSPACE_FOLDER; - } else if (!types.isUndefined(settings.workspace)) { - settingsTarget = ConfigurationTarget.WORKSPACE; - } else { - settingsTarget = ConfigurationTarget.USER; - } - } - - if (settingsTarget === ConfigurationTarget.USER) { - if (value === settings.user) { - return Promise.resolve(undefined); // nothing to do - } else if (value === settings.default) { - if (types.isUndefined(settings.user)) { - return Promise.resolve(undefined); // nothing to do - } - value = undefined; // remove configuration from user settings - } - } else if (settingsTarget === ConfigurationTarget.WORKSPACE || settingsTarget === ConfigurationTarget.WORKSPACE_FOLDER) { - if (value === settings.value) { - return Promise.resolve(undefined); // nothing to do - } - } - return this.configurationService.updateValue(key, value, settingsTarget); - } -} - // Configuration: Themes const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -694,3 +689,4 @@ const tokenColorCustomizationConfiguration: IConfigurationNode = { }; configurationRegistry.registerConfiguration(tokenColorCustomizationConfiguration); +registerSingleton(IWorkbenchThemeService, WorkbenchThemeService); \ No newline at end of file diff --git a/src/vs/workbench/services/themes/common/colorExtensionPoint.ts b/src/vs/workbench/services/themes/common/colorExtensionPoint.ts index 61ebf6f133..2accc770f6 100644 --- a/src/vs/workbench/services/themes/common/colorExtensionPoint.ts +++ b/src/vs/workbench/services/themes/common/colorExtensionPoint.ts @@ -22,7 +22,6 @@ const colorIdPattern = '^\\w+[.\\w+]*$'; const configurationExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'colors', - isDynamic: true, jsonSchema: { description: nls.localize('contributes.color', 'Contributes extension defined themable colors'), type: 'array', diff --git a/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/common/fileIconThemeData.ts similarity index 92% rename from src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts rename to src/vs/workbench/services/themes/common/fileIconThemeData.ts index 6eb6c48f9b..4358f672bb 100644 --- a/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/common/fileIconThemeData.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import * as nls from 'vs/nls'; -import * as Paths from 'path'; +import * as Paths from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import * as Json from 'vs/base/common/json'; import { ExtensionData, IThemeExtensionPoint, IFileIconTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; @@ -23,21 +23,29 @@ export class FileIconThemeData implements IFileIconTheme { isLoaded: boolean; location?: URI; extensionData?: ExtensionData; - watch: boolean; + watch?: boolean; styleSheetContent?: string; - private constructor() { } + private constructor(id: string, label: string, settingsId: string | null) { + this.id = id; + this.label = label; + this.settingsId = settingsId; + this.isLoaded = false; + this.hasFileIcons = false; + this.hasFolderIcons = false; + this.hidesExplorerArrows = false; + } - public ensureLoaded(fileService: IFileService): Promise { + public ensureLoaded(fileService: IFileService): Promise { return !this.isLoaded ? this.load(fileService) : Promise.resolve(this.styleSheetContent); } - public reload(fileService: IFileService): Promise { + public reload(fileService: IFileService): Promise { return this.load(fileService); } - private load(fileService: IFileService): Promise { + private load(fileService: IFileService): Promise { if (!this.location) { return Promise.resolve(this.styleSheetContent); } @@ -53,10 +61,12 @@ export class FileIconThemeData implements IFileIconTheme { } static fromExtensionTheme(iconTheme: IThemeExtensionPoint, iconThemeLocation: URI, extensionData: ExtensionData): FileIconThemeData { - let themeData = new FileIconThemeData(); - themeData.id = extensionData.extensionId + '-' + iconTheme.id; - themeData.label = iconTheme.label || Paths.basename(iconTheme.path); - themeData.settingsId = iconTheme.id; + const id = extensionData.extensionId + '-' + iconTheme.id; + const label = iconTheme.label || Paths.basename(iconTheme.path); + const settingsId = iconTheme.id; + + const themeData = new FileIconThemeData(id, label, settingsId); + themeData.description = iconTheme.description; themeData.location = iconThemeLocation; themeData.extensionData = extensionData; @@ -70,10 +80,7 @@ export class FileIconThemeData implements IFileIconTheme { static noIconTheme(): FileIconThemeData { let themeData = FileIconThemeData._noIconTheme; if (!themeData) { - themeData = FileIconThemeData._noIconTheme = new FileIconThemeData(); - themeData.id = ''; - themeData.label = ''; - themeData.settingsId = null; + themeData = FileIconThemeData._noIconTheme = new FileIconThemeData('', '', null); themeData.hasFileIcons = false; themeData.hasFolderIcons = false; themeData.hidesExplorerArrows = false; @@ -85,10 +92,7 @@ export class FileIconThemeData implements IFileIconTheme { } static createUnloadedTheme(id: string): FileIconThemeData { - let themeData = new FileIconThemeData(); - themeData.id = id; - themeData.label = ''; - themeData.settingsId = '__' + id; + const themeData = new FileIconThemeData(id, '', '__' + id); themeData.isLoaded = false; themeData.hasFileIcons = false; themeData.hasFolderIcons = false; @@ -101,7 +105,7 @@ export class FileIconThemeData implements IFileIconTheme { static fromStorageData(input: string): FileIconThemeData | null { try { let data = JSON.parse(input); - let theme = new FileIconThemeData(); + const theme = new FileIconThemeData('', '', null); for (let key in data) { switch (key) { case 'id': @@ -202,7 +206,7 @@ function _processIconThemeDocument(id: string, iconThemeDocumentLocation: URI, i const iconThemeDocumentLocationDirname = resources.dirname(iconThemeDocumentLocation); function resolvePath(path: string) { - return resources.joinPath(iconThemeDocumentLocationDirname!, path); + return resources.joinPath(iconThemeDocumentLocationDirname, path); } function collectSelectors(associations: IconsAssociation | undefined, baseThemeClassName?: string) { diff --git a/src/vs/workbench/services/themes/electron-browser/fileIconThemeStore.ts b/src/vs/workbench/services/themes/common/fileIconThemeStore.ts similarity index 94% rename from src/vs/workbench/services/themes/electron-browser/fileIconThemeStore.ts rename to src/vs/workbench/services/themes/common/fileIconThemeStore.ts index fba825f30f..9e8fc68ec5 100644 --- a/src/vs/workbench/services/themes/electron-browser/fileIconThemeStore.ts +++ b/src/vs/workbench/services/themes/common/fileIconThemeStore.ts @@ -11,12 +11,11 @@ import { ExtensionsRegistry, ExtensionMessageCollector } from 'vs/workbench/serv import { ExtensionData, IThemeExtensionPoint } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { Event, Emitter } from 'vs/base/common/event'; -import { FileIconThemeData } from 'vs/workbench/services/themes/electron-browser/fileIconThemeData'; +import { FileIconThemeData } from 'vs/workbench/services/themes/common/fileIconThemeData'; import { URI } from 'vs/base/common/uri'; const iconThemeExtPoint = ExtensionsRegistry.registerExtensionPoint({ extensionPoint: 'iconThemes', - isDynamic: true, jsonSchema: { description: nls.localize('vscode.extension.contributes.iconThemes', 'Contributes file icon themes.'), type: 'array', @@ -154,6 +153,15 @@ export class FileIconThemeStore { }); } + public findThemeDataByParentLocation(parentLocation: URI | undefined): any { + if (parentLocation) { + return this.getFileIconThemes().then(allThemes => { + return allThemes.filter(t => t.location && resources.isEqualOrParent(t.location, parentLocation)); + }); + } + return Promise.resolve([]); + } + public getFileIconThemes(): Promise { return this.extensionService.whenInstalledExtensionsRegistered().then(isReady => { return this.knownIconThemes; diff --git a/src/vs/workbench/services/themes/common/plistParser.ts b/src/vs/workbench/services/themes/common/plistParser.ts new file mode 100644 index 0000000000..ab25827796 --- /dev/null +++ b/src/vs/workbench/services/themes/common/plistParser.ts @@ -0,0 +1,497 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +const enum ChCode { + BOM = 65279, + + SPACE = 32, + TAB = 9, + CARRIAGE_RETURN = 13, + LINE_FEED = 10, + + SLASH = 47, + + LESS_THAN = 60, + QUESTION_MARK = 63, + EXCLAMATION_MARK = 33, +} + +const enum State { + ROOT_STATE = 0, + DICT_STATE = 1, + ARR_STATE = 2 +} + +export function parseWithLocation(content: string, filename: string, locationKeyName: string): any { + return _parse(content, filename, locationKeyName); +} + +/** + * A very fast plist parser + */ +export function parse(content: string): any { + return _parse(content, null, null); +} + +function _parse(content: string, filename: string | null, locationKeyName: string | null): any { + const len = content.length; + + let pos = 0; + let line = 1; + let char = 0; + + // Skip UTF8 BOM + if (len > 0 && content.charCodeAt(0) === ChCode.BOM) { + pos = 1; + } + + function advancePosBy(by: number): void { + if (locationKeyName === null) { + pos = pos + by; + } else { + while (by > 0) { + let chCode = content.charCodeAt(pos); + if (chCode === ChCode.LINE_FEED) { + pos++; line++; char = 0; + } else { + pos++; char++; + } + by--; + } + } + } + function advancePosTo(to: number): void { + if (locationKeyName === null) { + pos = to; + } else { + advancePosBy(to - pos); + } + } + + function skipWhitespace(): void { + while (pos < len) { + let chCode = content.charCodeAt(pos); + if (chCode !== ChCode.SPACE && chCode !== ChCode.TAB && chCode !== ChCode.CARRIAGE_RETURN && chCode !== ChCode.LINE_FEED) { + break; + } + advancePosBy(1); + } + } + + function advanceIfStartsWith(str: string): boolean { + if (content.substr(pos, str.length) === str) { + advancePosBy(str.length); + return true; + } + return false; + } + + function advanceUntil(str: string): void { + let nextOccurence = content.indexOf(str, pos); + if (nextOccurence !== -1) { + advancePosTo(nextOccurence + str.length); + } else { + // EOF + advancePosTo(len); + } + } + + function captureUntil(str: string): string { + let nextOccurence = content.indexOf(str, pos); + if (nextOccurence !== -1) { + let r = content.substring(pos, nextOccurence); + advancePosTo(nextOccurence + str.length); + return r; + } else { + // EOF + let r = content.substr(pos); + advancePosTo(len); + return r; + } + } + + let state = State.ROOT_STATE; + + let cur: any = null; + let stateStack: State[] = []; + let objStack: any[] = []; + let curKey: string | null = null; + + function pushState(newState: State, newCur: any): void { + stateStack.push(state); + objStack.push(cur); + state = newState; + cur = newCur; + } + + function popState(): void { + if (stateStack.length === 0) { + return fail('illegal state stack'); + } + state = stateStack.pop()!; + cur = objStack.pop(); + } + + function fail(msg: string): void { + throw new Error('Near offset ' + pos + ': ' + msg + ' ~~~' + content.substr(pos, 50) + '~~~'); + } + + const dictState = { + enterDict: function () { + if (curKey === null) { + return fail('missing '); + } + let newDict = {}; + if (locationKeyName !== null) { + newDict[locationKeyName] = { + filename: filename, + line: line, + char: char + }; + } + cur[curKey] = newDict; + curKey = null; + pushState(State.DICT_STATE, newDict); + }, + enterArray: function () { + if (curKey === null) { + return fail('missing '); + } + let newArr: any[] = []; + cur[curKey] = newArr; + curKey = null; + pushState(State.ARR_STATE, newArr); + } + }; + + const arrState = { + enterDict: function () { + let newDict = {}; + if (locationKeyName !== null) { + newDict[locationKeyName] = { + filename: filename, + line: line, + char: char + }; + } + cur.push(newDict); + pushState(State.DICT_STATE, newDict); + }, + enterArray: function () { + let newArr: any[] = []; + cur.push(newArr); + pushState(State.ARR_STATE, newArr); + } + }; + + + function enterDict() { + if (state === State.DICT_STATE) { + dictState.enterDict(); + } else if (state === State.ARR_STATE) { + arrState.enterDict(); + } else { // ROOT_STATE + cur = {}; + if (locationKeyName !== null) { + cur[locationKeyName] = { + filename: filename, + line: line, + char: char + }; + } + pushState(State.DICT_STATE, cur); + } + } + function leaveDict() { + if (state === State.DICT_STATE) { + popState(); + } else if (state === State.ARR_STATE) { + return fail('unexpected '); + } else { // ROOT_STATE + return fail('unexpected '); + } + } + function enterArray() { + if (state === State.DICT_STATE) { + dictState.enterArray(); + } else if (state === State.ARR_STATE) { + arrState.enterArray(); + } else { // ROOT_STATE + cur = []; + pushState(State.ARR_STATE, cur); + } + } + function leaveArray() { + if (state === State.DICT_STATE) { + return fail('unexpected '); + } else if (state === State.ARR_STATE) { + popState(); + } else { // ROOT_STATE + return fail('unexpected '); + } + } + function acceptKey(val: string) { + if (state === State.DICT_STATE) { + if (curKey !== null) { + return fail('too many '); + } + curKey = val; + } else if (state === State.ARR_STATE) { + return fail('unexpected '); + } else { // ROOT_STATE + return fail('unexpected '); + } + } + function acceptString(val: string) { + if (state === State.DICT_STATE) { + if (curKey === null) { + return fail('missing '); + } + cur[curKey] = val; + curKey = null; + } else if (state === State.ARR_STATE) { + cur.push(val); + } else { // ROOT_STATE + cur = val; + } + } + function acceptReal(val: number) { + if (isNaN(val)) { + return fail('cannot parse float'); + } + if (state === State.DICT_STATE) { + if (curKey === null) { + return fail('missing '); + } + cur[curKey] = val; + curKey = null; + } else if (state === State.ARR_STATE) { + cur.push(val); + } else { // ROOT_STATE + cur = val; + } + } + function acceptInteger(val: number) { + if (isNaN(val)) { + return fail('cannot parse integer'); + } + if (state === State.DICT_STATE) { + if (curKey === null) { + return fail('missing '); + } + cur[curKey] = val; + curKey = null; + } else if (state === State.ARR_STATE) { + cur.push(val); + } else { // ROOT_STATE + cur = val; + } + } + function acceptDate(val: Date) { + if (state === State.DICT_STATE) { + if (curKey === null) { + return fail('missing '); + } + cur[curKey] = val; + curKey = null; + } else if (state === State.ARR_STATE) { + cur.push(val); + } else { // ROOT_STATE + cur = val; + } + } + function acceptData(val: string) { + if (state === State.DICT_STATE) { + if (curKey === null) { + return fail('missing '); + } + cur[curKey] = val; + curKey = null; + } else if (state === State.ARR_STATE) { + cur.push(val); + } else { // ROOT_STATE + cur = val; + } + } + function acceptBool(val: boolean) { + if (state === State.DICT_STATE) { + if (curKey === null) { + return fail('missing '); + } + cur[curKey] = val; + curKey = null; + } else if (state === State.ARR_STATE) { + cur.push(val); + } else { // ROOT_STATE + cur = val; + } + } + + function escapeVal(str: string): string { + return str.replace(/&#([0-9]+);/g, function (_: string, m0: string) { + return (String).fromCodePoint(parseInt(m0, 10)); + }).replace(/&#x([0-9a-f]+);/g, function (_: string, m0: string) { + return (String).fromCodePoint(parseInt(m0, 16)); + }).replace(/&|<|>|"|'/g, function (_: string) { + switch (_) { + case '&': return '&'; + case '<': return '<'; + case '>': return '>'; + case '"': return '"'; + case ''': return '\''; + } + return _; + }); + } + + interface IParsedTag { + name: string; + isClosed: boolean; + } + + function parseOpenTag(): IParsedTag { + let r = captureUntil('>'); + let isClosed = false; + if (r.charCodeAt(r.length - 1) === ChCode.SLASH) { + isClosed = true; + r = r.substring(0, r.length - 1); + } + + return { + name: r.trim(), + isClosed: isClosed + }; + } + + function parseTagValue(tag: IParsedTag): string { + if (tag.isClosed) { + return ''; + } + let val = captureUntil(''); + return escapeVal(val); + } + + while (pos < len) { + skipWhitespace(); + if (pos >= len) { + break; + } + + const chCode = content.charCodeAt(pos); + advancePosBy(1); + if (chCode !== ChCode.LESS_THAN) { + return fail('expected <'); + } + + if (pos >= len) { + return fail('unexpected end of input'); + } + + const peekChCode = content.charCodeAt(pos); + + if (peekChCode === ChCode.QUESTION_MARK) { + advancePosBy(1); + advanceUntil('?>'); + continue; + } + + if (peekChCode === ChCode.EXCLAMATION_MARK) { + advancePosBy(1); + + if (advanceIfStartsWith('--')) { + advanceUntil('-->'); + continue; + } + + advanceUntil('>'); + continue; + } + + if (peekChCode === ChCode.SLASH) { + advancePosBy(1); + skipWhitespace(); + + if (advanceIfStartsWith('plist')) { + advanceUntil('>'); + continue; + } + + if (advanceIfStartsWith('dict')) { + advanceUntil('>'); + leaveDict(); + continue; + } + + if (advanceIfStartsWith('array')) { + advanceUntil('>'); + leaveArray(); + continue; + } + + return fail('unexpected closed tag'); + } + + let tag = parseOpenTag(); + + switch (tag.name) { + case 'dict': + enterDict(); + if (tag.isClosed) { + leaveDict(); + } + continue; + + case 'array': + enterArray(); + if (tag.isClosed) { + leaveArray(); + } + continue; + + case 'key': + acceptKey(parseTagValue(tag)); + continue; + + case 'string': + acceptString(parseTagValue(tag)); + continue; + + case 'real': + acceptReal(parseFloat(parseTagValue(tag))); + continue; + + case 'integer': + acceptInteger(parseInt(parseTagValue(tag), 10)); + continue; + + case 'date': + acceptDate(new Date(parseTagValue(tag))); + continue; + + case 'data': + acceptData(parseTagValue(tag)); + continue; + + case 'true': + parseTagValue(tag); + acceptBool(true); + continue; + + case 'false': + parseTagValue(tag); + acceptBool(false); + continue; + } + + if (/^plist/.test(tag.name)) { + continue; + } + + return fail('unexpected opened tag ' + tag.name); + } + + return cur; +} diff --git a/src/vs/workbench/services/themes/common/workbenchThemeService.ts b/src/vs/workbench/services/themes/common/workbenchThemeService.ts index 6dbc5a4c68..b9acc6d0bb 100644 --- a/src/vs/workbench/services/themes/common/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/common/workbenchThemeService.ts @@ -27,7 +27,7 @@ export interface IColorTheme extends ITheme { readonly id: string; readonly label: string; readonly settingsId: string; - readonly extensionData: ExtensionData; + readonly extensionData?: ExtensionData; readonly description?: string; readonly isLoaded: boolean; readonly tokenColors: ITokenColorizationRule[]; diff --git a/src/vs/workbench/services/timer/electron-browser/timerService.ts b/src/vs/workbench/services/timer/electron-browser/timerService.ts index 26fb6de60d..5bbce190fa 100644 --- a/src/vs/workbench/services/timer/electron-browser/timerService.ts +++ b/src/vs/workbench/services/timer/electron-browser/timerService.ts @@ -7,8 +7,6 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { virtualMachineHint } from 'vs/base/node/id'; import * as perf from 'vs/base/common/performance'; import * as os from 'os'; -import { getAccessibilitySupport } from 'vs/base/browser/browser'; -import { AccessibilitySupport } from 'vs/base/common/platform'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -19,6 +17,7 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IAccessibilityService, AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility'; /* __GDPR__FRAGMENT__ @@ -53,9 +52,6 @@ export interface IMemoryInfo { "timers.ellapsedExtensions" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedExtensionsReady" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedRequire" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "timers.ellapsedGlobalStorageInitMain" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "timers.ellapsedGlobalStorageInitRenderer" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, - "timers.ellapsedWorkspaceStorageRequire" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedWorkspaceStorageInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedWorkspaceServiceInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, "timers.ellapsedViewletRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }, @@ -197,31 +193,6 @@ export interface IStartupMetrics { */ readonly ellapsedWindowLoadToRequire: number; - /** - * The time it took to require the global storage DB, connect to it - * and load the initial set of values. - * - * * Happens in the main-process - * * Measured with the `main:willInitGlobalStorage` and `main:didInitGlobalStorage` performance marks. - */ - readonly ellapsedGlobalStorageInitMain: number; - - /** - * The time it took to load the initial set of values from the global storage. - * - * * Happens in the renderer-process - * * Measured with the `willInitGlobalStorage` and `didInitGlobalStorage` performance marks. - */ - readonly ellapsedGlobalStorageInitRenderer: number; - - /** - * The time it took to require the workspace storage DB. - * - * * Happens in the renderer-process - * * Measured with the `willRequireSQLite` and `didRequireSQLite` performance marks. - */ - readonly ellapsedWorkspaceStorageRequire: number; - /** * The time it took to require the workspace storage DB, connect to it * and load the initial set of values. @@ -349,6 +320,7 @@ class TimerService implements ITimerService { @IViewletService private readonly _viewletService: IViewletService, @IPanelService private readonly _panelService: IPanelService, @IEditorService private readonly _editorService: IEditorService, + @IAccessibilityService private readonly _accessibilityService: IAccessibilityService ) { } get startupMetrics(): Promise { @@ -395,6 +367,8 @@ class TimerService implements ITimerService { // ignore, be on the safe side with these hardware method calls } + const activeViewlet = this._viewletService.getActiveViewlet(); + const activePanel = this._panelService.getActivePanel(); return { version: 2, ellapsed: perf.getDuration(startMark, 'didStartWorkbench'), @@ -404,9 +378,9 @@ class TimerService implements ITimerService { didUseCachedData: didUseCachedData(), windowKind: this._lifecycleService.startupKind, windowCount: await this._windowsService.getWindowCount(), - viewletId: this._viewletService.getActiveViewlet() ? this._viewletService.getActiveViewlet().getId() : undefined, + viewletId: activeViewlet ? activeViewlet.getId() : undefined, editorIds: this._editorService.visibleEditors.map(input => input.getTypeId()), - panelId: this._panelService.getActivePanel() ? this._panelService.getActivePanel().getId() : undefined, + panelId: activePanel ? activePanel.getId() : undefined, // timers timers: { @@ -415,9 +389,6 @@ class TimerService implements ITimerService { ellapsedWindowLoad: initialStartup ? perf.getDuration('main:appReady', 'main:loadWindow') : undefined, ellapsedWindowLoadToRequire: perf.getDuration('main:loadWindow', 'willLoadWorkbenchMain'), ellapsedRequire: perf.getDuration('willLoadWorkbenchMain', 'didLoadWorkbenchMain'), - ellapsedGlobalStorageInitMain: perf.getDuration('main:willInitGlobalStorage', 'main:didInitGlobalStorage'), - ellapsedGlobalStorageInitRenderer: perf.getDuration('willInitGlobalStorage', 'didInitGlobalStorage'), - ellapsedWorkspaceStorageRequire: perf.getDuration('willRequireSQLite', 'didRequireSQLite'), ellapsedWorkspaceStorageInit: perf.getDuration('willInitWorkspaceStorage', 'didInitWorkspaceStorage'), ellapsedWorkspaceServiceInit: perf.getDuration('willInitWorkspaceService', 'didInitWorkspaceService'), ellapsedExtensions: perf.getDuration('willLoadExtensions', 'didLoadExtensions'), @@ -440,7 +411,7 @@ class TimerService implements ITimerService { loadavg, initialStartup, isVMLikelyhood, - hasAccessibilitySupport: getAccessibilitySupport() === AccessibilitySupport.Enabled, + hasAccessibilitySupport: this._accessibilityService.getAccessibilitySupport() === AccessibilitySupport.Enabled, emptyWorkbench: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY }; } diff --git a/src/vs/workbench/services/title/common/titleService.ts b/src/vs/workbench/services/title/common/titleService.ts index 104d17c938..f0acf4d2ba 100644 --- a/src/vs/workbench/services/title/common/titleService.ts +++ b/src/vs/workbench/services/title/common/titleService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { Event } from 'vs/base/common/event'; export const ITitleService = createDecorator('titleService'); @@ -15,6 +16,11 @@ export interface ITitleProperties { export interface ITitleService { _serviceBrand: any; + /** + * An event when the menubar visibility changes. + */ + readonly onMenubarVisibilityChange: Event; + /** * Update some environmental title properties. */ diff --git a/src/vs/workbench/services/untitled/common/untitledEditorService.ts b/src/vs/workbench/services/untitled/common/untitledEditorService.ts index 9f6c50d6dc..fe2865f0d0 100644 --- a/src/vs/workbench/services/untitled/common/untitledEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledEditorService.ts @@ -14,6 +14,8 @@ import { ResourceMap } from 'vs/base/common/map'; import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; import { Schemas } from 'vs/base/common/network'; import { Disposable } from 'vs/base/common/lifecycle'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { basename } from 'vs/base/common/resources'; export const IUntitledEditorService = createDecorator('untitledEditorService'); @@ -106,7 +108,7 @@ export interface IUntitledEditorService { /** * Get the configured encoding for the given untitled resource if any. */ - getEncoding(resource: URI): string; + getEncoding(resource: URI): string | undefined; } export class UntitledEditorService extends Disposable implements IUntitledEditorService { @@ -135,7 +137,7 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor super(); } - protected get(resource: URI): UntitledEditorInput { + protected get(resource: URI): UntitledEditorInput | undefined { return this.mapResourceToInput.get(resource); } @@ -171,7 +173,7 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor isDirty(resource: URI): boolean { const input = this.get(resource); - return input && input.isDirty(); + return input ? input.isDirty() : false; } getDirty(resources?: URI[]): URI[] { @@ -207,7 +209,7 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor // Return existing instance if asked for it if (resource && this.mapResourceToInput.has(resource)) { - return this.mapResourceToInput.get(resource); + return this.mapResourceToInput.get(resource)!; } // Create new otherwise @@ -236,19 +238,19 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor const input = this.instantiationService.createInstance(UntitledEditorInput, resource, hasAssociatedFilePath, modeId, initialValue, encoding); const contentListener = input.onDidModelChangeContent(() => { - this._onDidChangeContent.fire(resource); + this._onDidChangeContent.fire(resource!); }); const dirtyListener = input.onDidChangeDirty(() => { - this._onDidChangeDirty.fire(resource); + this._onDidChangeDirty.fire(resource!); }); const encodingListener = input.onDidModelChangeEncoding(() => { - this._onDidChangeEncoding.fire(resource); + this._onDidChangeEncoding.fire(resource!); }); const disposeListener = input.onDispose(() => { - this._onDidDisposeModel.fire(resource); + this._onDidDisposeModel.fire(resource!); }); // Remove from cache on dispose @@ -275,12 +277,14 @@ export class UntitledEditorService extends Disposable implements IUntitledEditor suggestFileName(resource: URI): string { const input = this.get(resource); - return input ? input.suggestFileName() : undefined; + return input ? input.suggestFileName() : basename(resource); } - getEncoding(resource: URI): string { + getEncoding(resource: URI): string | undefined { const input = this.get(resource); return input ? input.getEncoding() : undefined; } } + +registerSingleton(IUntitledEditorService, UntitledEditorService, true); \ No newline at end of file diff --git a/src/vs/workbench/services/viewlet/browser/viewlet.ts b/src/vs/workbench/services/viewlet/browser/viewlet.ts index 9c2d695f5d..39624a7d04 100644 --- a/src/vs/workbench/services/viewlet/browser/viewlet.ts +++ b/src/vs/workbench/services/viewlet/browser/viewlet.ts @@ -22,12 +22,12 @@ export interface IViewletService { /** * Opens a viewlet with the given identifier and pass keyboard focus to it if specified. */ - openViewlet(id: string, focus?: boolean): Promise; + openViewlet(id: string | undefined, focus?: boolean): Promise; /** * Returns the current active viewlet or null if none. */ - getActiveViewlet(): IViewlet; + getActiveViewlet(): IViewlet | null; /** * Returns the id of the default viewlet. @@ -37,7 +37,7 @@ export interface IViewletService { /** * Returns the viewlet by id. */ - getViewlet(id: string): ViewletDescriptor; + getViewlet(id: string): ViewletDescriptor | undefined; /** * Returns all enabled viewlets @@ -45,7 +45,17 @@ export interface IViewletService { getViewlets(): ViewletDescriptor[]; /** - * + * Returns the progress indicator for the side bar. */ getProgressIndicator(id: string): IProgressService | null; + + /** + * Hide the active viewlet. + */ + hideActiveViewlet(): void; + + /** + * Return the last active viewlet id. + */ + getLastActiveViewletId(): string; } diff --git a/src/vs/workbench/services/workspace/common/workspaceEditing.ts b/src/vs/workbench/services/workspace/common/workspaceEditing.ts index ad1a5f1960..d198672ed0 100644 --- a/src/vs/workbench/services/workspace/common/workspaceEditing.ts +++ b/src/vs/workbench/services/workspace/common/workspaceEditing.ts @@ -51,4 +51,9 @@ export interface IWorkspaceEditingService { * copies current workspace settings to the target workspace. */ copyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier): Promise; + + /** + * picks a new workspace path + */ + pickNewWorkspacePath(): Promise; } \ No newline at end of file diff --git a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts index 0f6a734388..9aa943e4bb 100644 --- a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts @@ -9,8 +9,7 @@ import * as nls from 'vs/nls'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IWindowService, MessageBoxOptions, IWindowsService } from 'vs/platform/windows/common/windows'; import { IJSONEditingService, JSONEditingError, JSONEditingErrorCode } from 'vs/workbench/services/configuration/common/jsonEditing'; -import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, isWorkspaceIdentifier, toWorkspaceIdentifier, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { IWorkspaceIdentifier, IWorkspaceFolderCreationData, IWorkspacesService, rewriteWorkspaceFileForNewLocation, WORKSPACE_FILTER } from 'vs/platform/workspaces/common/workspaces'; import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { StorageService } from 'vs/platform/storage/node/storageService'; @@ -21,11 +20,17 @@ import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { BackupFileService } from 'vs/workbench/services/backup/node/backupFileService'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { distinct } from 'vs/base/common/arrays'; -import { isLinux } from 'vs/base/common/platform'; -import { isEqual, basename } from 'vs/base/common/resources'; +import { isLinux, isWindows, isMacintosh } from 'vs/base/common/platform'; +import { isEqual, basename, isEqualOrParent } from 'vs/base/common/resources'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IFileService } from 'vs/platform/files/common/files'; -import { rewriteWorkspaceFileForNewLocation } from 'vs/platform/workspaces/node/workspaces'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; +import { IFileDialogService, IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { mnemonicButtonLabel } from 'vs/base/common/labels'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ILabelService } from 'vs/platform/label/common/label'; export class WorkspaceEditingService implements IWorkspaceEditingService { @@ -35,7 +40,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { @IJSONEditingService private readonly jsonEditingService: IJSONEditingService, @IWorkspaceContextService private readonly contextService: WorkspaceService, @IWindowService private readonly windowService: IWindowService, - @IWorkspaceConfigurationService private readonly workspaceConfigurationService: IWorkspaceConfigurationService, + @IConfigurationService private readonly configurationService: IConfigurationService, @IStorageService private readonly storageService: IStorageService, @IExtensionService private readonly extensionService: IExtensionService, @IBackupFileService private readonly backupFileService: IBackupFileService, @@ -43,8 +48,99 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { @ICommandService private readonly commandService: ICommandService, @IFileService private readonly fileSystemService: IFileService, @IWindowsService private readonly windowsService: IWindowsService, - @IWorkspacesService private readonly workspaceService: IWorkspacesService + @IWorkspacesService private readonly workspaceService: IWorkspacesService, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IDialogService private readonly dialogService: IDialogService, + @ILifecycleService readonly lifecycleService: ILifecycleService, + @ILabelService readonly labelService: ILabelService ) { + + lifecycleService.onBeforeShutdown(async e => { + const saveOperation = this.saveUntitedBeforeShutdown(e.reason); + if (saveOperation) { + e.veto(saveOperation); + } + }); + + } + + private saveUntitedBeforeShutdown(reason: ShutdownReason): Promise | undefined { + if (reason !== ShutdownReason.LOAD && reason !== ShutdownReason.CLOSE) { + return undefined; // only interested when window is closing or loading + } + const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); + if (!workspaceIdentifier || !isEqualOrParent(workspaceIdentifier.configPath, this.environmentService.untitledWorkspacesHome)) { + return undefined; // only care about untitled workspaces to ask for saving + } + + return this.windowsService.getWindowCount().then(windowCount => { + if (reason === ShutdownReason.CLOSE && !isMacintosh && windowCount === 1) { + return false; // Windows/Linux: quits when last window is closed, so do not ask then + } + enum ConfirmResult { + SAVE, + DONT_SAVE, + CANCEL + } + + const save = { label: mnemonicButtonLabel(nls.localize('save', "Save")), result: ConfirmResult.SAVE }; + const dontSave = { label: mnemonicButtonLabel(nls.localize('doNotSave', "Don't Save")), result: ConfirmResult.DONT_SAVE }; + const cancel = { label: nls.localize('cancel', "Cancel"), result: ConfirmResult.CANCEL }; + + const buttons: { label: string; result: ConfirmResult; }[] = []; + if (isWindows) { + buttons.push(save, dontSave, cancel); + } else if (isLinux) { + buttons.push(dontSave, cancel, save); + } else { + buttons.push(save, cancel, dontSave); + } + + const message = nls.localize('saveWorkspaceMessage', "Do you want to save your workspace configuration as a file?"); + const detail = nls.localize('saveWorkspaceDetail', "Save your workspace if you plan to open it again."); + const cancelId = buttons.indexOf(cancel); + + return this.dialogService.show(Severity.Warning, message, buttons.map(button => button.label), { detail, cancelId }).then(res => { + switch (buttons[res].result) { + + // Cancel: veto unload + case ConfirmResult.CANCEL: + return true; + + // Don't Save: delete workspace + case ConfirmResult.DONT_SAVE: + this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); + return false; + + // Save: save workspace, but do not veto unload + case ConfirmResult.SAVE: { + return this.pickNewWorkspacePath().then(newWorkspacePath => { + if (newWorkspacePath) { + return this.saveWorkspaceAs(workspaceIdentifier, newWorkspacePath).then(_ => { + return this.workspaceService.getWorkspaceIdentifier(newWorkspacePath).then(newWorkspaceIdentifier => { + const label = this.labelService.getWorkspaceLabel(newWorkspaceIdentifier, { verbose: true }); + this.windowsService.addRecentlyOpened([{ label, workspace: newWorkspaceIdentifier }]); + this.workspaceService.deleteUntitledWorkspace(workspaceIdentifier); + return false; + }); + }, () => false); + } + return true; // keep veto if no target was provided + }); + } + } + }); + }); + } + + pickNewWorkspacePath(): Promise { + return this.fileDialogService.showSaveDialog({ + saveLabel: mnemonicButtonLabel(nls.localize('save', "Save")), + title: nls.localize('saveWorkspace', "Save Workspace"), + filters: WORKSPACE_FILTER, + defaultUri: this.fileDialogService.defaultWorkspacePath() + }); } updateFolders(index: number, deleteCount?: number, foldersToAdd?: IWorkspaceFolderCreationData[], donotNotifyError?: boolean): Promise { @@ -63,7 +159,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } // Add Folders - if (wantsToAdd && !wantsToDelete) { + if (wantsToAdd && !wantsToDelete && Array.isArray(foldersToAdd)) { return this.doAddFolders(foldersToAdd, index, donotNotifyError); } @@ -79,16 +175,16 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { // other folders, we handle this specially and just enter workspace // mode with the folders that are being added. if (this.includesSingleFolderWorkspace(foldersToDelete)) { - return this.createAndEnterWorkspace(foldersToAdd); + return this.createAndEnterWorkspace(foldersToAdd!); } // if we are not in workspace-state, we just add the folders if (this.contextService.getWorkbenchState() !== WorkbenchState.WORKSPACE) { - return this.doAddFolders(foldersToAdd, index, donotNotifyError); + return this.doAddFolders(foldersToAdd!, index, donotNotifyError); } // finally, update folders within the workspace - return this.doUpdateFolders(foldersToAdd, foldersToDelete, index, donotNotifyError); + return this.doUpdateFolders(foldersToAdd!, foldersToDelete, index, donotNotifyError); } } @@ -154,7 +250,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { if (path) { await this.saveWorkspaceAs(untitledWorkspace, path); } else { - path = URI.file(untitledWorkspace.configPath); + path = untitledWorkspace.configPath; } return this.enterWorkspace(path); } @@ -163,11 +259,11 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { if (!this.isValidTargetWorkspacePath(path)) { return Promise.reject(null); } - const currentWorkspaceIdentifier = toWorkspaceIdentifier(this.contextService.getWorkspace()); - if (!isWorkspaceIdentifier(currentWorkspaceIdentifier)) { + const workspaceIdentifier = this.getCurrentWorkspaceIdentifier(); + if (!workspaceIdentifier) { return Promise.reject(null); } - await this.saveWorkspaceAs(currentWorkspaceIdentifier, path); + await this.saveWorkspaceAs(workspaceIdentifier, path); return this.enterWorkspace(path); } @@ -177,7 +273,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { const windows = await this.windowsService.getWindows(); // Prevent overwriting a workspace that is currently opened in another window - if (windows.some(window => window.workspace && isEqual(URI.file(window.workspace.configPath), path))) { + if (windows.some(window => !!window.workspace && isEqual(window.workspace.configPath, path))) { const options: MessageBoxOptions = { type: 'info', buttons: [nls.localize('ok', "OK")], @@ -192,7 +288,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } private async saveWorkspaceAs(workspace: IWorkspaceIdentifier, targetConfigPathURI: URI): Promise { - const configPathURI = URI.file(workspace.configPath); + const configPathURI = workspace.configPath; // Return early if target is same as source if (isEqual(configPathURI, targetConfigPathURI)) { @@ -239,12 +335,20 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { } enterWorkspace(path: URI): Promise { + if (!!this.environmentService.extensionTestsLocationURI) { + return Promise.reject(new Error('Entering a new workspace is not possible in tests.')); + } + // Restart extension host if first root folder changed (impact on deprecated workspace.rootPath API) // Stop the extension host first to give extensions most time to shutdown this.extensionService.stopExtensionHost(); let extensionHostStarted: boolean = false; const startExtensionHost = () => { + if (this.windowService.getConfiguration().remoteAuthority) { + this.windowService.reloadWindow(); // TODO aeschli: workaround until restarting works + } + this.extensionService.startExtensionHost(); extensionHostStarted = true; }; @@ -257,7 +361,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { // Reinitialize backup service if (this.backupFileService instanceof BackupFileService) { - this.backupFileService.initialize(result.backupPath); + this.backupFileService.initialize(result.backupPath!); } // Reinitialize configuration service @@ -307,16 +411,26 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { private doCopyWorkspaceSettings(toWorkspace: IWorkspaceIdentifier, filter?: (config: IConfigurationPropertySchema) => boolean): Promise { const configurationProperties = Registry.as(ConfigurationExtensions.Configuration).getConfigurationProperties(); const targetWorkspaceConfiguration = {}; - for (const key of this.workspaceConfigurationService.keys().workspace) { + for (const key of this.configurationService.keys().workspace) { if (configurationProperties[key]) { if (filter && !filter(configurationProperties[key])) { continue; } - targetWorkspaceConfiguration[key] = this.workspaceConfigurationService.inspect(key).workspace; + targetWorkspaceConfiguration[key] = this.configurationService.inspect(key).workspace; } } - return this.jsonEditingService.write(URI.file(toWorkspace.configPath), { key: 'settings', value: targetWorkspaceConfiguration }, true); + return this.jsonEditingService.write(toWorkspace.configPath, { key: 'settings', value: targetWorkspaceConfiguration }, true); + } + + private getCurrentWorkspaceIdentifier(): IWorkspaceIdentifier | undefined { + const workspace = this.contextService.getWorkspace(); + if (workspace && workspace.configuration) { + return { id: workspace.id, configPath: workspace.configuration }; + } + return undefined; } } + +registerSingleton(IWorkspaceEditingService, WorkspaceEditingService, true); \ No newline at end of file diff --git a/src/vs/workbench/test/browser/part.test.ts b/src/vs/workbench/test/browser/part.test.ts index 09e3e9394c..a80ea38af0 100644 --- a/src/vs/workbench/test/browser/part.test.ts +++ b/src/vs/workbench/test/browser/part.test.ts @@ -8,13 +8,30 @@ import { Part } from 'vs/workbench/browser/part'; import * as Types from 'vs/base/common/types'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { append, $, hide } from 'vs/base/browser/dom'; -import { TestStorageService } from 'vs/workbench/test/workbenchTestServices'; +import { TestStorageService, TestLayoutService } from 'vs/workbench/test/workbenchTestServices'; import { StorageScope } from 'vs/platform/storage/common/storage'; +import { Orientation } from 'vs/base/browser/ui/grid/grid'; -class MyPart extends Part { +class SimplePart extends Part { + + minimumWidth: number; + maximumWidth: number; + minimumHeight: number; + maximumHeight: number; + + layout(width: number, height: number, orientation: Orientation): void { + throw new Error('Method not implemented.'); + } + + toJSON(): object { + throw new Error('Method not implemented.'); + } +} + +class MyPart extends SimplePart { constructor(private expectedParent: HTMLElement) { - super('myPart', { hasTitle: true }, new TestThemeService(), new TestStorageService()); + super('myPart', { hasTitle: true }, new TestThemeService(), new TestStorageService(), new TestLayoutService()); } createTitleArea(parent: HTMLElement): HTMLElement { @@ -36,10 +53,10 @@ class MyPart extends Part { } } -class MyPart2 extends Part { +class MyPart2 extends SimplePart { constructor() { - super('myPart2', { hasTitle: true }, new TestThemeService(), new TestStorageService()); + super('myPart2', { hasTitle: true }, new TestThemeService(), new TestStorageService(), new TestLayoutService()); } createTitleArea(parent: HTMLElement): HTMLElement { @@ -61,10 +78,10 @@ class MyPart2 extends Part { } } -class MyPart3 extends Part { +class MyPart3 extends SimplePart { constructor() { - super('myPart2', { hasTitle: false }, new TestThemeService(), new TestStorageService()); + super('myPart2', { hasTitle: false }, new TestThemeService(), new TestStorageService(), new TestLayoutService()); } createTitleArea(parent: HTMLElement): HTMLElement { diff --git a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts index 9edfd329b1..5906a7e3a5 100644 --- a/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts +++ b/src/vs/workbench/test/browser/parts/editor/baseEditor.test.ts @@ -154,10 +154,10 @@ suite('Workbench base editor', () => { let inst = new TestInstantiationService(); - const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake'))).instantiate(inst); + const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake')))!.instantiate(inst); assert.strictEqual(editor.getId(), 'myEditor'); - const otherEditor = EditorRegistry.getEditor(inst.createInstance(ResourceEditorInput, 'fake', '', URI.file('/fake'))).instantiate(inst); + const otherEditor = EditorRegistry.getEditor(inst.createInstance(ResourceEditorInput, 'fake', '', URI.file('/fake')))!.instantiate(inst); assert.strictEqual(otherEditor.getId(), 'myOtherEditor'); (EditorRegistry).setEditors(oldEditors); @@ -173,14 +173,14 @@ suite('Workbench base editor', () => { let inst = new TestInstantiationService(); - const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake'))).instantiate(inst); + const editor = EditorRegistry.getEditor(inst.createInstance(MyResourceInput, 'fake', '', URI.file('/fake')))!.instantiate(inst); assert.strictEqual('myOtherEditor', editor.getId()); (EditorRegistry).setEditors(oldEditors); }); test('Editor Input Factory', function () { - EditorInputRegistry.setInstantiationService(workbenchInstantiationService()); + workbenchInstantiationService().invokeFunction(accessor => EditorInputRegistry.start(accessor)); EditorInputRegistry.registerEditorInputFactory('myInputId', MyInputFactory); let factory = EditorInputRegistry.getEditorInputFactory('myInputId'); @@ -211,12 +211,12 @@ suite('Workbench base editor', () => { memento.saveEditorState(testGroup0, URI.file('/A'), { line: 3 }); res = memento.loadEditorState(testGroup0, URI.file('/A')); assert.ok(res); - assert.equal(res.line, 3); + assert.equal(res!.line, 3); memento.saveEditorState(testGroup1, URI.file('/A'), { line: 5 }); res = memento.loadEditorState(testGroup1, URI.file('/A')); assert.ok(res); - assert.equal(res.line, 5); + assert.equal(res!.line, 5); // Ensure capped at 3 elements memento.saveEditorState(testGroup0, URI.file('/B'), { line: 1 }); @@ -289,7 +289,7 @@ suite('Workbench base editor', () => { memento.saveEditorState(testGroup0, testInputA, { line: 3 }); res = memento.loadEditorState(testGroup0, testInputA); assert.ok(res); - assert.equal(res.line, 3); + assert.equal(res!.line, 3); // State removed when input gets disposed testInputA.dispose(); diff --git a/src/vs/workbench/test/browser/parts/views/views.test.ts b/src/vs/workbench/test/browser/parts/views/views.test.ts index 690051012d..d4fc28a779 100644 --- a/src/vs/workbench/test/browser/parts/views/views.test.ts +++ b/src/vs/workbench/test/browser/parts/views/views.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import { ContributableViewsModel, ViewsService } from 'vs/workbench/browser/parts/views/views'; -import { ViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService } from 'vs/workbench/common/views'; +import { IViewsRegistry, IViewDescriptor, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService } from 'vs/workbench/common/views'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { move } from 'vs/base/common/arrays'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -15,6 +15,7 @@ import { TestInstantiationService } from 'vs/platform/instantiation/test/common/ import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; const container = Registry.as(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer('test'); +const ViewsRegistry = Registry.as(ViewContainerExtensions.ViewsRegistry); class ViewDescriptorSequence { @@ -63,7 +64,7 @@ suite('ContributableViewsModel', () => { const viewDescriptor: IViewDescriptor = { id: 'view1', - ctor: null, + ctorDescriptor: null!, name: 'Test View 1' }; @@ -89,7 +90,7 @@ suite('ContributableViewsModel', () => { const viewDescriptor: IViewDescriptor = { id: 'view1', - ctor: null, + ctorDescriptor: null!, name: 'Test View 1', when: ContextKeyExpr.equals('showview1', true) }; @@ -128,8 +129,8 @@ suite('ContributableViewsModel', () => { const model = new ContributableViewsModel(container, viewsService); const seq = new ViewDescriptorSequence(model); - const view1: IViewDescriptor = { id: 'view1', ctor: null, name: 'Test View 1' }; - const view2: IViewDescriptor = { id: 'view2', ctor: null, name: 'Test View 2', when: ContextKeyExpr.equals('showview2', true) }; + const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; + const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', when: ContextKeyExpr.equals('showview2', true) }; ViewsRegistry.registerViews([view1, view2], container); assert.deepEqual(model.visibleViewDescriptors, [view1], 'only view1 should be visible'); @@ -151,8 +152,8 @@ suite('ContributableViewsModel', () => { const model = new ContributableViewsModel(container, viewsService); const seq = new ViewDescriptorSequence(model); - const view1: IViewDescriptor = { id: 'view1', ctor: null, name: 'Test View 1', when: ContextKeyExpr.equals('showview1', true) }; - const view2: IViewDescriptor = { id: 'view2', ctor: null, name: 'Test View 2' }; + const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', when: ContextKeyExpr.equals('showview1', true) }; + const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' }; ViewsRegistry.registerViews([view1, view2], container); assert.deepEqual(model.visibleViewDescriptors, [view2], 'only view2 should be visible'); @@ -174,9 +175,9 @@ suite('ContributableViewsModel', () => { const model = new ContributableViewsModel(container, viewsService); const seq = new ViewDescriptorSequence(model); - const view1: IViewDescriptor = { id: 'view1', ctor: null, name: 'Test View 1', canToggleVisibility: true }; - const view2: IViewDescriptor = { id: 'view2', ctor: null, name: 'Test View 2', canToggleVisibility: true }; - const view3: IViewDescriptor = { id: 'view3', ctor: null, name: 'Test View 3', canToggleVisibility: true }; + const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1', canToggleVisibility: true }; + const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2', canToggleVisibility: true }; + const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3', canToggleVisibility: true }; ViewsRegistry.registerViews([view1, view2, view3], container); assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3]); @@ -219,9 +220,9 @@ suite('ContributableViewsModel', () => { const model = new ContributableViewsModel(container, viewsService); const seq = new ViewDescriptorSequence(model); - const view1: IViewDescriptor = { id: 'view1', ctor: null, name: 'Test View 1' }; - const view2: IViewDescriptor = { id: 'view2', ctor: null, name: 'Test View 2' }; - const view3: IViewDescriptor = { id: 'view3', ctor: null, name: 'Test View 3' }; + const view1: IViewDescriptor = { id: 'view1', ctorDescriptor: null!, name: 'Test View 1' }; + const view2: IViewDescriptor = { id: 'view2', ctorDescriptor: null!, name: 'Test View 2' }; + const view3: IViewDescriptor = { id: 'view3', ctorDescriptor: null!, name: 'Test View 3' }; ViewsRegistry.registerViews([view1, view2, view3], container); assert.deepEqual(model.visibleViewDescriptors, [view1, view2, view3], 'model views should be OK'); diff --git a/src/vs/workbench/test/common/editor/editor.test.ts b/src/vs/workbench/test/common/editor/editor.test.ts index aa6e553a95..13f6438c92 100644 --- a/src/vs/workbench/test/common/editor/editor.test.ts +++ b/src/vs/workbench/test/common/editor/editor.test.ts @@ -32,7 +32,7 @@ class FileEditorInput extends EditorInput { return this.resource; } - resolve(): Promise { + resolve(): Promise { return Promise.resolve(null); } } @@ -55,22 +55,22 @@ suite('Workbench editor', () => { test('toResource', () => { const service = accessor.untitledEditorService; - assert.ok(!toResource(null)); + assert.ok(!toResource(null!)); const untitled = service.createOrGet(); - assert.equal(toResource(untitled).toString(), untitled.getResource().toString()); - assert.equal(toResource(untitled, { supportSideBySide: true }).toString(), untitled.getResource().toString()); - assert.equal(toResource(untitled, { filter: Schemas.untitled }).toString(), untitled.getResource().toString()); - assert.equal(toResource(untitled, { filter: [Schemas.file, Schemas.untitled] }).toString(), untitled.getResource().toString()); + assert.equal(toResource(untitled)!.toString(), untitled.getResource().toString()); + assert.equal(toResource(untitled, { supportSideBySide: true })!.toString(), untitled.getResource().toString()); + assert.equal(toResource(untitled, { filter: Schemas.untitled })!.toString(), untitled.getResource().toString()); + assert.equal(toResource(untitled, { filter: [Schemas.file, Schemas.untitled] })!.toString(), untitled.getResource().toString()); assert.ok(!toResource(untitled, { filter: Schemas.file })); const file = new FileEditorInput(URI.file('/some/path.txt')); - assert.equal(toResource(file).toString(), file.getResource().toString()); - assert.equal(toResource(file, { supportSideBySide: true }).toString(), file.getResource().toString()); - assert.equal(toResource(file, { filter: Schemas.file }).toString(), file.getResource().toString()); - assert.equal(toResource(file, { filter: [Schemas.file, Schemas.untitled] }).toString(), file.getResource().toString()); + assert.equal(toResource(file)!.toString(), file.getResource().toString()); + assert.equal(toResource(file, { supportSideBySide: true })!.toString(), file.getResource().toString()); + assert.equal(toResource(file, { filter: Schemas.file })!.toString(), file.getResource().toString()); + assert.equal(toResource(file, { filter: [Schemas.file, Schemas.untitled] })!.toString(), file.getResource().toString()); assert.ok(!toResource(file, { filter: Schemas.untitled })); const diffEditorInput = new DiffEditorInput('name', 'description', untitled, file); @@ -79,8 +79,8 @@ suite('Workbench editor', () => { assert.ok(!toResource(diffEditorInput, { filter: Schemas.file })); assert.ok(!toResource(diffEditorInput, { supportSideBySide: false })); - assert.equal(toResource(file, { supportSideBySide: true }).toString(), file.getResource().toString()); - assert.equal(toResource(file, { supportSideBySide: true, filter: Schemas.file }).toString(), file.getResource().toString()); - assert.equal(toResource(file, { supportSideBySide: true, filter: [Schemas.file, Schemas.untitled] }).toString(), file.getResource().toString()); + assert.equal(toResource(file, { supportSideBySide: true })!.toString(), file.getResource().toString()); + assert.equal(toResource(file, { supportSideBySide: true, filter: Schemas.file })!.toString(), file.getResource().toString()); + assert.equal(toResource(file, { supportSideBySide: true, filter: [Schemas.file, Schemas.untitled] })!.toString(), file.getResource().toString()); }); }); \ No newline at end of file diff --git a/src/vs/workbench/test/common/editor/editorGroups.test.ts b/src/vs/workbench/test/common/editor/editorGroups.test.ts index 3cb0dac282..453e5d3707 100644 --- a/src/vs/workbench/test/common/editor/editorGroups.test.ts +++ b/src/vs/workbench/test/common/editor/editorGroups.test.ts @@ -215,7 +215,7 @@ suite('Workbench editor groups', () => { }); test('group serialization', function () { - Registry.as(EditorExtensions.EditorInputFactories).setInstantiationService(inst()); + inst().invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); const group = createGroup(); const input1 = input(); @@ -656,7 +656,7 @@ suite('Workbench editor groups', () => { config.setUserConfiguration('workbench', { editor: { focusRecentEditorAfterClose: false } }); inst.stub(IConfigurationService, config); - const group = inst.createInstance(EditorGroup); + const group = inst.createInstance(EditorGroup, undefined); const events = groupListener(group); const input1 = input(); @@ -1003,7 +1003,7 @@ suite('Workbench editor groups', () => { config.setUserConfiguration('workbench', { editor: { openPositioning: 'right' } }); inst.stub(IConfigurationService, config); - (Registry.as(EditorExtensions.EditorInputFactories)).setInstantiationService(inst); + inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); let group = createGroup(); @@ -1037,7 +1037,7 @@ suite('Workbench editor groups', () => { config.setUserConfiguration('workbench', { editor: { openPositioning: 'right' } }); inst.stub(IConfigurationService, config); - (Registry.as(EditorExtensions.EditorInputFactories)).setInstantiationService(inst); + inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); let group1 = createGroup(); @@ -1107,7 +1107,7 @@ suite('Workbench editor groups', () => { config.setUserConfiguration('workbench', { editor: { openPositioning: 'right' } }); inst.stub(IConfigurationService, config); - (Registry.as(EditorExtensions.EditorInputFactories)).setInstantiationService(inst); + inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); let group = createGroup(); @@ -1151,7 +1151,7 @@ suite('Workbench editor groups', () => { config.setUserConfiguration('workbench', { editor: { openPositioning: 'right' } }); inst.stub(IConfigurationService, config); - (Registry.as(EditorExtensions.EditorInputFactories)).setInstantiationService(inst); + inst.invokeFunction(accessor => Registry.as(EditorExtensions.EditorInputFactories).start(accessor)); let group1 = createGroup(); let group2 = createGroup(); diff --git a/src/vs/workbench/test/common/editor/resourceEditorInput.test.ts b/src/vs/workbench/test/common/editor/resourceEditorInput.test.ts index 240647d3c1..34b7322f31 100644 --- a/src/vs/workbench/test/common/editor/resourceEditorInput.test.ts +++ b/src/vs/workbench/test/common/editor/resourceEditorInput.test.ts @@ -36,9 +36,9 @@ suite('Workbench resource editor input', () => { accessor.modelService.createModel('function test() {}', accessor.modeService.create('text'), resource); let input: ResourceEditorInput = instantiationService.createInstance(ResourceEditorInput, 'The Name', 'The Description', resource); - return input.resolve().then((model: ResourceEditorModel) => { + return input.resolve().then(model => { assert.ok(model); - assert.equal(snapshotToString(model.createSnapshot()), 'function test() {}'); + assert.equal(snapshotToString((model as ResourceEditorModel).createSnapshot()!), 'function test() {}'); }); }); }); \ No newline at end of file diff --git a/src/vs/workbench/test/common/editor/untitledEditor.test.ts b/src/vs/workbench/test/common/editor/untitledEditor.test.ts index ae9303ae0c..bf27acf264 100644 --- a/src/vs/workbench/test/common/editor/untitledEditor.test.ts +++ b/src/vs/workbench/test/common/editor/untitledEditor.test.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { URI } from 'vs/base/common/uri'; import * as assert from 'assert'; -import { join } from 'vs/base/common/paths'; +import { join } from 'vs/base/common/path'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -18,7 +18,7 @@ import { snapshotToString } from 'vs/platform/files/common/files'; import { timeout } from 'vs/base/common/async'; export class TestUntitledEditorService extends UntitledEditorService { - get(resource: URI): UntitledEditorInput { return super.get(resource); } + get(resource: URI) { return super.get(resource); } getAll(resources?: URI[]): UntitledEditorInput[] { return super.getAll(resources); } } @@ -68,7 +68,7 @@ suite('Workbench untitled editors', () => { assert.equal(service.getAll().length, 1); // dirty - input2.resolve().then((model: UntitledEditorModel) => { + input2.resolve().then(model => { assert.ok(!service.isDirty(input2.getResource())); const listener = service.onDidChangeDirty(resource => { @@ -112,7 +112,7 @@ suite('Workbench untitled editors', () => { const input = service.createOrGet(); // dirty - return input.resolve().then((model: UntitledEditorModel) => { + return input.resolve().then(model => { model.textEditorModel.setValue('foo bar'); assert.ok(model.isDirty()); @@ -126,14 +126,14 @@ suite('Workbench untitled editors', () => { test('Untitled via loadOrCreate', function () { const service = accessor.untitledEditorService; service.loadOrCreate().then(model1 => { - model1.textEditorModel.setValue('foo bar'); + model1.textEditorModel!.setValue('foo bar'); assert.ok(model1.isDirty()); - model1.textEditorModel.setValue(''); + model1.textEditorModel!.setValue(''); assert.ok(!model1.isDirty()); return service.loadOrCreate({ initialValue: 'Hello World' }).then(model2 => { - assert.equal(snapshotToString(model2.createSnapshot()), 'Hello World'); + assert.equal(snapshotToString(model2.createSnapshot()!), 'Hello World'); const input = service.createOrGet(); @@ -169,7 +169,7 @@ suite('Workbench untitled editors', () => { const input = service.createOrGet(file); // dirty - return input.resolve().then((model: UntitledEditorModel) => { + return input.resolve().then(model => { model.textEditorModel.setValue('foo bar'); assert.ok(model.isDirty()); @@ -223,7 +223,7 @@ suite('Workbench untitled editors', () => { }); // dirty - return input.resolve().then((model: UntitledEditorModel) => { + return input.resolve().then(model => { model.setEncoding('utf16'); assert.equal(counter, 1); @@ -245,7 +245,7 @@ suite('Workbench untitled editors', () => { assert.equal(r.toString(), input.getResource().toString()); }); - return input.resolve().then((model: UntitledEditorModel) => { + return input.resolve().then(model => { model.textEditorModel.setValue('foo'); assert.equal(counter, 0, 'Dirty model should not trigger event immediately'); @@ -288,7 +288,7 @@ suite('Workbench untitled editors', () => { assert.equal(r.toString(), input.getResource().toString()); }); - return input.resolve().then((model: UntitledEditorModel) => { + return input.resolve().then(model => { assert.equal(counter, 0); input.dispose(); assert.equal(counter, 1); diff --git a/src/vs/workbench/test/electron-browser/api/extHost.api.impl.test.ts b/src/vs/workbench/test/electron-browser/api/extHost.api.impl.test.ts index 0002b7e573..6752ee297a 100644 --- a/src/vs/workbench/test/electron-browser/api/extHost.api.impl.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHost.api.impl.test.ts @@ -5,14 +5,17 @@ import * as assert from 'assert'; import { URI } from 'vs/base/common/uri'; -import { originalFSPath } from 'vs/workbench/api/node/extHost.api.impl'; +import { originalFSPath } from 'vs/base/common/resources'; +import { isWindows } from 'vs/base/common/platform'; suite('ExtHost API', function () { test('issue #51387: originalFSPath', function () { - assert.equal(originalFSPath(URI.file('C:\\test')).charAt(0), 'C'); - assert.equal(originalFSPath(URI.file('c:\\test')).charAt(0), 'c'); + if (isWindows) { + assert.equal(originalFSPath(URI.file('C:\\test')).charAt(0), 'C'); + assert.equal(originalFSPath(URI.file('c:\\test')).charAt(0), 'c'); - assert.equal(originalFSPath(URI.revive(JSON.parse(JSON.stringify(URI.file('C:\\test'))))).charAt(0), 'C'); - assert.equal(originalFSPath(URI.revive(JSON.parse(JSON.stringify(URI.file('c:\\test'))))).charAt(0), 'c'); + assert.equal(originalFSPath(URI.revive(JSON.parse(JSON.stringify(URI.file('C:\\test'))))).charAt(0), 'C'); + assert.equal(originalFSPath(URI.revive(JSON.parse(JSON.stringify(URI.file('c:\\test'))))).charAt(0), 'c'); + } }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts index d90849cbb4..69e55e8691 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts @@ -27,10 +27,11 @@ import { MainContext, ExtHostContext } from 'vs/workbench/api/node/extHost.proto import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics'; import * as vscode from 'vscode'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import 'vs/workbench/parts/search/electron-browser/search.contribution'; +import 'vs/workbench/contrib/search/browser/search.contribution'; import { NullLogService } from 'vs/platform/log/common/log'; import { ITextModel } from 'vs/editor/common/model'; import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { dispose } from 'vs/base/common/lifecycle'; const defaultSelector = { scheme: 'far' }; const model: ITextModel = EditorModel.createFromString( @@ -68,9 +69,8 @@ suite('ExtHostLanguageFeatureCommands', function () { rpcProtocol = new TestRPCProtocol(); instantiationService.stub(IHeapService, { _serviceBrand: undefined, - trackRecursive(args: any) { + trackObject(_obj: any) { // nothing - return args; } }); instantiationService.stub(ICommandService, { @@ -87,15 +87,15 @@ suite('ExtHostLanguageFeatureCommands', function () { instantiationService.stub(IModelService, { _serviceBrand: IModelService, getModel(): any { return model; }, - createModel(): any { throw new Error(); }, - updateModel(): any { throw new Error(); }, - setMode(): any { throw new Error(); }, - destroyModel(): any { throw new Error(); }, - getModels(): any { throw new Error(); }, - onModelAdded: undefined, - onModelModeChanged: undefined, - onModelRemoved: undefined, - getCreationOptions(): any { throw new Error(); } + createModel() { throw new Error(); }, + updateModel() { throw new Error(); }, + setMode() { throw new Error(); }, + destroyModel() { throw new Error(); }, + getModels() { throw new Error(); }, + onModelAdded: undefined!, + onModelModeChanged: undefined!, + onModelRemoved: undefined!, + getCreationOptions() { throw new Error(); } }); inst = instantiationService; } @@ -138,10 +138,8 @@ suite('ExtHostLanguageFeatureCommands', function () { mainThread.dispose(); }); - teardown(function () { - while (disposables.length) { - disposables.pop().dispose(); - } + teardown(() => { + disposables = dispose(disposables); return rpcProtocol.sync(); }); @@ -192,8 +190,8 @@ suite('ExtHostLanguageFeatureCommands', function () { test('executeWorkspaceSymbolProvider should accept empty string, #39522', async function () { disposables.push(extHost.registerWorkspaceSymbolProvider(nullExtensionDescription, { - provideWorkspaceSymbols(query) { - return [new types.SymbolInformation('hello', types.SymbolKind.Array, new types.Range(0, 0, 0, 0), URI.parse('foo:bar'))]; + provideWorkspaceSymbols(query): vscode.SymbolInformation[] { + return [new types.SymbolInformation('hello', types.SymbolKind.Array, new types.Range(0, 0, 0, 0), URI.parse('foo:bar')) as vscode.SymbolInformation]; } })); @@ -423,32 +421,32 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.equal(values.length, 4); let [first, second, third, fourth] = values; assert.equal(first.label, 'item1'); - assert.equal(first.textEdit.newText, 'item1'); - assert.equal(first.textEdit.range.start.line, 0); - assert.equal(first.textEdit.range.start.character, 0); - assert.equal(first.textEdit.range.end.line, 0); - assert.equal(first.textEdit.range.end.character, 4); + assert.equal(first.textEdit!.newText, 'item1'); + assert.equal(first.textEdit!.range.start.line, 0); + assert.equal(first.textEdit!.range.start.character, 0); + assert.equal(first.textEdit!.range.end.line, 0); + assert.equal(first.textEdit!.range.end.character, 4); assert.equal(second.label, 'item2'); - assert.equal(second.textEdit.newText, 'foo'); - assert.equal(second.textEdit.range.start.line, 0); - assert.equal(second.textEdit.range.start.character, 4); - assert.equal(second.textEdit.range.end.line, 0); - assert.equal(second.textEdit.range.end.character, 8); + assert.equal(second.textEdit!.newText, 'foo'); + assert.equal(second.textEdit!.range.start.line, 0); + assert.equal(second.textEdit!.range.start.character, 4); + assert.equal(second.textEdit!.range.end.line, 0); + assert.equal(second.textEdit!.range.end.character, 8); assert.equal(third.label, 'item3'); - assert.equal(third.textEdit.newText, 'foobar'); - assert.equal(third.textEdit.range.start.line, 0); - assert.equal(third.textEdit.range.start.character, 1); - assert.equal(third.textEdit.range.end.line, 0); - assert.equal(third.textEdit.range.end.character, 6); + assert.equal(third.textEdit!.newText, 'foobar'); + assert.equal(third.textEdit!.range.start.line, 0); + assert.equal(third.textEdit!.range.start.character, 1); + assert.equal(third.textEdit!.range.end.line, 0); + assert.equal(third.textEdit!.range.end.character, 6); assert.equal(fourth.label, 'item4'); assert.equal(fourth.textEdit, undefined); - assert.equal(fourth.range.start.line, 0); - assert.equal(fourth.range.start.character, 1); - assert.equal(fourth.range.end.line, 0); - assert.equal(fourth.range.end.character, 4); + assert.equal(fourth.range!.start.line, 0); + assert.equal(fourth.range!.start.character, 1); + assert.equal(fourth.range!.end.line, 0); + assert.equal(fourth.range!.end.character, 4); assert.ok(fourth.insertText instanceof types.SnippetString); assert.equal((fourth.insertText).value, 'foo$0bar'); }); @@ -631,11 +629,11 @@ suite('ExtHostLanguageFeatureCommands', function () { return rpcProtocol.sync().then(() => { return commands.executeCommand('vscode.executeCodeActionProvider', model.uri, new types.Range(0, 0, 1, 1)).then(value => { assert.equal(value.length, 1); - let [first] = value; + const [first] = value; assert.ok(first.command); - assert.equal(first.command.command, 'command'); - assert.equal(first.command.title, 'command_title'); - assert.equal(first.kind.value, 'foo'); + assert.equal(first.command!.command, 'command'); + assert.equal(first.command!.title, 'command_title'); + assert.equal(first.kind!.value, 'foo'); assert.equal(first.title, 'title'); }); @@ -661,13 +659,13 @@ suite('ExtHostLanguageFeatureCommands', function () { return rpcProtocol.sync().then(() => { return commands.executeCommand('vscode.executeCodeLensProvider', model.uri).then(value => { assert.equal(value.length, 1); - let [first] = value; + const [first] = value; - assert.equal(first.command.title, 'Title'); - assert.equal(first.command.command, 'cmd'); - assert.equal(first.command.arguments[0], 1); - assert.equal(first.command.arguments[1], true); - assert.equal(first.command.arguments[2], complexArg); + assert.equal(first.command!.title, 'Title'); + assert.equal(first.command!.command, 'cmd'); + assert.equal(first.command!.arguments![0], 1); + assert.equal(first.command!.arguments![1], true); + assert.equal(first.command!.arguments![2], complexArg); }); }); }); @@ -719,7 +717,7 @@ suite('ExtHostLanguageFeatureCommands', function () { assert.equal(value.length, 1); let [first] = value; - assert.equal(first.target.toString(), 'foo:bar'); + assert.equal(first.target + '', 'foo:bar'); assert.equal(first.range.start.line, 0); assert.equal(first.range.start.character, 0); assert.equal(first.range.end.line, 0); @@ -765,16 +763,16 @@ suite('ExtHostLanguageFeatureCommands', function () { let [first] = value; assert.equal(first.label, '#ABC'); - assert.equal(first.textEdit.newText, '#ABC'); - assert.equal(first.textEdit.range.start.line, 1); - assert.equal(first.textEdit.range.start.character, 0); - assert.equal(first.textEdit.range.end.line, 1); - assert.equal(first.textEdit.range.end.character, 20); - assert.equal(first.additionalTextEdits.length, 1); - assert.equal(first.additionalTextEdits[0].range.start.line, 2); - assert.equal(first.additionalTextEdits[0].range.start.character, 20); - assert.equal(first.additionalTextEdits[0].range.end.line, 2); - assert.equal(first.additionalTextEdits[0].range.end.character, 20); + assert.equal(first.textEdit!.newText, '#ABC'); + assert.equal(first.textEdit!.range.start.line, 1); + assert.equal(first.textEdit!.range.start.character, 0); + assert.equal(first.textEdit!.range.end.line, 1); + assert.equal(first.textEdit!.range.end.character, 20); + assert.equal(first.additionalTextEdits!.length, 1); + assert.equal(first.additionalTextEdits![0].range.start.line, 2); + assert.equal(first.additionalTextEdits![0].range.start.character, 20); + assert.equal(first.additionalTextEdits![0].range.end.line, 2); + assert.equal(first.additionalTextEdits![0].range.end.character, 20); }); }); }); @@ -797,20 +795,20 @@ suite('ExtHostLanguageFeatureCommands', function () { // --- selection ranges - test('Links, back and forth', async function () { + test('Selection Range, back and forth', async function () { disposables.push(extHost.registerSelectionRangeProvider(nullExtensionDescription, defaultSelector, { provideSelectionRanges() { return [ - new types.SelectionRange(new types.Range(0, 10, 0, 18), types.SelectionRangeKind.Empty), - new types.SelectionRange(new types.Range(0, 2, 0, 20), types.SelectionRangeKind.Empty) + new types.SelectionRange(new types.Range(0, 10, 0, 18), new types.SelectionRange(new types.Range(0, 2, 0, 20))), ]; } })); await rpcProtocol.sync(); - let value = await commands.executeCommand('vscode.executeSelectionRangeProvider', model.uri, new types.Position(0, 10)); - assert.ok(value.length >= 2); + let value = await commands.executeCommand('vscode.executeSelectionRangeProvider', model.uri, [new types.Position(0, 10)]); + assert.equal(value.length, 1); + assert.ok(value[0].length >= 2); }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts index 75ae3f5f29..0b89cb156d 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -31,7 +31,7 @@ suite('ExtHostConfiguration', function () { if (!shape) { shape = new class extends mock() { }; } - return new ExtHostConfigProvider(shape, new ExtHostWorkspace(new TestRPCProtocol(), null, new NullLogService(), new Counter()), createConfigurationData(contents)); + return new ExtHostConfigProvider(shape, new ExtHostWorkspace(new TestRPCProtocol(), new NullLogService(), new Counter()), createConfigurationData(contents)); } function createConfigurationData(contents: any): IConfigurationInitData { @@ -56,7 +56,7 @@ suite('ExtHostConfiguration', function () { assert.equal(extHostConfig.getConfiguration('search.exclude')['**/node_modules'], true); assert.equal(extHostConfig.getConfiguration('search.exclude').get('**/node_modules'), true); - assert.equal(extHostConfig.getConfiguration('search').get('exclude')['**/node_modules'], true); + assert.equal(extHostConfig.getConfiguration('search').get('exclude')!['**/node_modules'], true); assert.equal(extHostConfig.getConfiguration('search.exclude').has('**/node_modules'), true); assert.equal(extHostConfig.getConfiguration('search').has('exclude.**/node_modules'), true); @@ -111,38 +111,38 @@ suite('ExtHostConfiguration', function () { }); let testObject = all.getConfiguration(); - let actual = testObject.get('farboo'); + let actual = testObject.get('farboo')!; actual['nested']['config1'] = 41; assert.equal(41, actual['nested']['config1']); actual['farboo1'] = 'newValue'; assert.equal('newValue', actual['farboo1']); testObject = all.getConfiguration(); - actual = testObject.get('farboo'); + actual = testObject.get('farboo')!; assert.equal(actual['nested']['config1'], 42); assert.equal(actual['farboo1'], undefined); testObject = all.getConfiguration(); - actual = testObject.get('farboo'); + actual = testObject.get('farboo')!; assert.equal(actual['config0'], true); actual['config0'] = false; assert.equal(actual['config0'], false); testObject = all.getConfiguration(); - actual = testObject.get('farboo'); + actual = testObject.get('farboo')!; assert.equal(actual['config0'], true); testObject = all.getConfiguration(); - actual = testObject.inspect('farboo'); + actual = testObject.inspect('farboo')!; actual['value'] = 'effectiveValue'; assert.equal('effectiveValue', actual['value']); testObject = all.getConfiguration('workbench'); - actual = testObject.get('colorCustomizations'); + actual = testObject.get('colorCustomizations')!; delete actual['statusBar.foreground']; assert.equal(actual['statusBar.foreground'], undefined); testObject = all.getConfiguration('workbench'); - actual = testObject.get('colorCustomizations'); + actual = testObject.get('colorCustomizations')!; assert.equal(actual['statusBar.foreground'], 'somevalue'); }); @@ -166,8 +166,8 @@ suite('ExtHostConfiguration', function () { } }); - let testObject = all.getConfiguration(); - let actual = testObject.get('farboo'); + const testObject = all.getConfiguration(); + let actual: any = testObject.get('farboo'); assert.deepEqual(JSON.stringify({ 'config0': true, 'nested': { @@ -179,7 +179,7 @@ suite('ExtHostConfiguration', function () { assert.deepEqual(undefined, JSON.stringify(testObject.get('unknownkey'))); - actual = testObject.get('farboo'); + actual = testObject.get('farboo')!; actual['config0'] = false; assert.deepEqual(JSON.stringify({ 'config0': false, @@ -190,7 +190,7 @@ suite('ExtHostConfiguration', function () { 'config4': '' }), JSON.stringify(actual)); - actual = testObject.get('workbench')['colorCustomizations']; + actual = testObject.get('workbench')!['colorCustomizations']!; actual['statusBar.background'] = 'anothervalue'; assert.deepEqual(JSON.stringify({ 'statusBar.foreground': 'somevalue', @@ -241,7 +241,7 @@ suite('ExtHostConfiguration', function () { } }); - let testObject = all.getConfiguration(); + let testObject: any = all.getConfiguration(); try { testObject['get'] = null; @@ -265,7 +265,7 @@ suite('ExtHostConfiguration', function () { test('inspect in no workspace context', function () { const testObject = new ExtHostConfigProvider( new class extends mock() { }, - new ExtHostWorkspace(new TestRPCProtocol(), null, new NullLogService(), new Counter()), + new ExtHostWorkspace(new TestRPCProtocol(), new NullLogService(), new Counter()), { defaults: new ConfigurationModel({ 'editor': { @@ -284,13 +284,13 @@ suite('ExtHostConfiguration', function () { } ); - let actual = testObject.getConfiguration().inspect('editor.wordWrap'); + let actual = testObject.getConfiguration().inspect('editor.wordWrap')!; assert.equal(actual.defaultValue, 'off'); assert.equal(actual.globalValue, 'on'); assert.equal(actual.workspaceValue, undefined); assert.equal(actual.workspaceFolderValue, undefined); - actual = testObject.getConfiguration('editor').inspect('wordWrap'); + actual = testObject.getConfiguration('editor').inspect('wordWrap')!; assert.equal(actual.defaultValue, 'off'); assert.equal(actual.globalValue, 'on'); assert.equal(actual.workspaceValue, undefined); @@ -306,13 +306,15 @@ suite('ExtHostConfiguration', function () { } }, ['editor.wordWrap']); folders[workspaceUri.toString()] = workspace; + const extHostWorkspace = new ExtHostWorkspace(new TestRPCProtocol(), new NullLogService(), new Counter()); + extHostWorkspace.$initializeWorkspace({ + 'id': 'foo', + 'folders': [aWorkspaceFolder(URI.file('foo'), 0)], + 'name': 'foo' + }); const testObject = new ExtHostConfigProvider( new class extends mock() { }, - new ExtHostWorkspace(new TestRPCProtocol(), { - 'id': 'foo', - 'folders': [aWorkspaceFolder(URI.file('foo'), 0)], - 'name': 'foo' - }, new NullLogService(), new Counter()), + extHostWorkspace, { defaults: new ConfigurationModel({ 'editor': { @@ -331,25 +333,25 @@ suite('ExtHostConfiguration', function () { } ); - let actual1 = testObject.getConfiguration().inspect('editor.wordWrap'); + let actual1 = testObject.getConfiguration().inspect('editor.wordWrap')!; assert.equal(actual1.defaultValue, 'off'); assert.equal(actual1.globalValue, 'on'); assert.equal(actual1.workspaceValue, 'bounded'); assert.equal(actual1.workspaceFolderValue, undefined); - actual1 = testObject.getConfiguration('editor').inspect('wordWrap'); + actual1 = testObject.getConfiguration('editor').inspect('wordWrap')!; assert.equal(actual1.defaultValue, 'off'); assert.equal(actual1.globalValue, 'on'); assert.equal(actual1.workspaceValue, 'bounded'); assert.equal(actual1.workspaceFolderValue, undefined); - let actual2 = testObject.getConfiguration(null, workspaceUri).inspect('editor.wordWrap'); + let actual2 = testObject.getConfiguration(undefined, workspaceUri).inspect('editor.wordWrap')!; assert.equal(actual2.defaultValue, 'off'); assert.equal(actual2.globalValue, 'on'); assert.equal(actual2.workspaceValue, 'bounded'); assert.equal(actual2.workspaceFolderValue, 'bounded'); - actual2 = testObject.getConfiguration('editor', workspaceUri).inspect('wordWrap'); + actual2 = testObject.getConfiguration('editor', workspaceUri).inspect('wordWrap')!; assert.equal(actual2.defaultValue, 'off'); assert.equal(actual2.globalValue, 'on'); assert.equal(actual2.workspaceValue, 'bounded'); @@ -380,13 +382,15 @@ suite('ExtHostConfiguration', function () { }, ['editor.wordWrap']); folders[thirdRoot.toString()] = new ConfigurationModel({}, []); + const extHostWorkspace = new ExtHostWorkspace(new TestRPCProtocol(), new NullLogService(), new Counter()); + extHostWorkspace.$initializeWorkspace({ + 'id': 'foo', + 'folders': [aWorkspaceFolder(firstRoot, 0), aWorkspaceFolder(secondRoot, 1)], + 'name': 'foo' + }); const testObject = new ExtHostConfigProvider( new class extends mock() { }, - new ExtHostWorkspace(new TestRPCProtocol(), { - 'id': 'foo', - 'folders': [aWorkspaceFolder(firstRoot, 0), aWorkspaceFolder(secondRoot, 1)], - 'name': 'foo' - }, new NullLogService(), new Counter()), + extHostWorkspace, { defaults: new ConfigurationModel({ 'editor': { @@ -406,62 +410,62 @@ suite('ExtHostConfiguration', function () { } ); - let actual1 = testObject.getConfiguration().inspect('editor.wordWrap'); + let actual1 = testObject.getConfiguration().inspect('editor.wordWrap')!; assert.equal(actual1.defaultValue, 'off'); assert.equal(actual1.globalValue, 'on'); assert.equal(actual1.workspaceValue, 'bounded'); assert.equal(actual1.workspaceFolderValue, undefined); - actual1 = testObject.getConfiguration('editor').inspect('wordWrap'); + actual1 = testObject.getConfiguration('editor').inspect('wordWrap')!; assert.equal(actual1.defaultValue, 'off'); assert.equal(actual1.globalValue, 'on'); assert.equal(actual1.workspaceValue, 'bounded'); assert.equal(actual1.workspaceFolderValue, undefined); - actual1 = testObject.getConfiguration('editor').inspect('lineNumbers'); + actual1 = testObject.getConfiguration('editor').inspect('lineNumbers')!; assert.equal(actual1.defaultValue, 'on'); assert.equal(actual1.globalValue, undefined); assert.equal(actual1.workspaceValue, undefined); assert.equal(actual1.workspaceFolderValue, undefined); - let actual2 = testObject.getConfiguration(null, firstRoot).inspect('editor.wordWrap'); + let actual2 = testObject.getConfiguration(undefined, firstRoot).inspect('editor.wordWrap')!; assert.equal(actual2.defaultValue, 'off'); assert.equal(actual2.globalValue, 'on'); assert.equal(actual2.workspaceValue, 'bounded'); assert.equal(actual2.workspaceFolderValue, 'off'); - actual2 = testObject.getConfiguration('editor', firstRoot).inspect('wordWrap'); + actual2 = testObject.getConfiguration('editor', firstRoot).inspect('wordWrap')!; assert.equal(actual2.defaultValue, 'off'); assert.equal(actual2.globalValue, 'on'); assert.equal(actual2.workspaceValue, 'bounded'); assert.equal(actual2.workspaceFolderValue, 'off'); - actual2 = testObject.getConfiguration('editor', firstRoot).inspect('lineNumbers'); + actual2 = testObject.getConfiguration('editor', firstRoot).inspect('lineNumbers')!; assert.equal(actual2.defaultValue, 'on'); assert.equal(actual2.globalValue, undefined); assert.equal(actual2.workspaceValue, undefined); assert.equal(actual2.workspaceFolderValue, 'relative'); - actual2 = testObject.getConfiguration(null, secondRoot).inspect('editor.wordWrap'); + actual2 = testObject.getConfiguration(undefined, secondRoot).inspect('editor.wordWrap')!; assert.equal(actual2.defaultValue, 'off'); assert.equal(actual2.globalValue, 'on'); assert.equal(actual2.workspaceValue, 'bounded'); assert.equal(actual2.workspaceFolderValue, 'on'); - actual2 = testObject.getConfiguration('editor', secondRoot).inspect('wordWrap'); + actual2 = testObject.getConfiguration('editor', secondRoot).inspect('wordWrap')!; assert.equal(actual2.defaultValue, 'off'); assert.equal(actual2.globalValue, 'on'); assert.equal(actual2.workspaceValue, 'bounded'); assert.equal(actual2.workspaceFolderValue, 'on'); - actual2 = testObject.getConfiguration(null, thirdRoot).inspect('editor.wordWrap'); + actual2 = testObject.getConfiguration(undefined, thirdRoot).inspect('editor.wordWrap')!; assert.equal(actual2.defaultValue, 'off'); assert.equal(actual2.globalValue, 'on'); assert.equal(actual2.workspaceValue, 'bounded'); assert.ok(Object.keys(actual2).indexOf('workspaceFolderValue') !== -1); assert.equal(actual2.workspaceFolderValue, undefined); - actual2 = testObject.getConfiguration('editor', thirdRoot).inspect('wordWrap'); + actual2 = testObject.getConfiguration('editor', thirdRoot).inspect('wordWrap')!; assert.equal(actual2.defaultValue, 'off'); assert.equal(actual2.globalValue, 'on'); assert.equal(actual2.workspaceValue, 'bounded'); @@ -589,13 +593,15 @@ suite('ExtHostConfiguration', function () { test('configuration change event', (done) => { const workspaceFolder = aWorkspaceFolder(URI.file('folder1'), 0); + const extHostWorkspace = new ExtHostWorkspace(new TestRPCProtocol(), new NullLogService(), new Counter()); + extHostWorkspace.$initializeWorkspace({ + 'id': 'foo', + 'folders': [workspaceFolder], + 'name': 'foo' + }); const testObject = new ExtHostConfigProvider( new class extends mock() { }, - new ExtHostWorkspace(new TestRPCProtocol(), { - 'id': 'foo', - 'folders': [workspaceFolder], - 'name': 'foo' - }, new NullLogService(), new Counter()), + extHostWorkspace, createConfigurationData({ 'farboo': { 'config': false, diff --git a/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts index e2bbd5dbb8..3187cdc555 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts @@ -188,7 +188,7 @@ suite('ExtHostDiagnostics', () => { lastEntries = undefined!; }); - test('don\'t send message when not making a change', function () { + test('do send message when not making a change', function () { let changeCount = 0; let eventCount = 0; @@ -209,7 +209,7 @@ suite('ExtHostDiagnostics', () => { assert.equal(eventCount, 1); collection.set(uri, [diag]); - assert.equal(changeCount, 1); + assert.equal(changeCount, 2); assert.equal(eventCount, 2); }); @@ -418,10 +418,10 @@ suite('ExtHostDiagnostics', () => { assert.equal(callCount, 1); collection.set(URI.parse('test:me'), array); - assert.equal(callCount, 1); // equal array + assert.equal(callCount, 2); // equal array array.push(diag2); collection.set(URI.parse('test:me'), array); - assert.equal(callCount, 2); // same but un-equal array + assert.equal(callCount, 3); // same but un-equal array }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts index 9469ba608d..73e72f4489 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts @@ -13,11 +13,10 @@ import { SingleProxyRPCProtocol } from './testRPCProtocol'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import * as vscode from 'vscode'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; import { NullLogService } from 'vs/platform/log/common/log'; import { isResourceTextEdit, ResourceTextEdit } from 'vs/editor/common/modes'; import { timeout } from 'vs/base/common/async'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; suite('ExtHostDocumentSaveParticipant', () => { @@ -341,7 +340,7 @@ suite('ExtHostDocumentSaveParticipant', () => { rangeLength: undefined!, }], eol: undefined!, - versionId: documents.getDocumentData(uri).version + 1 + versionId: documents.getDocumentData(uri)!.version + 1 }, true); } } @@ -350,7 +349,7 @@ suite('ExtHostDocumentSaveParticipant', () => { } }); - const document = documents.getDocumentData(resource).document; + const document = documents.getDocument(resource); let sub1 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { // the document state we started with diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index 0ef87f513b..c0ca6dab0e 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -29,11 +29,11 @@ import { getHover } from 'vs/editor/contrib/hover/getHover'; import { getOccurrencesAtPosition } from 'vs/editor/contrib/wordHighlighter/wordHighlighter'; import { provideReferences } from 'vs/editor/contrib/referenceSearch/referenceSearch'; import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; -import { getWorkspaceSymbols } from 'vs/workbench/parts/search/common/search'; +import { getWorkspaceSymbols } from 'vs/workbench/contrib/search/common/search'; import { rename } from 'vs/editor/contrib/rename/rename'; import { provideSignatureHelp } from 'vs/editor/contrib/parameterHints/provideSignatureHelp'; -import { provideSuggestionItems } from 'vs/editor/contrib/suggest/suggest'; -import { getDocumentFormattingEdits, getDocumentRangeFormattingEdits, getOnTypeFormattingEdits } from 'vs/editor/contrib/format/format'; +import { provideSuggestionItems, CompletionOptions } from 'vs/editor/contrib/suggest/suggest'; +import { getDocumentFormattingEdits, getDocumentRangeFormattingEdits, getOnTypeFormattingEdits, FormatMode } from 'vs/editor/contrib/format/format'; import { getLinks } from 'vs/editor/contrib/links/getLinks'; import { MainContext, ExtHostContext } from 'vs/workbench/api/node/extHost.protocol'; import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics'; @@ -46,6 +46,10 @@ import { getColors } from 'vs/editor/contrib/colorPicker/color'; import { CancellationToken } from 'vs/base/common/cancellation'; import { nullExtensionDescription as defaultExtension } from 'vs/workbench/services/extensions/common/extensions'; import { provideSelectionRanges } from 'vs/editor/contrib/smartSelect/smartSelect'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { mock } from 'vs/workbench/test/electron-browser/api/mock'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { dispose } from 'vs/base/common/lifecycle'; const defaultSelector = { scheme: 'far' }; const model: ITextModel = EditorModel.createFromString( @@ -64,6 +68,8 @@ let disposables: vscode.Disposable[] = []; let rpcProtocol: TestRPCProtocol; let originalErrorHandler: (e: any) => any; + + suite('ExtHostLanguageFeatures', function () { suiteSetup(() => { @@ -77,9 +83,8 @@ suite('ExtHostLanguageFeatures', function () { instantiationService.stub(IMarkerService, MarkerService); instantiationService.stub(IHeapService, { _serviceBrand: undefined, - trackRecursive(args: any) { + trackObject(_obj: any) { // nothing - return args; } }); inst = instantiationService; @@ -123,10 +128,8 @@ suite('ExtHostLanguageFeatures', function () { mainThread.dispose(); }); - teardown(function () { - while (disposables.length) { - disposables.pop().dispose(); - } + teardown(() => { + disposables = dispose(disposables); return rpcProtocol.sync(); }); @@ -215,10 +218,10 @@ suite('ExtHostLanguageFeatures', function () { await rpcProtocol.sync(); const value = await getCodeLensData(model, CancellationToken.None); assert.equal(value.length, 1); - let data = value[0]; - const symbol = await Promise.resolve(data.provider.resolveCodeLens(model, data.symbol, CancellationToken.None)); - assert.equal(symbol.command.id, 'id'); - assert.equal(symbol.command.title, 'Title'); + const data = value[0]; + const symbol = await Promise.resolve(data.provider.resolveCodeLens!(model, data.symbol, CancellationToken.None)); + assert.equal(symbol!.command!.id, 'id'); + assert.equal(symbol!.command!.title, 'Title'); }); test('CodeLens, missing command', async () => { @@ -233,9 +236,9 @@ suite('ExtHostLanguageFeatures', function () { const value = await getCodeLensData(model, CancellationToken.None); assert.equal(value.length, 1); let data = value[0]; - const symbol = await Promise.resolve(data.provider.resolveCodeLens(model, data.symbol, CancellationToken.None)); - assert.equal(symbol.command.id, 'missing'); - assert.equal(symbol.command.title, '!!MISSING: command!!'); + const symbol = await Promise.resolve(data.provider.resolveCodeLens!(model, data.symbol, CancellationToken.None)); + assert.equal(symbol!.command!.id, 'missing'); + assert.equal(symbol!.command!.title, '!!MISSING: command!!'); }); // --- definition @@ -457,9 +460,9 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - let value = await getOccurrencesAtPosition(model, new EditorPosition(1, 2), CancellationToken.None); + const value = (await getOccurrencesAtPosition(model, new EditorPosition(1, 2), CancellationToken.None))!; assert.equal(value.length, 1); - let [entry] = value; + const [entry] = value; assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 5 }); assert.equal(entry.kind, modes.DocumentHighlightKind.Text); }); @@ -478,9 +481,9 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - let value = await getOccurrencesAtPosition(model, new EditorPosition(1, 2), CancellationToken.None); + const value = (await getOccurrencesAtPosition(model, new EditorPosition(1, 2), CancellationToken.None))!; assert.equal(value.length, 1); - let [entry] = value; + const [entry] = value; assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 5 }); assert.equal(entry.kind, modes.DocumentHighlightKind.Text); }); @@ -499,9 +502,9 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - let value = await getOccurrencesAtPosition(model, new EditorPosition(1, 2), CancellationToken.None); + const value = (await getOccurrencesAtPosition(model, new EditorPosition(1, 2), CancellationToken.None))!; assert.equal(value.length, 1); - let [entry] = value; + const [entry] = value; assert.deepEqual(entry.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 3 }); assert.equal(entry.kind, modes.DocumentHighlightKind.Text); }); @@ -522,7 +525,7 @@ suite('ExtHostLanguageFeatures', function () { await rpcProtocol.sync(); const value = await getOccurrencesAtPosition(model, new EditorPosition(1, 2), CancellationToken.None); - assert.equal(value.length, 1); + assert.equal(value!.length, 1); }); // --- references @@ -597,13 +600,13 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - let value = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); - assert.equal(value.length, 2); - const [first, second] = value; + const { actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + assert.equal(actions.length, 2); + const [first, second] = actions; assert.equal(first.title, 'Testing1'); - assert.equal(first.command.id, 'test1'); + assert.equal(first.command!.id, 'test1'); assert.equal(second.title, 'Testing2'); - assert.equal(second.command.id, 'test2'); + assert.equal(second.command!.id, 'test2'); }); test('Quick Fix, code action data conversion', async () => { @@ -621,12 +624,12 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - let value = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); - assert.equal(value.length, 1); - const [first] = value; + const { actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + assert.equal(actions.length, 1); + const [first] = actions; assert.equal(first.title, 'Testing1'); - assert.equal(first.command.title, 'Testing1Command'); - assert.equal(first.command.id, 'test1'); + assert.equal(first.command!.title, 'Testing1Command'); + assert.equal(first.command!.id, 'test1'); assert.equal(first.kind, 'test.scope'); }); @@ -644,8 +647,8 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); - assert.equal(value.length, 1); + const { actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + assert.equal(actions.length, 1); }); test('Quick Fix, evil provider', async () => { @@ -662,8 +665,8 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - const value = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); - assert.equal(value.length, 1); + const { actions } = await getCodeActions(model, model.getFullModelRange(), { type: 'manual' }, CancellationToken.None); + assert.equal(actions.length, 1); }); // --- navigate types @@ -825,7 +828,7 @@ suite('ExtHostLanguageFeatures', function () { }, [])); await rpcProtocol.sync(); - const value = await provideSuggestionItems(model, new EditorPosition(1, 1), 'none'); + const value = await provideSuggestionItems(model, new EditorPosition(1, 1), new CompletionOptions(undefined, new Set().add(modes.CompletionItemKind.Snippet))); assert.equal(value.length, 1); assert.equal(value[0].completion.insertText, 'testing2'); }); @@ -845,7 +848,7 @@ suite('ExtHostLanguageFeatures', function () { }, [])); await rpcProtocol.sync(); - const value = await provideSuggestionItems(model, new EditorPosition(1, 1), 'none'); + const value = await provideSuggestionItems(model, new EditorPosition(1, 1), new CompletionOptions(undefined, new Set().add(modes.CompletionItemKind.Snippet))); assert.equal(value.length, 1); assert.equal(value[0].completion.insertText, 'weak-selector'); }); @@ -865,7 +868,7 @@ suite('ExtHostLanguageFeatures', function () { }, [])); await rpcProtocol.sync(); - const value = await provideSuggestionItems(model, new EditorPosition(1, 1), 'none'); + const value = await provideSuggestionItems(model, new EditorPosition(1, 1), new CompletionOptions(undefined, new Set().add(modes.CompletionItemKind.Snippet))); assert.equal(value.length, 2); assert.equal(value[0].completion.insertText, 'strong-1'); // sort by label assert.equal(value[1].completion.insertText, 'strong-2'); @@ -887,7 +890,7 @@ suite('ExtHostLanguageFeatures', function () { await rpcProtocol.sync(); - const value = await provideSuggestionItems(model, new EditorPosition(1, 1), 'none'); + const value = await provideSuggestionItems(model, new EditorPosition(1, 1), new CompletionOptions(undefined, new Set().add(modes.CompletionItemKind.Snippet))); assert.equal(value[0].container.incomplete, undefined); }); @@ -900,13 +903,19 @@ suite('ExtHostLanguageFeatures', function () { }, [])); await rpcProtocol.sync(); - provideSuggestionItems(model, new EditorPosition(1, 1), 'none').then(value => { + provideSuggestionItems(model, new EditorPosition(1, 1), new CompletionOptions(undefined, new Set().add(modes.CompletionItemKind.Snippet))).then(value => { assert.equal(value[0].container.incomplete, true); }); }); // --- format + const NullWorkerService = new class extends mock() { + computeMoreMinimalEdits(resource: URI, edits: modes.TextEdit[] | null | undefined): Promise { + return Promise.resolve(edits); + } + }; + test('Format Doc, data conversion', async () => { disposables.push(extHost.registerDocumentFormattingEditProvider(defaultExtension, defaultSelector, new class implements vscode.DocumentFormattingEditProvider { provideDocumentFormattingEdits(): any { @@ -915,7 +924,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - let value = await getDocumentFormattingEdits(model, { insertSpaces: true, tabSize: 4 }, CancellationToken.None); + let value = (await getDocumentFormattingEdits(NullTelemetryService, NullWorkerService, model, { insertSpaces: true, tabSize: 4 }, FormatMode.Auto, CancellationToken.None))!; assert.equal(value.length, 2); let [first, second] = value; assert.equal(first.text, 'testing'); @@ -933,7 +942,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - return getDocumentFormattingEdits(model, { insertSpaces: true, tabSize: 4 }, CancellationToken.None); + return getDocumentFormattingEdits(NullTelemetryService, NullWorkerService, model, { insertSpaces: true, tabSize: 4 }, FormatMode.Auto, CancellationToken.None); }); test('Format Doc, order', async () => { @@ -957,7 +966,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - let value = await getDocumentFormattingEdits(model, { insertSpaces: true, tabSize: 4 }, CancellationToken.None); + let value = (await getDocumentFormattingEdits(NullTelemetryService, NullWorkerService, model, { insertSpaces: true, tabSize: 4 }, FormatMode.Auto, CancellationToken.None))!; assert.equal(value.length, 1); let [first] = value; assert.equal(first.text, 'testing'); @@ -972,9 +981,9 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - let value = await getDocumentRangeFormattingEdits(model, new EditorRange(1, 1, 1, 1), { insertSpaces: true, tabSize: 4 }, CancellationToken.None); + const value = (await getDocumentRangeFormattingEdits(NullTelemetryService, NullWorkerService, model, new EditorRange(1, 1, 1, 1), { insertSpaces: true, tabSize: 4 }, FormatMode.Auto, CancellationToken.None))!; assert.equal(value.length, 1); - let [first] = value; + const [first] = value; assert.equal(first.text, 'testing'); assert.deepEqual(first.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }); }); @@ -996,9 +1005,9 @@ suite('ExtHostLanguageFeatures', function () { } })); await rpcProtocol.sync(); - let value = await getDocumentRangeFormattingEdits(model, new EditorRange(1, 1, 1, 1), { insertSpaces: true, tabSize: 4 }, CancellationToken.None); + const value = (await getDocumentRangeFormattingEdits(NullTelemetryService, NullWorkerService, model, new EditorRange(1, 1, 1, 1), { insertSpaces: true, tabSize: 4 }, FormatMode.Auto, CancellationToken.None))!; assert.equal(value.length, 1); - let [first] = value; + const [first] = value; assert.equal(first.text, 'range2'); assert.equal(first.range.startLineNumber, 3); assert.equal(first.range.startColumn, 4); @@ -1014,7 +1023,7 @@ suite('ExtHostLanguageFeatures', function () { })); await rpcProtocol.sync(); - return getDocumentRangeFormattingEdits(model, new EditorRange(1, 1, 1, 1), { insertSpaces: true, tabSize: 4 }, CancellationToken.None); + return getDocumentRangeFormattingEdits(NullTelemetryService, NullWorkerService, model, new EditorRange(1, 1, 1, 1), { insertSpaces: true, tabSize: 4 }, FormatMode.Auto, CancellationToken.None); }); test('Format on Type, data conversion', async () => { @@ -1026,9 +1035,9 @@ suite('ExtHostLanguageFeatures', function () { }, [';'])); await rpcProtocol.sync(); - let value = await getOnTypeFormattingEdits(model, new EditorPosition(1, 1), ';', { insertSpaces: true, tabSize: 2 }); + const value = (await getOnTypeFormattingEdits(NullTelemetryService, NullWorkerService, model, new EditorPosition(1, 1), ';', { insertSpaces: true, tabSize: 2 }))!; assert.equal(value.length, 1); - let [first] = value; + const [first] = value; assert.equal(first.text, ';'); assert.deepEqual(first.range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }); }); @@ -1096,16 +1105,29 @@ suite('ExtHostLanguageFeatures', function () { disposables.push(extHost.registerSelectionRangeProvider(defaultExtension, defaultSelector, new class implements vscode.SelectionRangeProvider { provideSelectionRanges() { return [ - new types.SelectionRange(new types.Range(0, 10, 0, 18), types.SelectionRangeKind.Empty), - new types.SelectionRange(new types.Range(0, 2, 0, 20), types.SelectionRangeKind.Empty) + new types.SelectionRange(new types.Range(0, 10, 0, 18), new types.SelectionRange(new types.Range(0, 2, 0, 20))), ]; } })); await rpcProtocol.sync(); - provideSelectionRanges(model, new Position(1, 17), CancellationToken.None).then(ranges => { - assert.ok(ranges.length >= 2); + provideSelectionRanges(model, [new Position(1, 17)], CancellationToken.None).then(ranges => { + assert.equal(ranges.length, 1); + assert.ok(ranges[0].length >= 2); }); }); + + test('Selection Ranges, bad data', async () => { + + try { + let _a = new types.SelectionRange(new types.Range(0, 10, 0, 18), + new types.SelectionRange(new types.Range(0, 11, 0, 18)) + ); + assert.ok(false, String(_a)); + } catch (err) { + assert.ok(true); + } + + }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts index 89891f7109..72b1838fcc 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -10,7 +10,7 @@ import { dispose } from 'vs/base/common/lifecycle'; import { joinPath } from 'vs/base/common/resources'; import { URI, UriComponents } from 'vs/base/common/uri'; import * as extfs from 'vs/base/node/extfs'; -import { IFileMatch, IFileQuery, IPatternInfo, IRawFileMatch2, ISearchCompleteStats, ISearchQuery, ITextQuery, QueryType, resultIsMatch } from 'vs/platform/search/common/search'; +import { IFileMatch, IFileQuery, IPatternInfo, IRawFileMatch2, ISearchCompleteStats, ISearchQuery, ITextQuery, QueryType, resultIsMatch } from 'vs/workbench/services/search/common/search'; import { MainContext, MainThreadSearchShape } from 'vs/workbench/api/node/extHost.protocol'; import { ExtHostSearch } from 'vs/workbench/api/node/extHostSearch'; import { Range } from 'vs/workbench/api/node/extHostTypes'; @@ -33,10 +33,6 @@ class MockMainThreadSearch implements MainThreadSearchShape { this.lastHandle = handle; } - $registerFileIndexProvider(handle: number, scheme: string): void { - this.lastHandle = handle; - } - $registerTextSearchProvider(handle: number, scheme: string): void { this.lastHandle = handle; } diff --git a/src/vs/workbench/test/electron-browser/api/extHostTextEditor.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTextEditor.test.ts index 1800d6abed..a3a9d8555a 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTextEditor.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTextEditor.test.ts @@ -19,7 +19,7 @@ suite('ExtHostTextEditor', () => { ], '\n', 'text', 1, false); setup(() => { - editor = new ExtHostTextEditor(null!, 'fake', doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4 }, [], 1); + editor = new ExtHostTextEditor(null!, 'fake', doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1); }); test('disposed editor', () => { @@ -45,7 +45,7 @@ suite('ExtHostTextEditor', () => { applyCount += 1; return Promise.resolve(true); } - }, 'edt1', doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4 }, [], 1); + }, 'edt1', doc, [], { cursorStyle: 0, insertSpaces: true, lineNumbers: 1, tabSize: 4, indentSize: 4 }, [], 1); await editor.edit(edit => { }); assert.equal(applyCount, 0); @@ -88,6 +88,7 @@ suite('ExtHostTextEditorOptions', () => { }; opts = new ExtHostTextEditorOptions(mockProxy, '1', { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -102,6 +103,7 @@ suite('ExtHostTextEditorOptions', () => { function assertState(opts: ExtHostTextEditorOptions, expected: IResolvedTextEditorConfiguration): void { let actual = { tabSize: opts.tabSize, + indentSize: opts.indentSize, insertSpaces: opts.insertSpaces, cursorStyle: opts.cursorStyle, lineNumbers: opts.lineNumbers @@ -113,6 +115,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = 4; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -124,6 +127,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = 1; assertState(opts, { tabSize: 1, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -135,6 +139,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = 2.3; assertState(opts, { tabSize: 2, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -146,6 +151,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = '2'; assertState(opts, { tabSize: 2, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -157,6 +163,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = 'auto'; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -168,6 +175,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = null!; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -179,6 +187,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = -5; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -190,6 +199,7 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = 'hello'; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -201,6 +211,127 @@ suite('ExtHostTextEditorOptions', () => { opts.tabSize = '-17'; assertState(opts, { tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, []); + }); + + test('can set indentSize to the same value', () => { + opts.indentSize = 4; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, []); + }); + + test('can change indentSize to positive integer', () => { + opts.indentSize = 1; + assertState(opts, { + tabSize: 4, + indentSize: 1, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, [{ indentSize: 1 }]); + }); + + test('can change indentSize to positive float', () => { + opts.indentSize = 2.3; + assertState(opts, { + tabSize: 4, + indentSize: 2, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, [{ indentSize: 2 }]); + }); + + test('can change indentSize to a string number', () => { + opts.indentSize = '2'; + assertState(opts, { + tabSize: 4, + indentSize: 2, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, [{ indentSize: 2 }]); + }); + + test('indentSize can request to use tabSize', () => { + opts.indentSize = 'tabSize'; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, [{ indentSize: 'tabSize' }]); + }); + + test('indentSize cannot request indentation detection', () => { + opts.indentSize = 'auto'; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, []); + }); + + test('ignores invalid indentSize 1', () => { + opts.indentSize = null!; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, []); + }); + + test('ignores invalid indentSize 2', () => { + opts.indentSize = -5; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, []); + }); + + test('ignores invalid indentSize 3', () => { + opts.indentSize = 'hello'; + assertState(opts, { + tabSize: 4, + indentSize: 4, + insertSpaces: false, + cursorStyle: TextEditorCursorStyle.Line, + lineNumbers: TextEditorLineNumbersStyle.On + }); + assert.deepEqual(calls, []); + }); + + test('ignores invalid indentSize 4', () => { + opts.indentSize = '-17'; + assertState(opts, { + tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -212,6 +343,7 @@ suite('ExtHostTextEditorOptions', () => { opts.insertSpaces = false; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -223,6 +355,7 @@ suite('ExtHostTextEditorOptions', () => { opts.insertSpaces = true; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: true, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -234,6 +367,7 @@ suite('ExtHostTextEditorOptions', () => { opts.insertSpaces = 'false'; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -245,6 +379,7 @@ suite('ExtHostTextEditorOptions', () => { opts.insertSpaces = 'hello'; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: true, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -256,6 +391,7 @@ suite('ExtHostTextEditorOptions', () => { opts.insertSpaces = 'auto'; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -267,6 +403,7 @@ suite('ExtHostTextEditorOptions', () => { opts.cursorStyle = TextEditorCursorStyle.Line; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -278,6 +415,7 @@ suite('ExtHostTextEditorOptions', () => { opts.cursorStyle = TextEditorCursorStyle.Block; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Block, lineNumbers: TextEditorLineNumbersStyle.On @@ -289,6 +427,7 @@ suite('ExtHostTextEditorOptions', () => { opts.lineNumbers = TextEditorLineNumbersStyle.On; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -300,6 +439,7 @@ suite('ExtHostTextEditorOptions', () => { opts.lineNumbers = TextEditorLineNumbersStyle.Off; assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.Off @@ -316,6 +456,7 @@ suite('ExtHostTextEditorOptions', () => { }); assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -330,6 +471,7 @@ suite('ExtHostTextEditorOptions', () => { }); assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: true, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -344,6 +486,7 @@ suite('ExtHostTextEditorOptions', () => { }); assertState(opts, { tabSize: 3, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Line, lineNumbers: TextEditorLineNumbersStyle.On @@ -358,6 +501,7 @@ suite('ExtHostTextEditorOptions', () => { }); assertState(opts, { tabSize: 4, + indentSize: 4, insertSpaces: false, cursorStyle: TextEditorCursorStyle.Block, lineNumbers: TextEditorLineNumbersStyle.Relative diff --git a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts index a79b9d7d0c..4c0b26683e 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts @@ -18,7 +18,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { TreeItemCollapsibleState, ITreeItem } from 'vs/workbench/common/views'; import { NullLogService } from 'vs/platform/log/common/log'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; suite('ExtHostTreeView', function () { @@ -29,12 +29,12 @@ suite('ExtHostTreeView', function () { $registerTreeViewDataProvider(treeViewId: string): void { } - $refresh(viewId: string, itemsToRefresh?: { [treeItemHandle: string]: ITreeItem }): Promise { + $refresh(viewId: string, itemsToRefresh: { [treeItemHandle: string]: ITreeItem }): Promise { return Promise.resolve(null).then(() => this.onRefresh.fire(itemsToRefresh)); } $reveal(): Promise { - return null; + return Promise.resolve(); } } @@ -625,7 +625,7 @@ suite('ExtHostTreeView', function () { getTreeItem: (element: { key: string }): TreeItem => { return getTreeItem(element.key); }, - getParent: ({ key }: { key: string }): { key: string } => { + getParent: ({ key }: { key: string }): { key: string } | undefined => { const parentKey = key.substring(0, key.length - 1); return parentKey ? new Key(parentKey) : undefined; }, @@ -672,7 +672,7 @@ suite('ExtHostTreeView', function () { return parent; } - function getChildren(key: string): string[] { + function getChildren(key: string | undefined): string[] { if (!key) { return Object.keys(tree); } diff --git a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts index f0b31bf251..4b95db5c91 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts @@ -4,17 +4,25 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { URI } from 'vs/base/common/uri'; -import { basename } from 'path'; -import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; -import { TestRPCProtocol } from './testRPCProtocol'; -import { normalize } from 'vs/base/common/paths'; -import { IWorkspaceFolderData } from 'vs/platform/workspace/common/workspace'; -import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; -import { NullLogService } from 'vs/platform/log/common/log'; -import { IMainContext } from 'vs/workbench/api/node/extHost.protocol'; +import { CancellationToken } from 'vs/base/common/cancellation'; import { Counter } from 'vs/base/common/numbers'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; +import { basename } from 'vs/base/common/path'; +import { URI, UriComponents } from 'vs/base/common/uri'; +import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ILogService, NullLogService } from 'vs/platform/log/common/log'; +import { IWorkspaceFolderData } from 'vs/platform/workspace/common/workspace'; +import { MainThreadWorkspace } from 'vs/workbench/api/electron-browser/mainThreadWorkspace'; +import { IMainContext, IWorkspaceData, MainContext } from 'vs/workbench/api/node/extHost.protocol'; +import { RelativePattern } from 'vs/workbench/api/node/extHostTypes'; +import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; +import { mock } from 'vs/workbench/test/electron-browser/api/mock'; +import { TestRPCProtocol } from './testRPCProtocol'; + +function createExtHostWorkspace(mainContext: IMainContext, data: IWorkspaceData, logService: ILogService, requestIdProvider: Counter): ExtHostWorkspace { + const result = new ExtHostWorkspace(mainContext, logService, requestIdProvider); + result.$initializeWorkspace(data); + return result; +} suite('ExtHostWorkspace', function () { @@ -32,16 +40,12 @@ suite('ExtHostWorkspace', function () { function assertAsRelativePath(workspace: ExtHostWorkspace, input: string, expected: string, includeWorkspace?: boolean) { const actual = workspace.getRelativePath(input, includeWorkspace); - if (actual === expected) { - assert.ok(true); - } else { - assert.equal(actual, normalize(expected, true)); - } + assert.equal(actual, expected); } test('asRelativePath', () => { - const ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/Applications/NewsWoWBot'), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/Applications/NewsWoWBot'), 0)], name: 'Test' }, new NullLogService(), new Counter()); assertAsRelativePath(ws, '/Coding/Applications/NewsWoWBot/bernd/das/brot', 'bernd/das/brot'); assertAsRelativePath(ws, '/Apps/DartPubCache/hosted/pub.dartlang.org/convert-2.0.1/lib/src/hex.dart', @@ -55,29 +59,29 @@ suite('ExtHostWorkspace', function () { test('asRelativePath, same paths, #11402', function () { const root = '/home/aeschli/workspaces/samples/docker'; const input = '/home/aeschli/workspaces/samples/docker'; - const ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); - assertAsRelativePath(ws, (input), input); + assertAsRelativePath(ws, input, input); const input2 = '/home/aeschli/workspaces/samples/docker/a.file'; - assertAsRelativePath(ws, (input2), 'a.file'); + assertAsRelativePath(ws, input2, 'a.file'); }); test('asRelativePath, no workspace', function () { - const ws = new ExtHostWorkspace(new TestRPCProtocol(), null!, new NullLogService(), new Counter()); - assertAsRelativePath(ws, (''), ''); - assertAsRelativePath(ws, ('/foo/bar'), '/foo/bar'); + const ws = createExtHostWorkspace(new TestRPCProtocol(), null!, new NullLogService(), new Counter()); + assertAsRelativePath(ws, '', ''); + assertAsRelativePath(ws, '/foo/bar', '/foo/bar'); }); test('asRelativePath, multiple folders', function () { - const ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService(), new Counter()); assertAsRelativePath(ws, '/Coding/One/file.txt', 'One/file.txt'); assertAsRelativePath(ws, '/Coding/Two/files/out.txt', 'Two/files/out.txt'); assertAsRelativePath(ws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt'); }); test('slightly inconsistent behaviour of asRelativePath and getWorkspaceFolder, #31553', function () { - const mrws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService(), new Counter()); + const mrws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService(), new Counter()); assertAsRelativePath(mrws, '/Coding/One/file.txt', 'One/file.txt'); assertAsRelativePath(mrws, '/Coding/One/file.txt', 'One/file.txt', true); @@ -89,7 +93,7 @@ suite('ExtHostWorkspace', function () { assertAsRelativePath(mrws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt', true); assertAsRelativePath(mrws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt', false); - const srws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0)], name: 'Test' }, new NullLogService(), new Counter()); + const srws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0)], name: 'Test' }, new NullLogService(), new Counter()); assertAsRelativePath(srws, '/Coding/One/file.txt', 'file.txt'); assertAsRelativePath(srws, '/Coding/One/file.txt', 'file.txt', false); assertAsRelativePath(srws, '/Coding/One/file.txt', 'One/file.txt', true); @@ -99,26 +103,26 @@ suite('ExtHostWorkspace', function () { }); test('getPath, legacy', function () { - let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); assert.equal(ws.getPath(), undefined); - ws = new ExtHostWorkspace(new TestRPCProtocol(), null!, new NullLogService(), new Counter()); + ws = createExtHostWorkspace(new TestRPCProtocol(), null!, new NullLogService(), new Counter()); assert.equal(ws.getPath(), undefined); - ws = new ExtHostWorkspace(new TestRPCProtocol(), undefined!, new NullLogService(), new Counter()); + ws = createExtHostWorkspace(new TestRPCProtocol(), undefined!, new NullLogService(), new Counter()); assert.equal(ws.getPath(), undefined); - ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('Folder'), 0), aWorkspaceFolderData(URI.file('Another/Folder'), 1)] }, new NullLogService(), new Counter()); - assert.equal(ws.getPath().replace(/\\/g, '/'), '/Folder'); + ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('Folder'), 0), aWorkspaceFolderData(URI.file('Another/Folder'), 1)] }, new NullLogService(), new Counter()); + assert.equal(ws.getPath()!.replace(/\\/g, '/'), '/Folder'); - ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('/Folder'), 0)] }, new NullLogService(), new Counter()); - assert.equal(ws.getPath().replace(/\\/g, '/'), '/Folder'); + ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('/Folder'), 0)] }, new NullLogService(), new Counter()); + assert.equal(ws.getPath()!.replace(/\\/g, '/'), '/Folder'); }); test('WorkspaceFolder has name and index', function () { - const ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }, new NullLogService(), new Counter()); - const [one, two] = ws.getWorkspaceFolders(); + const [one, two] = ws.getWorkspaceFolders()!; assert.equal(one.name, 'One'); assert.equal(one.index, 0); @@ -127,7 +131,7 @@ suite('ExtHostWorkspace', function () { }); test('getContainingWorkspaceFolder', () => { - const ws = new ExtHostWorkspace(new TestRPCProtocol(), { + const ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [ @@ -140,42 +144,42 @@ suite('ExtHostWorkspace', function () { let folder = ws.getWorkspaceFolder(URI.file('/foo/bar')); assert.equal(folder, undefined); - folder = ws.getWorkspaceFolder(URI.file('/Coding/One/file/path.txt')); + folder = ws.getWorkspaceFolder(URI.file('/Coding/One/file/path.txt'))!; assert.equal(folder.name, 'One'); - folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/file/path.txt')); + folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/file/path.txt'))!; assert.equal(folder.name, 'Two'); - folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nest')); + folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nest'))!; assert.equal(folder.name, 'Two'); - folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested/file')); + folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested/file'))!; assert.equal(folder.name, 'Nested'); - folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested/f')); + folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested/f'))!; assert.equal(folder.name, 'Nested'); - folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested'), true); + folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested'), true)!; assert.equal(folder.name, 'Two'); - folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested/'), true); + folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested/'), true)!; assert.equal(folder.name, 'Two'); - folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested')); + folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested'))!; assert.equal(folder.name, 'Nested'); - folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested/')); + folder = ws.getWorkspaceFolder(URI.file('/Coding/Two/Nested/'))!; assert.equal(folder.name, 'Nested'); - folder = ws.getWorkspaceFolder(URI.file('/Coding/Two'), true); + folder = ws.getWorkspaceFolder(URI.file('/Coding/Two'), true)!; assert.equal(folder, undefined); - folder = ws.getWorkspaceFolder(URI.file('/Coding/Two'), false); + folder = ws.getWorkspaceFolder(URI.file('/Coding/Two'), false)!; assert.equal(folder.name, 'Two'); }); test('Multiroot change event should have a delta, #29641', function (done) { - let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); let finished = false; const finish = (error?: any) => { @@ -238,27 +242,27 @@ suite('ExtHostWorkspace', function () { }); test('Multiroot change keeps existing workspaces live', function () { - let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }, new NullLogService(), new Counter()); + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }, new NullLogService(), new Counter()); - let firstFolder = ws.getWorkspaceFolders()[0]; + let firstFolder = ws.getWorkspaceFolders()![0]; ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar2'), 0), aWorkspaceFolderData(URI.parse('foo:bar'), 1, 'renamed')] }); - assert.equal(ws.getWorkspaceFolders()[1], firstFolder); + assert.equal(ws.getWorkspaceFolders()![1], firstFolder); assert.equal(firstFolder.index, 1); assert.equal(firstFolder.name, 'renamed'); ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0), aWorkspaceFolderData(URI.parse('foo:bar2'), 1), aWorkspaceFolderData(URI.parse('foo:bar'), 2)] }); - assert.equal(ws.getWorkspaceFolders()[2], firstFolder); + assert.equal(ws.getWorkspaceFolders()![2], firstFolder); assert.equal(firstFolder.index, 2); ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0)] }); ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0), aWorkspaceFolderData(URI.parse('foo:bar'), 1)] }); - assert.notEqual(firstFolder, ws.workspace.folders[0]); + assert.notEqual(firstFolder, ws.workspace!.folders[0]); }); test('updateWorkspaceFolders - invalid arguments', function () { - let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, null!, null!)); assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 0, 0)); @@ -267,7 +271,7 @@ suite('ExtHostWorkspace', function () { assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, -1, 0)); assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, -1, -1)); - ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }, new NullLogService(), new Counter()); + ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }, new NullLogService(), new Counter()); assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 1, 1)); assert.equal(false, ws.updateWorkspaceFolders(extensionDescriptor, 0, 2)); @@ -289,17 +293,17 @@ suite('ExtHostWorkspace', function () { assertRegistered: undefined! }; - const ws = new ExtHostWorkspace(protocol, { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + const ws = createExtHostWorkspace(protocol, { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); // // Add one folder // assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 0, 0, asUpdateWorkspaceFolderData(URI.parse('foo:bar')))); - assert.equal(1, ws.workspace.folders.length); - assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar').toString()); + assert.equal(1, ws.workspace!.folders.length); + assert.equal(ws.workspace!.folders[0].uri.toString(), URI.parse('foo:bar').toString()); - const firstAddedFolder = ws.getWorkspaceFolders()[0]; + const firstAddedFolder = ws.getWorkspaceFolders()![0]; let gotEvent = false; let sub = ws.onDidChangeWorkspace(e => { @@ -316,20 +320,20 @@ suite('ExtHostWorkspace', function () { ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0)] }); // simulate acknowledgement from main side assert.equal(gotEvent, true); sub.dispose(); - assert.equal(ws.getWorkspaceFolders()[0], firstAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![0], firstAddedFolder); // verify object is still live // // Add two more folders // assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 1, 0, asUpdateWorkspaceFolderData(URI.parse('foo:bar1')), asUpdateWorkspaceFolderData(URI.parse('foo:bar2')))); - assert.equal(3, ws.workspace.folders.length); - assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar').toString()); - assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar1').toString()); - assert.equal(ws.workspace.folders[2].uri.toString(), URI.parse('foo:bar2').toString()); + assert.equal(3, ws.workspace!.folders.length); + assert.equal(ws.workspace!.folders[0].uri.toString(), URI.parse('foo:bar').toString()); + assert.equal(ws.workspace!.folders[1].uri.toString(), URI.parse('foo:bar1').toString()); + assert.equal(ws.workspace!.folders[2].uri.toString(), URI.parse('foo:bar2').toString()); - const secondAddedFolder = ws.getWorkspaceFolders()[1]; - const thirdAddedFolder = ws.getWorkspaceFolders()[2]; + const secondAddedFolder = ws.getWorkspaceFolders()![1]; + const thirdAddedFolder = ws.getWorkspaceFolders()![2]; gotEvent = false; sub = ws.onDidChangeWorkspace(e => { @@ -348,18 +352,18 @@ suite('ExtHostWorkspace', function () { ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0), aWorkspaceFolderData(URI.parse('foo:bar1'), 1), aWorkspaceFolderData(URI.parse('foo:bar2'), 2)] }); // simulate acknowledgement from main side assert.equal(gotEvent, true); sub.dispose(); - assert.equal(ws.getWorkspaceFolders()[0], firstAddedFolder); // verify object is still live - assert.equal(ws.getWorkspaceFolders()[1], secondAddedFolder); // verify object is still live - assert.equal(ws.getWorkspaceFolders()[2], thirdAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![0], firstAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![1], secondAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![2], thirdAddedFolder); // verify object is still live // // Remove one folder // assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 2, 1)); - assert.equal(2, ws.workspace.folders.length); - assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar').toString()); - assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar1').toString()); + assert.equal(2, ws.workspace!.folders.length); + assert.equal(ws.workspace!.folders[0].uri.toString(), URI.parse('foo:bar').toString()); + assert.equal(ws.workspace!.folders[1].uri.toString(), URI.parse('foo:bar1').toString()); gotEvent = false; sub = ws.onDidChangeWorkspace(e => { @@ -375,21 +379,21 @@ suite('ExtHostWorkspace', function () { ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0), aWorkspaceFolderData(URI.parse('foo:bar1'), 1)] }); // simulate acknowledgement from main side assert.equal(gotEvent, true); sub.dispose(); - assert.equal(ws.getWorkspaceFolders()[0], firstAddedFolder); // verify object is still live - assert.equal(ws.getWorkspaceFolders()[1], secondAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![0], firstAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![1], secondAddedFolder); // verify object is still live // // Rename folder // assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 0, 2, asUpdateWorkspaceFolderData(URI.parse('foo:bar'), 'renamed 1'), asUpdateWorkspaceFolderData(URI.parse('foo:bar1'), 'renamed 2'))); - assert.equal(2, ws.workspace.folders.length); - assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar').toString()); - assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar1').toString()); - assert.equal(ws.workspace.folders[0].name, 'renamed 1'); - assert.equal(ws.workspace.folders[1].name, 'renamed 2'); - assert.equal(ws.getWorkspaceFolders()[0].name, 'renamed 1'); - assert.equal(ws.getWorkspaceFolders()[1].name, 'renamed 2'); + assert.equal(2, ws.workspace!.folders.length); + assert.equal(ws.workspace!.folders[0].uri.toString(), URI.parse('foo:bar').toString()); + assert.equal(ws.workspace!.folders[1].uri.toString(), URI.parse('foo:bar1').toString()); + assert.equal(ws.workspace!.folders[0].name, 'renamed 1'); + assert.equal(ws.workspace!.folders[1].name, 'renamed 2'); + assert.equal(ws.getWorkspaceFolders()![0].name, 'renamed 1'); + assert.equal(ws.getWorkspaceFolders()![1].name, 'renamed 2'); gotEvent = false; sub = ws.onDidChangeWorkspace(e => { @@ -404,24 +408,24 @@ suite('ExtHostWorkspace', function () { ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar'), 0, 'renamed 1'), aWorkspaceFolderData(URI.parse('foo:bar1'), 1, 'renamed 2')] }); // simulate acknowledgement from main side assert.equal(gotEvent, true); sub.dispose(); - assert.equal(ws.getWorkspaceFolders()[0], firstAddedFolder); // verify object is still live - assert.equal(ws.getWorkspaceFolders()[1], secondAddedFolder); // verify object is still live - assert.equal(ws.workspace.folders[0].name, 'renamed 1'); - assert.equal(ws.workspace.folders[1].name, 'renamed 2'); - assert.equal(ws.getWorkspaceFolders()[0].name, 'renamed 1'); - assert.equal(ws.getWorkspaceFolders()[1].name, 'renamed 2'); + assert.equal(ws.getWorkspaceFolders()![0], firstAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![1], secondAddedFolder); // verify object is still live + assert.equal(ws.workspace!.folders[0].name, 'renamed 1'); + assert.equal(ws.workspace!.folders[1].name, 'renamed 2'); + assert.equal(ws.getWorkspaceFolders()![0].name, 'renamed 1'); + assert.equal(ws.getWorkspaceFolders()![1].name, 'renamed 2'); // // Add and remove folders // assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 0, 2, asUpdateWorkspaceFolderData(URI.parse('foo:bar3')), asUpdateWorkspaceFolderData(URI.parse('foo:bar4')))); - assert.equal(2, ws.workspace.folders.length); - assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar3').toString()); - assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar4').toString()); + assert.equal(2, ws.workspace!.folders.length); + assert.equal(ws.workspace!.folders[0].uri.toString(), URI.parse('foo:bar3').toString()); + assert.equal(ws.workspace!.folders[1].uri.toString(), URI.parse('foo:bar4').toString()); - const fourthAddedFolder = ws.getWorkspaceFolders()[0]; - const fifthAddedFolder = ws.getWorkspaceFolders()[1]; + const fourthAddedFolder = ws.getWorkspaceFolders()![0]; + const fifthAddedFolder = ws.getWorkspaceFolders()![1]; gotEvent = false; sub = ws.onDidChangeWorkspace(e => { @@ -440,20 +444,20 @@ suite('ExtHostWorkspace', function () { ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar3'), 0), aWorkspaceFolderData(URI.parse('foo:bar4'), 1)] }); // simulate acknowledgement from main side assert.equal(gotEvent, true); sub.dispose(); - assert.equal(ws.getWorkspaceFolders()[0], fourthAddedFolder); // verify object is still live - assert.equal(ws.getWorkspaceFolders()[1], fifthAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![0], fourthAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![1], fifthAddedFolder); // verify object is still live // // Swap folders // assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 0, 2, asUpdateWorkspaceFolderData(URI.parse('foo:bar4')), asUpdateWorkspaceFolderData(URI.parse('foo:bar3')))); - assert.equal(2, ws.workspace.folders.length); - assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar4').toString()); - assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar3').toString()); + assert.equal(2, ws.workspace!.folders.length); + assert.equal(ws.workspace!.folders[0].uri.toString(), URI.parse('foo:bar4').toString()); + assert.equal(ws.workspace!.folders[1].uri.toString(), URI.parse('foo:bar3').toString()); - assert.equal(ws.getWorkspaceFolders()[0], fifthAddedFolder); // verify object is still live - assert.equal(ws.getWorkspaceFolders()[1], fourthAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![0], fifthAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![1], fourthAddedFolder); // verify object is still live gotEvent = false; sub = ws.onDidChangeWorkspace(e => { @@ -468,8 +472,8 @@ suite('ExtHostWorkspace', function () { ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.parse('foo:bar4'), 0), aWorkspaceFolderData(URI.parse('foo:bar3'), 1)] }); // simulate acknowledgement from main side assert.equal(gotEvent, true); sub.dispose(); - assert.equal(ws.getWorkspaceFolders()[0], fifthAddedFolder); // verify object is still live - assert.equal(ws.getWorkspaceFolders()[1], fourthAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![0], fifthAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![1], fourthAddedFolder); // verify object is still live assert.equal(fifthAddedFolder.index, 0); assert.equal(fourthAddedFolder.index, 1); @@ -479,12 +483,12 @@ suite('ExtHostWorkspace', function () { assert.equal(true, ws.updateWorkspaceFolders(extensionDescriptor, 2, 0, asUpdateWorkspaceFolderData(URI.parse('foo:bar5')))); - assert.equal(3, ws.workspace.folders.length); - assert.equal(ws.workspace.folders[0].uri.toString(), URI.parse('foo:bar4').toString()); - assert.equal(ws.workspace.folders[1].uri.toString(), URI.parse('foo:bar3').toString()); - assert.equal(ws.workspace.folders[2].uri.toString(), URI.parse('foo:bar5').toString()); + assert.equal(3, ws.workspace!.folders.length); + assert.equal(ws.workspace!.folders[0].uri.toString(), URI.parse('foo:bar4').toString()); + assert.equal(ws.workspace!.folders[1].uri.toString(), URI.parse('foo:bar3').toString()); + assert.equal(ws.workspace!.folders[2].uri.toString(), URI.parse('foo:bar5').toString()); - const sixthAddedFolder = ws.getWorkspaceFolders()[2]; + const sixthAddedFolder = ws.getWorkspaceFolders()![2]; gotEvent = false; sub = ws.onDidChangeWorkspace(e => { @@ -506,17 +510,42 @@ suite('ExtHostWorkspace', function () { assert.equal(gotEvent, true); sub.dispose(); - assert.equal(ws.getWorkspaceFolders()[0], fifthAddedFolder); // verify object is still live - assert.equal(ws.getWorkspaceFolders()[1], fourthAddedFolder); // verify object is still live - assert.equal(ws.getWorkspaceFolders()[2], sixthAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![0], fifthAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![1], fourthAddedFolder); // verify object is still live + assert.equal(ws.getWorkspaceFolders()![2], sixthAddedFolder); // verify object is still live finish(); }); - // {{SQL CARBON EDIT}} remove broken test + test('Multiroot change event is immutable', function (done) { + let finished = false; + const finish = (error?: any) => { + if (!finished) { + finished = true; + done(error); + } + }; + + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }, new NullLogService(), new Counter()); + let sub = ws.onDidChangeWorkspace(e => { + try { + assert.throws(() => { + (e).added = []; + }); + // assert.throws(() => { + // (e.added)[0] = null; + // }); + } catch (error) { + finish(error); + } + }); + ws.$acceptWorkspaceData({ id: 'foo', name: 'Test', folders: [] }); + sub.dispose(); + finish(); + }); test('`vscode.workspace.getWorkspaceFolder(file)` don\'t return workspace folder when file open from command line. #36221', function () { - let ws = new ExtHostWorkspace(new TestRPCProtocol(), { + let ws = createExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [ aWorkspaceFolderData(URI.file('c:/Users/marek/Desktop/vsc_test/'), 0) ] @@ -537,4 +566,107 @@ suite('ExtHostWorkspace', function () { function asUpdateWorkspaceFolderData(uri: URI, name?: string): { uri: URI, name?: string } { return { uri, name }; } + + test('findFiles - string include', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + $startFileSearch(includePattern: string, _includeFolder: UriComponents | undefined, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.equal(includePattern, 'foo'); + assert.equal(_includeFolder, undefined); + assert.equal(excludePatternOrDisregardExcludes, undefined); + assert.equal(maxResults, 10); + return Promise.resolve(undefined); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + return ws.findFiles('foo', undefined!, 10, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + }); + + test('findFiles - RelativePattern include', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + $startFileSearch(includePattern: string, _includeFolder: UriComponents | undefined, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.equal(includePattern, 'glob/**'); + assert.deepEqual(_includeFolder, URI.file('/other/folder').toJSON()); + assert.equal(excludePatternOrDisregardExcludes, undefined); + return Promise.resolve(undefined); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), undefined!, 10, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + }); + + test('findFiles - no excludes', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + $startFileSearch(includePattern: string, _includeFolder: UriComponents | undefined, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + mainThreadCalled = true; + assert.equal(includePattern, 'glob/**'); + assert.deepEqual(_includeFolder, URI.file('/other/folder').toJSON()); + assert.equal(excludePatternOrDisregardExcludes, false); + return Promise.resolve(undefined); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), null!, 10, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + }); + + test('findFiles - with cancelled token', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + $startFileSearch(includePattern: string, _includeFolder: UriComponents | undefined, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + mainThreadCalled = true; + return Promise.resolve(undefined); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + + const token = CancellationToken.Cancelled; + return ws.findFiles(new RelativePattern('/other/folder', 'glob/**'), null!, 10, new ExtensionIdentifier('test'), token).then(() => { + assert(!mainThreadCalled, '!mainThreadCalled'); + }); + }); + + test('findFiles - RelativePattern exclude', () => { + const root = '/project/foo'; + const rpcProtocol = new TestRPCProtocol(); + + let mainThreadCalled = false; + rpcProtocol.set(MainContext.MainThreadWorkspace, new class extends mock() { + $startFileSearch(includePattern: string, _includeFolder: UriComponents | undefined, excludePatternOrDisregardExcludes: string | false, maxResults: number, token: CancellationToken): Promise { + mainThreadCalled = true; + assert(excludePatternOrDisregardExcludes, 'glob/**'); // Note that the base portion is ignored, see #52651 + return Promise.resolve(undefined); + } + }); + + const ws = createExtHostWorkspace(rpcProtocol, { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }, new NullLogService(), new Counter()); + return ws.findFiles('', new RelativePattern(root, 'glob/**'), 10, new ExtensionIdentifier('test')).then(() => { + assert(mainThreadCalled, 'mainThreadCalled'); + }); + }); }); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts index 91eef48898..10b34b6d6e 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts @@ -63,7 +63,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', null); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -81,7 +81,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', null); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -90,7 +90,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', null); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -117,7 +117,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', null); + testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -162,7 +162,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', null); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -180,7 +180,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', null); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -189,7 +189,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', null); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); @@ -216,7 +216,7 @@ suite('MainThreadConfiguration', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(proxy)); - testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', null); + testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', undefined); assert.equal(ConfigurationTarget.WORKSPACE, target.args[0][3]); }); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentContentProviders.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentContentProviders.test.ts index 24d7554a36..69488d9420 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentContentProviders.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentContentProviders.test.ts @@ -19,7 +19,7 @@ suite('MainThreadDocumentContentProviders', function () { let uri = URI.parse('test:uri'); let model = TextModel.createFromString('1', undefined, undefined, uri); - let providers = new MainThreadDocumentContentProviders(new TestRPCProtocol(), null, null, + let providers = new MainThreadDocumentContentProviders(new TestRPCProtocol(), null!, null!, new class extends mock() { getModel(_uri) { assert.equal(uri.toString(), _uri.toString()); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts index c40996b937..d565969f15 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts @@ -29,7 +29,7 @@ suite('MainThreadDocumentsAndEditors', () => { let deltas: IDocumentsAndEditorsDelta[] = []; const hugeModelString = new Array(2 + (50 * 1024 * 1024)).join('-'); - function myCreateTestCodeEditor(model: ITextModel): TestCodeEditor { + function myCreateTestCodeEditor(model: ITextModel | undefined): TestCodeEditor { return createTestCodeEditor({ model: model, serviceCollection: new ServiceCollection( @@ -68,12 +68,12 @@ suite('MainThreadDocumentsAndEditors', () => { textFileService, workbenchEditorService, codeEditorService, - null, + null!, fileService, - null, - null, + null!, + null!, editorGroupService, - null, + null!, new class extends mock() implements IPanelService { _serviceBrand: any; onDidPanelOpen = Event.None; @@ -95,7 +95,7 @@ suite('MainThreadDocumentsAndEditors', () => { assert.equal(deltas.length, 1); const [delta] = deltas; - assert.equal(delta.addedDocuments.length, 1); + assert.equal(delta.addedDocuments!.length, 1); assert.equal(delta.removedDocuments, undefined); assert.equal(delta.addedEditors, undefined); assert.equal(delta.removedEditors, undefined); @@ -146,7 +146,7 @@ suite('MainThreadDocumentsAndEditors', () => { }); test('ignore editor w/o model', () => { - const editor = myCreateTestCodeEditor(null); + const editor = myCreateTestCodeEditor(undefined); assert.equal(deltas.length, 1); const [delta] = deltas; assert.equal(delta.newActiveEditor, null); @@ -166,13 +166,13 @@ suite('MainThreadDocumentsAndEditors', () => { assert.equal(deltas.length, 2); const [first, second] = deltas; - assert.equal(first.addedDocuments.length, 1); + assert.equal(first.addedDocuments!.length, 1); assert.equal(first.newActiveEditor, null); assert.equal(first.removedDocuments, undefined); assert.equal(first.addedEditors, undefined); assert.equal(first.removedEditors, undefined); - assert.equal(second.addedEditors.length, 1); + assert.equal(second.addedEditors!.length, 1); assert.equal(second.addedDocuments, undefined); assert.equal(second.removedDocuments, undefined); assert.equal(second.removedEditors, undefined); @@ -194,8 +194,8 @@ suite('MainThreadDocumentsAndEditors', () => { const [first] = deltas; assert.equal(first.newActiveEditor, null); - assert.equal(first.removedEditors.length, 1); - assert.equal(first.removedDocuments.length, 1); + assert.equal(first.removedEditors!.length, 1); + assert.equal(first.removedDocuments!.length, 1); assert.equal(first.addedDocuments, undefined); assert.equal(first.addedEditors, undefined); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts index 8286bdb141..81aa62625c 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts @@ -19,11 +19,11 @@ import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { IModelService } from 'vs/editor/common/services/modelService'; import { EditOperation } from 'vs/editor/common/core/editOperation'; -import { TestFileService, TestEditorService, TestEditorGroupsService, TestEnvironmentService, TestContextService, TestTextResourcePropertiesService, TestWindowService } from 'vs/workbench/test/workbenchTestServices'; +import { TestFileService, TestEditorService, TestEditorGroupsService, TestEnvironmentService, TestContextService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; import { ResourceTextEdit } from 'vs/editor/common/modes'; -import { BulkEditService } from 'vs/workbench/services/bulkEdit/electron-browser/bulkEditService'; +import { BulkEditService } from 'vs/workbench/services/bulkEdit/browser/bulkEditService'; import { NullLogService } from 'vs/platform/log/common/log'; -import { ITextModelService, ITextEditorModel } from 'vs/editor/common/services/resolverService'; +import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService'; import { IReference, ImmortalReference } from 'vs/base/common/lifecycle'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; @@ -73,16 +73,16 @@ suite('MainThreadEditors', () => { const workbenchEditorService = new TestEditorService(); const editorGroupService = new TestEditorGroupsService(); const textModelService = new class extends mock() { - createModelReference(resource: URI): Promise> { - const textEditorModel: ITextEditorModel = new class extends mock() { - textEditorModel = modelService.getModel(resource); + createModelReference(resource: URI): Promise> { + const textEditorModel = new class extends mock() { + textEditorModel = modelService.getModel(resource)!; }; textEditorModel.isReadonly = () => false; return Promise.resolve(new ImmortalReference(textEditorModel)); } }; - const bulkEditService = new BulkEditService(new NullLogService(), modelService, new TestEditorService(), textModelService, new TestFileService(), textFileService, new LabelService(TestEnvironmentService, new TestContextService(), new TestWindowService()), configService); + const bulkEditService = new BulkEditService(new NullLogService(), modelService, new TestEditorService(), textModelService, new TestFileService(), textFileService, new LabelService(TestEnvironmentService, new TestContextService()), configService); const rpcProtocol = new TestRPCProtocol(); rpcProtocol.set(ExtHostContext.ExtHostDocuments, new class extends mock() { @@ -100,10 +100,10 @@ suite('MainThreadEditors', () => { textFileService, workbenchEditorService, codeEditorService, - null, + null!, fileService, - null, - null, + null!, + null!, editorGroupService, bulkEditService, new class extends mock() implements IPanelService { diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts index fe2b15a597..6e1a75deb0 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts @@ -13,7 +13,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; -import { ITextFileService, SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; +import { ITextFileService, SaveReason, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { snapshotToString } from 'vs/platform/files/common/files'; @@ -38,45 +38,45 @@ suite('MainThreadSaveParticipant', function () { }); test('insert final new line', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/final_new_line.txt'), 'utf8'); + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/final_new_line.txt'), 'utf8') as IResolvedTextFileEditorModel; await model.load(); const configService = new TestConfigurationService(); configService.setUserConfiguration('files', { 'insertFinalNewline': true }); - const participant = new FinalNewLineParticipant(configService, undefined); + const participant = new FinalNewLineParticipant(configService, undefined!); // No new line for empty lines let lineContent = ''; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()), lineContent); + assert.equal(snapshotToString(model.createSnapshot()!), lineContent); // No new line if last line already empty lineContent = `Hello New Line${model.textEditorModel.getEOL()}`; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()), lineContent); + assert.equal(snapshotToString(model.createSnapshot()!), lineContent); // New empty line added (single line) lineContent = 'Hello New Line'; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()), `${lineContent}${model.textEditorModel.getEOL()}`); + assert.equal(snapshotToString(model.createSnapshot()!), `${lineContent}${model.textEditorModel.getEOL()}`); // New empty line added (multi line) lineContent = `Hello New Line${model.textEditorModel.getEOL()}Hello New Line${model.textEditorModel.getEOL()}Hello New Line`; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()), `${lineContent}${model.textEditorModel.getEOL()}`); + assert.equal(snapshotToString(model.createSnapshot()!), `${lineContent}${model.textEditorModel.getEOL()}`); }); test('trim final new lines', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8'); + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8') as IResolvedTextFileEditorModel; await model.load(); const configService = new TestConfigurationService(); configService.setUserConfiguration('files', { 'trimFinalNewlines': true }); - const participant = new TrimFinalNewLinesParticipant(configService, undefined); + const participant = new TrimFinalNewLinesParticipant(configService, undefined!); const textContent = 'Trim New Line'; const eol = `${model.textEditorModel.getEOL()}`; @@ -84,34 +84,34 @@ suite('MainThreadSaveParticipant', function () { let lineContent = `${textContent}`; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()), lineContent); + assert.equal(snapshotToString(model.createSnapshot()!), lineContent); // No new line removal if last line is single new line lineContent = `${textContent}${eol}`; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()), lineContent); + assert.equal(snapshotToString(model.createSnapshot()!), lineContent); // Remove new line (single line with two new lines) lineContent = `${textContent}${eol}${eol}`; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()), `${textContent}${eol}`); + assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}${eol}`); // Remove new lines (multiple lines with multiple new lines) lineContent = `${textContent}${eol}${textContent}${eol}${eol}${eol}`; model.textEditorModel.setValue(lineContent); await participant.participate(model, { reason: SaveReason.EXPLICIT }); - assert.equal(snapshotToString(model.createSnapshot()), `${textContent}${eol}${textContent}${eol}`); + assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}${eol}${textContent}${eol}`); }); test('trim final new lines bug#39750', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8'); + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8') as IResolvedTextFileEditorModel; await model.load(); const configService = new TestConfigurationService(); configService.setUserConfiguration('files', { 'trimFinalNewlines': true }); - const participant = new TrimFinalNewLinesParticipant(configService, undefined); + const participant = new TrimFinalNewLinesParticipant(configService, undefined!); const textContent = 'Trim New Line'; // single line @@ -124,21 +124,21 @@ suite('MainThreadSaveParticipant', function () { // undo model.textEditorModel.undo(); - assert.equal(snapshotToString(model.createSnapshot()), `${textContent}`); + assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}`); // trim final new lines should not mess the undo stack await participant.participate(model, { reason: SaveReason.EXPLICIT }); model.textEditorModel.redo(); - assert.equal(snapshotToString(model.createSnapshot()), `${textContent}.`); + assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}.`); }); test('trim final new lines bug#46075', async function () { - const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8'); + const model = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8') as IResolvedTextFileEditorModel; await model.load(); const configService = new TestConfigurationService(); configService.setUserConfiguration('files', { 'trimFinalNewlines': true }); - const participant = new TrimFinalNewLinesParticipant(configService, undefined); + const participant = new TrimFinalNewLinesParticipant(configService, undefined!); const textContent = 'Test'; const eol = `${model.textEditorModel.getEOL()}`; let content = `${textContent}${eol}${eol}`; @@ -150,12 +150,12 @@ suite('MainThreadSaveParticipant', function () { } // confirm trimming - assert.equal(snapshotToString(model.createSnapshot()), `${textContent}${eol}`); + assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}${eol}`); // undo should go back to previous content immediately model.textEditorModel.undo(); - assert.equal(snapshotToString(model.createSnapshot()), `${textContent}${eol}${eol}`); + assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}${eol}${eol}`); model.textEditorModel.redo(); - assert.equal(snapshotToString(model.createSnapshot()), `${textContent}${eol}`); + assert.equal(snapshotToString(model.createSnapshot()!), `${textContent}${eol}`); }); }); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadWorkspace.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadWorkspace.test.ts new file mode 100644 index 0000000000..3282e10c55 --- /dev/null +++ b/src/vs/workbench/test/electron-browser/api/mainThreadWorkspace.test.ts @@ -0,0 +1,101 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { workbenchInstantiationService } from 'vs/workbench/test/workbenchTestServices'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { ISearchService, IFileQuery } from 'vs/workbench/services/search/common/search'; +import { MainThreadWorkspace } from 'vs/workbench/api/electron-browser/mainThreadWorkspace'; +import * as assert from 'assert'; +import { SingleProxyRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; + +suite('MainThreadWorkspace', () => { + + let configService: TestConfigurationService; + let instantiationService: TestInstantiationService; + + setup(() => { + instantiationService = workbenchInstantiationService() as TestInstantiationService; + + configService = instantiationService.get(IConfigurationService) as TestConfigurationService; + configService.setUserConfiguration('search', {}); + }); + + test('simple', () => { + instantiationService.stub(ISearchService, { + fileSearch(query: IFileQuery) { + assert.equal(query.folderQueries.length, 1); + assert.equal(query.folderQueries[0].disregardIgnoreFiles, true); + + assert.deepEqual(query.includePattern, { 'foo': true }); + assert.equal(query.maxResults, 10); + + return Promise.resolve({ results: [] }); + } + }); + + const mtw: MainThreadWorkspace = instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } })); + return mtw.$startFileSearch('foo', undefined, undefined, 10, new CancellationTokenSource().token); + }); + + test('exclude defaults', () => { + configService.setUserConfiguration('search', { + 'exclude': { 'searchExclude': true } + }); + configService.setUserConfiguration('files', { + 'exclude': { 'filesExclude': true } + }); + + instantiationService.stub(ISearchService, { + fileSearch(query: IFileQuery) { + assert.equal(query.folderQueries.length, 1); + assert.equal(query.folderQueries[0].disregardIgnoreFiles, true); + assert.deepEqual(query.folderQueries[0].excludePattern, { 'filesExclude': true }); + + return Promise.resolve({ results: [] }); + } + }); + + const mtw: MainThreadWorkspace = instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } })); + return mtw.$startFileSearch('', undefined, undefined, 10, new CancellationTokenSource().token); + }); + + test('disregard excludes', () => { + configService.setUserConfiguration('search', { + 'exclude': { 'searchExclude': true } + }); + configService.setUserConfiguration('files', { + 'exclude': { 'filesExclude': true } + }); + + instantiationService.stub(ISearchService, { + fileSearch(query: IFileQuery) { + assert.equal(query.folderQueries[0].excludePattern, undefined); + assert.deepEqual(query.excludePattern, undefined); + + return Promise.resolve({ results: [] }); + } + }); + + const mtw: MainThreadWorkspace = instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } })); + return mtw.$startFileSearch('', undefined, false, 10, new CancellationTokenSource().token); + }); + + test('exclude string', () => { + instantiationService.stub(ISearchService, { + fileSearch(query: IFileQuery) { + assert.equal(query.folderQueries[0].excludePattern, undefined); + assert.deepEqual(query.excludePattern, { 'exclude/**': true }); + + return Promise.resolve({ results: [] }); + } + }); + + const mtw: MainThreadWorkspace = instantiationService.createInstance(MainThreadWorkspace, SingleProxyRPCProtocol({ $initializeWorkspace: () => { } })); + return mtw.$startFileSearch('', undefined, 'exclude/**', 10, new CancellationTokenSource().token); + }); +}); diff --git a/src/vs/workbench/test/electron-browser/api/testRPCProtocol.ts b/src/vs/workbench/test/electron-browser/api/testRPCProtocol.ts index e324e0a8e5..bbf2f8066a 100644 --- a/src/vs/workbench/test/electron-browser/api/testRPCProtocol.ts +++ b/src/vs/workbench/test/electron-browser/api/testRPCProtocol.ts @@ -10,23 +10,23 @@ import { isThenable } from 'vs/base/common/async'; export function SingleProxyRPCProtocol(thing: any): IExtHostContext { return { - remoteAuthority: null, + remoteAuthority: null!, getProxy(): T { return thing; }, set(identifier: ProxyIdentifier, value: R): R { return value; }, - assertRegistered: undefined + assertRegistered: undefined! }; } export class TestRPCProtocol implements IExtHostContext { - public remoteAuthority = null; + public remoteAuthority = null!; private _callCountValue: number = 0; - private _idle: Promise; + private _idle?: Promise; private _completeIdle: Function; private readonly _locals: { [id: string]: any; }; diff --git a/src/vs/platform/theme/test/electron-browser/colorRegistry.releaseTest.ts b/src/vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts similarity index 87% rename from src/vs/platform/theme/test/electron-browser/colorRegistry.releaseTest.ts rename to src/vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts index df8efd43ee..3912070edb 100644 --- a/src/vs/platform/theme/test/electron-browser/colorRegistry.releaseTest.ts +++ b/src/vs/workbench/test/electron-browser/colorRegistry.releaseTest.ts @@ -6,15 +6,15 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IColorRegistry, Extensions, ColorContribution } from 'vs/platform/theme/common/colorRegistry'; import { editorMarkerNavigationError } from 'vs/editor/contrib/gotoError/gotoErrorWidget'; -import { overviewRulerModifiedForeground } from 'vs/workbench/parts/scm/electron-browser/dirtydiffDecorator'; -import { STATUS_BAR_DEBUGGING_BACKGROUND } from 'vs/workbench/parts/debug/browser/statusbarColorProvider'; -import { debugExceptionWidgetBackground } from 'vs/workbench/parts/debug/browser/exceptionWidget'; -import { debugToolBarBackground } from 'vs/workbench/parts/debug/browser/debugToolbar'; -import { buttonBackground } from 'vs/workbench/parts/welcome/page/electron-browser/welcomePage'; -import { embeddedEditorBackground } from 'vs/workbench/parts/welcome/walkThrough/electron-browser/walkThroughPart'; +import { overviewRulerModifiedForeground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator'; +import { STATUS_BAR_DEBUGGING_BACKGROUND } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider'; +import { debugExceptionWidgetBackground } from 'vs/workbench/contrib/debug/browser/exceptionWidget'; +import { debugToolBarBackground } from 'vs/workbench/contrib/debug/browser/debugToolbar'; +import { buttonBackground } from 'vs/workbench/contrib/welcome/page/browser/welcomePage'; +import { embeddedEditorBackground } from 'vs/workbench/contrib/welcome/walkThrough/browser/walkThroughPart'; import { request, asText } from 'vs/base/node/request'; import * as pfs from 'vs/base/node/pfs'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import * as assert from 'assert'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { CancellationToken } from 'vs/base/common/cancellation'; diff --git a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts index c4cf97c8d8..f532fb714a 100644 --- a/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/quickopen.perf.integrationTest.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import * as minimist from 'minimist'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { CancellationToken } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; import { IModelService } from 'vs/editor/common/services/modelService'; @@ -18,14 +18,14 @@ import { createSyncDescriptor } from 'vs/platform/instantiation/common/descripto import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { Registry } from 'vs/platform/registry/common/platform'; -import { ISearchService } from 'vs/platform/search/common/search'; +import { ISearchService } from 'vs/workbench/services/search/common/search'; import { ITelemetryInfo, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { Extensions, IQuickOpenRegistry } from 'vs/workbench/browser/quickopen'; -import 'vs/workbench/parts/search/electron-browser/search.contribution'; // load contributions +import 'vs/workbench/contrib/search/browser/search.contribution'; // load contributions import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { SearchService } from 'vs/workbench/services/search/node/searchService'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { TestContextService, TestEditorGroupsService, TestEditorService, TestEnvironmentService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; diff --git a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts index 3fddcb65a0..28b820a536 100644 --- a/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts +++ b/src/vs/workbench/test/electron-browser/textsearch.perf.integrationTest.ts @@ -3,18 +3,18 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/workbench/parts/search/electron-browser/search.contribution'; // load contributions +import 'vs/workbench/contrib/search/browser/search.contribution'; // load contributions import * as assert from 'assert'; import * as fs from 'fs'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { createSyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; -import { ISearchService } from 'vs/platform/search/common/search'; +import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { ISearchService } from 'vs/workbench/services/search/common/search'; import { ITelemetryService, ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry'; import { IUntitledEditorService, UntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import * as minimist from 'minimist'; -import * as path from 'path'; +import * as path from 'vs/base/common/path'; import { SearchService } from 'vs/workbench/services/search/node/searchService'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { TestEnvironmentService, TestContextService, TestEditorService, TestEditorGroupsService, TestTextResourcePropertiesService } from 'vs/workbench/test/workbenchTestServices'; @@ -26,8 +26,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { SearchModel } from 'vs/workbench/parts/search/common/searchModel'; -import { QueryBuilder, ITextQueryBuilderOptions } from 'vs/workbench/parts/search/common/queryBuilder'; +import { SearchModel } from 'vs/workbench/contrib/search/common/searchModel'; +import { QueryBuilder, ITextQueryBuilderOptions } from 'vs/workbench/contrib/search/common/queryBuilder'; import { Event, Emitter } from 'vs/base/common/event'; import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index f07e665873..c34c2f62a7 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -3,21 +3,21 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/workbench/parts/files/electron-browser/files.contribution'; // load our contribution into the test -import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; +import 'vs/workbench/contrib/files/browser/files.contribution'; // load our contribution into the test +import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; -import * as paths from 'vs/base/common/paths'; +import { join } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; -import { ConfirmResult, IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent } from 'vs/workbench/common/editor'; -import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView, EditorGroupsServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; +import { ConfirmResult, IEditorInputWithOptions, CloseDirection, IEditorIdentifier, IUntitledResourceInput, IResourceDiffInput, IResourceSideBySideInput, IEditorInput, IEditor, IEditorCloseEvent, IEditorPartOptions } from 'vs/workbench/common/editor'; +import { IEditorOpeningEvent, EditorServiceImpl, IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor'; import { Event, Emitter } from 'vs/base/common/event'; import Severity from 'vs/base/common/severity'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IPartService, Parts, Position as PartPosition, IDimension } from 'vs/workbench/services/part/common/partService'; +import { IWorkbenchLayoutService, Parts, Position as PartPosition } from 'vs/workbench/services/layout/browser/layoutService'; import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IEditorOptions, IResourceInput } from 'vs/platform/editor/common/editor'; @@ -26,7 +26,7 @@ import { IWorkspaceContextService, IWorkspace as IWorkbenchWorkspace, WorkbenchS import { ILifecycleService, BeforeShutdownEvent, ShutdownReason, StartupKind, LifecyclePhase, WillShutdownEvent } from 'vs/platform/lifecycle/common/lifecycle'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; -import { FileOperationEvent, IFileService, IResolveContentOptions, FileOperationError, IFileStat, IResolveFileResult, FileChangesEvent, IResolveFileOptions, IContent, IUpdateContentOptions, IStreamContent, ICreateFileOptions, ITextSnapshot, IResourceEncodings } from 'vs/platform/files/common/files'; +import { FileOperationEvent, IFileService, IResolveContentOptions, FileOperationError, IFileStat, IResolveFileResult, FileChangesEvent, IResolveFileOptions, IContent, IUpdateContentOptions, IStreamContent, ICreateFileOptions, ITextSnapshot, IResourceEncodings, IResourceEncoding } from 'vs/platform/files/common/files'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; @@ -37,15 +37,14 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { IInstantiationService, ServicesAccessor, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; -import { IWindowsService, IWindowService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IWindowConfiguration, MenuBarVisibility } from 'vs/platform/windows/common/windows'; +import { IWindowsService, IWindowService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IWindowConfiguration, MenuBarVisibility, IURIToOpen } from 'vs/platform/windows/common/windows'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { generateUuid } from 'vs/base/common/uuid'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { IRecentlyOpened } from 'vs/platform/history/common/history'; +import { IRecentlyOpened, IRecent } from 'vs/platform/history/common/history'; import { ITextResourceConfigurationService, ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration'; import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/position'; import { IMenuService, MenuId, IMenu, ISerializableCommandAction } from 'vs/platform/actions/common/actions'; @@ -57,13 +56,12 @@ import { Range } from 'vs/editor/common/core/range'; import { IConfirmation, IConfirmationResult, IDialogService, IDialogOptions, IPickAndOpenOptions, ISaveDialogOptions, IOpenDialogOptions, IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { TestNotificationService } from 'vs/platform/notification/test/common/testNotificationService'; -import { IExtensionService, ProfileSession, IExtensionsStatus, ExtensionPointContribution, IExtensionDescription, IWillActivateEvent, IResponsiveStateChangeEvent } from '../services/extensions/common/extensions'; -import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry'; +import { IExtensionService, NullExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IDecorationsService, IResourceDecorationChangeEvent, IDecoration, IDecorationData, IDecorationsProvider } from 'vs/workbench/services/decorations/browser/decorations'; import { IDisposable, toDisposable, Disposable } from 'vs/base/common/lifecycle'; -import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, EditorsOrder, IFindGroupScope, EditorGroupLayout } from 'vs/workbench/services/group/common/editorGroupsService'; -import { IEditorService, IOpenEditorOverrideHandler } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupsService, IEditorGroup, GroupsOrder, GroupsArrangement, GroupDirection, IAddGroupOptions, IMergeGroupOptions, IMoveEditorOptions, ICopyEditorOptions, IEditorReplacement, IGroupChangeEvent, EditorsOrder, IFindGroupScope, EditorGroupLayout, ICloseEditorOptions } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { IEditorService, IOpenEditorOverrideHandler, IVisibleEditor } from 'vs/workbench/services/editor/common/editorService'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDecorationRenderOptions } from 'vs/editor/common/editorCommon'; @@ -73,13 +71,17 @@ import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { ILabelService } from 'vs/platform/label/common/label'; import { timeout } from 'vs/base/common/async'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; +import { ViewletDescriptor, Viewlet } from 'vs/workbench/browser/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet'; -import { IProgressService } from 'vs/platform/progress/common/progress'; import { IStorageService, InMemoryStorageService } from 'vs/platform/storage/common/storage'; import { isLinux, isMacintosh } from 'vs/base/common/platform'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { LabelService } from 'vs/workbench/services/label/common/labelService'; +import { IDimension } from 'vs/platform/layout/browser/layoutService'; +import { Part } from 'vs/workbench/browser/part'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IPanel } from 'vs/workbench/common/panel'; +import { IBadge } from 'vs/workbench/services/activity/common/activity'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined); @@ -140,7 +142,7 @@ export class TestContextService implements IWorkspaceContextService { return this.workspace; } - public getWorkspaceFolder(resource: URI): IWorkspaceFolder { + public getWorkspaceFolder(resource: URI): IWorkspaceFolder | null { return this.workspace.getFolder(resource); } @@ -165,7 +167,7 @@ export class TestContextService implements IWorkspaceContextService { } public toResource(workspaceRelativePath: string): URI { - return URI.file(paths.join('C:\\', workspaceRelativePath)); + return URI.file(join('C:\\', workspaceRelativePath)); } public isCurrentWorkspace(workspaceIdentifier: ISingleFolderWorkspaceIdentifier | IWorkspaceIdentifier): boolean { @@ -178,24 +180,48 @@ export class TestTextFileService extends TextFileService { private promptPath: URI; private confirmResult: ConfirmResult; - private resolveTextContentError: FileOperationError; + private resolveTextContentError: FileOperationError | null; constructor( - @ILifecycleService lifecycleService: ILifecycleService, @IWorkspaceContextService contextService: IWorkspaceContextService, - @IConfigurationService configurationService: IConfigurationService, - @IFileService fileService: IFileService, + @IFileService protected fileService: IFileService, @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @ILifecycleService lifecycleService: ILifecycleService, @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService configurationService: IConfigurationService, + @IModeService modeService: IModeService, + @IModelService modelService: IModelService, + @IWindowService windowService: IWindowService, + @IEnvironmentService environmentService: IEnvironmentService, @INotificationService notificationService: INotificationService, @IBackupFileService backupFileService: IBackupFileService, @IWindowsService windowsService: IWindowsService, - @IWindowService windowService: IWindowService, @IHistoryService historyService: IHistoryService, @IContextKeyService contextKeyService: IContextKeyService, - @IModelService modelService: IModelService + @IDialogService dialogService: IDialogService, + @IFileDialogService fileDialogService: IFileDialogService, + @IEditorService editorService: IEditorService ) { - super(lifecycleService, contextService, configurationService, fileService, untitledEditorService, instantiationService, notificationService, TestEnvironmentService, backupFileService, windowsService, windowService, historyService, contextKeyService, modelService); + super( + contextService, + fileService, + untitledEditorService, + lifecycleService, + instantiationService, + configurationService, + modeService, + modelService, + windowService, + environmentService, + notificationService, + backupFileService, + windowsService, + historyService, + contextKeyService, + dialogService, + fileDialogService, + editorService + ); } public setPromptPath(path: URI): void { @@ -238,6 +264,10 @@ export class TestTextFileService extends TextFileService { return Promise.resolve(this.confirmResult); } + public confirmOverwrite(_resource: URI): Promise { + return Promise.resolve(true); + } + public onFilesConfigurationChange(configuration: any): void { super.onFilesConfigurationChange(configuration); } @@ -250,6 +280,7 @@ export class TestTextFileService extends TextFileService { export function workbenchInstantiationService(): IInstantiationService { let instantiationService = new TestInstantiationService(new ServiceCollection([ILifecycleService, new TestLifecycleService()])); + instantiationService.stub(IEnvironmentService, TestEnvironmentService); instantiationService.stub(IContextKeyService, instantiationService.createInstance(MockContextKeyService)); const workspaceContextService = new TestContextService(TestWorkspace); instantiationService.stub(IWorkspaceContextService, workspaceContextService); @@ -258,7 +289,7 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(ITextResourceConfigurationService, new TestTextResourceConfigurationService(configService)); instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); instantiationService.stub(IStorageService, new TestStorageService()); - instantiationService.stub(IPartService, new TestPartService()); + instantiationService.stub(IWorkbenchLayoutService, new TestLayoutService()); instantiationService.stub(IModeService, instantiationService.createInstance(ModeServiceImpl)); instantiationService.stub(IHistoryService, new TestHistoryService()); instantiationService.stub(ITextResourcePropertiesService, new TestTextResourcePropertiesService(configService)); @@ -276,7 +307,6 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(IWindowsService, new TestWindowsService()); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); - instantiationService.stub(IEnvironmentService, TestEnvironmentService); instantiationService.stub(IThemeService, new TestThemeService()); instantiationService.stub(IHashService, new TestHashService()); instantiationService.stub(ILogService, new TestLogService()); @@ -307,31 +337,10 @@ export class TestDecorationsService implements IDecorationsService { _serviceBrand: any; onDidChangeDecorations: Event = Event.None; registerDecorationsProvider(_provider: IDecorationsProvider): IDisposable { return Disposable.None; } - getDecoration(_uri: URI, _includeChildren: boolean, _overwrite?: IDecorationData): IDecoration { return undefined; } + getDecoration(_uri: URI, _includeChildren: boolean, _overwrite?: IDecorationData): IDecoration | undefined { return undefined; } } -export class TestExtensionService implements IExtensionService { - _serviceBrand: any; - onDidRegisterExtensions: Event = Event.None; - onDidChangeExtensionsStatus: Event = Event.None; - onDidChangeExtensions: Event = Event.None; - onWillActivateByEvent: Event = Event.None; - onDidChangeResponsiveChange: Event = Event.None; - activateByEvent(_activationEvent: string): Promise { return Promise.resolve(undefined); } - whenInstalledExtensionsRegistered(): Promise { return Promise.resolve(true); } - getExtensions(): Promise { return Promise.resolve([]); } - getExtension() { return Promise.resolve(undefined); } - readExtensionPointContributions(_extPoint: IExtensionPoint): Promise[]> { return Promise.resolve(Object.create(null)); } - getExtensionsStatus(): { [id: string]: IExtensionsStatus; } { return Object.create(null); } - canProfileExtensionHost(): boolean { return false; } - getInspectPort(): number { return 0; } - startExtensionHostProfile(): Promise { return Promise.resolve(Object.create(null)); } - restartExtensionHost(): void { } - startExtensionHost(): void { } - stopExtensionHost(): void { } - canAddExtension(): boolean { return false; } - canRemoveExtension(): boolean { return false; } -} +export class TestExtensionService extends NullExtensionService { } export class TestMenuService implements IMenuService { @@ -378,11 +387,11 @@ export class TestHistoryService implements IHistoryService { return []; } - public getLastActiveWorkspaceRoot(_schemeFilter: string): URI { + public getLastActiveWorkspaceRoot(_schemeFilter: string): URI | undefined { return this.root; } - public getLastActiveFile(_schemeFilter: string): URI { + public getLastActiveFile(_schemeFilter: string): URI | undefined { return undefined; } @@ -407,13 +416,13 @@ export class TestFileDialogService implements IFileDialogService { public _serviceBrand: any; - public defaultFilePath(_schemeFilter: string): URI { + public defaultFilePath(_schemeFilter?: string): URI | undefined { return undefined; } - public defaultFolderPath(_schemeFilter: string): URI { + public defaultFolderPath(_schemeFilter?: string): URI | undefined { return undefined; } - public defaultWorkspacePath(_schemeFilter: string): URI { + public defaultWorkspacePath(_schemeFilter?: string): URI | undefined { return undefined; } public pickFileFolderAndOpen(_options: IPickAndOpenOptions): Promise { @@ -428,21 +437,27 @@ export class TestFileDialogService implements IFileDialogService { public pickWorkspaceAndOpen(_options: IPickAndOpenOptions): Promise { return Promise.resolve(0); } - public showSaveDialog(_options: ISaveDialogOptions): Promise { - return Promise.resolve(); + public showSaveDialog(_options: ISaveDialogOptions): Promise { + return Promise.resolve(undefined); } - public showOpenDialog(_options: IOpenDialogOptions): Promise { - return Promise.resolve(); + public showOpenDialog(_options: IOpenDialogOptions): Promise { + return Promise.resolve(undefined); } } -export class TestPartService implements IPartService { +export class TestLayoutService implements IWorkbenchLayoutService { public _serviceBrand: any; + dimension: IDimension = { width: 800, height: 600 }; + + container: HTMLElement = window.document.body; + + onZenModeChange: Event = Event.None; + onLayout = Event.None; + private _onTitleBarVisibilityChange = new Emitter(); private _onMenubarVisibilityChange = new Emitter(); - private _onEditorLayout = new Emitter(); public get onTitleBarVisibilityChange(): Event { return this._onTitleBarVisibilityChange.event; @@ -452,10 +467,6 @@ export class TestPartService implements IPartService { return this._onMenubarVisibilityChange.event; } - public get onEditorLayout(): Event { - return this._onEditorLayout.event; - } - public isRestored(): boolean { return true; } @@ -469,7 +480,7 @@ export class TestPartService implements IPartService { } public getContainer(_part: Parts): HTMLElement { - return null; + return null!; } public isTitleBarHidden(): boolean { @@ -494,15 +505,19 @@ export class TestPartService implements IPartService { return false; } - public setEditorHidden(_hidden: boolean): Promise { return Promise.resolve(null); } + public get hasWorkbench(): boolean { + return true; + } - public setSideBarHidden(_hidden: boolean): Promise { return Promise.resolve(null); } + public setEditorHidden(_hidden: boolean): Promise { return Promise.resolve(); } + + public setSideBarHidden(_hidden: boolean): Promise { return Promise.resolve(); } public isPanelHidden(): boolean { return false; } - public setPanelHidden(_hidden: boolean): Promise { return Promise.resolve(null); } + public setPanelHidden(_hidden: boolean): Promise { return Promise.resolve(); } public toggleMaximizedPanel(): void { } @@ -511,7 +526,7 @@ export class TestPartService implements IPartService { } public getMenubarVisibility(): MenuBarVisibility { - return null; + throw new Error('not implemented'); } public getSideBarPosition() { @@ -523,12 +538,12 @@ export class TestPartService implements IPartService { } public setPanelPosition(_position: PartPosition): Promise { - return Promise.resolve(null); + return Promise.resolve(); } public addClass(_clazz: string): void { } public removeClass(_clazz: string): void { } - public getWorkbenchElement(): HTMLElement { return undefined; } + public getWorkbenchElement(): HTMLElement { throw new Error('not implemented'); } public toggleZenMode(): void { } @@ -537,23 +552,121 @@ export class TestPartService implements IPartService { public resizePart(_part: Parts, _sizeChange: number): void { } + + public registerPart(part: Part): void { } +} + +let activeViewlet: Viewlet = {} as any; + +export class TestViewletService implements IViewletService { + public _serviceBrand: any; + + onDidViewletRegisterEmitter = new Emitter(); + onDidViewletDeregisterEmitter = new Emitter(); + onDidViewletOpenEmitter = new Emitter(); + onDidViewletCloseEmitter = new Emitter(); + + onDidViewletRegister = this.onDidViewletRegisterEmitter.event; + onDidViewletDeregister = this.onDidViewletDeregisterEmitter.event; + onDidViewletOpen = this.onDidViewletOpenEmitter.event; + onDidViewletClose = this.onDidViewletCloseEmitter.event; + + public openViewlet(id: string, focus?: boolean): Promise { + return Promise.resolve(null!); + } + + public getViewlets(): ViewletDescriptor[] { + return []; + } + + public getAllViewlets(): ViewletDescriptor[] { + return []; + } + + public getActiveViewlet(): IViewlet { + return activeViewlet; + } + + public dispose() { + } + + public getDefaultViewletId(): string { + return 'workbench.view.explorer'; + } + + public getViewlet(id: string): ViewletDescriptor | undefined { + return undefined; + } + + public getProgressIndicator(id: string) { + return null!; + } + + public hideActiveViewlet(): void { } + + public getLastActiveViewletId(): string { + return undefined!; + } +} + +export class TestPanelService implements IPanelService { + public _serviceBrand: any; + + onDidPanelOpen = new Emitter<{ panel: IPanel, focus: boolean }>().event; + onDidPanelClose = new Emitter().event; + + public openPanel(id: string, focus?: boolean): IPanel { + return null!; + } + + public getPanels(): any[] { + return []; + } + + public getPinnedPanels(): any[] { + return []; + } + + public getActivePanel(): IViewlet { + return activeViewlet; + } + + public setPanelEnablement(id: string, enabled: boolean): void { } + + public dispose() { + } + + public showActivity(panelId: string, badge: IBadge, clazz?: string): IDisposable { + throw new Error('Method not implemented.'); + } + + public hideActivePanel(): void { } + + public getLastActivePanelId(): string { + return undefined!; + } } export class TestStorageService extends InMemoryStorageService { } -export class TestEditorGroupsService implements EditorGroupsServiceImpl { +export class TestEditorGroupsService implements IEditorGroupsService { + _serviceBrand: ServiceIdentifier; constructor(public groups: TestEditorGroup[] = []) { } onDidActiveGroupChange: Event = Event.None; + onDidActivateGroup: Event = Event.None; onDidAddGroup: Event = Event.None; onDidRemoveGroup: Event = Event.None; onDidMoveGroup: Event = Event.None; + onDidLayout: Event = Event.None; orientation: any; whenRestored: Promise = Promise.resolve(undefined); + dimension = { width: 800, height: 600 }; + get activeGroup(): IEditorGroup { return this.groups[0]; } @@ -573,7 +686,7 @@ export class TestEditorGroupsService implements EditorGroupsServiceImpl { } } - return undefined; + return undefined!; } getLabel(_identifier: number): string { @@ -581,11 +694,11 @@ export class TestEditorGroupsService implements EditorGroupsServiceImpl { } findGroup(_scope: IFindGroupScope, _source?: number | IEditorGroup, _wrap?: boolean): IEditorGroup { - return null; + throw new Error('not implemented'); } activateGroup(_group: number | IEditorGroup): IEditorGroup { - return null; + throw new Error('not implemented'); } getSize(_group: number | IEditorGroup): number { @@ -601,21 +714,32 @@ export class TestEditorGroupsService implements EditorGroupsServiceImpl { setGroupOrientation(_orientation: any): void { } addGroup(_location: number | IEditorGroup, _direction: GroupDirection, _options?: IAddGroupOptions): IEditorGroup { - return null; + throw new Error('not implemented'); } removeGroup(_group: number | IEditorGroup): void { } moveGroup(_group: number | IEditorGroup, _location: number | IEditorGroup, _direction: GroupDirection): IEditorGroup { - return null; + throw new Error('not implemented'); } mergeGroup(_group: number | IEditorGroup, _target: number | IEditorGroup, _options?: IMergeGroupOptions): IEditorGroup { - return null; + throw new Error('not implemented'); } copyGroup(_group: number | IEditorGroup, _location: number | IEditorGroup, _direction: GroupDirection): IEditorGroup { - return null; + throw new Error('not implemented'); + } + + centerLayout(active: boolean): void { } + + isLayoutCentered(): boolean { + return false; + } + + partOptions: IEditorPartOptions; + enforcePartOptions(options: IEditorPartOptions): IDisposable { + return Disposable.None; } } @@ -623,8 +747,8 @@ export class TestEditorGroup implements IEditorGroupView { constructor(public id: number) { } - group: EditorGroup = undefined; - activeControl: IEditor; + get group(): EditorGroup { throw new Error('not implemented'); } + activeControl: IVisibleEditor; activeEditor: IEditorInput; previewEditor: IEditorInput; count: number; @@ -652,7 +776,7 @@ export class TestEditorGroup implements IEditorGroupView { } getEditor(_index: number): IEditorInput { - return null; + throw new Error('not implemented'); } getIndexOfEditor(_editor: IEditorInput): number { @@ -660,11 +784,11 @@ export class TestEditorGroup implements IEditorGroupView { } openEditor(_editor: IEditorInput, _options?: IEditorOptions): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } openEditors(_editors: IEditorInputWithOptions[]): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } isOpened(_editor: IEditorInput): boolean { @@ -683,11 +807,11 @@ export class TestEditorGroup implements IEditorGroupView { copyEditor(_editor: IEditorInput, _target: IEditorGroup, _options?: ICopyEditorOptions): void { } - closeEditor(_editor?: IEditorInput): Promise { + closeEditor(_editor?: IEditorInput, options?: ICloseEditorOptions): Promise { return Promise.resolve(); } - closeEditors(_editors: IEditorInput[] | { except?: IEditorInput; direction?: CloseDirection; savedOnly?: boolean; }): Promise { + closeEditors(_editors: IEditorInput[] | { except?: IEditorInput; direction?: CloseDirection; savedOnly?: boolean; }, options?: ICloseEditorOptions): Promise { return Promise.resolve(); } @@ -704,7 +828,7 @@ export class TestEditorGroup implements IEditorGroupView { focus(): void { } invokeWithinContext(fn: (accessor: ServicesAccessor) => T): T { - return fn(null); + throw new Error('not implemented'); } isEmpty(): boolean { return true; } @@ -725,11 +849,11 @@ export class TestEditorService implements EditorServiceImpl { onDidCloseEditor: Event = Event.None; onDidOpenEditorFail: Event = Event.None; - activeControl: IEditor; + activeControl: IVisibleEditor; activeTextEditorWidget: any; activeEditor: IEditorInput; editors: ReadonlyArray = []; - visibleControls: ReadonlyArray = []; + visibleControls: ReadonlyArray = []; visibleTextEditorWidgets = []; visibleEditors: ReadonlyArray = []; @@ -737,12 +861,12 @@ export class TestEditorService implements EditorServiceImpl { return toDisposable(() => undefined); } - openEditor(_editor: any, _options?: any, _group?: any) { - return Promise.resolve(null); + openEditor(_editor: any, _options?: any, _group?: any): Promise { + throw new Error('not implemented'); } - openEditors(_editors: any, _group?: any) { - return Promise.resolve(null); + openEditors(_editors: any, _group?: any): Promise { + throw new Error('not implemented'); } isOpen(_editor: IEditorInput | IResourceInput | IUntitledResourceInput): boolean { @@ -750,7 +874,7 @@ export class TestEditorService implements EditorServiceImpl { } getOpened(_editor: IEditorInput | IResourceInput | IUntitledResourceInput): IEditorInput { - return undefined; + throw new Error('not implemented'); } replaceEditors(_editors: any, _group: any) { @@ -758,11 +882,11 @@ export class TestEditorService implements EditorServiceImpl { } invokeWithinEditorContext(fn: (accessor: ServicesAccessor) => T): T { - return fn(null); + throw new Error('not implemented'); } createInput(_input: IResourceInput | IUntitledResourceInput | IResourceDiffInput | IResourceSideBySideInput): IEditorInput { - return null; + throw new Error('not implemented'); } } @@ -813,7 +937,7 @@ export class TestFileService implements IFileService { encoding: 'utf8', mtime: Date.now(), isDirectory: false, - name: paths.basename(resource.fsPath) + name: resources.basename(resource) }); } @@ -822,7 +946,7 @@ export class TestFileService implements IFileService { } existsFile(_resource: URI): Promise { - return Promise.resolve(null); + return Promise.resolve(true); } resolveContent(resource: URI, _options?: IResolveContentOptions): Promise { @@ -832,7 +956,7 @@ export class TestFileService implements IFileService { etag: 'index.txt', encoding: 'utf8', mtime: Date.now(), - name: paths.basename(resource.fsPath) + name: resources.basename(resource) }); } @@ -852,7 +976,7 @@ export class TestFileService implements IFileService { etag: 'index.txt', encoding: 'utf8', mtime: Date.now(), - name: paths.basename(resource.fsPath) + name: resources.basename(resource) }); } @@ -863,20 +987,20 @@ export class TestFileService implements IFileService { encoding: 'utf8', mtime: Date.now(), isDirectory: false, - name: paths.basename(resource.fsPath) + name: resources.basename(resource) })); } moveFile(_source: URI, _target: URI, _overwrite?: boolean): Promise { - return Promise.resolve(null); + return Promise.resolve(null!); } copyFile(_source: URI, _target: URI, _overwrite?: boolean): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } createFile(_resource: URI, _content?: string, _options?: ICreateFileOptions): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } readFolder(_resource: URI) { @@ -884,7 +1008,7 @@ export class TestFileService implements IFileService { } createFolder(_resource: URI): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } onDidChangeFileSystemProviderRegistrations = Event.None; @@ -893,8 +1017,8 @@ export class TestFileService implements IFileService { return { dispose() { } }; } - activateProvider(_scheme: string) { - return Promise.resolve(null); + activateProvider(_scheme: string): Promise { + throw new Error('not implemented'); } canHandleResource(resource: URI): boolean { @@ -902,7 +1026,7 @@ export class TestFileService implements IFileService { } del(_resource: URI, _options?: { useTrash?: boolean, recursive?: boolean }): Promise { - return Promise.resolve(null); + return Promise.resolve(); } watchFileChanges(_resource: URI): void { @@ -911,8 +1035,8 @@ export class TestFileService implements IFileService { unwatchFileChanges(_resource: URI): void { } - getWriteEncoding(_resource: URI): string { - return 'utf8'; + getWriteEncoding(_resource: URI): IResourceEncoding { + return { encoding: 'utf8', hasBOM: false }; } dispose(): void { @@ -930,7 +1054,7 @@ export class TestBackupFileService implements IBackupFileService { return Promise.resolve(false); } - public loadBackupResource(resource: URI): Promise { + public loadBackupResource(resource: URI): Promise { return this.hasBackup(resource).then(hasBackup => { if (hasBackup) { return this.toBackupResource(resource); @@ -949,7 +1073,7 @@ export class TestBackupFileService implements IBackupFileService { } public toBackupResource(_resource: URI): URI { - return null; + throw new Error('not implemented'); } public backupResource(_resource: URI, _content: ITextSnapshot): Promise { @@ -968,7 +1092,7 @@ export class TestBackupFileService implements IBackupFileService { } public resolveBackupContent(_backup: URI): Promise { - return Promise.resolve(null); + throw new Error('not implemented'); } public discardResourceBackup(_resource: URI): Promise { @@ -995,14 +1119,14 @@ export class TestCodeEditorService implements ICodeEditorService { addDiffEditor(_editor: IDiffEditor): void { } removeDiffEditor(_editor: IDiffEditor): void { } listDiffEditors(): IDiffEditor[] { return []; } - getFocusedCodeEditor(): ICodeEditor { return null; } + getFocusedCodeEditor(): ICodeEditor | null { return null; } registerDecorationType(_key: string, _options: IDecorationRenderOptions, _parentTypeKey?: string): void { } removeDecorationType(_key: string): void { } resolveDecorationOptions(_typeKey: string, _writable: boolean): IModelDecorationOptions { return Object.create(null); } setTransientModelProperty(_model: ITextModel, _key: string, _value: any): void { } getTransientModelProperty(_model: ITextModel, _key: string) { } - getActiveCodeEditor(): ICodeEditor { return null; } - openCodeEditor(_input: IResourceInput, _source: ICodeEditor, _sideBySide?: boolean): Promise { return Promise.resolve(); } + getActiveCodeEditor(): ICodeEditor | null { return null; } + openCodeEditor(_input: IResourceInput, _source: ICodeEditor, _sideBySide?: boolean): Promise { return Promise.resolve(null); } } export class TestWindowService implements IWindowService { @@ -1062,8 +1186,8 @@ export class TestWindowService implements IWindowService { return Promise.resolve(); } - enterWorkspace(_path: URI): Promise { - return Promise.resolve(); + enterWorkspace(_path: URI): Promise { + return Promise.resolve(undefined); } toggleFullScreen(): Promise { @@ -1075,7 +1199,10 @@ export class TestWindowService implements IWindowService { } getRecentlyOpened(): Promise { - return Promise.resolve(); + return Promise.resolve({ + workspaces: [], + files: [] + }); } focusWindow(): Promise { @@ -1094,7 +1221,7 @@ export class TestWindowService implements IWindowService { return Promise.resolve(); } - openWindow(_paths: URI[], _options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): Promise { + openWindow(_uris: IURIToOpen[], _options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): Promise { return Promise.resolve(); } @@ -1119,11 +1246,11 @@ export class TestWindowService implements IWindowService { } showSaveDialog(_options: Electron.SaveDialogOptions): Promise { - return Promise.resolve(undefined); + throw new Error('not implemented'); } showOpenDialog(_options: Electron.OpenDialogOptions): Promise { - return Promise.resolve(undefined); + throw new Error('not implemented'); } updateTouchBar(_items: ISerializableCommandAction[][]): Promise { @@ -1223,8 +1350,8 @@ export class TestWindowsService implements IWindowsService { return Promise.resolve(); } - enterWorkspace(_windowId: number, _path: URI): Promise { - return Promise.resolve(); + enterWorkspace(_windowId: number, _path: URI): Promise { + return Promise.resolve(undefined); } toggleFullScreen(_windowId: number): Promise { @@ -1235,7 +1362,7 @@ export class TestWindowsService implements IWindowsService { return Promise.resolve(); } - addRecentlyOpened(_files: URI[]): Promise { + addRecentlyOpened(_recents: IRecent[]): Promise { return Promise.resolve(); } @@ -1248,7 +1375,10 @@ export class TestWindowsService implements IWindowsService { } getRecentlyOpened(_windowId: number): Promise { - return Promise.resolve(); + return Promise.resolve({ + workspaces: [], + files: [] + }); } focusWindow(_windowId: number): Promise { @@ -1260,7 +1390,7 @@ export class TestWindowsService implements IWindowsService { } isMaximized(_windowId: number): Promise { - return Promise.resolve(); + return Promise.resolve(false); } maximizeWindow(_windowId: number): Promise { @@ -1300,7 +1430,7 @@ export class TestWindowsService implements IWindowsService { } // Global methods - openWindow(_windowId: number, _paths: URI[], _options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): Promise { + openWindow(_windowId: number, _uris: IURIToOpen[], _options?: { forceNewWindow?: boolean, forceReuseWindow?: boolean, forceOpenWorkspaceAsFile?: boolean }): Promise { return Promise.resolve(); } @@ -1313,7 +1443,7 @@ export class TestWindowsService implements IWindowsService { } getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]> { - return Promise.resolve(); + throw new Error('not implemented'); } getWindowCount(): Promise { @@ -1324,7 +1454,7 @@ export class TestWindowsService implements IWindowsService { return Promise.resolve(); } - showItemInFolder(_path: string): Promise { + showItemInFolder(_path: URI): Promise { return Promise.resolve(); } @@ -1372,15 +1502,15 @@ export class TestWindowsService implements IWindowsService { } showMessageBox(_windowId: number, _options: Electron.MessageBoxOptions): Promise { - return Promise.resolve(); + throw new Error('not implemented'); } showSaveDialog(_windowId: number, _options: Electron.SaveDialogOptions): Promise { - return Promise.resolve(); + throw new Error('not implemented'); } showOpenDialog(_windowId: number, _options: Electron.OpenDialogOptions): Promise { - return Promise.resolve(); + throw new Error('not implemented'); } openAboutDialog(): Promise { @@ -1404,8 +1534,8 @@ export class TestTextResourceConfigurationService implements ITextResourceConfig } getValue(resource: URI, arg2?: any, arg3?: any): T { - const position: IPosition = EditorPosition.isIPosition(arg2) ? arg2 : null; - const section: string = position ? (typeof arg3 === 'string' ? arg3 : undefined) : (typeof arg2 === 'string' ? arg2 : undefined); + const position: IPosition | null = EditorPosition.isIPosition(arg2) ? arg2 : null; + const section: string | undefined = position ? (typeof arg3 === 'string' ? arg3 : undefined) : (typeof arg2 === 'string' ? arg2 : undefined); return this.configurationService.getValue(section, { resource }); } } @@ -1434,36 +1564,18 @@ export class TestTextResourcePropertiesService implements ITextResourcePropertie export class TestHashService implements IHashService { _serviceBrand: any; - createSHA1(content: string): string { - return content; + createSHA1(content: string): Thenable { + return Promise.resolve(content); } } -export class TestViewletService implements IViewletService { +export class TestSharedProcessService implements ISharedProcessService { _serviceBrand: ServiceIdentifier; - readonly onDidViewletRegister: Event = new Emitter().event; - readonly onDidViewletDeregister: Event = new Emitter().event; - onDidViewletOpen: Event = new Emitter().event; - onDidViewletClose: Event = new Emitter().event; + getChannel(channelName: string): any { + return undefined; + } - openViewlet(_id: string, _focus?: boolean): Promise { return null; } - - getActiveViewlet(): IViewlet { return null; } - - getDefaultViewletId(): string { return null; } - - getViewlet(_id: string): ViewletDescriptor { return null; } - - getAllViewlets(): ViewletDescriptor[] { return null; } - - getViewlets(): ViewletDescriptor[] { return null; } - - getProgressIndicator(_id: string): IProgressService { return null; } - -} - -export function getRandomTestPath(tmpdir: string, ...segments: string[]): string { - return paths.join(tmpdir, ...segments, generateUuid()); -} + registerChannel(channelName: string, channel: any): void { } +} \ No newline at end of file diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index eead2016c2..1c8180d9da 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -9,7 +9,7 @@ import 'vs/editor/editor.all'; import 'vs/workbench/api/electron-browser/extensionHost.contribution'; -import 'vs/workbench/electron-browser/shell.contribution'; +import 'vs/workbench/electron-browser/main.contribution'; import 'vs/workbench/browser/workbench.contribution'; import 'vs/workbench/electron-browser/main'; @@ -22,7 +22,7 @@ import 'vs/workbench/electron-browser/main'; import 'vs/workbench/browser/actions/layoutActions'; import 'vs/workbench/browser/actions/listCommands'; import 'vs/workbench/browser/actions/navigationActions'; -import 'vs/workbench/browser/parts/quickopen/quickopenActions'; +import 'vs/workbench/browser/parts/quickopen/quickOpenActions'; import 'vs/workbench/browser/parts/quickinput/quickInputActions'; //#endregion @@ -38,145 +38,289 @@ import 'vs/workbench/api/browser/viewsExtensionPoint'; //#region --- workbench services +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IMenuService } from 'vs/platform/actions/common/actions'; +import { MenuService } from 'vs/platform/actions/common/menuService'; +import { IListService, ListService } from 'vs/platform/list/browser/listService'; +import { OpenerService } from 'vs/editor/browser/services/openerService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl'; +import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; +import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; +import { IMarkerService } from 'vs/platform/markers/common/markers'; +import { MarkerService } from 'vs/platform/markers/common/markerService'; +import { IDownloadService } from 'vs/platform/download/common/download'; +import { DownloadService } from 'vs/platform/download/node/downloadService'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { ClipboardService } from 'vs/platform/clipboard/electron-browser/clipboardService'; +import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { AccessibilityService } from 'vs/platform/accessibility/node/accessibilityService'; +import { IExtensionEnablementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; +import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; +import { IRequestService } from 'vs/platform/request/node/request'; +import { RequestService } from 'vs/platform/request/electron-browser/requestService'; +import { LifecycleService } from 'vs/platform/lifecycle/electron-browser/lifecycleService'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; +import { LocalizationsService } from 'vs/platform/localizations/electron-browser/localizationsService'; +import { ISharedProcessService, SharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-browser/remoteAuthorityResolverService'; +import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { TelemetryService } from 'vs/platform/telemetry/electron-browser/telemetryService'; +import { IProductService } from 'vs/platform/product/common/product'; +import { ProductService } from 'vs/platform/product/node/productService'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; +import { IUpdateService } from 'vs/platform/update/common/update'; +import { UpdateService } from 'vs/platform/update/electron-browser/updateService'; +import { IIssueService } from 'vs/platform/issue/common/issue'; +import { IssueService } from 'vs/platform/issue/electron-browser/issueService'; +import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { WorkspacesService } from 'vs/platform/workspaces/electron-browser/workspacesService'; +import { IMenubarService } from 'vs/platform/menubar/common/menubar'; +import { MenubarService } from 'vs/platform/menubar/electron-browser/menubarService'; +import { IURLService } from 'vs/platform/url/common/url'; +import { RelayURLService } from 'vs/platform/url/electron-browser/urlService'; -import 'vs/workbench/services/bulkEdit/electron-browser/bulkEditService'; +import 'vs/workbench/services/bulkEdit/browser/bulkEditService'; +import 'vs/workbench/services/integrity/node/integrityService'; +import 'vs/workbench/services/keybinding/common/keybindingEditing'; +import 'vs/workbench/services/hash/node/hashService'; +import 'vs/workbench/services/textMate/electron-browser/textMateService'; +import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; +import 'vs/workbench/services/workspace/node/workspaceEditingService'; +import 'vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler'; +import 'vs/workbench/services/decorations/browser/decorationsService'; +import 'vs/workbench/services/search/node/searchService'; +import 'vs/workbench/services/progress/browser/progressService2'; +import 'vs/workbench/services/editor/browser/codeEditorService'; +import 'vs/workbench/services/broadcast/electron-browser/broadcastService'; +import 'vs/workbench/services/preferences/browser/preferencesService'; +import 'vs/workbench/services/output/node/outputChannelModelService'; +import 'vs/workbench/services/configuration/common/jsonEditingService'; +import 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; +import 'vs/workbench/services/textfile/common/textFileService'; +import 'vs/workbench/services/dialogs/browser/fileDialogService'; +import 'vs/workbench/services/dialogs/electron-browser/dialogService'; +import 'vs/workbench/services/backup/node/backupFileService'; +import 'vs/workbench/services/editor/browser/editorService'; +import 'vs/workbench/services/history/browser/history'; +import 'vs/workbench/services/files/node/remoteFileService'; +import 'vs/workbench/services/activity/browser/activityService'; +import 'vs/workbench/browser/parts/views/views'; +import 'vs/workbench/services/keybinding/electron-browser/keybindingService'; +import 'vs/workbench/services/untitled/common/untitledEditorService'; +import 'vs/workbench/services/textfile/node/textResourcePropertiesService'; +import 'vs/workbench/services/mode/common/workbenchModeService'; +import 'vs/workbench/services/commands/common/commandService'; +import 'vs/workbench/services/themes/browser/workbenchThemeService'; +import 'vs/workbench/services/extensions/electron-browser/extensionService'; +import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService'; +import 'vs/workbench/services/extensionManagement/node/multiExtensionManagement'; +import 'vs/workbench/services/label/common/labelService'; +import 'vs/workbench/services/extensions/electron-browser/extensionManagementServerService'; +import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; +import 'vs/workbench/services/notification/common/notificationService'; +import 'vs/workbench/services/remote/common/remoteEnvironmentService'; + +registerSingleton(IMenuService, MenuService, true); +registerSingleton(IListService, ListService, true); +registerSingleton(IOpenerService, OpenerService, true); +registerSingleton(IEditorWorkerService, EditorWorkerServiceImpl); +registerSingleton(IMarkerDecorationsService, MarkerDecorationsService); +registerSingleton(IMarkerService, MarkerService, true); +registerSingleton(IDownloadService, DownloadService, true); +registerSingleton(IClipboardService, ClipboardService, true); +registerSingleton(IContextKeyService, ContextKeyService); +registerSingleton(IModelService, ModelServiceImpl, true); +registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService); +registerSingleton(IAccessibilityService, AccessibilityService, true); +registerSingleton(IExtensionEnablementService, ExtensionEnablementService, true); +registerSingleton(IContextViewService, ContextViewService, true); +registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); +registerSingleton(IRequestService, RequestService, true); +registerSingleton(ILifecycleService, LifecycleService); +registerSingleton(ILocalizationsService, LocalizationsService); +registerSingleton(ISharedProcessService, SharedProcessService, true); +registerSingleton(IRemoteAuthorityResolverService, RemoteAuthorityResolverService, true); +registerSingleton(ITelemetryService, TelemetryService); +registerSingleton(IProductService, ProductService, true); +registerSingleton(IWindowsService, WindowsService); +registerSingleton(IUpdateService, UpdateService); +registerSingleton(IIssueService, IssueService); +registerSingleton(IWorkspacesService, WorkspacesService); +registerSingleton(IMenubarService, MenubarService); +registerSingleton(IURLService, RelayURLService); //#endregion - //#region --- workbench parts +import 'vs/workbench/browser/parts/quickinput/quickInput'; +import 'vs/workbench/browser/parts/quickopen/quickOpenController'; +import 'vs/workbench/browser/parts/titlebar/titlebarPart'; +import 'vs/workbench/browser/parts/editor/editorPart'; +import 'vs/workbench/browser/parts/activitybar/activitybarPart'; +import 'vs/workbench/browser/parts/panel/panelPart'; +import 'vs/workbench/browser/parts/sidebar/sidebarPart'; +import 'vs/workbench/browser/parts/statusbar/statusbarPart'; + +//#endregion + +//#region --- workbench contributions + +// Telemetry +import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; + // Localizations -import 'vs/workbench/parts/localizations/electron-browser/localizations.contribution'; +import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; // Preferences -import 'vs/workbench/parts/preferences/electron-browser/preferences.contribution'; -import 'vs/workbench/parts/preferences/browser/keybindingsEditorContribution'; +import 'vs/workbench/contrib/preferences/electron-browser/preferences.contribution'; +import 'vs/workbench/contrib/preferences/browser/keybindingsEditorContribution'; // Logs -import 'vs/workbench/parts/logs/electron-browser/logs.contribution'; +import 'vs/workbench/contrib/logs/common/logs.contribution'; // Quick Open Handlers -import 'vs/workbench/parts/quickopen/browser/quickopen.contribution'; +import 'vs/workbench/contrib/quickopen/browser/quickopen.contribution'; // Explorer -import 'vs/workbench/parts/files/electron-browser/explorerViewlet'; -import 'vs/workbench/parts/files/electron-browser/fileActions.contribution'; -import 'vs/workbench/parts/files/electron-browser/files.contribution'; +import 'vs/workbench/contrib/files/browser/explorerViewlet'; +import 'vs/workbench/contrib/files/browser/fileActions.contribution'; +import 'vs/workbench/contrib/files/browser/files.contribution'; // Backup -import 'vs/workbench/parts/backup/common/backup.contribution'; +import 'vs/workbench/contrib/backup/common/backup.contribution'; // Stats -import 'vs/workbench/parts/stats/node/stats.contribution'; +import 'vs/workbench/contrib/stats/node/stats.contribution'; // Rapid Render Splash -import 'vs/workbench/parts/splash/electron-browser/partsSplash.contribution'; +import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; // Search -import 'vs/workbench/parts/search/electron-browser/search.contribution'; -import 'vs/workbench/parts/search/browser/searchView'; -import 'vs/workbench/parts/search/browser/openAnythingHandler'; +import 'vs/workbench/contrib/search/browser/search.contribution'; +import 'vs/workbench/contrib/search/browser/searchView'; +import 'vs/workbench/contrib/search/browser/openAnythingHandler'; // SCM -import 'vs/workbench/parts/scm/electron-browser/scm.contribution'; -import 'vs/workbench/parts/scm/electron-browser/scmViewlet'; +import 'vs/workbench/contrib/scm/browser/scm.contribution'; +import 'vs/workbench/contrib/scm/browser/scmViewlet'; // {{SQL CARBON EDIT}} // Debug -// import 'vs/workbench/parts/debug/electron-browser/debug.contribution'; -// import 'vs/workbench/parts/debug/browser/debugQuickOpen'; -// import 'vs/workbench/parts/debug/electron-browser/repl'; -// import 'vs/workbench/parts/debug/browser/debugViewlet'; +// import 'vs/workbench/contrib/debug/electron-browser/debug.contribution'; +// import 'vs/workbench/contrib/debug/browser/debugQuickOpen'; +// import 'vs/workbench/contrib/debug/browser/debugEditorContribution'; +// import 'vs/workbench/contrib/debug/browser/repl'; +// import 'vs/workbench/contrib/debug/browser/debugViewlet'; // Markers -import 'vs/workbench/parts/markers/electron-browser/markers.contribution'; +import 'vs/workbench/contrib/markers/browser/markers.contribution'; // Comments -import 'vs/workbench/parts/comments/electron-browser/comments.contribution'; - -// HTML Preview -import 'vs/workbench/parts/html/electron-browser/html.contribution'; +import 'vs/workbench/contrib/comments/electron-browser/comments.contribution'; // URL Support -import 'vs/workbench/parts/url/electron-browser/url.contribution'; +import 'vs/workbench/contrib/url/common/url.contribution'; -// {{SQL CARBON EDIT}} // Webview -// import 'vs/workbench/parts/webview/electron-browser/webview.contribution'; +import 'vs/workbench/contrib/webview/electron-browser/webview.contribution'; // Extensions Management -import 'vs/workbench/parts/extensions/electron-browser/extensions.contribution'; -import 'vs/workbench/parts/extensions/browser/extensionsQuickOpen'; -import 'vs/workbench/parts/extensions/electron-browser/extensionsViewlet'; +import 'vs/workbench/contrib/extensions/electron-browser/extensions.contribution'; +import 'vs/workbench/contrib/extensions/browser/extensionsQuickOpen'; +import 'vs/workbench/contrib/extensions/electron-browser/extensionsViewlet'; // Output Panel -import 'vs/workbench/parts/output/electron-browser/output.contribution'; -import 'vs/workbench/parts/output/browser/outputPanel'; +import 'vs/workbench/contrib/output/browser/output.contribution'; +import 'vs/workbench/contrib/output/browser/outputPanel'; // Terminal -import 'vs/workbench/parts/terminal/electron-browser/terminal.contribution'; -import 'vs/workbench/parts/terminal/browser/terminalQuickOpen'; -import 'vs/workbench/parts/terminal/electron-browser/terminalPanel'; +import 'vs/workbench/contrib/terminal/browser/terminal.contribution'; +import 'vs/workbench/contrib/terminal/electron-browser/terminal.contribution'; +import 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; +import 'vs/workbench/contrib/terminal/browser/terminalPanel'; // Relauncher -import 'vs/workbench/parts/relauncher/electron-browser/relauncher.contribution'; +import 'vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution'; // Tasks -import 'vs/workbench/parts/tasks/electron-browser/task.contribution'; +import 'vs/workbench/contrib/tasks/electron-browser/task.contribution'; // {{SQL CARBON EDIT}} // Emmet -// import 'vs/workbench/parts/emmet/browser/emmet.browser.contribution'; -// import 'vs/workbench/parts/emmet/electron-browser/emmet.contribution'; +// import 'vs/workbench/contrib/emmet/browser/emmet.contribution'; // CodeEditor Contributions -import 'vs/workbench/parts/codeEditor/electron-browser/codeEditor.contribution'; +import 'vs/workbench/contrib/codeEditor/browser/codeEditor.contribution'; +import 'vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution'; // Execution -import 'vs/workbench/parts/execution/electron-browser/execution.contribution'; +import 'vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.contribution'; // Snippets -import 'vs/workbench/parts/snippets/electron-browser/snippets.contribution'; -import 'vs/workbench/parts/snippets/electron-browser/snippetsService'; -import 'vs/workbench/parts/snippets/electron-browser/insertSnippet'; -import 'vs/workbench/parts/snippets/electron-browser/configureSnippets'; -import 'vs/workbench/parts/snippets/electron-browser/tabCompletion'; +import 'vs/workbench/contrib/snippets/browser/snippets.contribution'; +import 'vs/workbench/contrib/snippets/browser/snippetsService'; +import 'vs/workbench/contrib/snippets/browser/insertSnippet'; +import 'vs/workbench/contrib/snippets/browser/configureSnippets'; +import 'vs/workbench/contrib/snippets/browser/tabCompletion'; + +// Formatter Help +import 'vs/workbench/contrib/format/browser/format.contribution'; // Send a Smile -import 'vs/workbench/parts/feedback/electron-browser/feedback.contribution'; +import 'vs/workbench/contrib/feedback/electron-browser/feedback.contribution'; // Update -import 'vs/workbench/parts/update/electron-browser/update.contribution'; +import 'vs/workbench/contrib/update/electron-browser/update.contribution'; // Surveys -import 'vs/workbench/parts/surveys/electron-browser/nps.contribution'; -import 'vs/workbench/parts/surveys/electron-browser/languageSurveys.contribution'; +import 'vs/workbench/contrib/surveys/electron-browser/nps.contribution'; +import 'vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution'; // Performance -import 'vs/workbench/parts/performance/electron-browser/performance.contribution'; +import 'vs/workbench/contrib/performance/electron-browser/performance.contribution'; // CLI -import 'vs/workbench/parts/cli/electron-browser/cli.contribution'; +import 'vs/workbench/contrib/cli/node/cli.contribution'; // Themes Support -import 'vs/workbench/parts/themes/electron-browser/themes.contribution'; -import 'vs/workbench/parts/themes/test/electron-browser/themes.test.contribution'; +import 'vs/workbench/contrib/themes/browser/themes.contribution'; +import 'vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution'; // Watermark -import 'vs/workbench/parts/watermark/electron-browser/watermark'; +import 'vs/workbench/contrib/watermark/browser/watermark'; // Welcome -import 'vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution'; -import 'vs/workbench/parts/welcome/gettingStarted/electron-browser/gettingStarted.contribution'; -import 'vs/workbench/parts/welcome/overlay/browser/welcomeOverlay'; -import 'vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution'; +import 'vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution'; +import 'vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution'; +import 'vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay'; +import 'vs/workbench/contrib/welcome/page/browser/welcomePage.contribution'; // Outline -import 'vs/workbench/parts/outline/electron-browser/outline.contribution'; +import 'vs/workbench/contrib/outline/browser/outline.contribution'; // Experiments -import 'vs/workbench/parts/experiments/electron-browser/experiments.contribution'; +import 'vs/workbench/contrib/experiments/electron-browser/experiments.contribution'; + +// Code Insets +import 'vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution'; + +// Issues +import 'vs/workbench/contrib/issue/electron-browser/issue.contribution'; // {{SQL CARBON EDIT}} // SQL diff --git a/src/typings/original-fs.d.ts.temp b/src/vs/workbench/workbench.nodeless.main.css similarity index 78% rename from src/typings/original-fs.d.ts.temp rename to src/vs/workbench/workbench.nodeless.main.css index 7b23b70974..04a7af7a08 100644 --- a/src/typings/original-fs.d.ts.temp +++ b/src/vs/workbench/workbench.nodeless.main.css @@ -3,8 +3,7 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -declare module 'original-fs' { - import * as fs from 'fs'; +/* NOTE: THIS FILE WILL BE OVERWRITTEN DURING BUILD TIME, DO NOT EDIT */ - //export = fs; -} \ No newline at end of file +div.monaco.main.css { +} diff --git a/src/typings/splash.d.ts b/src/vs/workbench/workbench.nodeless.main.nls.js similarity index 80% rename from src/typings/splash.d.ts rename to src/vs/workbench/workbench.nodeless.main.nls.js index 3ced19979d..28f20a5e93 100644 --- a/src/typings/splash.d.ts +++ b/src/vs/workbench/workbench.nodeless.main.nls.js @@ -3,6 +3,6 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -declare function hideSplash(): void; +// NOTE: THIS FILE WILL BE OVERWRITTEN DURING BUILD TIME, DO NOT EDIT -declare const splash: Electron.BrowserWindow; \ No newline at end of file +define([], {}); diff --git a/src/vs/workbench/workbench.nodeless.main.ts b/src/vs/workbench/workbench.nodeless.main.ts new file mode 100644 index 0000000000..4135e9f59c --- /dev/null +++ b/src/vs/workbench/workbench.nodeless.main.ts @@ -0,0 +1,329 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the Source EULA. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//#region --- workbench/editor core + +import 'vs/editor/editor.all'; + +// import 'vs/workbench/api/electron-browser/extensionHost.contribution'; + +// import 'vs/workbench/electron-browser/main.contribution'; +import 'vs/workbench/browser/workbench.contribution'; + +import 'vs/workbench/browser/nodeless.main'; + +//#endregion + + +//#region --- workbench actions + +import 'vs/workbench/browser/actions/layoutActions'; +import 'vs/workbench/browser/actions/listCommands'; +import 'vs/workbench/browser/actions/navigationActions'; +import 'vs/workbench/browser/parts/quickopen/quickOpenActions'; +import 'vs/workbench/browser/parts/quickinput/quickInputActions'; + +//#endregion + + +//#region --- API Extension Points + +import 'vs/workbench/api/common/menusExtensionPoint'; +import 'vs/workbench/api/common/configurationExtensionPoint'; +import 'vs/workbench/api/browser/viewsExtensionPoint'; + +//#endregion + + +//#region --- workbench services +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IMenuService } from 'vs/platform/actions/common/actions'; +import { MenuService } from 'vs/platform/actions/common/menuService'; +import { IListService, ListService } from 'vs/platform/list/browser/listService'; +import { OpenerService } from 'vs/editor/browser/services/openerService'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { EditorWorkerServiceImpl } from 'vs/editor/common/services/editorWorkerServiceImpl'; +import { MarkerDecorationsService } from 'vs/editor/common/services/markerDecorationsServiceImpl'; +import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService'; +import { IMarkerService } from 'vs/platform/markers/common/markers'; +import { MarkerService } from 'vs/platform/markers/common/markerService'; +// import { IDownloadService } from 'vs/platform/download/common/download'; +// import { DownloadService } from 'vs/platform/download/node/downloadService'; +// import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +// import { ClipboardService } from 'vs/platform/clipboard/electron-browser/clipboardService'; +import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { TextResourceConfigurationService } from 'vs/editor/common/services/resourceConfigurationImpl'; +import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; +import { BrowserAccessibilityService } from 'vs/platform/accessibility/common/accessibilityService'; +import { IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; +import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; +import { ContextMenuService } from 'vs/platform/contextview/browser/contextMenuService'; +import { ContextViewService } from 'vs/platform/contextview/browser/contextViewService'; +// import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; +// import { IRequestService } from 'vs/platform/request/node/request'; +// import { RequestService } from 'vs/platform/request/electron-browser/requestService'; +// import { LifecycleService } from 'vs/platform/lifecycle/electron-browser/lifecycleService'; +// import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; +// import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; +// import { LocalizationsService } from 'vs/platform/localizations/electron-browser/localizationsService'; +// import { ISharedProcessService, SharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService'; +// import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-browser/remoteAuthorityResolverService'; +// import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; +// import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +// import { TelemetryService } from 'vs/platform/telemetry/electron-browser/telemetryService'; +// import { IProductService } from 'vs/platform/product/common/product'; +// import { ProductService } from 'vs/platform/product/node/productService'; +// import { IWindowsService } from 'vs/platform/windows/common/windows'; +// import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; +// import { IUpdateService } from 'vs/platform/update/common/update'; +// import { UpdateService } from 'vs/platform/update/electron-browser/updateService'; +// import { IIssueService } from 'vs/platform/issue/common/issue'; +// import { IssueService } from 'vs/platform/issue/electron-browser/issueService'; +// import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +// import { WorkspacesService } from 'vs/platform/workspaces/electron-browser/workspacesService'; +// import { IMenubarService } from 'vs/platform/menubar/common/menubar'; +// import { MenubarService } from 'vs/platform/menubar/electron-browser/menubarService'; +// import { IURLService } from 'vs/platform/url/common/url'; +// import { RelayURLService } from 'vs/platform/url/electron-browser/urlService'; + +import 'vs/workbench/browser/nodeless.simpleservices'; + +import 'vs/workbench/services/bulkEdit/browser/bulkEditService'; +// import 'vs/workbench/services/integrity/node/integrityService'; +import 'vs/workbench/services/keybinding/common/keybindingEditing'; +import 'vs/workbench/services/hash/common/hashService'; +// import 'vs/workbench/services/textMate/electron-browser/textMateService'; +import 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; +// import 'vs/workbench/services/workspace/node/workspaceEditingService'; +// import 'vs/workbench/services/extensions/electron-browser/inactiveExtensionUrlHandler'; +import 'vs/workbench/services/decorations/browser/decorationsService'; +// import 'vs/workbench/services/search/node/searchService'; +import 'vs/workbench/services/progress/browser/progressService2'; +import 'vs/workbench/services/editor/browser/codeEditorService'; +// import 'vs/workbench/services/broadcast/electron-browser/broadcastService'; +import 'vs/workbench/services/preferences/browser/preferencesService'; +import 'vs/workbench/services/output/common/outputChannelModelService'; +import 'vs/workbench/services/configuration/common/jsonEditingService'; +import 'vs/workbench/services/textmodelResolver/common/textModelResolverService'; +import 'vs/workbench/services/textfile/common/textFileService'; +import 'vs/workbench/services/dialogs/browser/fileDialogService'; +// import 'vs/workbench/services/dialogs/electron-browser/dialogService'; +// import 'vs/workbench/services/backup/node/backupFileService'; +import 'vs/workbench/services/editor/browser/editorService'; +import 'vs/workbench/services/history/browser/history'; +// import 'vs/workbench/services/files/node/remoteFileService'; +import 'vs/workbench/services/activity/browser/activityService'; +import 'vs/workbench/browser/parts/views/views'; +// import 'vs/workbench/services/keybinding/electron-browser/keybindingService'; +import 'vs/workbench/services/untitled/common/untitledEditorService'; +// import 'vs/workbench/services/textfile/node/textResourcePropertiesService'; +import 'vs/workbench/services/mode/common/workbenchModeService'; +import 'vs/workbench/services/commands/common/commandService'; +import 'vs/workbench/services/themes/browser/workbenchThemeService'; +// import 'vs/workbench/services/extensions/electron-browser/extensionService'; +// import 'vs/workbench/services/contextmenu/electron-browser/contextmenuService'; +// import 'vs/workbench/services/extensionManagement/node/multiExtensionManagement'; +import 'vs/workbench/services/label/common/labelService'; +// import 'vs/workbench/services/extensions/electron-browser/extensionManagementServerService'; +// import 'vs/workbench/services/remote/electron-browser/remoteAgentServiceImpl'; +import 'vs/workbench/services/notification/common/notificationService'; +import 'vs/workbench/services/remote/common/remoteEnvironmentService'; + + +registerSingleton(IMenuService, MenuService, true); +registerSingleton(IListService, ListService, true); +registerSingleton(IOpenerService, OpenerService, true); +registerSingleton(IEditorWorkerService, EditorWorkerServiceImpl); +registerSingleton(IMarkerDecorationsService, MarkerDecorationsService); +registerSingleton(IMarkerService, MarkerService, true); +// registerSingleton(IDownloadService, DownloadService, true); +// registerSingleton(IClipboardService, ClipboardService, true); +registerSingleton(IContextKeyService, ContextKeyService); +registerSingleton(IModelService, ModelServiceImpl, true); +registerSingleton(ITextResourceConfigurationService, TextResourceConfigurationService); +registerSingleton(IAccessibilityService, BrowserAccessibilityService, true); +registerSingleton(IExtensionEnablementService, ExtensionEnablementService, true); +registerSingleton(IContextViewService, ContextViewService, true); +// registerSingleton(IExtensionGalleryService, ExtensionGalleryService, true); +// registerSingleton(IRequestService, RequestService, true); +// registerSingleton(ILifecycleService, LifecycleService); +// registerSingleton(ILocalizationsService, LocalizationsService); +// registerSingleton(ISharedProcessService, SharedProcessService, true); +// registerSingleton(IRemoteAuthorityResolverService, RemoteAuthorityResolverService, true); +// registerSingleton(ITelemetryService, TelemetryService); +// registerSingleton(IProductService, ProductService, true); +// registerSingleton(IWindowsService, WindowsService); +// registerSingleton(IUpdateService, UpdateService); +// registerSingleton(IIssueService, IssueService); +// registerSingleton(IWorkspacesService, WorkspacesService); +// registerSingleton(IMenubarService, MenubarService); +// registerSingleton(IURLService, RelayURLService); + +registerSingleton(IContextMenuService, ContextMenuService); + +//#endregion + +//#region --- workbench parts + +import 'vs/workbench/browser/parts/quickinput/quickInput'; +import 'vs/workbench/browser/parts/quickopen/quickOpenController'; +import 'vs/workbench/browser/parts/titlebar/titlebarPart'; +import 'vs/workbench/browser/parts/editor/editorPart'; +import 'vs/workbench/browser/parts/activitybar/activitybarPart'; +import 'vs/workbench/browser/parts/panel/panelPart'; +import 'vs/workbench/browser/parts/sidebar/sidebarPart'; +import 'vs/workbench/browser/parts/statusbar/statusbarPart'; + +//#endregion + +//#region --- workbench contributions + +// Telemetry +import 'vs/workbench/contrib/telemetry/browser/telemetry.contribution'; + +// Localizations +// import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; + +// Preferences +// import 'vs/workbench/contrib/preferences/electron-browser/preferences.contribution'; +import 'vs/workbench/contrib/preferences/browser/keybindingsEditorContribution'; + +// Logs +import 'vs/workbench/contrib/logs/common/logs.contribution'; + +// Quick Open Handlers +import 'vs/workbench/contrib/quickopen/browser/quickopen.contribution'; + +// Explorer +import 'vs/workbench/contrib/files/browser/explorerViewlet'; +import 'vs/workbench/contrib/files/browser/fileActions.contribution'; +import 'vs/workbench/contrib/files/browser/files.contribution'; + +// Backup +import 'vs/workbench/contrib/backup/common/backup.contribution'; + +// Stats +// import 'vs/workbench/contrib/stats/node/stats.contribution'; + +// Rapid Render Splash +// import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; + +// Search +import 'vs/workbench/contrib/search/browser/search.contribution'; +import 'vs/workbench/contrib/search/browser/searchView'; +import 'vs/workbench/contrib/search/browser/openAnythingHandler'; + +// SCM +import 'vs/workbench/contrib/scm/browser/scm.contribution'; +import 'vs/workbench/contrib/scm/browser/scmViewlet'; + +// Debug +// import 'vs/workbench/contrib/debug/electron-browser/debug.contribution'; +// import 'vs/workbench/contrib/debug/browser/debugQuickOpen'; +// import 'vs/workbench/contrib/debug/browser/debugEditorContribution'; +// import 'vs/workbench/contrib/debug/browser/repl'; +// import 'vs/workbench/contrib/debug/browser/debugViewlet'; + +// Markers +import 'vs/workbench/contrib/markers/browser/markers.contribution'; + +// Comments +// import 'vs/workbench/contrib/comments/electron-browser/comments.contribution'; + +// URL Support +import 'vs/workbench/contrib/url/common/url.contribution'; + +// Webview +// import 'vs/workbench/contrib/webview/electron-browser/webview.contribution'; + +// Extensions Management +// import 'vs/workbench/contrib/extensions/electron-browser/extensions.contribution'; +// import 'vs/workbench/contrib/extensions/browser/extensionsQuickOpen'; +// import 'vs/workbench/contrib/extensions/electron-browser/extensionsViewlet'; + +// Output Panel +import 'vs/workbench/contrib/output/browser/output.contribution'; +import 'vs/workbench/contrib/output/browser/outputPanel'; + +// Terminal +// import 'vs/workbench/contrib/terminal/browser/terminal.contribution'; +// import 'vs/workbench/contrib/terminal/electron-browser/terminal.contribution'; +// import 'vs/workbench/contrib/terminal/browser/terminalQuickOpen'; +// import 'vs/workbench/contrib/terminal/browser/terminalPanel'; + +// Relauncher +// import 'vs/workbench/contrib/relauncher/electron-browser/relauncher.contribution'; + +// Tasks +// import 'vs/workbench/contrib/tasks/electron-browser/task.contribution'; + +// Emmet +import 'vs/workbench/contrib/emmet/browser/emmet.contribution'; + +// CodeEditor Contributions +import 'vs/workbench/contrib/codeEditor/browser/codeEditor.contribution'; +// import 'vs/workbench/contrib/codeEditor/electron-browser/codeEditor.contribution'; + +// Execution +// import 'vs/workbench/contrib/externalTerminal/electron-browser/externalTerminal.contribution'; + +// Snippets +import 'vs/workbench/contrib/snippets/browser/snippets.contribution'; +import 'vs/workbench/contrib/snippets/browser/snippetsService'; +import 'vs/workbench/contrib/snippets/browser/insertSnippet'; +import 'vs/workbench/contrib/snippets/browser/configureSnippets'; +import 'vs/workbench/contrib/snippets/browser/tabCompletion'; + +// Formatter Help +import 'vs/workbench/contrib/format/browser/format.contribution'; + +// Send a Smile +// import 'vs/workbench/contrib/feedback/electron-browser/feedback.contribution'; + +// Update +// import 'vs/workbench/contrib/update/electron-browser/update.contribution'; + +// Surveys +// import 'vs/workbench/contrib/surveys/electron-browser/nps.contribution'; +// import 'vs/workbench/contrib/surveys/electron-browser/languageSurveys.contribution'; + +// Performance +// import 'vs/workbench/contrib/performance/electron-browser/performance.contribution'; + +// CLI +// import 'vs/workbench/contrib/cli/node/cli.contribution'; + +// Themes Support +import 'vs/workbench/contrib/themes/browser/themes.contribution'; +// import 'vs/workbench/contrib/themes/test/electron-browser/themes.test.contribution'; + +// Watermark +import 'vs/workbench/contrib/watermark/browser/watermark'; + +// Welcome +import 'vs/workbench/contrib/welcome/walkThrough/browser/walkThrough.contribution'; +// import 'vs/workbench/contrib/welcome/gettingStarted/electron-browser/gettingStarted.contribution'; +import 'vs/workbench/contrib/welcome/overlay/browser/welcomeOverlay'; +// import 'vs/workbench/contrib/welcome/page/browser/welcomePage.contribution'; + +// Outline +import 'vs/workbench/contrib/outline/browser/outline.contribution'; + +// Experiments +// import 'vs/workbench/contrib/experiments/electron-browser/experiments.contribution'; + +// Code Insets +// import 'vs/workbench/contrib/codeinset/electron-browser/codeInset.contribution'; + +// Issues +// import 'vs/workbench/contrib/issue/electron-browser/issue.contribution'; + +//#endregion diff --git a/test/smoke/README.md b/test/smoke/README.md index c61944fee9..57c8ada658 100644 --- a/test/smoke/README.md +++ b/test/smoke/README.md @@ -1,5 +1,7 @@ # VS Code Smoke Test +Make sure you are on **Node v10.x**. + ### Run ```bash @@ -7,7 +9,7 @@ yarn smoketest # Build -yarn smoketest --build PATH_TO_BUILD +yarn smoketest --build PATH_TO_BUILD --stable-build PATH_TO_STABLE_BUILD ``` ### Run for a release @@ -17,7 +19,7 @@ You must always run the smoketest version which matches the release you are test ```bash git checkout release/1.22 yarn -yarn smoketest --build PATH_TO_RELEASE_BUILD +yarn smoketest --build PATH_TO_RELEASE_BUILD --stable-build PATH_TO_STABLE_BUILD ``` ### Debug diff --git a/test/smoke/package.json b/test/smoke/package.json index fe5adc1935..bef1e5f59d 100644 --- a/test/smoke/package.json +++ b/test/smoke/package.json @@ -22,7 +22,7 @@ "@types/webdriverio": "4.6.1", "concurrently": "^3.5.1", "cpx": "^1.5.0", - "electron": "3.1.2", + "electron": "3.1.6", "htmlparser2": "^3.9.2", "mkdirp": "^0.5.1", "mocha": "^5.2.0", diff --git a/test/smoke/src/application.ts b/test/smoke/src/application.ts index 5d5dfd611c..0d816a5ff0 100644 --- a/test/smoke/src/application.ts +++ b/test/smoke/src/application.ts @@ -60,7 +60,7 @@ export class Application { return this.options.userDataDir; } - async start(): Promise { + async start(expectWalkthroughPart = true): Promise { await this._start(); //{{SQL CARBON EDIT}} await this.code.waitForElement('.object-explorer-view'); @@ -68,9 +68,12 @@ export class Application { //Original /* await this.code.waitForElement('.explorer-folders-view'); - await this.code.waitForActiveElement(`.editor-instance[id="workbench.editor.walkThroughPart"] > div > div[tabIndex="0"]`); - */ - //{{END}} + + if (expectWalkthroughPart) { + await this.code.waitForActiveElement(`.editor-instance[id="workbench.editor.walkThroughPart"] > div > div[tabIndex="0"]`); + } + */ + //{{SQL CARBON EDIT}} } async restart(options: { workspaceOrFolder?: string, extraArgs?: string[] }): Promise { diff --git a/test/smoke/src/areas/git/git.test.ts b/test/smoke/src/areas/git/git.test.ts index 5d2454380e..30ea90edaf 100644 --- a/test/smoke/src/areas/git/git.test.ts +++ b/test/smoke/src/areas/git/git.test.ts @@ -27,13 +27,13 @@ export function setup() { await app.workbench.editor.waitForTypeInEditor('app.js', '.foo{}'); await app.workbench.editors.saveOpenedFile(); - await app.workbench.quickopen.openFile('index.jade'); - await app.workbench.editor.waitForTypeInEditor('index.jade', 'hello world'); + await app.workbench.quickopen.openFile('index.pug'); + await app.workbench.editor.waitForTypeInEditor('index.pug', 'hello world'); await app.workbench.editors.saveOpenedFile(); await app.workbench.scm.refreshSCMViewlet(); await app.workbench.scm.waitForChange('app.js', 'Modified'); - await app.workbench.scm.waitForChange('index.jade', 'Modified'); + await app.workbench.scm.waitForChange('index.pug', 'Modified'); }); it('opens diff editor', async function () { @@ -66,7 +66,7 @@ export function setup() { await app.code.waitForTextContent(SYNC_STATUSBAR, ' 0↓ 1↑'); await app.workbench.quickopen.runCommand('Git: Stage All Changes'); - await app.workbench.scm.waitForChange('index.jade', 'Index Modified'); + await app.workbench.scm.waitForChange('index.pug', 'Index Modified'); await app.workbench.scm.commit('second commit'); await app.code.waitForTextContent(SYNC_STATUSBAR, ' 0↓ 2↑'); diff --git a/test/smoke/src/areas/search/search.test.ts b/test/smoke/src/areas/search/search.test.ts index 286cd4a1ef..92e870c197 100644 --- a/test/smoke/src/areas/search/search.test.ts +++ b/test/smoke/src/areas/search/search.test.ts @@ -19,7 +19,7 @@ export function setup() { await app.workbench.search.openSearchViewlet(); await app.workbench.search.searchFor('body'); - await app.workbench.search.waitForResultText('21 results in 6 files'); + await app.workbench.search.waitForResultText('16 results in 5 files'); }); it('searches only for *.js files & checks for correct result number', async function () { @@ -38,7 +38,7 @@ export function setup() { const app = this.app as Application; await app.workbench.search.searchFor('body'); await app.workbench.search.removeFileMatch('app.js'); - await app.workbench.search.waitForResultText('17 results in 5 files'); + await app.workbench.search.waitForResultText('12 results in 4 files'); }); it('replaces first search result with a replace term', async function () { @@ -48,7 +48,7 @@ export function setup() { await app.workbench.search.expandReplace(); await app.workbench.search.setReplaceText('ydob'); await app.workbench.search.replaceFileMatch('app.js'); - await app.workbench.search.waitForResultText('17 results in 5 files'); + await app.workbench.search.waitForResultText('16 results in 5 files'); await app.workbench.search.searchFor('ydob'); await app.workbench.search.setReplaceText('body'); diff --git a/test/smoke/src/areas/search/search.ts b/test/smoke/src/areas/search/search.ts index 22426e8e37..ffc2511518 100644 --- a/test/smoke/src/areas/search/search.ts +++ b/test/smoke/src/areas/search/search.ts @@ -6,7 +6,7 @@ import { Viewlet } from '../workbench/viewlet'; import { Code } from '../../vscode/code'; -const VIEWLET = 'div[id="workbench.view.search"].search-view'; +const VIEWLET = '.search-view'; const INPUT = `${VIEWLET} .search-widget .search-container .monaco-inputbox textarea`; const INCLUDE_INPUT = `${VIEWLET} .query-details .file-types.includes .monaco-inputbox input`; const FILE_MATCH = filename => `${VIEWLET} .results .filematch[data-resource$="${filename}"]`; diff --git a/test/smoke/src/areas/workbench/data-migration.test.ts b/test/smoke/src/areas/workbench/data-migration.test.ts index 2ea5674984..02f5d6f5bd 100644 --- a/test/smoke/src/areas/workbench/data-migration.test.ts +++ b/test/smoke/src/areas/workbench/data-migration.test.ts @@ -3,109 +3,93 @@ * Licensed under the Source EULA. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Application, Quality } from '../../application'; -import * as rimraf from 'rimraf'; +import { Application, ApplicationOptions } from '../../application'; +import { join } from 'path'; -export interface ICreateAppFn { - (quality: Quality): Application; -} +export function setup(stableCodePath: string, testDataPath: string) { -export function setup(userDataDir: string, createApp: ICreateAppFn) { - describe('Data Migration', () => { + describe('Data Migration: This test MUST run before releasing by providing the --stable-build command line argument', () => { + it(`verifies opened editors are restored`, async function () { + if (!stableCodePath) { + this.skip(); + } - afterEach(async function () { - await new Promise((c, e) => rimraf(userDataDir, { maxBusyTries: 10 }, err => err ? e(err) : c())); + const userDataDir = join(testDataPath, 'd2'); // different data dir from the other tests + + const stableOptions: ApplicationOptions = Object.assign({}, this.defaultOptions); + stableOptions.codePath = stableCodePath; + stableOptions.userDataDir = userDataDir; + + const stableApp = new Application(stableOptions); + await stableApp!.start(); + + // Open 3 editors and pin 2 of them + await stableApp.workbench.quickopen.openFile('www'); + await stableApp.workbench.quickopen.runCommand('View: Keep Editor'); + + await stableApp.workbench.quickopen.openFile('app.js'); + await stableApp.workbench.quickopen.runCommand('View: Keep Editor'); + + await stableApp.workbench.editors.newUntitledFile(); + + await stableApp.stop(); + + const insiderOptions: ApplicationOptions = Object.assign({}, this.defaultOptions); + insiderOptions.userDataDir = userDataDir; + + const insidersApp = new Application(insiderOptions); + await insidersApp!.start(false /* not expecting walkthrough parth */); + + // Verify 3 editors are open + await insidersApp.workbench.editors.waitForEditorFocus('Untitled-1'); + await insidersApp.workbench.editors.selectTab('app.js'); + await insidersApp.workbench.editors.selectTab('www'); + + await insidersApp.stop(); }); - // it('checks if the Untitled file is restored migrating from stable to latest', async function () { - // const stableApp = createApp(Quality.Stable); + it(`verifies that 'hot exit' works for dirty files`, async function () { + if (!stableCodePath) { + this.skip(); + } - // if (!stableApp) { - // this.skip(); - // return; - // } + const userDataDir = join(testDataPath, 'd3'); // different data dir from the other tests - // await stableApp.start(); + const stableOptions: ApplicationOptions = Object.assign({}, this.defaultOptions); + stableOptions.codePath = stableCodePath; + stableOptions.userDataDir = userDataDir; - // const textToType = 'Very dirty file'; + const stableApp = new Application(stableOptions); + await stableApp!.start(); - // await stableApp.workbench.editors.newUntitledFile(); - // await stableApp.workbench.editor.waitForTypeInEditor('Untitled-1', textToType); + await stableApp.workbench.editors.newUntitledFile(); - // await stableApp.stop(); - // await new Promise(c => setTimeout(c, 500)); // wait until all resources are released (e.g. locked local storage) + const untitled = 'Untitled-1'; + const textToTypeInUntitled = 'Hello, Unitled Code'; + await stableApp.workbench.editor.waitForTypeInEditor(untitled, textToTypeInUntitled); - // // Checking latest version for the restored state - // const app = createApp(Quality.Insiders); + const readmeMd = 'readme.md'; + const textToType = 'Hello, Code'; + await stableApp.workbench.quickopen.openFile(readmeMd); + await stableApp.workbench.editor.waitForTypeInEditor(readmeMd, textToType); - // await app.start(false); + await stableApp.stop(); - // await app.workbench.editors.waitForActiveTab('Untitled-1', true); - // await app.workbench.editor.waitForEditorContents('Untitled-1', c => c.indexOf(textToType) > -1); + const insiderOptions: ApplicationOptions = Object.assign({}, this.defaultOptions); + insiderOptions.userDataDir = userDataDir; - // await app.stop(); - // }); + const insidersApp = new Application(insiderOptions); + await insidersApp!.start(false /* not expecting walkthrough parth */); - // it('checks if the newly created dirty file is restored migrating from stable to latest', async function () { - // const stableApp = createApp(Quality.Stable); + await insidersApp.workbench.editors.waitForActiveTab(readmeMd, true); + await insidersApp.workbench.editor.waitForEditorContents(readmeMd, c => c.indexOf(textToType) > -1); - // if (!stableApp) { - // this.skip(); - // return; - // } + await insidersApp.workbench.editors.waitForTab(untitled, true); + await insidersApp.workbench.editors.selectTab(untitled, true); + await insidersApp.workbench.editor.waitForEditorContents(untitled, c => c.indexOf(textToTypeInUntitled) > -1); - // await stableApp.start(); - - // const fileName = 'app.js'; - // const textPart = 'This is going to be an unsaved file'; - - // await stableApp.workbench.quickopen.openFile(fileName); - - // await stableApp.workbench.editor.waitForTypeInEditor(fileName, textPart); - - // await stableApp.stop(); - // await new Promise(c => setTimeout(c, 500)); // wait until all resources are released (e.g. locked local storage) - - // // Checking latest version for the restored state - // const app = createApp(Quality.Insiders); - - // await app.start(false); - - // await app.workbench.editors.waitForActiveTab(fileName); - // await app.workbench.editor.waitForEditorContents(fileName, c => c.indexOf(textPart) > -1); - - // await app.stop(); - // }); - - // it('checks if opened tabs are restored migrating from stable to latest', async function () { - // const stableApp = createApp(Quality.Stable); - - // if (!stableApp) { - // this.skip(); - // return; - // } - - // await stableApp.start(); - - // const fileName1 = 'app.js', fileName2 = 'jsconfig.json', fileName3 = 'readme.md'; - - // await stableApp.workbench.quickopen.openFile(fileName1); - // await stableApp.workbench.runCommand('View: Keep Editor'); - // await stableApp.workbench.quickopen.openFile(fileName2); - // await stableApp.workbench.runCommand('View: Keep Editor'); - // await stableApp.workbench.quickopen.openFile(fileName3); - // await stableApp.stop(); - - // const app = createApp(Quality.Insiders); - - // await app.start(false); - - // await app.workbench.editors.waitForTab(fileName1); - // await app.workbench.editors.waitForTab(fileName2); - // await app.workbench.editors.waitForTab(fileName3); - - // await app.stop(); - // }); + await insidersApp.stop(); + }); }); } \ No newline at end of file diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 090043f3de..458bf4c52c 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -12,11 +12,12 @@ import * as rimraf from 'rimraf'; import * as mkdirp from 'mkdirp'; import { ncp } from 'ncp'; import { Application, Quality, ApplicationOptions } from './application'; + //{{SQL CARBON EDIT}} import { setup as runProfilerTests } from './sql/profiler/profiler.test'; //Original /* -// import { setup as setupDataMigrationTests } from './areas/workbench/data-migration.test'; +import { setup as setupDataMigrationTests } from './areas/workbench/data-migration.test'; import { setup as setupDataLossTests } from './areas/workbench/data-loss.test'; import { setup as setupDataExplorerTests } from './areas/explorer/explorer.test'; import { setup as setupDataPreferencesTests } from './areas/preferences/preferences.test'; @@ -35,6 +36,11 @@ import { setup as setupLaunchTests } from './areas/workbench/launch.test'; //{{END}} import { MultiLogger, Logger, ConsoleLogger, FileLogger } from './logger'; +if (!/^v10/.test(process.version)) { + console.error('Error: Smoketest must be run using Node 10. Currently running', process.version); + process.exit(1); +} + const tmpDir = tmp.dirSync({ prefix: 't' }) as { name: string; removeCallback: Function; }; const testDataPath = tmpDir.name; process.once('exit', () => rimraf.sync(testDataPath)); @@ -113,16 +119,16 @@ function getBuildElectronPath(root: string): string { } let testCodePath = opts.build; -// let stableCodePath = opts['stable-build']; +let stableCodePath = opts['stable-build']; let electronPath: string; -// let stablePath: string; +let stablePath: string | undefined = undefined; if (testCodePath) { electronPath = getBuildElectronPath(testCodePath); - // if (stableCodePath) { - // stablePath = getBuildElectronPath(stableCodePath); - // } + if (stableCodePath) { + stablePath = getBuildElectronPath(stableCodePath); + } } else { testCodePath = getDevElectronPath(); electronPath = testCodePath; @@ -135,6 +141,10 @@ if (!fs.existsSync(electronPath || '')) { fail(`Can't find Code at ${electronPath}.`); } +if (typeof stablePath === 'string' && !fs.existsSync(stablePath)) { + fail(`Can't find Stable Code at ${stablePath}.`); +} + const userDataDir = path.join(testDataPath, 'd'); let quality: Quality; @@ -223,10 +233,6 @@ after(async function () { await new Promise((c, e) => rimraf(testDataPath, { maxBusyTries: 10 }, err => err ? e(err) : c())); }); -// describe('Data Migration', () => { -// setupDataMigrationTests(userDataDir, createApp); -// }); - describe('Running Code', () => { before(async function () { const app = new Application(this.defaultOptions); @@ -295,4 +301,4 @@ describe('Running Code', () => { }); // {{SQL CARBON EDIT}} -// setupLaunchTests(); \ No newline at end of file +// setupLaunchTests(); diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index 6ca02b80ba..d90dbd6007 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -596,10 +596,10 @@ electron-download@^4.1.0: semver "^5.4.1" sumchecker "^2.0.2" -electron@3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/electron/-/electron-3.1.2.tgz#e410b976c56fc2f783c3b0fb6d757e02eaeab902" - integrity sha512-B/mXRCN8jGBBx8dvtIgLyW+nE8i9y7K9G6wijU+cLoneqF5al9BgZA1l5xgZEiUrwTtt0cgXIWNwhStt7EDoQQ== +electron@3.1.6: + version "3.1.6" + resolved "https://registry.yarnpkg.com/electron/-/electron-3.1.6.tgz#f6222e1964838b31c5806dd041b1b58a941998f6" + integrity sha512-elEKKlFMnR0bhR/Uttk0TI496ZadxYsecyKgj2tZgrWx/F/anzfxbEYNcv134vT+qMFC/BXvoaeaIIj2YYdVuA== dependencies: "@types/node" "^8.0.24" electron-download "^4.1.0" diff --git a/tslint.json b/tslint.json index 2365f636ed..f464db92dc 100644 --- a/tslint.json +++ b/tslint.json @@ -43,4 +43,4 @@ "no-standalone-editor": true }, "defaultSeverity": "warning" -} \ No newline at end of file +} diff --git a/yarn.lock b/yarn.lock index 108731ce0d..9e05f661c5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -57,10 +57,26 @@ resolved "https://registry.yarnpkg.com/@angular/upgrade/-/upgrade-4.1.3.tgz#8f683730f0133358263e923739c740e377c086b7" integrity sha1-j2g3MPATM1gmPpI3OcdA43fAhrc= +"@babel/code-frame@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" + integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/highlight@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" + integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + "@types/chart.js@^2.7.31": - version "2.7.31" - resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.7.31.tgz#fe2c28d3defa461f5d5cd01f1fac635df649472b" - integrity sha512-TRu4uLgXi6gsPbokXY+YEooHlCtFpFZ+3/xQ8zqM0NOBfoRvp9IH9jEG1d2pY0t86PW/4Amm37+Oi9M0OL41cg== + version "2.7.48" + resolved "https://registry.yarnpkg.com/@types/chart.js/-/chart.js-2.7.48.tgz#db7b6d6ed33659f97ee49181f22c980bb0790a7b" + integrity sha512-U8paSPZGkW2WrHf8sgJj7s9MhfRgSz7wfU3CN73JVrcGJ13jGiqiXyr3Bg/4UFl4cbN3S8avHTsbtzYBrnWeVg== "@types/commander@^2.11.0": version "2.12.2" @@ -69,6 +85,11 @@ dependencies: commander "*" +"@types/fancy-log@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@types/fancy-log/-/fancy-log-1.3.0.tgz#a61ab476e5e628cd07a846330df53b85e05c8ce0" + integrity sha512-mQjDxyOM1Cpocd+vm1kZBP7smwKZ4TNokFeds9LV7OZibmPJFEzY3+xZMrKfUdNT71lv8GoCPD6upKwHxubClw== + "@types/htmlparser2@*", "@types/htmlparser2@^3.7.31": version "3.7.31" resolved "https://registry.yarnpkg.com/@types/htmlparser2/-/htmlparser2-3.7.31.tgz#ae89353691ce37fa2463c3b8b4698f20ef67a59b" @@ -91,7 +112,7 @@ resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.39.tgz#f68d63db8b69c38e9558b4073525cf96c4f7a829" integrity sha1-9o1j24tpw46VWLQHNSXPlsT3qCk= -"@types/node@*", "@types/node@10.12.12", "@types/node@^10.12.12": +"@types/node@*", "@types/node@10.12.12", "@types/node@^10.11.7", "@types/node@^10.12.12": version "10.12.12" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.12.tgz#e15a9d034d9210f00320ef718a50c4a799417c47" integrity sha512-Pr+6JRiKkfsFvmU/LK68oBRCQeEg36TyAbPhc2xpez24OOZZCuoIhWGTd39VZy6nGafSbxzGouFPTFD/rR1A0A== @@ -333,6 +354,11 @@ acorn-jsx@^3.0.0: dependencies: acorn "^3.0.4" +acorn-jsx@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.0.1.tgz#32a064fd925429216a09b141102bfdd185fae40e" + integrity sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg== + acorn@^3.0.4: version "3.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" @@ -348,6 +374,11 @@ acorn@^5.2.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.2.1.tgz#317ac7821826c22c702d66189ab8359675f135d7" integrity sha512-jG0u7c4Ly+3QkkW18V+NRDN+4bWHdln30NL1ZL2AvFZZmQe/BfopYCtghCKKVBUSetZ4QKcyA0pY6/4Gw8Pv8w== +acorn@^6.0.2: + version "6.0.7" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.0.7.tgz#490180ce18337270232d9488a44be83d9afb7fd3" + integrity sha512-HNJNgE60C9eOTgn974Tlp3dpLZdUr+SoxxDwPaY9J/kDNOLQTkaDgwBUXAF4SSsrAwD9RpdxuHK/EbuF+W9Ahw== + agent-base@4, agent-base@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce" @@ -400,10 +431,20 @@ ajv@^6.1.0: json-schema-traverse "^0.4.1" uri-js "^4.2.1" +ajv@^6.5.3, ajv@^6.6.1: + version "6.8.1" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.8.1.tgz#0890b93742985ebf8973cd365c5b23920ce3cb20" + integrity sha512-eqxCp82P+JfqL683wwsL73XmFs1eG6qjw+RD3YHx+Jll1r0jNd4dh8QG9NYAeNGA/hnZjeEDgtTskgJULbxpWQ== + dependencies: + fast-deep-equal "^2.0.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ajv@^6.5.5: - version "6.9.1" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.9.1.tgz#a4d3683d74abc5670e75f0b16520f70a20ea8dc1" - integrity sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA== + version "6.10.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" + integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== dependencies: fast-deep-equal "^2.0.1" fast-json-stable-stringify "^2.0.0" @@ -424,11 +465,6 @@ alphanum-sort@^1.0.1, alphanum-sort@^1.0.2: resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM= -amdefine@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.0.tgz#fd17474700cb5cc9c2b709f0be9d23ce3c198c33" - integrity sha1-/RdHRwDLXMnCtwnwvp0jzjwZjDM= - amdefine@>=0.0.4: version "1.0.1" resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" @@ -450,6 +486,11 @@ ansi-colors@^1.0.1: dependencies: ansi-wrap "^0.1.0" +ansi-colors@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" + integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== + ansi-cyan@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ansi-cyan/-/ansi-cyan-0.1.1.tgz#538ae528af8982f28ae30d86f2f17456d2609873" @@ -467,6 +508,11 @@ ansi-escapes@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.1.0.tgz#f73207bb81207d75fd6c83f125af26eea378ca30" integrity sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw== +ansi-escapes@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" + integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== + ansi-gray@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/ansi-gray/-/ansi-gray-0.1.1.tgz#2962cf54ec9792c48510a3deb524436861ef7251" @@ -481,11 +527,6 @@ ansi-red@^0.1.1: dependencies: ansi-wrap "0.1.0" -ansi-regex@^0.2.0, ansi-regex@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9" - integrity sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk= - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -496,10 +537,10 @@ ansi-regex@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= -ansi-styles@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" - integrity sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94= +ansi-regex@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.0.0.tgz#70de791edf021404c3fd615aa89118ae0432e5a9" + integrity sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w== ansi-styles@^2.2.1: version "2.2.1" @@ -513,7 +554,7 @@ ansi-styles@^3.1.0: dependencies: color-convert "^1.9.0" -ansi-styles@^3.2.1: +ansi-styles@^3.2.0, ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== @@ -599,6 +640,13 @@ arr-diff@^4.0.0: resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= +arr-filter@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/arr-filter/-/arr-filter-1.1.2.tgz#43fdddd091e8ef11aa4c45d9cdc18e2dff1711ee" + integrity sha1-Q/3d0JHo7xGqTEXZzcGOLf8XEe4= + dependencies: + make-iterator "^1.0.0" + arr-flatten@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.1.tgz#e5ffe54d45e19f32f216e91eb99c8ce892bb604b" @@ -609,6 +657,13 @@ arr-flatten@^1.1.0: resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== +arr-map@^2.0.0, arr-map@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/arr-map/-/arr-map-2.0.2.tgz#3a77345ffc1cf35e2a91825601f9e58f2e24cac4" + integrity sha1-Onc0X/wc814qkYJWAfnljy4kysQ= + dependencies: + make-iterator "^1.0.0" + arr-union@^2.0.1: version "2.1.0" resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-2.1.0.tgz#20f9eab5ec70f5c7d215b1077b1c39161d292c7d" @@ -624,7 +679,7 @@ array-differ@^1.0.0: resolved "https://registry.yarnpkg.com/array-differ/-/array-differ-1.0.0.tgz#eff52e3758249d33be402b8bb8e564bb2b5d4031" integrity sha1-7/UuN1gknTO+QCuLuOVkuytdQDE= -array-each@^1.0.1: +array-each@^1.0.0, array-each@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/array-each/-/array-each-1.0.1.tgz#a794af0c05ab1752846ee753a1f211a05ba0c44f" integrity sha1-p5SvDAWrF1KEbudTofIRoFugxE8= @@ -639,6 +694,21 @@ array-flatten@1.1.1: resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= +array-initial@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/array-initial/-/array-initial-1.1.0.tgz#2fa74b26739371c3947bd7a7adc73be334b3d795" + integrity sha1-L6dLJnOTccOUe9enrcc74zSz15U= + dependencies: + array-slice "^1.0.0" + is-number "^4.0.0" + +array-last@^1.1.1: + version "1.3.0" + resolved "https://registry.yarnpkg.com/array-last/-/array-last-1.3.0.tgz#7aa77073fec565ddab2493f5f88185f404a9d336" + integrity sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg== + dependencies: + is-number "^4.0.0" + array-slice@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/array-slice/-/array-slice-0.2.3.tgz#dd3cfb80ed7973a75117cdac69b0b99ec86186f5" @@ -750,7 +820,22 @@ assign-symbols@^1.0.0: resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -async-each@^1.0.0: +astral-regex@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" + integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== + +async-done@^1.2.0, async-done@^1.2.2: + version "1.3.1" + resolved "https://registry.yarnpkg.com/async-done/-/async-done-1.3.1.tgz#14b7b73667b864c8f02b5b253fc9c6eddb777f3e" + integrity sha512-R1BaUeJ4PMoLNJuk+0tLJgjmEqVsdN118+Z8O+alhnQDQgy0kmD5Mqi0DNEmMx2LM0Ed5yekKu+ZXYvIHceicg== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.2" + process-nextick-args "^1.0.7" + stream-exhaust "^1.0.1" + +async-each@^1.0.0, async-each@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" integrity sha1-GdOGodntxufByF04iu28xW0zYC0= @@ -760,17 +845,24 @@ async-limiter@~1.0.0: resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" integrity sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg== -async@1.x, async@^1.4.0, async@^1.5.0: +async-settle@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/async-settle/-/async-settle-1.0.0.tgz#1d0a914bb02575bec8a8f3a74e5080f72b2c0c6b" + integrity sha1-HQqRS7Aldb7IqPOnTlCA9yssDGs= + dependencies: + async-done "^1.2.2" + +async@1.x, async@^1.4.0: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" integrity sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo= -async@^2.0.1: - version "2.6.0" - resolved "https://registry.yarnpkg.com/async/-/async-2.6.0.tgz#61a29abb6fcc026fea77e56d1c6ec53a795951f4" - integrity sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw== +async@^2.1.5: + version "2.6.1" + resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" + integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ== dependencies: - lodash "^4.14.0" + lodash "^4.17.10" async@~0.9.0: version "0.9.2" @@ -838,21 +930,22 @@ azure-storage@^0.3.1: xml2js "0.2.7" xmlbuilder "0.4.3" -azure-storage@^1.3.1: - version "1.4.0" - resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-1.4.0.tgz#fb52fa68b3efa6980c33fd7c5cd489b7adc46ed1" - integrity sha1-+1L6aLPvppgMM/18XNSJt63EbtE= +azure-storage@^2.10.2: + version "2.10.2" + resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-2.10.2.tgz#3bcabdbf10e72fd0990db81116e49023c4a675b6" + integrity sha512-pOyGPya9+NDpAfm5YcFfklo57HfjDbYLXxs4lomPwvRxmb0Di/A+a+RkUmEFzaQ8S13CqxK40bRRB0sjj2ZQxA== dependencies: browserify-mime "~1.2.9" - extend "~1.2.1" + extend "^3.0.2" json-edm-parser "0.1.2" - node-uuid "~1.4.0" + md5.js "1.3.4" readable-stream "~2.0.0" - request "~2.74.0" - underscore "~1.4.4" - validator "~3.22.2" - xml2js "0.2.7" - xmlbuilder "0.4.3" + request "^2.86.0" + underscore "~1.8.3" + uuid "^3.0.0" + validator "~9.4.1" + xml2js "0.2.8" + xmlbuilder "^9.0.7" babel-code-frame@^6.16.0, babel-code-frame@^6.22.0: version "6.26.0" @@ -863,6 +956,21 @@ babel-code-frame@^6.16.0, babel-code-frame@^6.22.0: esutils "^2.0.2" js-tokens "^3.0.2" +bach@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/bach/-/bach-1.2.0.tgz#4b3ce96bf27134f79a1b414a51c14e34c3bd9880" + integrity sha1-Szzpa/JxNPeaG0FKUcFONMO9mIA= + dependencies: + arr-filter "^1.1.1" + arr-flatten "^1.0.1" + arr-map "^2.0.0" + array-each "^1.0.0" + array-initial "^1.0.0" + array-last "^1.1.1" + async-done "^1.2.2" + async-settle "^1.0.0" + now-and-later "^2.0.0" + balanced-match@^0.4.2: version "0.4.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" @@ -898,11 +1006,6 @@ bcrypt-pbkdf@^1.0.0: dependencies: tweetnacl "^0.14.3" -beeper@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/beeper/-/beeper-1.1.1.tgz#e6d5ea8c5dad001304a70b22638447f69cb2f809" - integrity sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak= - big-integer@^1.6.25: version "1.6.25" resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.25.tgz#1de45a9f57542ac20121c682f8d642220a34e823" @@ -949,13 +1052,6 @@ bl@^1.0.0: readable-stream "^2.3.5" safe-buffer "^5.1.1" -bl@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.1.2.tgz#fdca871a99713aa00d19e3bbba41c44787a65398" - integrity sha1-/cqHGplxOqANGeO7ukHER4emU5g= - dependencies: - readable-stream "~2.0.5" - bluebird@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" @@ -1032,7 +1128,7 @@ braces@^1.8.2: preserve "^0.2.0" repeat-element "^1.1.2" -braces@^2.3.0, braces@^2.3.1: +braces@^2.3.0, braces@^2.3.1, braces@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== @@ -1182,13 +1278,6 @@ buffers@~0.1.1: resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb" integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s= -bufferstreams@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/bufferstreams/-/bufferstreams-1.1.1.tgz#0161373060ac5988eff99058731114f6e195d51e" - integrity sha1-AWE3MGCsWYjv+ZBYcxEU9uGV1R4= - dependencies: - readable-stream "^2.0.2" - builtin-modules@^1.0.0, builtin-modules@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" @@ -1250,6 +1339,11 @@ callsites@^0.2.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" integrity sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo= +callsites@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.0.0.tgz#fb7eb569b72ad7a45812f93fd9430a3e410b3dd3" + integrity sha512-tWnkwu9YEq2uzlBDI4RcLn8jrFvF9AOi8PxDNU3hZZjJcjkcRAq3vCI+vZcg1SuxISDYe86k9VZFwAxDiJGoAw== + camelcase-keys@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-2.1.0.tgz#308beeaffdf28119051efa1d932213c91b8f92e7" @@ -1318,16 +1412,14 @@ chainsaw@~0.1.0: dependencies: traverse ">=0.3.0 <0.4" -chalk@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" - integrity sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ= +chalk@2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.1.tgz#523fe2678aec7b04e8041909292fe8b17059b796" + integrity sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g== dependencies: - ansi-styles "^1.1.0" - escape-string-regexp "^1.0.0" - has-ansi "^0.1.0" - strip-ansi "^0.3.0" - supports-color "^0.2.0" + ansi-styles "^3.2.0" + escape-string-regexp "^1.0.5" + supports-color "^5.2.0" chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" @@ -1349,6 +1441,15 @@ chalk@^2.0.0, chalk@^2.4.1: escape-string-regexp "^1.0.5" supports-color "^5.3.0" +chalk@^2.1.0, chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + chalk@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.3.0.tgz#b5ea48efc9c1793dccc9b4767c93914d3f2d52ba" @@ -1363,6 +1464,11 @@ chardet@^0.5.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.5.0.tgz#fe3ac73c00c3d865ffcc02a0682e2c20b6a06029" integrity sha512-9ZTaoBaePSCFvNlNGrsyI8ZVACP2svUtq0DkM7t4K2ClAa96sqOIRjAzDTc8zXzFt1cZR46rRzLTiHFSJ+Qw0g== +chardet@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" + integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== + charenc@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" @@ -1403,6 +1509,25 @@ cheerio@^1.0.0-rc.1: lodash "^4.15.0" parse5 "^3.0.1" +chokidar@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.0.tgz#5fcb70d0b28ebe0867eb0f09d5f6a08f29a1efa0" + integrity sha512-5t6G2SH8eO6lCvYOoUpaRnF5Qfd//gd7qJAkwRUw9qlGVkiQ13uwQngqbWWaurOsaAm9+kUGbITADxt6H0XFNQ== + dependencies: + anymatch "^2.0.0" + async-each "^1.0.1" + braces "^2.3.2" + glob-parent "^3.1.0" + inherits "^2.0.3" + is-binary-path "^1.0.0" + is-glob "^4.0.0" + normalize-path "^3.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.2.1" + upath "^1.1.0" + optionalDependencies: + fsevents "^1.2.7" + chokidar@^2.0.2: version "2.0.4" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" @@ -1542,7 +1667,7 @@ clone-buffer@^1.0.0: resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" integrity sha1-4+JbIHrE5wGvch4staFnksrD3Fg= -clone-stats@^0.0.1, clone-stats@~0.0.1: +clone-stats@^0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-0.0.1.tgz#b88f94a82cf38b8791d58046ea4029ad88ca99d1" integrity sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE= @@ -1552,11 +1677,6 @@ clone-stats@^1.0.0: resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" integrity sha1-s3gt/4u1R04Yuba/D9/ngvh3doA= -clone@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/clone/-/clone-0.2.0.tgz#c6126a90ad4f72dbf5acdb243cc37724fe93fc1f" - integrity sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8= - clone@^1.0.0, clone@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.3.tgz#298d7e2231660f40c003c2ed3140decf3f53085f" @@ -1598,6 +1718,15 @@ coffee-script@^1.10.0: resolved "https://registry.yarnpkg.com/coffee-script/-/coffee-script-1.12.7.tgz#c05dae0cb79591d05b3070a8433a98c9a89ccc53" integrity sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw== +collection-map@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-map/-/collection-map-1.0.0.tgz#aea0f06f8d26c780c2b75494385544b2255af18c" + integrity sha1-rqDwb40mx4DCt1SUOFVEsiVa8Yw= + dependencies: + arr-map "^2.0.2" + for-own "^1.0.0" + make-iterator "^1.0.0" + collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" @@ -1716,6 +1845,11 @@ commander@^2.12.1, commander@~2.13.0: resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" integrity sha512-MVuS359B+YzaWqjCL/c+22gfryv+mCBPHAv3zyVI2GN8EY6IRP8VwtasXn8jyyhvvq84R4ImN1OKRtcbIasjYA== +commander@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" + integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== + commandpost@^1.0.0: version "1.2.1" resolved "https://registry.yarnpkg.com/commandpost/-/commandpost-1.2.1.tgz#2e9c4c7508b9dc704afefaa91cab92ee6054cc68" @@ -1762,10 +1896,10 @@ concat-with-sourcemaps@^1.0.0: dependencies: source-map "^0.5.1" -config-chain@~1.1.5: - version "1.1.11" - resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.11.tgz#aba09747dfbe4c3e70e766a6e41586e1859fc6f2" - integrity sha1-q6CXR9++TD5w52am5BWG4YWfxvI= +config-chain@^1.1.12: + version "1.1.12" + resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.12.tgz#0fde8d091200eb5e808caf25fe618c02f48e4efa" + integrity sha512-a1eOIcu8+7lUInge4Rpf/n4Krkf3Dd9lqhljRzII1/Zno/kRtUWnznPO3jOKBmTEktkt3fkxisUcivoj0ebzoA== dependencies: ini "^1.3.4" proto-list "~1.2.1" @@ -2099,19 +2233,6 @@ date-now@^0.1.4: resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= -dateformat@^1.0.11, dateformat@^1.0.7-1.2.3: - version "1.0.12" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-1.0.12.tgz#9f124b67594c937ff706932e4a642cca8dbbfee9" - integrity sha1-nxJLZ1lMk3/3BpMuSmQsyo27/uk= - dependencies: - get-stdin "^4.0.1" - meow "^3.3.0" - -dateformat@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-2.2.0.tgz#4065e2013cf9fb916ddfd82efb506ad4c6769062" - integrity sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI= - debounce@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.1.0.tgz#6a1a4ee2a9dc4b7c24bb012558dbcdb05b37f408" @@ -2138,6 +2259,13 @@ debug@3.1.0, debug@^3.1.0: dependencies: ms "2.0.0" +debug@^4.0.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -2185,10 +2313,10 @@ deep-is@~0.1.2, deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deepmerge@~0.2.7: - version "0.2.10" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-0.2.10.tgz#8906bf9e525a4fbf1b203b2afcb4640249821219" - integrity sha1-iQa/nlJaT78bIDsq/LRkAkmCEhk= +deepmerge@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.1.0.tgz#a612626ce4803da410d77554bfd80361599c034d" + integrity sha512-/TnecbwXEdycfbsM2++O3eGiatEFHjjNciHEwJclM+T5Kd94qD1AP+2elP/Mq0L5b9VZJao5znR01Mz6eX8Seg== default-compare@^1.0.0: version "1.0.0" @@ -2197,12 +2325,10 @@ default-compare@^1.0.0: dependencies: kind-of "^5.0.2" -defaults@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" - integrity sha1-xlYFHpgX2f8I7YgUd/P+QBnz730= - dependencies: - clone "^1.0.2" +default-resolution@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/default-resolution/-/default-resolution-2.0.0.tgz#bcb82baa72ad79b426a76732f1a81ad6df26d684" + integrity sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ= define-properties@^1.1.2: version "1.1.3" @@ -2281,11 +2407,6 @@ depd@1.1.1, depd@~1.1.1: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" integrity sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k= -deprecated@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/deprecated/-/deprecated-0.0.1.tgz#f9c9af5464afa1e7a971458a8bdef2aa94d5bb19" - integrity sha1-+cmvVGSvoeepcUWKi97yqpTVuxk= - des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" @@ -2299,26 +2420,15 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= -detect-file@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-0.1.0.tgz#4935dedfd9488648e006b0129566e9386711ea63" - integrity sha1-STXe39lIhkjgBrASlWbpOGcR6mM= - dependencies: - fs-exists-sync "^0.1.0" - detect-file@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/detect-file/-/detect-file-1.0.0.tgz#f0d66d03672a825cb1b73bdb3fe62310c8e552b7" integrity sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc= -detect-indent@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-2.0.0.tgz#720ff51e4d97b76884f6bf57292348b13dfde939" - integrity sha1-cg/1Hk2Xt2iE9r9XKSNIsT396Tk= - dependencies: - get-stdin "^3.0.0" - minimist "^1.1.0" - repeating "^1.1.0" +detect-indent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" + integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= detect-libc@^1.0.2, detect-libc@^1.0.3: version "1.0.3" @@ -2372,6 +2482,13 @@ doctrine@^2.0.0: esutils "^2.0.2" isarray "^1.0.0" +doctrine@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.1.0.tgz#5cd01fc101621b42c4cd7f5d1a66243716d3f39d" + integrity sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw== + dependencies: + esutils "^2.0.2" + documentdb@^1.5.1: version "1.14.1" resolved "https://registry.yarnpkg.com/documentdb/-/documentdb-1.14.1.tgz#1a4716c0b38a40daf375dc9a4b2a2beb4e26294a" @@ -2402,6 +2519,11 @@ domelementtype@1, domelementtype@^1.3.0: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.0.tgz#b17aed82e8ab59e52dd9c19b1756e0fc187204c2" integrity sha1-sXrtguirWeUt2cGbF1bg/BhyBMI= +domelementtype@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.3.1.tgz#d048c44b37b0d10a7f2a3d5fee3f4333d790481f" + integrity sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w== + domelementtype@~1.1.1: version "1.1.3" resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-1.1.3.tgz#bd28773e2642881aec51544924299c5cd822185b" @@ -2430,13 +2552,6 @@ domutils@^1.5.1: dom-serializer "0" domelementtype "1" -duplexer2@0.0.2: - version "0.0.2" - resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.0.2.tgz#c614dcf67e2fb14995a91711e5a617e8a60a31db" - integrity sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds= - dependencies: - readable-stream "~1.1.9" - duplexer@~0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -2463,9 +2578,9 @@ duplexify@^3.4.2: stream-shift "^1.0.0" duplexify@^3.6.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.1.tgz#b1a7a29c4abfd639585efaecce80d666b1e34125" - integrity sha512-vM58DwdnKmty+FSPzT14K9JXb90H+j5emaR4KYbr2KTIz00WHGbWOe5ghQTx233ZCLZtrGDALzKwcjEtSt35mA== + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== dependencies: end-of-stream "^1.0.0" inherits "^2.0.1" @@ -2512,6 +2627,18 @@ editorconfig@^0.15.0: semver "^5.4.1" sigmund "^1.0.1" +editorconfig@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/editorconfig/-/editorconfig-0.15.2.tgz#047be983abb9ab3c2eefe5199cb2b7c5689f0702" + integrity sha512-GWjSI19PVJAM9IZRGOS+YKI8LN+/sjkSjNyvxL5ucqP9/IqtYNXBaQ/6c/hkPNYQHyOHra2KoXZI/JVpuqwmcQ== + dependencies: + "@types/node" "^10.11.7" + "@types/semver" "^5.5.0" + commander "^2.19.0" + lru-cache "^4.1.3" + semver "^5.6.0" + sigmund "^1.0.1" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -2575,13 +2702,6 @@ end-of-stream@^1.0.0, end-of-stream@^1.1.0: dependencies: once "^1.4.0" -end-of-stream@~0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-0.1.5.tgz#8e177206c3c80837d85632e8b9359dfe8b2f6eaf" - integrity sha1-jhdyBsPICDfYVjLouTWd/osvbq8= - dependencies: - once "~1.3.0" - enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" @@ -2695,7 +2815,7 @@ escape-string-regexp@1.0.2: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1" integrity sha1-Tbwv5nTnGUnK8/smlc5/LcHZqNE= -escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5: +escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -2742,7 +2862,17 @@ eslint-scope@^4.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint@^3.0.0, eslint@^3.4.0: +eslint-utils@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.3.1.tgz#9a851ba89ee7c460346f97cf8939c7298827e512" + integrity sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q== + +eslint-visitor-keys@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" + integrity sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ== + +eslint@^3.4.0: version "3.19.0" resolved "https://registry.yarnpkg.com/eslint/-/eslint-3.19.0.tgz#c8fc6201c7f40dd08941b87c085767386a679acc" integrity sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw= @@ -2783,6 +2913,48 @@ eslint@^3.0.0, eslint@^3.4.0: text-table "~0.2.0" user-home "^2.0.0" +eslint@^5.0.1: + version "5.13.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-5.13.0.tgz#ce71cc529c450eed9504530939aa97527861ede9" + integrity sha512-nqD5WQMisciZC5EHZowejLKQjWGuFS5c70fxqSKlnDME+oz9zmE8KTlX+lHSg+/5wsC/kf9Q9eMkC8qS3oM2fg== + dependencies: + "@babel/code-frame" "^7.0.0" + ajv "^6.5.3" + chalk "^2.1.0" + cross-spawn "^6.0.5" + debug "^4.0.1" + doctrine "^2.1.0" + eslint-scope "^4.0.0" + eslint-utils "^1.3.1" + eslint-visitor-keys "^1.0.0" + espree "^5.0.0" + esquery "^1.0.1" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + functional-red-black-tree "^1.0.1" + glob "^7.1.2" + globals "^11.7.0" + ignore "^4.0.6" + import-fresh "^3.0.0" + imurmurhash "^0.1.4" + inquirer "^6.1.0" + js-yaml "^3.12.0" + json-stable-stringify-without-jsonify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.5" + minimatch "^3.0.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + progress "^2.0.0" + regexpp "^2.0.1" + semver "^5.5.1" + strip-ansi "^4.0.0" + strip-json-comments "^2.0.1" + table "^5.0.2" + text-table "^0.2.0" + espree@^3.4.0: version "3.5.2" resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.2.tgz#756ada8b979e9dcfcdb30aad8d1a9304a905e1ca" @@ -2791,6 +2963,15 @@ espree@^3.4.0: acorn "^5.2.1" acorn-jsx "^3.0.0" +espree@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-5.0.0.tgz#fc7f984b62b36a0f543b13fb9cd7b9f4a7f5b65c" + integrity sha512-1MpUfwsdS9MMoN7ZXqAr9e9UKdVHDcvrJpyx7mm1WuQlx/ygErEQBzgi5Nh5qBHIoYweprhtMkTCb9GhcAIcsA== + dependencies: + acorn "^6.0.2" + acorn-jsx "^5.0.0" + eslint-visitor-keys "^1.0.0" + esprima@2.5.x: version "2.5.0" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.5.0.tgz#f387a46fd344c1b1a39baf8c20bfb43b6d0058cc" @@ -2818,6 +2999,13 @@ esquery@^1.0.0: dependencies: estraverse "^4.0.0" +esquery@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" + integrity sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA== + dependencies: + estraverse "^4.0.0" + esrecurse@^4.1.0: version "4.2.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.0.tgz#fa9568d98d3823f9a41d91e902dcab9ea6e5b163" @@ -2930,13 +3118,6 @@ expand-template@^1.0.2: resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-1.1.1.tgz#981f188c0c3a87d2e28f559bc541426ff94f21dd" integrity sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg== -expand-tilde@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-1.2.2.tgz#0b81eba897e5a3d31d1c3d102f8f01441e559449" - integrity sha1-C4HrqJflo9MdHD0QL48BRB5VlEk= - dependencies: - os-homedir "^1.0.1" - expand-tilde@^2.0.0, expand-tilde@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/expand-tilde/-/expand-tilde-2.0.2.tgz#97e801aa052df02454de46b02bf621642cdc8502" @@ -3007,16 +3188,16 @@ extend@^3.0.0, extend@~3.0.0, extend@~3.0.1: resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" integrity sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ= +extend@^3.0.2, extend@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== + extend@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/extend/-/extend-1.2.1.tgz#a0f5fd6cfc83a5fe49ef698d60ec8a624dd4576c" integrity sha1-oPX9bPyDpf5J72mNYOyKYk3UV2w= -extend@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" - integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== - external-editor@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.0.tgz#dc35c48c6f98a30ca27a20e9687d7f3c77704bb6" @@ -3026,6 +3207,15 @@ external-editor@^3.0.0: iconv-lite "^0.4.22" tmp "^0.0.33" +external-editor@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.0.3.tgz#5866db29a97826dbe4bf3afd24070ead9ea43a27" + integrity sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA== + dependencies: + chardet "^0.7.0" + iconv-lite "^0.4.24" + tmp "^0.0.33" + extglob@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" @@ -3071,15 +3261,7 @@ extsprintf@1.3.0, extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= -fancy-log@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.0.tgz#45be17d02bb9917d60ccffd4995c999e6c8c9948" - integrity sha1-Rb4X0Cu5kX1gzP/UmVyZnmyMmUg= - dependencies: - chalk "^1.1.1" - time-stamp "^1.0.0" - -fancy-log@^1.3.2: +fancy-log@1.3.2, fancy-log@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.2.tgz#f41125e3d84f2e7d89a43d06d958c8f78be16be1" integrity sha1-9BEl49hPLn2JpD0G2VjI94vha+E= @@ -3088,6 +3270,16 @@ fancy-log@^1.3.2: color-support "^1.1.3" time-stamp "^1.0.0" +fancy-log@^1.3.3: + version "1.3.3" + resolved "https://registry.yarnpkg.com/fancy-log/-/fancy-log-1.3.3.tgz#dbc19154f558690150a23953a0adbd035be45fc7" + integrity sha512-k9oEhlyc0FrVh25qYuSELjr8oxsCoc4/LEZfg2iJJrfEk/tZL9bCoJE47gqAvI2m/AUjluCS4+3I0eTx8n3AEw== + dependencies: + ansi-gray "^0.1.1" + color-support "^1.1.3" + parse-node-version "^1.0.0" + time-stamp "^1.0.0" + fast-deep-equal@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff" @@ -3204,24 +3396,11 @@ find-cache-dir@^1.0.0: make-dir "^1.0.0" pkg-dir "^2.0.0" -find-index@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/find-index/-/find-index-0.1.1.tgz#675d358b2ca3892d795a1ab47232f8b6e2e0dde4" - integrity sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ= - find-parent-dir@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.0.tgz#33c44b429ab2b2f0646299c5f9f718f376ff8d54" integrity sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ= -find-remove@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/find-remove/-/find-remove-1.2.1.tgz#afd93400d23890e018ea197591e9d850d3d049a2" - integrity sha512-zcspBi9mWAyM9YTcVJLkI/x6rbjSDqHijjPa0vTwEmVZnYSmvYMtixDkUnSnuv2xAAkc9fblpkCg91paBIJaLw== - dependencies: - fmerge "1.2.0" - rimraf "2.6.2" - find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -3244,16 +3423,6 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -findup-sync@^0.4.2: - version "0.4.3" - resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-0.4.3.tgz#40043929e7bc60adf0b7f4827c4c6e75a0deca12" - integrity sha1-QAQ5Kee8YK3wt/SCfExudaDeyhI= - dependencies: - detect-file "^0.1.0" - is-glob "^2.0.1" - micromatch "^2.3.7" - resolve-dir "^0.1.0" - findup-sync@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/findup-sync/-/findup-sync-2.0.0.tgz#9326b1488c22d1a6088650a86901b2d9a90a2cbc" @@ -3280,11 +3449,6 @@ first-chunk-stream@^1.0.0: resolved "https://registry.yarnpkg.com/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz#59bfb50cd905f60d7c394cd3d9acaab4e6ad934e" integrity sha1-Wb+1DNkF9g18OUzT2ayqtOatk04= -flagged-respawn@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-0.3.2.tgz#ff191eddcd7088a675b2610fffc976be9b8074b5" - integrity sha1-/xke3c1wiKZ1smEP/8l2vpuAdLU= - flagged-respawn@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/flagged-respawn/-/flagged-respawn-1.0.0.tgz#4e79ae9b2eb38bf86b3bb56bf3e0a56aa5fcabd7" @@ -3313,11 +3477,6 @@ flush-write-stream@^1.0.0, flush-write-stream@^1.0.2: inherits "^2.0.1" readable-stream "^2.0.4" -fmerge@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/fmerge/-/fmerge-1.2.0.tgz#36e99d2ae255e3ee1af666b4df780553671cf692" - integrity sha1-NumdKuJV4+4a9ma033gFU2cc9pI= - for-in@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.5.tgz#007374e2b6d5c67420a1479bdb75a04872b738c4" @@ -3361,15 +3520,6 @@ form-data@~0.1.0: combined-stream "~0.0.4" mime "~1.2.11" -form-data@~1.0.0-rc4: - version "1.0.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.1.tgz#ae315db9a4907fa065502304a66d7733475ee37c" - integrity sha1-rjFduaSQf6BlUCMEpm13M0de43w= - dependencies: - async "^2.0.1" - combined-stream "^1.0.5" - mime-types "^2.1.11" - form-data@~2.1.1: version "2.1.4" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" @@ -3439,11 +3589,6 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-exists-sync@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" - integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0= - fs-extra@0.26.7, fs-extra@^0.26.5: version "0.26.7" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-0.26.7.tgz#9ae1fdd94897798edab76d0918cf42d0c3184fa9" @@ -3463,15 +3608,6 @@ fs-extra@^2.0.0: graceful-fs "^4.1.2" jsonfile "^2.1.0" -fs-extra@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-3.0.1.tgz#3794f378c58b342ea7dbbb23095109c4b3b62291" - integrity sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE= - dependencies: - graceful-fs "^4.1.2" - jsonfile "^3.0.0" - universalify "^0.1.0" - fs-extra@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.0.tgz#8cc3f47ce07ef7b3593a11b9fb245f7e34c041d6" @@ -3519,11 +3655,24 @@ fsevents@^1.2.2: nan "^2.9.2" node-pre-gyp "^0.10.0" +fsevents@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4" + integrity sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw== + dependencies: + nan "^2.9.2" + node-pre-gyp "^0.10.0" + function-bind@^1.0.2, function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +functional-red-black-tree@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" + integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= + gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" @@ -3538,17 +3687,10 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -gaze@^0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/gaze/-/gaze-0.5.2.tgz#40b709537d24d1d45767db5a908689dfe69ac44f" - integrity sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8= - dependencies: - globule "~0.1.0" - -gc-signals@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/gc-signals/-/gc-signals-0.0.1.tgz#91e3b7904168b58aa3dc78b619b7b4495b4038ab" - integrity sha1-keO3kEFotYqj3Hi2Gbe0SVtAOKs= +gc-signals@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/gc-signals/-/gc-signals-0.0.2.tgz#1cfa8a00adecaeeb93ea0dda72dad9e9f333e62f" + integrity sha512-Ghj4Co6x5bd3dvbAFuiDc6gN+BVK8ic8CBn70dXjzrtbC5hq4a+s4S6acEvftMP7LcQuHKN5v+30PGXhkCLoCQ== generate-function@^2.0.0: version "2.0.0" @@ -3567,11 +3709,6 @@ get-caller-file@^1.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" integrity sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U= -get-stdin@^3.0.0: - version "3.0.2" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-3.0.2.tgz#c1ced24b9039b38ded85bdf161e57713b6dd4abe" - integrity sha1-wc7SS5A5s43thb3xYeV3E7bdSr4= - get-stdin@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe" @@ -3640,18 +3777,6 @@ glob-parent@^3.0.0, glob-parent@^3.1.0: is-glob "^3.1.0" path-dirname "^1.0.0" -glob-stream@^3.1.5: - version "3.1.18" - resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-3.1.18.tgz#9170a5f12b790306fdfe598f313f8f7954fd143b" - integrity sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs= - dependencies: - glob "^4.3.1" - glob2base "^0.0.12" - minimatch "^2.0.1" - ordered-read-streams "^0.1.0" - through2 "^0.6.1" - unique-stream "^1.0.0" - glob-stream@^5.3.2: version "5.3.5" resolved "https://registry.yarnpkg.com/glob-stream/-/glob-stream-5.3.5.tgz#a55665a9a8ccdc41915a87c701e32d4e016fad22" @@ -3682,19 +3807,17 @@ glob-stream@^6.1.0: to-absolute-glob "^2.0.0" unique-stream "^2.0.2" -glob-watcher@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-0.0.6.tgz#b95b4a8df74b39c83298b0c05c978b4d9a3b710b" - integrity sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs= +glob-watcher@^5.0.0: + version "5.0.3" + resolved "https://registry.yarnpkg.com/glob-watcher/-/glob-watcher-5.0.3.tgz#88a8abf1c4d131eb93928994bc4a593c2e5dd626" + integrity sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg== dependencies: - gaze "^0.5.1" - -glob2base@^0.0.12: - version "0.0.12" - resolved "https://registry.yarnpkg.com/glob2base/-/glob2base-0.0.12.tgz#9d419b3e28f12e83a362164a277055922c9c0d56" - integrity sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY= - dependencies: - find-index "^0.1.1" + anymatch "^2.0.0" + async-done "^1.2.0" + chokidar "^2.0.0" + is-negated-glob "^1.0.0" + just-debounce "^1.0.0" + object.defaults "^1.1.0" glob@3.2.11: version "3.2.11" @@ -3704,7 +3827,7 @@ glob@3.2.11: inherits "2" minimatch "0.3" -glob@5.x, glob@^5.0.13, glob@^5.0.3: +glob@5.x, glob@^5.0.13, glob@^5.0.15, glob@^5.0.3: version "5.0.15" resolved "https://registry.yarnpkg.com/glob/-/glob-5.0.15.tgz#1bc936b9e02f4a603fcc222ecf7633d30b8b93b1" integrity sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E= @@ -3715,16 +3838,6 @@ glob@5.x, glob@^5.0.13, glob@^5.0.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^4.3.1: - version "4.5.3" - resolved "https://registry.yarnpkg.com/glob/-/glob-4.5.3.tgz#c6cb73d3226c1efef04de3c56d012f03377ee15f" - integrity sha1-xstz0yJsHv7wTePFbQEvAzd+4V8= - dependencies: - inflight "^1.0.4" - inherits "2" - minimatch "^2.0.1" - once "^1.3.0" - glob@^6.0.4: version "6.0.4" resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" @@ -3748,28 +3861,23 @@ glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.0.6, glob@^7.1.1, glob@^7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -glob@~3.1.21: - version "3.1.21" - resolved "https://registry.yarnpkg.com/glob/-/glob-3.1.21.tgz#d29e0a055dea5138f4d07ed40e8982e83c2066cd" - integrity sha1-0p4KBV3qUTj00H7UDomC6DwgZs0= +glob@^7.1.3: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== dependencies: - graceful-fs "~1.2.0" - inherits "1" - minimatch "~0.2.11" + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" global-modules-path@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/global-modules-path/-/global-modules-path-2.3.0.tgz#b0e2bac6beac39745f7db5c59d26a36a0b94f7dc" integrity sha512-HchvMJNYh9dGSCy8pOQ2O8u/hoXaL+0XhnrwH0RyLiSXMMTl9W3N6KUU73+JFOg5PGjtzl6VZzUQsnrpm7Szag== -global-modules@^0.2.3: - version "0.2.3" - resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-0.2.3.tgz#ea5a3bed42c6d6ce995a4f8a1269b5dae223828d" - integrity sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0= - dependencies: - global-prefix "^0.1.4" - is-windows "^0.2.0" - global-modules@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/global-modules/-/global-modules-1.0.0.tgz#6d770f0eb523ac78164d72b5e71a8877265cc3ea" @@ -3779,16 +3887,6 @@ global-modules@^1.0.0: is-windows "^1.0.1" resolve-dir "^1.0.0" -global-prefix@^0.1.4: - version "0.1.5" - resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-0.1.5.tgz#8d3bc6b8da3ca8112a160d8d496ff0462bfef78f" - integrity sha1-jTvGuNo8qBEqFg2NSW/wRiv+948= - dependencies: - homedir-polyfill "^1.0.0" - ini "^1.3.4" - is-windows "^0.2.0" - which "^1.2.12" - global-prefix@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/global-prefix/-/global-prefix-1.0.2.tgz#dbf743c6c14992593c655568cb66ed32c0122ebe" @@ -3800,6 +3898,11 @@ global-prefix@^1.0.1: is-windows "^1.0.1" which "^1.2.14" +globals@^11.7.0: + version "11.10.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.10.0.tgz#1e09776dffda5e01816b3bb4077c8b59c24eaa50" + integrity sha512-0GZF1RiPKU97IHUO5TORo9w1PwrH/NBPl+fS7oMLdaTRiYmYbwK4NWoZWrAdd0/abG9R2BU+OiwyQpTpE6pdfQ== + globals@^9.14.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" @@ -3829,15 +3932,6 @@ globby@^7.1.1: pify "^3.0.0" slash "^1.0.0" -globule@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/globule/-/globule-0.1.0.tgz#d9c8edde1da79d125a151b79533b978676346ae5" - integrity sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU= - dependencies: - glob "~3.1.21" - lodash "~1.0.1" - minimatch "~0.2.11" - glogg@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/glogg/-/glogg-1.0.0.tgz#7fe0f199f57ac906cf512feead8f90ee4a284fc5" @@ -3850,23 +3944,11 @@ graceful-fs@4.1.11, graceful-fs@^4.0.0, graceful-fs@^4.1.2, graceful-fs@^4.1.3, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= -graceful-fs@^3.0.0: - version "3.0.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-3.0.11.tgz#7613c778a1afea62f25c630a086d7f3acbbdd818" - integrity sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg= - dependencies: - natives "^1.1.0" - graceful-fs@^4.1.11: version "4.1.15" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== -graceful-fs@~1.2.0: - version "1.2.3" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-1.2.3.tgz#15a4806a57547cb2d2dbf27f42e89a8c3451b364" - integrity sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q= - "graceful-readlink@>= 1.0.0": version "1.0.1" resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" @@ -3877,14 +3959,14 @@ growl@1.9.2: resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" integrity sha1-Dqd0NxXbjY3ixe3hd14bRayFwC8= -gulp-atom-electron@^1.19.2: - version "1.19.2" - resolved "https://registry.yarnpkg.com/gulp-atom-electron/-/gulp-atom-electron-1.19.2.tgz#e61e0278b77d3bbc78603e3564cebfce0a224d79" - integrity sha512-fARwXJmKRAjtYlBw3u4GltNAtnvAp+Mj9d/jWJFEw/kbRzAZuwd7N359tz2HA99NA7Z8cVW2kTW0IutQDoifWA== +gulp-atom-electron@^1.20.0: + version "1.20.0" + resolved "https://registry.yarnpkg.com/gulp-atom-electron/-/gulp-atom-electron-1.20.0.tgz#10b01f6a4c0257a8468c4da4ec9c67ecb28a32ed" + integrity sha512-gs7xvZvq8Mq60+DmbfCeXZnhqhOaJa/wrctix0RP/lCfSgusJnBTBssC6er1JIiqdHmQ8zFiYaYZh41mdG36kQ== dependencies: event-stream "3.3.4" github-releases-ms "^0.5.0" - gulp-filter "^4.0.0" + gulp-filter "^5.1.0" gulp-rename "1.2.2" gulp-symdest "^1.1.1" gulp-vinyl-zip "^2.1.2" @@ -3899,12 +3981,12 @@ gulp-atom-electron@^1.19.2: vinyl "^2.2.0" vinyl-fs "^3.0.3" -gulp-azure-storage@^0.8.2: - version "0.8.2" - resolved "https://registry.yarnpkg.com/gulp-azure-storage/-/gulp-azure-storage-0.8.2.tgz#2dba8946f141f746c2c0b83c032530d79905b40e" - integrity sha512-oiVt+DL3e/cgGrr7dlI5aTbpjVYMcosDdYmkBf4s1rfTsdNFUH8cQu+/IoT4MWm1cmgN/TX1I48NnN69JSRTJA== +gulp-azure-storage@^0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/gulp-azure-storage/-/gulp-azure-storage-0.10.0.tgz#7c3669b62c0e84002fd7539b231fba76bd8c1cf1" + integrity sha512-rrAUz3gpjgpiKanz+ahFIjVWoKTcjFQFqSHosI+RLkZUCh6WBS68g7Uj0hU92mk26xV1e7zC6ZcePpNUlShT3w== dependencies: - azure-storage "^1.3.1" + azure-storage "^2.10.2" delayed-stream "0.0.6" event-stream "3.3.4" mime "^1.3.4" @@ -3912,16 +3994,8 @@ gulp-azure-storage@^0.8.2: progress "^1.1.8" queue "^3.0.10" streamifier "^0.1.1" - vinyl "^0.4.5" - vinyl-fs "^0.3.13" - -gulp-bom@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gulp-bom/-/gulp-bom-1.0.0.tgz#38a183a07187bd57a7922d37977441f379df2abf" - integrity sha1-OKGDoHGHvVenki03l3RB83nfKr8= - dependencies: - gulp-util "^3.0.0" - through2 "^2.0.0" + vinyl "^2.2.0" + vinyl-fs "^3.0.3" gulp-buffer@0.0.2: version "0.0.2" @@ -3930,7 +4004,7 @@ gulp-buffer@0.0.2: dependencies: through2 "~0.4.0" -gulp-cli@^2.0.1: +gulp-cli@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/gulp-cli/-/gulp-cli-2.0.1.tgz#7847e220cb3662f2be8a6d572bf14e17be5a994b" integrity sha512-RxujJJdN8/O6IW2nPugl7YazhmrIEjmiVfPKrWt68r71UCaLKS71Hp0gpKT+F6qOUFtr7KqtifDKaAJPRVvMYQ== @@ -3954,7 +4028,7 @@ gulp-cli@^2.0.1: v8flags "^3.0.1" yargs "^7.1.0" -gulp-concat@^2.6.0: +gulp-concat@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/gulp-concat/-/gulp-concat-2.6.1.tgz#633d16c95d88504628ad02665663cee5a4793353" integrity sha1-Yz0WyV2IUEYorQJmVmPO5aR5M1M= @@ -3963,73 +4037,53 @@ gulp-concat@^2.6.0: through2 "^2.0.0" vinyl "^2.0.0" -gulp-cssnano@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/gulp-cssnano/-/gulp-cssnano-2.1.2.tgz#e08a09771ec5454a549f1a005bdd256cb8e5e0a3" - integrity sha1-4IoJdx7FRUpUnxoAW90lbLjl4KM= +gulp-cssnano@^2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/gulp-cssnano/-/gulp-cssnano-2.1.3.tgz#02007e2817af09b3688482b430ad7db807aebf72" + integrity sha512-r8qdX5pTXsBb/IRm9loE8Ijz8UiPW/URMC/bKJe4FPNHRaz4aEx8Bev03L0FYHd/7BSGu/ebmfumAkpGuTdenA== dependencies: + buffer-from "^1.0.0" cssnano "^3.0.0" - gulp-util "^3.0.6" object-assign "^4.0.1" + plugin-error "^1.0.1" vinyl-sourcemaps-apply "^0.2.1" -gulp-eslint@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/gulp-eslint/-/gulp-eslint-3.0.1.tgz#04e57e3e18c6974267c12cf6855dc717d4a313bd" - integrity sha1-BOV+PhjGl0JnwSz2hV3HF9SjE70= +gulp-eslint@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/gulp-eslint/-/gulp-eslint-5.0.0.tgz#2a2684095f774b2cf79310262078c56cc7a12b52" + integrity sha512-9GUqCqh85C7rP9120cpxXuZz2ayq3BZc85pCTuPJS03VQYxne0aWPIXWx6LSvsGPa3uRqtSO537vaugOh+5cXg== dependencies: - bufferstreams "^1.1.1" - eslint "^3.0.0" - gulp-util "^3.0.6" + eslint "^5.0.1" + fancy-log "^1.3.2" + plugin-error "^1.0.1" -gulp-filter@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/gulp-filter/-/gulp-filter-3.0.1.tgz#7c6ffce5b563e89de7a90dfceff16ec8a8cb1562" - integrity sha1-fG/85bVj6J3nqQ387/FuyKjLFWI= +gulp-filter@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/gulp-filter/-/gulp-filter-5.1.0.tgz#a05e11affb07cf7dcf41a7de1cb7b63ac3783e73" + integrity sha1-oF4Rr/sHz33PQafeHLe2OsN4PnM= dependencies: - gulp-util "^3.0.6" multimatch "^2.0.0" + plugin-error "^0.1.2" streamfilter "^1.0.5" -gulp-filter@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/gulp-filter/-/gulp-filter-4.0.0.tgz#395f58a256c559cdb9e0d157f1caaf5248a38dcb" - integrity sha1-OV9YolbFWc254NFX8cqvUkijjcs= +gulp-flatmap@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/gulp-flatmap/-/gulp-flatmap-1.0.2.tgz#b515ae6081d66af99daf56c612e2d92502720133" + integrity sha512-xm+Ax2vPL/xiMBqLFI++wUyPtncm3b55ztGHewmRcoG/sYb0OUTatjSacOud3fee77rnk+jOgnDEHhwBtMHgFA== dependencies: - gulp-util "^3.0.6" - multimatch "^2.0.0" - streamfilter "^1.0.5" + plugin-error "0.1.2" + through2 "2.0.3" -gulp-flatmap@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gulp-flatmap/-/gulp-flatmap-1.0.0.tgz#e634e03cffb263aebacfdc22dd8ce2f3d76ffe97" - integrity sha1-5jTgPP+yY666z9wi3Yzi89dv/pc= +gulp-json-editor@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/gulp-json-editor/-/gulp-json-editor-2.5.0.tgz#23aaa7d30f8425cf60cf1aefae098c257da11ada" + integrity sha512-HyrBSaE+Di6oQbKsfNM6X7dPFowOuTTuVYjxratU8QAiW7LR7Rydm+/fSS3OehdnuP++A/07q/nksihuD5FZSA== dependencies: - gulp-util "~2.2.14" - through2 "~0.6.3" - -gulp-json-editor@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/gulp-json-editor/-/gulp-json-editor-2.2.1.tgz#7c4dd7477e8d06dc5dc49c0b81e745cdb04f97bb" - integrity sha1-fE3XR36NBtxdxJwLgedFzbBPl7s= - dependencies: - deepmerge "~0.2.7" - detect-indent "^2.0.0" - gulp-util "~3.0.0" - js-beautify "~1.5.4" - through2 "~0.5.0" - -gulp-mocha@^2.1.3: - version "2.2.0" - resolved "https://registry.yarnpkg.com/gulp-mocha/-/gulp-mocha-2.2.0.tgz#1ce5eba4b94b40c7436afec3c4982c8eea894192" - integrity sha1-HOXrpLlLQMdDav7DxJgsjuqJQZI= - dependencies: - gulp-util "^3.0.0" - mocha "^2.0.1" - plur "^2.1.0" - resolve-from "^1.0.0" - temp "^0.8.3" - through "^2.3.4" + deepmerge "^3.0.0" + detect-indent "^5.0.0" + js-beautify "^1.8.9" + plugin-error "^1.0.1" + through2 "^3.0.0" gulp-plumber@^1.2.0: version "1.2.0" @@ -4066,15 +4120,18 @@ gulp-replace@^0.5.4: readable-stream "^2.0.1" replacestream "^4.0.0" -gulp-shell@^0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/gulp-shell/-/gulp-shell-0.5.2.tgz#a4959ca0651ad1c7bbfe70b2d0adbbb4e1aea98d" - integrity sha1-pJWcoGUa0ce7/nCy0K27tOGuqY0= +gulp-shell@^0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/gulp-shell/-/gulp-shell-0.6.5.tgz#f07b204ad8ad1c2659f7a1b6d76efa16d416a759" + integrity sha512-f3m1WcS0o2B72/PGj1Jbv9zYR9rynBh/EQJv64n01xQUo7j7anols0eww9GG/WtDTzGVQLrupVDYkifRFnj5Zg== dependencies: - async "^1.5.0" - gulp-util "^3.0.7" - lodash "^4.0.0" - through2 "^2.0.0" + async "^2.1.5" + chalk "^2.3.0" + fancy-log "^1.3.2" + lodash "^4.17.4" + lodash.template "^4.4.0" + plugin-error "^0.1.2" + through2 "^2.0.3" gulp-sourcemaps@1.6.0: version "1.6.0" @@ -4097,22 +4154,26 @@ gulp-symdest@^1.1.1: queue "^3.1.0" vinyl-fs "^2.4.3" -gulp-tsb@2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/gulp-tsb/-/gulp-tsb-2.0.5.tgz#7f7791f7f54ce41c406382360a21f5d8f1198d04" - integrity sha1-f3eR9/VM5BxAY4I2CiH12PEZjQQ= +gulp-tsb@2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/gulp-tsb/-/gulp-tsb-2.0.7.tgz#0e8c5cc20643d304979a59e90a6448260b314eba" + integrity sha512-9cllqseEkotum/aWHnCTQX/ATD3kyEi1aVzkGp0AEe768uNuU1DqPzRxvyVrfuIPcdYUUvtvmKvEvgIMffNmNA== dependencies: - gulp-util "^3.0.1" + ansi-colors "^1.0.1" + fancy-log "^1.3.2" through "^2.3.6" - vinyl "^0.4.3" + vinyl "^2.1.0" -gulp-tslint@^8.1.2: - version "8.1.2" - resolved "https://registry.yarnpkg.com/gulp-tslint/-/gulp-tslint-8.1.2.tgz#e0f43194b473d7e76bb45a58fe8c60e7dfe3beb2" - integrity sha512-0RNGqbp2TKPdbG+sWU3mNMXEMuF/noY1KS4+jd5lOStkvuFINkFL29dHX3IT1u+vVFD4Glwf+lkcdR2QMVNMzA== +gulp-tslint@^8.1.3: + version "8.1.3" + resolved "https://registry.yarnpkg.com/gulp-tslint/-/gulp-tslint-8.1.3.tgz#a89ed144038ae861ee7bfea9528272d126a93da1" + integrity sha512-KEP350N5B9Jg6o6jnyCyKVBPemJePYpMsGfIQq0G0ErvY7tw4Lrfb/y3L4WRf7ek0OsaE8nnj86w+lcLXW8ovw== dependencies: - gulp-util "~3.0.8" + "@types/fancy-log" "1.3.0" + chalk "2.3.1" + fancy-log "1.3.2" map-stream "~0.0.7" + plugin-error "1.0.1" through "~2.3.8" gulp-uglify@^3.0.0: @@ -4128,68 +4189,6 @@ gulp-uglify@^3.0.0: uglify-js "^3.0.5" vinyl-sourcemaps-apply "^0.2.0" -gulp-util@3.0.7: - version "3.0.7" - resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.7.tgz#78925c4b8f8b49005ac01a011c557e6218941cbb" - integrity sha1-eJJcS4+LSQBawBoBHFV+YhiUHLs= - dependencies: - array-differ "^1.0.0" - array-uniq "^1.0.2" - beeper "^1.0.0" - chalk "^1.0.0" - dateformat "^1.0.11" - fancy-log "^1.1.0" - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash._reescape "^3.0.0" - lodash._reevaluate "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.template "^3.0.0" - minimist "^1.1.0" - multipipe "^0.1.2" - object-assign "^3.0.0" - replace-ext "0.0.1" - through2 "^2.0.0" - vinyl "^0.5.0" - -gulp-util@^3.0.0, gulp-util@^3.0.1, gulp-util@^3.0.6, gulp-util@^3.0.7, gulp-util@^3.0.8, gulp-util@~3.0.0, gulp-util@~3.0.8: - version "3.0.8" - resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-3.0.8.tgz#0054e1e744502e27c04c187c3ecc505dd54bbb4f" - integrity sha1-AFTh50RQLifATBh8PsxQXdVLu08= - dependencies: - array-differ "^1.0.0" - array-uniq "^1.0.2" - beeper "^1.0.0" - chalk "^1.0.0" - dateformat "^2.0.0" - fancy-log "^1.1.0" - gulplog "^1.0.0" - has-gulplog "^0.1.0" - lodash._reescape "^3.0.0" - lodash._reevaluate "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.template "^3.0.0" - minimist "^1.1.0" - multipipe "^0.1.2" - object-assign "^3.0.0" - replace-ext "0.0.1" - through2 "^2.0.0" - vinyl "^0.5.0" - -gulp-util@~2.2.14: - version "2.2.20" - resolved "https://registry.yarnpkg.com/gulp-util/-/gulp-util-2.2.20.tgz#d7146e5728910bd8f047a6b0b1e549bc22dbd64c" - integrity sha1-1xRuVyiRC9jwR6awseVJvCLb1kw= - dependencies: - chalk "^0.5.0" - dateformat "^1.0.7-1.2.3" - lodash._reinterpolate "^2.4.1" - lodash.template "^2.4.1" - minimist "^0.2.0" - multipipe "^0.1.0" - through2 "^0.5.0" - vinyl "^0.2.1" - gulp-vinyl-zip@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/gulp-vinyl-zip/-/gulp-vinyl-zip-2.1.2.tgz#b79cc1a0e2c3b158ffee294590ade1e9caaf5e7b" @@ -4203,24 +4202,15 @@ gulp-vinyl-zip@^2.1.2: yauzl "^2.2.1" yazl "^2.2.1" -gulp@^3.9.1: - version "3.9.1" - resolved "https://registry.yarnpkg.com/gulp/-/gulp-3.9.1.tgz#571ce45928dd40af6514fc4011866016c13845b4" - integrity sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ= +gulp@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/gulp/-/gulp-4.0.0.tgz#95766c601dade4a77ed3e7b2b6dc03881b596366" + integrity sha1-lXZsYB2t5Kd+0+eyttwDiBtZY2Y= dependencies: - archy "^1.0.0" - chalk "^1.0.0" - deprecated "^0.0.1" - gulp-util "^3.0.0" - interpret "^1.0.0" - liftoff "^2.1.0" - minimist "^1.1.0" - orchestrator "^0.3.0" - pretty-hrtime "^1.0.0" - semver "^4.1.0" - tildify "^1.0.0" - v8flags "^2.0.2" - vinyl-fs "^0.3.0" + glob-watcher "^5.0.0" + gulp-cli "^2.0.0" + undertaker "^1.0.0" + vinyl-fs "^3.0.0" gulplog@^1.0.0: version "1.0.0" @@ -4271,13 +4261,6 @@ har-validator@~5.1.0: ajv "^6.5.5" har-schema "^2.0.0" -has-ansi@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" - integrity sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4= - dependencies: - ansi-regex "^0.2.0" - has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -4425,7 +4408,7 @@ hoek@4.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" integrity sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ== -homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: +homedir-polyfill@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" integrity sha1-TCu8inWJmP7r9e1oWA921GdotLw= @@ -4458,17 +4441,17 @@ html-comment-regex@^1.1.0: inherits "^2.0.1" readable-stream "^2.0.2" -htmlparser2@^3.9.0: - version "3.10.0" - resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.0.tgz#5f5e422dcf6119c0d983ed36260ce9ded0bee464" - integrity sha512-J1nEUGv+MkXS0weHNWVKJJ+UrLfePxRWpN3C9bEi9fLxL2+ggW94DQvgYVXsaT30PGwYRIZKNZXuyMhp3Di4bQ== +htmlparser2@^3.10.0: + version "3.10.1" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-3.10.1.tgz#bd679dc3f59897b6a34bb10749c855bb53a9392f" + integrity sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ== dependencies: - domelementtype "^1.3.0" + domelementtype "^1.3.1" domhandler "^2.3.0" domutils "^1.5.1" entities "^1.1.1" inherits "^2.0.1" - readable-stream "^3.0.6" + readable-stream "^3.1.1" http-errors@1.6.2, http-errors@~1.6.2: version "1.6.2" @@ -4550,6 +4533,13 @@ iconv-lite@0.4.23, iconv-lite@^0.4.22, iconv-lite@^0.4.4: dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + ieee754@^1.1.11, ieee754@^1.1.4: version "1.1.12" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" @@ -4577,6 +4567,19 @@ ignore@^3.3.5: resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.10.tgz#0a97fb876986e8081c631160f8f9f389157f0043" integrity sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug== +ignore@^4.0.6: + version "4.0.6" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" + integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== + +import-fresh@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.0.0.tgz#a3d897f420cab0e671236897f75bc14b4885c390" + integrity sha512-pOnA9tfM3Uwics+SaBLCNyZZZbK+4PTu0OPZtLlMIrv17EdBoC15S9Kn8ckJ9TZTyKb3ywNE5y1yeDxxGA7nTQ== + dependencies: + parent-module "^1.0.0" + resolve-from "^4.0.0" + import-local@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/import-local/-/import-local-1.0.0.tgz#5e4ffdc03f4fe6c009c6729beb29631c2f8227bc" @@ -4615,11 +4618,6 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-1.0.2.tgz#ca4309dadee6b54cc0b8d247e8d7c7a0975bdc9b" - integrity sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js= - inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -4678,6 +4676,25 @@ inquirer@^6.0.0: strip-ansi "^4.0.0" through "^2.3.6" +inquirer@^6.1.0: + version "6.2.2" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.2.2.tgz#46941176f65c9eb20804627149b743a218f25406" + integrity sha512-Z2rREiXA6cHRR9KBOarR3WuLlFzlIfAEIiB45ll5SSadMg7WqOh1MKEjjndfuH5ewXdixWCxqnVfGOQzPeiztA== + dependencies: + ansi-escapes "^3.2.0" + chalk "^2.4.2" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^3.0.3" + figures "^2.0.0" + lodash "^4.17.11" + mute-stream "0.0.7" + run-async "^2.2.0" + rxjs "^6.4.0" + string-width "^2.1.0" + strip-ansi "^5.0.0" + through "^2.3.6" + int64-buffer@^0.1.9: version "0.1.9" resolved "https://registry.yarnpkg.com/int64-buffer/-/int64-buffer-0.1.9.tgz#9e039da043b24f78b196b283e04653ef5e990f61" @@ -4708,11 +4725,6 @@ ipaddr.js@1.5.2: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.5.2.tgz#d4b505bde9946987ccf0fc58d9010ff9607e3fa0" integrity sha1-1LUFvemUaYfM8PxY2QEP+WB+P6A= -irregular-plurals@^1.0.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/irregular-plurals/-/irregular-plurals-1.4.0.tgz#2ca9b033651111855412f16be5d77c62a458a766" - integrity sha1-LKmwM2UREYVUEvFr5dd8YqRYp2Y= - is-absolute-url@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" @@ -4919,6 +4931,11 @@ is-number@^3.0.0: dependencies: kind-of "^3.0.2" +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== + is-path-cwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" @@ -5084,16 +5101,16 @@ isstream@~0.1.2: resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -istanbul@0.4.3: - version "0.4.3" - resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.3.tgz#5b714ee0ae493ac5ef204b99f3872bceef73d53a" - integrity sha1-W3FO4K5JOsXvIEuZ84crzu9z1To= +istanbul@0.4.5: + version "0.4.5" + resolved "https://registry.yarnpkg.com/istanbul/-/istanbul-0.4.5.tgz#65c7d73d4c4da84d4f3ac310b918fb0b8033733b" + integrity sha1-ZcfXPUxNqE1POsMQuRj7C4Azczs= dependencies: abbrev "1.0.x" async "1.x" escodegen "1.8.x" esprima "2.7.x" - fileset "0.2.x" + glob "^5.0.15" handlebars "^4.0.1" js-yaml "3.x" mkdirp "0.5.x" @@ -5160,20 +5177,27 @@ js-base64@^2.1.9: resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.3.2.tgz#a79a923666372b580f8e27f51845c6f7e8fbfbaf" integrity sha512-Y2/+DnfJJXT1/FCwUebUhLWb3QihxiSC42+ctHLGogmW2jPY6LCapMdFZXRvVP2z6qyKW7s6qncE/9gSqZiArw== -js-beautify@~1.5.4: - version "1.5.10" - resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.5.10.tgz#4d95371702699344a516ca26bf59f0a27bb75719" - integrity sha1-TZU3FwJpk0SlFsomv1nwonu3Vxk= +js-beautify@^1.8.9: + version "1.8.9" + resolved "https://registry.yarnpkg.com/js-beautify/-/js-beautify-1.8.9.tgz#08e3c05ead3ecfbd4f512c3895b1cda76c87d523" + integrity sha512-MwPmLywK9RSX0SPsUJjN7i+RQY9w/yC17Lbrq9ViEefpLRgqAR2BgrMN2AbifkUuhDV8tRauLhLda/9+bE0YQA== dependencies: - config-chain "~1.1.5" + config-chain "^1.1.12" + editorconfig "^0.15.2" + glob "^7.1.3" mkdirp "~0.5.0" - nopt "~3.0.1" + nopt "~4.0.1" js-tokens@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + js-yaml@3.6.1: version "3.6.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30" @@ -5190,6 +5214,14 @@ js-yaml@3.x, js-yaml@^3.5.1, js-yaml@^3.7.0: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^3.12.0: + version "3.12.1" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.12.1.tgz#295c8632a18a23e054cf5c9d3cecafe678167600" + integrity sha512-um46hB9wNOKlwkHgiuyEVAybXBjwFUV0Z/RaHJblRd9DXltue9FTYvzCr9ErQrK9Adz5MU4gHWVaNUfdmrC8qA== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + js-yaml@~3.7.0: version "3.7.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.7.0.tgz#5c967ddd837a9bfdca5f2de84253abe8a1c03b80" @@ -5250,6 +5282,11 @@ json-schema@0.2.3: resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= +json-stable-stringify-without-jsonify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" + integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= + json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" @@ -5274,13 +5311,6 @@ jsonfile@^2.1.0: optionalDependencies: graceful-fs "^4.1.6" -jsonfile@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66" - integrity sha1-pezG9l9T9mLEQVx2daAzHQmS7GY= - optionalDependencies: - graceful-fs "^4.1.6" - jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -5313,6 +5343,11 @@ jsprim@^1.2.2: json-schema "0.2.3" verror "1.10.0" +just-debounce@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea" + integrity sha1-h/zPrv/AtozRnVX2cilD+SnqNeo= + keytar@4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/keytar/-/keytar-4.2.1.tgz#8a06a6577fdf6373e0aa6b112277e63dec77fd12" @@ -5364,6 +5399,14 @@ klaw@^1.0.0: optionalDependencies: graceful-fs "^4.1.9" +last-run@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/last-run/-/last-run-1.1.1.tgz#45b96942c17b1c79c772198259ba943bebf8ca5b" + integrity sha1-RblpQsF7HHnHchmCWbqUO+v4yls= + dependencies: + default-resolution "^2.0.0" + es6-weak-map "^2.0.1" + lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" @@ -5416,21 +5459,6 @@ levn@~0.2.5: prelude-ls "~1.1.0" type-check "~0.3.1" -liftoff@^2.1.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.3.0.tgz#a98f2ff67183d8ba7cfaca10548bd7ff0550b385" - integrity sha1-qY8v9nGD2Lp8+soQVIvX/wVQs4U= - dependencies: - extend "^3.0.0" - findup-sync "^0.4.2" - fined "^1.0.1" - flagged-respawn "^0.3.2" - lodash.isplainobject "^4.0.4" - lodash.isstring "^4.0.1" - lodash.mapvalues "^4.4.0" - rechoir "^0.6.2" - resolve "^1.1.7" - liftoff@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/liftoff/-/liftoff-2.5.0.tgz#2009291bb31cea861bbf10a7c15a28caf75c31ec" @@ -5493,98 +5521,11 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" -lodash._basecopy@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" - integrity sha1-jaDmqHbPNEwK2KVIghEd08XHyjY= - -lodash._basetostring@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz#d1861d877f824a52f669832dcaf3ee15566a07d5" - integrity sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U= - -lodash._basevalues@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz#5b775762802bde3d3297503e26300820fdf661b7" - integrity sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc= - -lodash._escapehtmlchar@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._escapehtmlchar/-/lodash._escapehtmlchar-2.4.1.tgz#df67c3bb6b7e8e1e831ab48bfa0795b92afe899d" - integrity sha1-32fDu2t+jh6DGrSL+geVuSr+iZ0= - dependencies: - lodash._htmlescapes "~2.4.1" - -lodash._escapestringchar@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._escapestringchar/-/lodash._escapestringchar-2.4.1.tgz#ecfe22618a2ade50bfeea43937e51df66f0edb72" - integrity sha1-7P4iYYoq3lC/7qQ5N+Ud9m8O23I= - -lodash._getnative@^3.0.0: - version "3.9.1" - resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" - integrity sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U= - -lodash._htmlescapes@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._htmlescapes/-/lodash._htmlescapes-2.4.1.tgz#32d14bf0844b6de6f8b62a051b4f67c228b624cb" - integrity sha1-MtFL8IRLbeb4tioFG09nwii2JMs= - -lodash._isiterateecall@^3.0.0: - version "3.0.9" - resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" - integrity sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw= - -lodash._isnative@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._isnative/-/lodash._isnative-2.4.1.tgz#3ea6404b784a7be836c7b57580e1cdf79b14832c" - integrity sha1-PqZAS3hKe+g2x7V1gOHN95sUgyw= - -lodash._objecttypes@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._objecttypes/-/lodash._objecttypes-2.4.1.tgz#7c0b7f69d98a1f76529f890b0cdb1b4dfec11c11" - integrity sha1-fAt/admKH3ZSn4kLDNsbTf7BHBE= - -lodash._reescape@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reescape/-/lodash._reescape-3.0.0.tgz#2b1d6f5dfe07c8a355753e5f27fac7f1cde1616a" - integrity sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo= - -lodash._reevaluate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz#58bc74c40664953ae0b124d806996daca431e2ed" - integrity sha1-WLx0xAZklTrgsSTYBpltrKQx4u0= - -lodash._reinterpolate@^2.4.1, lodash._reinterpolate@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-2.4.1.tgz#4f1227aa5a8711fc632f5b07a1f4607aab8b3222" - integrity sha1-TxInqlqHEfxjL1sHofRgequLMiI= - -lodash._reinterpolate@^3.0.0: +lodash._reinterpolate@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= -lodash._reunescapedhtml@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._reunescapedhtml/-/lodash._reunescapedhtml-2.4.1.tgz#747c4fc40103eb3bb8a0976e571f7a2659e93ba7" - integrity sha1-dHxPxAED6zu4oJduVx96JlnpO6c= - dependencies: - lodash._htmlescapes "~2.4.1" - lodash.keys "~2.4.1" - -lodash._root@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" - integrity sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI= - -lodash._shimkeys@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash._shimkeys/-/lodash._shimkeys-2.4.1.tgz#6e9cc9666ff081f0b5a6c978b83e242e6949d203" - integrity sha1-bpzJZm/wgfC1psl4uD4kLmlJ0gM= - dependencies: - lodash._objecttypes "~2.4.1" - lodash.clone@^4.3.2: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clone/-/lodash.clone-4.5.0.tgz#195870450f5a13192478df4bc3d23d2dea1907b6" @@ -5600,45 +5541,11 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168= -lodash.defaults@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-2.4.1.tgz#a7e8885f05e68851144b6e12a8f3678026bc4c54" - integrity sha1-p+iIXwXmiFEUS24SqPNngCa8TFQ= - dependencies: - lodash._objecttypes "~2.4.1" - lodash.keys "~2.4.1" - -lodash.escape@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-3.2.0.tgz#995ee0dc18c1b48cc92effae71a10aab5b487698" - integrity sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg= - dependencies: - lodash._root "^3.0.0" - -lodash.escape@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-2.4.1.tgz#2ce12c5e084db0a57dda5e5d1eeeb9f5d175a3b4" - integrity sha1-LOEsXghNsKV92l5dHu659dF1o7Q= - dependencies: - lodash._escapehtmlchar "~2.4.1" - lodash._reunescapedhtml "~2.4.1" - lodash.keys "~2.4.1" - lodash.escaperegexp@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.escaperegexp/-/lodash.escaperegexp-4.1.2.tgz#64762c48618082518ac3df4ccf5d5886dae20347" integrity sha1-ZHYsSGGAglGKw99Mz11YhtriA0c= -lodash.isarguments@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" - integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo= - -lodash.isarray@^3.0.0: - version "3.0.4" - resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" - integrity sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U= - lodash.isequal@^4.0.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" @@ -5649,14 +5556,7 @@ lodash.isinteger@^4.0.4: resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" integrity sha1-YZwK89A/iwTDH1iChAt3sRzWg0M= -lodash.isobject@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-2.4.1.tgz#5a2e47fe69953f1ee631a7eba1fe64d2d06558f5" - integrity sha1-Wi5H/mmVPx7mMafrof5k0tBlWPU= - dependencies: - lodash._objecttypes "~2.4.1" - -lodash.isplainobject@^4.0.4, lodash.isplainobject@^4.0.6: +lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs= @@ -5671,106 +5571,42 @@ lodash.isundefined@^3.0.1: resolved "https://registry.yarnpkg.com/lodash.isundefined/-/lodash.isundefined-3.0.1.tgz#23ef3d9535565203a66cefd5b830f848911afb48" integrity sha1-I+89lTVWUgOmbO/VuDD4SJEa+0g= -lodash.keys@^3.0.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" - integrity sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo= - dependencies: - lodash._getnative "^3.0.0" - lodash.isarguments "^3.0.0" - lodash.isarray "^3.0.0" - -lodash.keys@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-2.4.1.tgz#48dea46df8ff7632b10d706b8acb26591e2b3727" - integrity sha1-SN6kbfj/djKxDXBrissmWR4rNyc= - dependencies: - lodash._isnative "~2.4.1" - lodash._shimkeys "~2.4.1" - lodash.isobject "~2.4.1" - -lodash.mapvalues@^4.4.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz#1bafa5005de9dd6f4f26668c30ca37230cc9689c" - integrity sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw= - lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= -lodash.mergewith@^4.6.0: +lodash.mergewith@^4.6.1: version "4.6.1" resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz#639057e726c3afbdb3e7d42741caa8d6e4335927" integrity sha512-eWw5r+PYICtEBgrBE5hhlT6aAa75f411bgDz/ZL2KZqYV03USvucsxcHUIlGTDTECs1eunpI7HOV7U+WLDvNdQ== -lodash.restparam@^3.0.0: - version "3.6.1" - resolved "https://registry.yarnpkg.com/lodash.restparam/-/lodash.restparam-3.6.1.tgz#936a4e309ef330a7645ed4145986c85ae5b20805" - integrity sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU= - lodash.some@^4.2.2: version "4.6.0" resolved "https://registry.yarnpkg.com/lodash.some/-/lodash.some-4.6.0.tgz#1bb9f314ef6b8baded13b549169b2a945eb68e4d" integrity sha1-G7nzFO9ri63tE7VJFpsqlF62jk0= -lodash.template@^2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-2.4.1.tgz#9e611007edf629129a974ab3c48b817b3e1cf20d" - integrity sha1-nmEQB+32KRKal0qzxIuBez4c8g0= +lodash.template@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0" + integrity sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A= dependencies: - lodash._escapestringchar "~2.4.1" - lodash._reinterpolate "~2.4.1" - lodash.defaults "~2.4.1" - lodash.escape "~2.4.1" - lodash.keys "~2.4.1" - lodash.templatesettings "~2.4.1" - lodash.values "~2.4.1" + lodash._reinterpolate "~3.0.0" + lodash.templatesettings "^4.0.0" -lodash.template@^3.0.0: - version "3.6.2" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-3.6.2.tgz#f8cdecc6169a255be9098ae8b0c53d378931d14f" - integrity sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8= +lodash.templatesettings@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316" + integrity sha1-K01OlbpEDZFf8IvImeRVNmZxMxY= dependencies: - lodash._basecopy "^3.0.0" - lodash._basetostring "^3.0.0" - lodash._basevalues "^3.0.0" - lodash._isiterateecall "^3.0.0" - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - lodash.keys "^3.0.0" - lodash.restparam "^3.0.0" - lodash.templatesettings "^3.0.0" - -lodash.templatesettings@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz#fb307844753b66b9f1afa54e262c745307dba8e5" - integrity sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU= - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.escape "^3.0.0" - -lodash.templatesettings@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-2.4.1.tgz#ea76c75d11eb86d4dbe89a83893bb861929ac699" - integrity sha1-6nbHXRHrhtTb6JqDiTu4YZKaxpk= - dependencies: - lodash._reinterpolate "~2.4.1" - lodash.escape "~2.4.1" + lodash._reinterpolate "~3.0.0" lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash.values@~2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/lodash.values/-/lodash.values-2.4.1.tgz#abf514436b3cb705001627978cbcf30b1280eea4" - integrity sha1-q/UUQ2s8twUAFieXjLzzCxKA7qQ= - dependencies: - lodash.keys "~2.4.1" - -lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.3.0: +lodash@^4.0.0, lodash@^4.13.1, lodash@^4.15.0, lodash@^4.3.0: version "4.17.4" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4= @@ -5780,10 +5616,10 @@ lodash@^4.17.10: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" integrity sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg== -lodash@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551" - integrity sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE= +lodash@^4.17.11, lodash@^4.17.4, lodash@^4.17.5: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== log-driver@1.2.5: version "1.2.5" @@ -5839,6 +5675,14 @@ lru-cache@^4.1.1: pseudomap "^1.0.2" yallist "^2.1.2" +lru-cache@^4.1.3: + version "4.1.5" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd" + integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g== + dependencies: + pseudomap "^1.0.2" + yallist "^2.1.2" + macaddress@^0.2.8: version "0.2.8" resolved "https://registry.yarnpkg.com/macaddress/-/macaddress-0.2.8.tgz#5904dc537c39ec6dbefeae902327135fa8511f12" @@ -5928,7 +5772,7 @@ math-expression-evaluator@^1.2.14: resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac" integrity sha1-3oGf282E3M2PrlnGrreWFbnSZqw= -md5.js@^1.3.4: +md5.js@1.3.4, md5.js@^1.3.4: version "1.3.4" resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" integrity sha1-6b296UogpawYsENA/Fdk1bCdkB0= @@ -5970,7 +5814,7 @@ memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1: errno "^0.1.3" readable-stream "^2.0.1" -meow@^3.1.0, meow@^3.3.0: +meow@^3.1.0: version "3.7.0" resolved "https://registry.yarnpkg.com/meow/-/meow-3.7.0.tgz#72cb668b425228290abbfa856892587308a801fb" integrity sha1-cstmi0JSKCkKu/qFaJJYcwioAfs= @@ -6029,7 +5873,7 @@ micromatch@^2.1.5, micromatch@^2.3.7: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.0.4, micromatch@^3.1.4, micromatch@^3.1.8: +micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -6066,7 +5910,7 @@ mime-db@~1.37.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8" integrity sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg== -mime-types@^2.1.11, mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: +mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.7: version "2.1.17" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" integrity sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo= @@ -6130,21 +5974,13 @@ minimatch@0.3: dependencies: brace-expansion "^1.1.7" -minimatch@2.x, minimatch@^2.0.1: +minimatch@2.x: version "2.0.10" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-2.0.10.tgz#8d087c39c6b38c001b97fca7ce6d0e1e80afbac7" integrity sha1-jQh8OcazjAAbl/ynzm0OHoCvusc= dependencies: brace-expansion "^1.0.0" -minimatch@~0.2.11: - version "0.2.14" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-0.2.14.tgz#c74e780574f63c6f9a090e90efbe6ef53a6a756a" - integrity sha1-x054BXT2PG+aCQ6Q775u9TpqdWo= - dependencies: - lru-cache "2" - sigmund "~1.0.0" - minimist@0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" @@ -6155,11 +5991,6 @@ minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.3, minimist@^1.2.0: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -minimist@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.2.0.tgz#4dffe525dae2b864c66c2e23c6271d7afdecefce" - integrity sha1-Tf/lJdriuGTGbC4jxicdev3s784= - minimist@~0.0.1: version "0.0.10" resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" @@ -6248,7 +6079,7 @@ mocha-junit-reporter@^1.17.0: strip-ansi "^4.0.0" xml "^1.0.0" -mocha@^2.0.1, mocha@^2.2.5: +mocha@^2.2.5: version "2.5.3" resolved "https://registry.yarnpkg.com/mocha/-/mocha-2.5.3.tgz#161be5bdeb496771eb9b35745050b622b5aefc58" integrity sha1-FhvlvetJZ3HrmzV0UFC2IrWu/Fg= @@ -6291,6 +6122,11 @@ ms@2.0.0: resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= +ms@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== + multimatch@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b" @@ -6301,13 +6137,6 @@ multimatch@^2.0.0: arrify "^1.0.0" minimatch "^3.0.0" -multipipe@^0.1.0, multipipe@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/multipipe/-/multipipe-0.1.2.tgz#2a8f2ddf70eed564dff2d57f1e1a137d9f05078b" - integrity sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s= - dependencies: - duplexer2 "0.0.2" - mute-stdout@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/mute-stdout/-/mute-stdout-1.0.1.tgz#acb0300eb4de23a7ddeec014e3e96044b3472331" @@ -6323,7 +6152,7 @@ mute-stream@0.0.7, mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -nan@2.12.1, nan@^2.0.9: +nan@2.12.1, nan@^2.12.1: version "2.12.1" resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== @@ -6338,6 +6167,11 @@ nan@^2.0.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.9.2.tgz#f564d75f5f8f36a6d9456cca7a6c4fe488ab7866" integrity sha512-ltW65co7f3PQWBDbqVvaU1WtFJUsNW7sWWm4HINhbMQIyVyzIeyZ8toX5TC5eeooE6piZoaEh4cZkueSKG3KYw== +nan@^2.0.9: + version "2.13.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.1.tgz#a15bee3790bde247e8f38f1d446edcdaeb05f2dd" + integrity sha512-I6YB/YEuDeUZMmhscXKxGgZlFnhsn5y0hgOZBadkzfTRrZBtJDZeg6eQf7PYMIEclwmorTKK8GztsyOUSVBREA== + nan@^2.10.0: version "2.11.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.0.tgz#574e360e4d954ab16966ec102c0c049fd961a099" @@ -6380,7 +6214,7 @@ native-watchdog@1.0.0: resolved "https://registry.yarnpkg.com/native-watchdog/-/native-watchdog-1.0.0.tgz#97344e83cd6815a8c8e6c44a52e7be05832e65ca" integrity sha512-HKQATz5KLUMPyQQ/QaalzgTXaGz2plYPBxjyalaR4ECIu/UznXY8YJD+a9SLkkcvtxnJ8/zHLY3xik06vUZ7uA== -natives@1.1.6, natives@^1.1.0: +natives@1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/natives/-/natives-1.1.6.tgz#a603b4a498ab77173612b9ea1acdec4d980f00bb" integrity sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA== @@ -6428,6 +6262,11 @@ node-abi@^2.2.0: dependencies: semver "^5.4.1" +node-addon-api@^1.3.0: + version "1.6.2" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.6.2.tgz#d8aad9781a5cfc4132cc2fecdbdd982534265217" + integrity sha512-479Bjw9nTE5DdBSZZWprFryHGjUaQC31y1wHo19We/k0BZlrmhqQitWoUL0cD8+scljCbIUL+E58oRDEakdGGA== + node-libs-browser@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df" @@ -6480,7 +6319,7 @@ node-pty@0.8.1: dependencies: nan "2.12.1" -node-uuid@~1.4.0, node-uuid@~1.4.7: +node-uuid@~1.4.0: version "1.4.8" resolved "https://registry.yarnpkg.com/node-uuid/-/node-uuid-1.4.8.tgz#b040eb0923968afabf8d32fb1f17f1167fdab907" integrity sha1-sEDrCSOWivq/jTL7HxfxFn/auQc= @@ -6504,14 +6343,14 @@ noop-logger@^0.1.1: resolved "https://registry.yarnpkg.com/noop-logger/-/noop-logger-0.1.1.tgz#94a2b1633c4f1317553007d8966fd0e841b6a4c2" integrity sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI= -nopt@3.x, nopt@^3.0.1, nopt@~3.0.1: +nopt@3.x, nopt@^3.0.1: version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" integrity sha1-xkZdvwirzU2zWTF/eaxopkayj/k= dependencies: abbrev "1" -nopt@^4.0.1: +nopt@^4.0.1, nopt@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= @@ -6553,6 +6392,11 @@ normalize-path@^2.0.1: resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.0.1.tgz#47886ac1662760d4261b7d979d241709d3ce3f7a" integrity sha1-R4hqwWYnYNQmG32XnSQXCdPOP3o= +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + normalize-range@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" @@ -6666,11 +6510,6 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" - integrity sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I= - object-assign@^4.0.0, object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -6712,7 +6551,7 @@ object.assign@^4.0.4: has-symbols "^1.0.0" object-keys "^1.0.11" -object.defaults@^1.1.0: +object.defaults@^1.0.0, object.defaults@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/object.defaults/-/object.defaults-1.1.0.tgz#3a7f868334b407dea06da16d88d5cd29e435fecf" integrity sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8= @@ -6745,6 +6584,14 @@ object.pick@^1.2.0, object.pick@^1.3.0: dependencies: isobject "^3.0.1" +object.reduce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object.reduce/-/object.reduce-1.0.1.tgz#6fe348f2ac7fa0f95ca621226599096825bb03ad" + integrity sha1-b+NI8qx/oPlcpiEiZZkJaCW7A60= + dependencies: + for-own "^1.0.0" + make-iterator "^1.0.0" + on-finished@~2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" @@ -6759,13 +6606,6 @@ once@1.x, once@^1.3.0, once@^1.3.1, once@^1.3.2, once@^1.4.0: dependencies: wrappy "1" -once@~1.3.0: - version "1.3.3" - resolved "https://registry.yarnpkg.com/once/-/once-1.3.3.tgz#b2e261557ce4c314ec8304f3fa82663e4297ca20" - integrity sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA= - dependencies: - wrappy "1" - onetime@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" @@ -6824,20 +6664,6 @@ optionator@^0.8.1, optionator@^0.8.2: type-check "~0.3.2" wordwrap "~1.0.0" -orchestrator@^0.3.0: - version "0.3.8" - resolved "https://registry.yarnpkg.com/orchestrator/-/orchestrator-0.3.8.tgz#14e7e9e2764f7315fbac184e506c7aa6df94ad7e" - integrity sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4= - dependencies: - end-of-stream "~0.1.5" - sequencify "~0.0.7" - stream-consume "~0.1.0" - -ordered-read-streams@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz#fd565a9af8eb4473ba69b6ed8a34352cb552f126" - integrity sha1-/VZamvjrRHO6abbtijQ1LLVS8SY= - ordered-read-streams@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/ordered-read-streams/-/ordered-read-streams-0.3.0.tgz#7137e69b3298bb342247a1bbee3881c80e2fd78b" @@ -6976,6 +6802,13 @@ parallel-transform@^1.1.0: inherits "^2.0.3" readable-stream "^2.1.5" +parent-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.0.tgz#df250bdc5391f4a085fb589dad761f5ad6b865b5" + integrity sha512-8Mf5juOMmiE4FcmzYc4IaiS9L3+9paz2KOiXzkRviCP6aDmN49Hz6EMWz0lGNp9pX80GvvAuLADtyGfW/Em3TA== + dependencies: + callsites "^3.0.0" + parse-asn1@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" @@ -7013,6 +6846,11 @@ parse-json@^2.2.0: dependencies: error-ex "^1.2.0" +parse-node-version@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/parse-node-version/-/parse-node-version-1.0.1.tgz#e2b5dbede00e7fa9bc363607f53327e8b073189b" + integrity sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA== + parse-passwd@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/parse-passwd/-/parse-passwd-1.0.0.tgz#6d5b934a456993b23d37f40a382d6f1666a8e5c6" @@ -7074,7 +6912,7 @@ path-is-absolute@^1.0.0: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@^1.0.1: +path-is-inside@^1.0.1, path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= @@ -7089,6 +6927,11 @@ path-parse@^1.0.5: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" integrity sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME= +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + path-root-regex@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/path-root-regex/-/path-root-regex-0.1.2.tgz#bfccdc8df5b12dc52c8b43ec38d18d72c04ba96d" @@ -7188,7 +7031,7 @@ plist@^3.0.1: xmlbuilder "^9.0.7" xmldom "0.1.x" -plugin-error@^0.1.2: +plugin-error@0.1.2, plugin-error@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-0.1.2.tgz#3b9bb3335ccf00f425e07437e19276967da47ace" integrity sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4= @@ -7199,7 +7042,7 @@ plugin-error@^0.1.2: arr-union "^2.0.1" extend-shallow "^1.1.2" -plugin-error@^1.0.1: +plugin-error@1.0.1, plugin-error@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/plugin-error/-/plugin-error-1.0.1.tgz#77016bd8919d0ac377fdcdd0322328953ca5781c" integrity sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA== @@ -7209,13 +7052,6 @@ plugin-error@^1.0.1: arr-union "^3.1.0" extend-shallow "^3.0.2" -plur@^2.1.0: - version "2.1.2" - resolved "https://registry.yarnpkg.com/plur/-/plur-2.1.2.tgz#7482452c1a0f508e3e344eaec312c91c29dc655a" - integrity sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo= - dependencies: - irregular-plurals "^1.0.0" - pluralize@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-1.2.1.tgz#d1a21483fd22bb41e58a12fa3421823140897c45" @@ -7466,14 +7302,14 @@ postcss@^5.0.10, postcss@^5.0.11, postcss@^5.0.12, postcss@^5.0.13, postcss@^5.0 source-map "^0.5.6" supports-color "^3.2.3" -postcss@^6.0.14: - version "6.0.23" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-6.0.23.tgz#61c82cc328ac60e677645f979054eb98bc0e3324" - integrity sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag== +postcss@^7.0.5: + version "7.0.14" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.14.tgz#4527ed6b1ca0d82c53ce5ec1a2041c2346bbd6e5" + integrity sha512-NsbD6XUUMZvBxtQAJuWDJeeC4QFsmWsfozWxCJPWf3M55K9iu2iMDaKqyoOdTJ1R4usBXuxlVFAIo8rZPQD4Bg== dependencies: - chalk "^2.4.1" + chalk "^2.4.2" source-map "^0.6.1" - supports-color "^5.4.0" + supports-color "^6.1.0" prebuild-install@^2.4.1: version "2.5.3" @@ -7542,7 +7378,7 @@ priorityqueuejs@1.0.0: resolved "https://registry.yarnpkg.com/priorityqueuejs/-/priorityqueuejs-1.0.0.tgz#2ee4f23c2560913e08c07ce5ccdd6de3df2c5af8" integrity sha1-LuTyPCVgkT4IwHzlzN1t498sWvg= -process-nextick-args@^1.0.6, process-nextick-args@~1.0.6: +process-nextick-args@^1.0.6, process-nextick-args@^1.0.7, process-nextick-args@~1.0.6: version "1.0.7" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" integrity sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M= @@ -7570,6 +7406,11 @@ progress@^1.1.8: resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" integrity sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74= +progress@^2.0.0: + version "2.0.3" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" + integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== + promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" @@ -7684,11 +7525,6 @@ qs@~0.6.0: resolved "https://registry.yarnpkg.com/qs/-/qs-0.6.6.tgz#6e015098ff51968b8a3c819001d5f2c89bc4b107" integrity sha1-bgFQmP9RlouKPIGQAdXyyJvEsQc= -qs@~6.2.0: - version "6.2.3" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.3.tgz#1cfcb25c10a9b2b483053ff39f5dfc9233908cfe" - integrity sha1-HPyyXBCpsrSDBT/zn138kjOQjP4= - qs@~6.3.0: version "6.3.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" @@ -7833,6 +7669,15 @@ read@^1.0.7: string_decoder "~1.1.1" util-deprecate "~1.0.1" +"readable-stream@2 || 3": + version "3.1.1" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.1.1.tgz#ed6bbc6c5ba58b090039ff18ce670515795aeb06" + integrity sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + "readable-stream@>=1.0.33-1 <1.1.0-0", readable-stream@~1.0.17: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" @@ -7866,16 +7711,16 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable string_decoder "~1.0.3" util-deprecate "~1.0.1" -readable-stream@^3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.0.6.tgz#351302e4c68b5abd6a2ed55376a7f9a25be3057a" - integrity sha512-9E1oLoOWfhSXHGv6QlwXJim7uNzd9EVlWK+21tCU9Ju/kR0/p2AZYPz4qSchgO8PlLIH4FpZYfzwS+rEksZjIg== +readable-stream@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d" + integrity sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw== dependencies: inherits "^2.0.3" string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@~2.0.0, readable-stream@~2.0.5: +readable-stream@~2.0.0: version "2.0.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.0.6.tgz#8f90341e68a53ccc928788dacfcd11b36eb9b78e" integrity sha1-j5A0HmilPMySh4jaz80Rs265t44= @@ -7897,6 +7742,15 @@ readdirp@^2.0.0: readable-stream "^2.0.2" set-immediate-shim "^1.0.1" +readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + readline2@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/readline2/-/readline2-1.0.1.tgz#41059608ffc154757b715d9989d199ffbf372e35" @@ -7958,16 +7812,21 @@ regex-not@^1.0.0, regex-not@^1.0.2: extend-shallow "^3.0.2" safe-regex "^1.1.0" -remap-istanbul@^0.6.4: - version "0.6.4" - resolved "https://registry.yarnpkg.com/remap-istanbul/-/remap-istanbul-0.6.4.tgz#ac551eff1aa641504b4f318d0303dda61e3bb695" - integrity sha1-rFUe/xqmQVBLTzGNAwPdph47tpU= +regexpp@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" + integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw== + +remap-istanbul@^0.13.0: + version "0.13.0" + resolved "https://registry.yarnpkg.com/remap-istanbul/-/remap-istanbul-0.13.0.tgz#a529dfd080bb760f5274e3671c9c065f29923ed1" + integrity sha512-rS5ZpVAx3fGtKZkiBe1esXg5mKYbgW9iz8kkADFt3p6lo3NsBBUX1q6SwdhwUtYCGnr7nK6gRlbYK3i8R0jbRA== dependencies: - amdefine "1.0.0" - gulp-util "3.0.7" - istanbul "0.4.3" - source-map ">=0.5.6" - through2 "2.0.1" + istanbul "0.4.5" + minimatch "^3.0.4" + plugin-error "^1.0.1" + source-map "0.6.1" + through2 "3.0.0" remove-bom-buffer@^3.0.0: version "3.0.0" @@ -8006,13 +7865,6 @@ repeat-string@^1.6.1: resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= -repeating@^1.1.0: - version "1.1.3" - resolved "https://registry.yarnpkg.com/repeating/-/repeating-1.1.3.tgz#3d4114218877537494f97f77f9785fab810fa4ac" - integrity sha1-PUEUIYh3U3SU+X93+Xhfq4EPpKw= - dependencies: - is-finite "^1.0.0" - repeating@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" @@ -8102,7 +7954,7 @@ request@2.79.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -request@^2.88.0: +request@^2.86.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== @@ -8146,33 +7998,6 @@ request@~2.27.0: qs "~0.6.0" tunnel-agent "~0.3.0" -request@~2.74.0: - version "2.74.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.74.0.tgz#7693ca768bbb0ea5c8ce08c084a45efa05b892ab" - integrity sha1-dpPKdou7DqXIzgjAhKRe+gW4kqs= - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - bl "~1.1.2" - caseless "~0.11.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~1.0.0-rc4" - har-validator "~2.0.6" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - node-uuid "~1.4.7" - oauth-sign "~0.8.1" - qs "~6.2.0" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "~0.4.1" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -8198,14 +8023,6 @@ resolve-cwd@^2.0.0: dependencies: resolve-from "^3.0.0" -resolve-dir@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-0.1.1.tgz#b219259a5602fac5c5c496ad894a6e8cc430261e" - integrity sha1-shklmlYC+sXFxJatiUpujMQwJh4= - dependencies: - expand-tilde "^1.2.2" - global-modules "^0.2.3" - resolve-dir@^1.0.0, resolve-dir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/resolve-dir/-/resolve-dir-1.0.1.tgz#79a40644c362be82f26effe739c9bb5382046f43" @@ -8224,6 +8041,11 @@ resolve-from@^3.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" integrity sha1-six699nWiBvItuZTM17rywoYh0g= +resolve-from@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" + integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g== + resolve-options@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/resolve-options/-/resolve-options-1.1.0.tgz#32bb9e39c06d67338dc9378c0d6d6074566ad131" @@ -8249,11 +8071,11 @@ resolve@^1.1.6, resolve@^1.1.7, resolve@^1.3.2: path-parse "^1.0.5" resolve@^1.4.0: - version "1.8.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" - integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== + version "1.10.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" + integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== dependencies: - path-parse "^1.0.5" + path-parse "^1.0.6" restore-cursor@^1.0.1: version "1.0.1" @@ -8283,13 +8105,6 @@ right-align@^0.1.1: dependencies: align-text "^0.1.1" -rimraf@2.6.2, rimraf@^2.4.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" - integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== - dependencies: - glob "^7.0.5" - rimraf@^2.2.8: version "2.6.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" @@ -8297,6 +8112,13 @@ rimraf@^2.2.8: dependencies: glob "^7.0.5" +rimraf@^2.4.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== + dependencies: + glob "^7.0.5" + rimraf@~2.2.6: version "2.2.8" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.2.8.tgz#e439be2aaee327321952730f99a8929e4fc50582" @@ -8350,6 +8172,13 @@ rxjs@^6.1.0: dependencies: tslib "^1.9.0" +rxjs@^6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.4.0.tgz#f3bb0fe7bda7fb69deac0c16f17b50b0b8790504" + integrity sha512-Z9Yfa11F6B9Sg/BK9MnqnQ+aQYicPLtilXBp2yUtDt2JRCE0h26d33EnfO3ZxoNxG0T92OUucP3Ct7cpfkdFfw== + dependencies: + tslib "^1.9.0" + safe-buffer@5.1.1, safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -8383,26 +8212,31 @@ samsam@~1.1: integrity sha1-n1CHQZtNCR8jJXHn+lLpCw9VJiE= sanitize-html@^1.19.1: - version "1.19.1" - resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.19.1.tgz#e8b33c69578054d6ee4f57ea152d6497f3f6fb7d" - integrity sha512-zNYr6FvBn4bZukr9x2uny6od/9YdjCLwF+FqxivqI0YOt/m9GIxfX+tWhm52tBAPUXiTTb4bJTGVagRz5b06bw== + version "1.20.0" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-1.20.0.tgz#9a602beb1c9faf960fb31f9890f61911cc4d9156" + integrity sha512-BpxXkBoAG+uKCHjoXFmox6kCSYpnulABoGcZ/R3QyY9ndXbIM5S94eOr1IqnzTG8TnbmXaxWoDDzKC5eJv7fEQ== dependencies: - chalk "^2.3.0" - htmlparser2 "^3.9.0" + chalk "^2.4.1" + htmlparser2 "^3.10.0" lodash.clonedeep "^4.5.0" lodash.escaperegexp "^4.1.2" lodash.isplainobject "^4.0.6" lodash.isstring "^4.0.1" - lodash.mergewith "^4.6.0" - postcss "^6.0.14" + lodash.mergewith "^4.6.1" + postcss "^7.0.5" srcset "^1.0.0" - xtend "^4.0.0" + xtend "^4.0.1" sax@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.2.tgz#735ffaa39a1cff8ffb9598f0223abdb03a9fb2ea" integrity sha1-c1/6o5oc/4/7lZjwIjq9sDqfsuo= +sax@0.5.x: + version "0.5.8" + resolved "https://registry.yarnpkg.com/sax/-/sax-0.5.8.tgz#d472db228eb331c2506b0e8c15524adb939d12c1" + integrity sha1-1HLbIo6zMcJQaw6MFVJK25OdEsE= + sax@>=0.6.0, sax@^1.2.4, sax@~1.2.1: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" @@ -8433,7 +8267,7 @@ semver-greatest-satisfied-range@^1.1.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" integrity sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg== -semver@^4.1.0, semver@^4.3.4: +semver@^4.3.4: version "4.3.6" resolved "https://registry.yarnpkg.com/semver/-/semver-4.3.6.tgz#300bc6e0e86374f7ba61068b5b1ecd57fc6532da" integrity sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto= @@ -8443,6 +8277,11 @@ semver@^5.0.1, semver@^5.4.1, semver@^5.5.0: resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" integrity sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA== +semver@^5.5.1, semver@^5.6.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== + send@0.16.1: version "0.16.1" resolved "https://registry.yarnpkg.com/send/-/send-0.16.1.tgz#a70e1ca21d1382c11d0d9f6231deb281080d7ab3" @@ -8462,11 +8301,6 @@ send@0.16.1: range-parser "~1.2.0" statuses "~1.3.1" -sequencify@~0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/sequencify/-/sequencify-0.0.7.tgz#90cff19d02e07027fd767f5ead3e7b95d1e7380c" - integrity sha1-kM/xnQLgcCf9dn9erT57ldHnOAw= - serialize-javascript@^1.4.0: version "1.5.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.5.0.tgz#1aa336162c88a890ddad5384baebc93a655161fe" @@ -8651,6 +8485,15 @@ slice-ansi@0.0.4: resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= +slice-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" + integrity sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ== + dependencies: + ansi-styles "^3.2.0" + astral-regex "^1.0.0" + is-fullwidth-code-point "^2.0.0" + "slickgrid@github:anthonydresser/SlickGrid#2.3.29": version "2.3.29" resolved "https://codeload.github.com/anthonydresser/SlickGrid/tar.gz/40b88f10d36d35f350838d1a2142c4b790701709" @@ -8765,7 +8608,7 @@ source-map@0.4.x, source-map@^0.4.4: dependencies: amdefine ">=0.0.4" -source-map@>=0.5.6, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: +source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== @@ -8905,11 +8748,6 @@ stream-combiner@~0.0.4: dependencies: duplexer "~0.1.1" -stream-consume@~0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/stream-consume/-/stream-consume-0.1.0.tgz#a41ead1a6d6081ceb79f65b061901b6d8f3d1d0f" - integrity sha1-pB6tGm1ggc63n2WwYZAbbY89HQ8= - stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" @@ -8918,6 +8756,11 @@ stream-each@^1.1.0: end-of-stream "^1.1.0" stream-shift "^1.0.0" +stream-exhaust@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/stream-exhaust/-/stream-exhaust-1.0.2.tgz#acdac8da59ef2bc1e17a2c0ccf6c320d120e555d" + integrity sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw== + stream-http@^2.7.2: version "2.8.3" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" @@ -8999,13 +8842,6 @@ stringstream@~0.0.4, stringstream@~0.0.5: resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" integrity sha1-TkhM1N5aC7vuGORjB3EKioFiGHg= -strip-ansi@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220" - integrity sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA= - dependencies: - ansi-regex "^0.2.1" - strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -9020,6 +8856,13 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" +strip-ansi@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.0.0.tgz#f78f68b5d0866c20b2c9b8c61b5298508dc8756f" + integrity sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow== + dependencies: + ansi-regex "^4.0.0" + strip-bom-stream@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-bom-stream/-/strip-bom-stream-1.0.0.tgz#e7144398577d51a6bed0fa1994fa05f43fd988ee" @@ -9028,14 +8871,6 @@ strip-bom-stream@^1.0.0: first-chunk-stream "^1.0.0" strip-bom "^2.0.0" -strip-bom@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-1.0.0.tgz#85b8862f3844b5a6d5ec8467a93598173a36f794" - integrity sha1-hbiGLzhEtabV7IRnqTWYFzo295Q= - dependencies: - first-chunk-stream "^1.0.0" - is-utf8 "^0.2.0" - strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -9060,7 +8895,7 @@ strip-indent@^1.0.1: dependencies: get-stdin "^4.0.1" -strip-json-comments@~2.0.1: +strip-json-comments@^2.0.1, strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= @@ -9082,11 +8917,6 @@ supports-color@1.2.0: resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-1.2.0.tgz#ff1ed1e61169d06b3cf2d588e188b18d8847e17e" integrity sha1-/x7R5hFp0Gs88tWI4YixjYhH4X4= -supports-color@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" - integrity sha1-2S3iaU6z9nMjlz1649i1W0wiGQo= - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" @@ -9106,13 +8936,20 @@ supports-color@^4.0.0: dependencies: has-flag "^2.0.0" -supports-color@^5.3.0, supports-color@^5.4.0: +supports-color@^5.2.0, supports-color@^5.3.0, supports-color@^5.4.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + sver-compat@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/sver-compat/-/sver-compat-1.5.0.tgz#3cf87dfeb4d07b4a3f14827bc186b3fd0c645cd8" @@ -9122,9 +8959,9 @@ sver-compat@^1.5.0: es6-symbol "^3.1.1" svg.js@^2.2.5: - version "2.6.5" - resolved "https://registry.yarnpkg.com/svg.js/-/svg.js-2.6.5.tgz#5b93d0c8c11e2b70812ef9de1562aa91975cc3b6" - integrity sha512-vBEAhnUl6fx9i2IP7QSNpOdpN4Z5d0dl+7sur7ENuKsDJbNMZPwxZFeslHAn79TmMWHJmtzhFhZUT0sNnSc0IA== + version "2.7.1" + resolved "https://registry.yarnpkg.com/svg.js/-/svg.js-2.7.1.tgz#eb977ed4737001eab859949b4a398ee1bb79948d" + integrity sha512-ycbxpizEQktk3FYvn/8BH+6/EuWXg7ZpQREJvgacqn46gIddG24tNNe4Son6omdXCnSOaApnpZw6MPCBA1dODA== svgo@^0.7.0: version "0.7.2" @@ -9156,6 +8993,16 @@ table@^3.7.8: slice-ansi "0.0.4" string-width "^2.0.0" +table@^5.0.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/table/-/table-5.2.2.tgz#61d474c9e4d8f4f7062c98c7504acb3c08aa738f" + integrity sha512-f8mJmuu9beQEDkKHLzOv4VxVYlU68NpdzjbGPl69i4Hx0sTopJuNxuzJd17iV2h24dAfa93u794OnDA5jqXvfQ== + dependencies: + ajv "^6.6.1" + lodash "^4.17.11" + slice-ansi "^2.0.0" + string-width "^2.1.1" + tapable@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2" @@ -9222,7 +9069,7 @@ temp@^0.8.3: os-tmpdir "^1.0.0" rimraf "~2.2.6" -text-table@~0.2.0: +text-table@^0.2.0, text-table@~0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= @@ -9245,31 +9092,7 @@ through2-filter@^2.0.0: through2 "~2.0.0" xtend "~4.0.0" -through2@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.1.tgz#384e75314d49f32de12eebb8136b8eb6b5d59da9" - integrity sha1-OE51MU1J8y3hLuu4E2uOtrXVnak= - dependencies: - readable-stream "~2.0.0" - xtend "~4.0.0" - -through2@^0.5.0, through2@~0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.5.1.tgz#dfdd012eb9c700e2323fd334f38ac622ab372da7" - integrity sha1-390BLrnHAOIyP9M084rGIqs3Lac= - dependencies: - readable-stream "~1.0.17" - xtend "~3.0.0" - -through2@^0.6.0, through2@^0.6.1, through2@~0.6.3: - version "0.6.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" - integrity sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg= - dependencies: - readable-stream ">=1.0.33-1 <1.1.0-0" - xtend ">=4.0.0 <4.1.0-0" - -through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0, through2@~2.0.3: +through2@2.0.3, through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0, through2@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" integrity sha1-AARWmzfHx0ujnEPzzteNGtlBQL4= @@ -9277,6 +9100,22 @@ through2@^2.0.0, through2@^2.0.1, through2@^2.0.3, through2@~2.0.0, through2@~2. readable-stream "^2.1.5" xtend "~4.0.1" +through2@3.0.0, through2@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/through2/-/through2-3.0.0.tgz#468b461df9cd9fcc170f22ebf6852e467e578ff2" + integrity sha512-8B+sevlqP4OiCjonI1Zw03Sf8PuV1eRsYQgLad5eonILOdyeRsY27A/2Ze8IlvlMvq31OH+3fz/styI7Ya62yQ== + dependencies: + readable-stream "2 || 3" + xtend "~4.0.1" + +through2@^0.6.0: + version "0.6.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-0.6.5.tgz#41ab9c67b29d57209071410e1d7a7a968cd3ad48" + integrity sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg= + dependencies: + readable-stream ">=1.0.33-1 <1.1.0-0" + xtend ">=4.0.0 <4.1.0-0" + through2@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/through2/-/through2-0.2.3.tgz#eb3284da4ea311b6cc8ace3653748a52abf25a3f" @@ -9293,18 +9132,11 @@ through2@~0.4.0: readable-stream "~1.0.17" xtend "~2.1.1" -through@2, through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.1, through@~2.3.8: +through@2, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.1, through@~2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= -tildify@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/tildify/-/tildify-1.2.0.tgz#dcec03f55dca9b7aa3e5b04f21817eb56e63588a" - integrity sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo= - dependencies: - os-homedir "^1.0.0" - time-stamp@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" @@ -9587,10 +9419,10 @@ typescript-tslint-plugin@^0.0.7: minimatch "^3.0.4" vscode-languageserver "^5.1.0" -typescript@3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.2.tgz#fe8101c46aa123f8353523ebdcf5730c2ae493e5" - integrity sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg== +typescript@3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.1.tgz#6de14e1db4b8a006ac535e482c8ba018c55f750b" + integrity sha512-cTmIDFW7O0IHbn1DPYjkiebHxwtCMU+eTy30ZtJNBPF9j2O1ITu5XH2YnBeVRKWHqF+3JQwWJv0Q0aUgX8W7IA== typescript@^2.6.2: version "2.6.2" @@ -9665,7 +9497,7 @@ unc-path-regex@^0.1.0, unc-path-regex@^0.1.2: resolved "https://registry.yarnpkg.com/unc-path-regex/-/unc-path-regex-0.1.2.tgz#e73dd3d7b0d7c5ed86fbac6b0ae7d8c6a69d50fa" integrity sha1-5z3T17DXxe2G+6xrCufYxqadUPo= -underscore@1.8.3, underscore@^1.8.3: +underscore@1.8.3, underscore@^1.8.2, underscore@^1.8.3, underscore@~1.8.3: version "1.8.3" resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI= @@ -9680,6 +9512,26 @@ underscore@~1.4.4: resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.4.4.tgz#61a6a32010622afa07963bf325203cf12239d604" integrity sha1-YaajIBBiKvoHljvzJSA88SI51gQ= +undertaker-registry@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/undertaker-registry/-/undertaker-registry-1.0.1.tgz#5e4bda308e4a8a2ae584f9b9a4359a499825cc50" + integrity sha1-XkvaMI5KiirlhPm5pDWaSZglzFA= + +undertaker@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/undertaker/-/undertaker-1.2.0.tgz#339da4646252d082dc378e708067299750e11b49" + integrity sha1-M52kZGJS0ILcN45wgGcpl1DhG0k= + dependencies: + arr-flatten "^1.0.1" + arr-map "^2.0.0" + bach "^1.0.0" + collection-map "^1.0.0" + es6-weak-map "^2.0.1" + last-run "^1.1.0" + object.defaults "^1.0.0" + object.reduce "^1.0.0" + undertaker-registry "^1.0.0" + union-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" @@ -9721,11 +9573,6 @@ unique-slug@^2.0.0: dependencies: imurmurhash "^0.1.4" -unique-stream@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-1.0.0.tgz#d59a4a75427447d9aa6c91e70263f8d26a4b104b" - integrity sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs= - unique-stream@^2.0.2: version "2.2.1" resolved "https://registry.yarnpkg.com/unique-stream/-/unique-stream-2.2.1.tgz#5aa003cfbe94c5ff866c4e7d668bb1c4dbadb369" @@ -9752,7 +9599,7 @@ unset-value@^1.0.0: has-value "^0.3.1" isobject "^3.0.0" -upath@^1.0.5: +upath@^1.0.5, upath@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" integrity sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw== @@ -9787,11 +9634,6 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== -user-home@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" - integrity sha1-K1viOjK2Onyd640PKNSFcko98ZA= - user-home@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/user-home/-/user-home-2.0.0.tgz#9c70bfd8169bc1dcbf48604e0f04b8b49cde9e9f" @@ -9845,13 +9687,6 @@ v8-inspect-profiler@^0.0.20: dependencies: chrome-remote-interface "0.26.1" -v8flags@^2.0.2: - version "2.1.1" - resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" - integrity sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ= - dependencies: - user-home "^1.1.1" - v8flags@^3.0.1: version "3.1.1" resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-3.1.1.tgz#42259a1461c08397e37fe1d4f1cfb59cad85a053" @@ -9877,10 +9712,10 @@ validator@~3.1.0: resolved "https://registry.yarnpkg.com/validator/-/validator-3.1.0.tgz#2ea1ff7e92254d69367f385f015299e5ead8755b" integrity sha1-LqH/fpIlTWk2fzhfAVKZ5erYdVs= -validator@~3.22.2: - version "3.22.2" - resolved "https://registry.yarnpkg.com/validator/-/validator-3.22.2.tgz#6f297ae67f7f82acc76d0afdb49f18d9a09c18c0" - integrity sha1-byl65n9/gqzHbQr9tJ8Y2aCcGMA= +validator@~9.4.1: + version "9.4.1" + resolved "https://registry.yarnpkg.com/validator/-/validator-9.4.1.tgz#abf466d398b561cd243050112c6ff1de6cc12663" + integrity sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA== value-or-function@^3.0.0: version "3.0.0" @@ -9906,20 +9741,6 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -vinyl-fs@^0.3.0, vinyl-fs@^0.3.13: - version "0.3.14" - resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-0.3.14.tgz#9a6851ce1cac1c1cea5fe86c0931d620c2cfa9e6" - integrity sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY= - dependencies: - defaults "^1.0.0" - glob-stream "^3.1.5" - glob-watcher "^0.0.6" - graceful-fs "^3.0.0" - mkdirp "^0.5.0" - strip-bom "^1.0.0" - through2 "^0.6.1" - vinyl "^0.4.0" - vinyl-fs@^2.4.3: version "2.4.4" resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-2.4.4.tgz#be6ff3270cb55dfd7d3063640de81f25d7532239" @@ -9943,7 +9764,7 @@ vinyl-fs@^2.4.3: vali-date "^1.0.0" vinyl "^1.0.0" -vinyl-fs@^3.0.3: +vinyl-fs@^3.0.0, vinyl-fs@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/vinyl-fs/-/vinyl-fs-3.0.3.tgz#c85849405f67428feabbbd5c5dbdd64f47d31bc7" integrity sha512-vIu34EkyNyJxmP0jscNzWBSygh7VWhqun6RmqVfXePrOwi9lhvRs//dOaGOTRUQr4tx7/zd26Tk5WeSVZitgng== @@ -9986,30 +9807,6 @@ vinyl-sourcemaps-apply@^0.2.0, vinyl-sourcemaps-apply@^0.2.1: dependencies: source-map "^0.5.1" -vinyl@^0.2.1: - version "0.2.3" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.2.3.tgz#bca938209582ec5a49ad538a00fa1f125e513252" - integrity sha1-vKk4IJWC7FpJrVOKAPofEl5RMlI= - dependencies: - clone-stats "~0.0.1" - -vinyl@^0.4.0, vinyl@^0.4.3, vinyl@^0.4.5: - version "0.4.6" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.4.6.tgz#2f356c87a550a255461f36bbeb2a5ba8bf784847" - integrity sha1-LzVsh6VQolVGHza76ypbqL94SEc= - dependencies: - clone "^0.2.0" - clone-stats "^0.0.1" - -vinyl@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-0.5.3.tgz#b0455b38fc5e0cf30d4325132e461970c2091cde" - integrity sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4= - dependencies: - clone "^1.0.0" - clone-stats "^0.0.1" - replace-ext "0.0.1" - vinyl@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/vinyl/-/vinyl-1.2.0.tgz#5c88036cf565e5df05558bfc911f8656df218884" @@ -10110,10 +9907,10 @@ vscode-chokidar@1.6.5: optionalDependencies: vscode-fsevents "0.3.10" -vscode-debugprotocol@1.33.0: - version "1.33.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.33.0.tgz#334d2e76ac965f33437a0298441facdcad377d99" - integrity sha512-d+l4lrEz6OP2kmGpweqe37x9H7icAMV8S4m8azTWGAIlNJxBP4rlSTnZa7NMLcbgqWkWG9lTGY7fJ+rSPaW7yg== +vscode-debugprotocol@1.34.0: + version "1.34.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.34.0.tgz#aef63274166ccbc6d1d68e68c7d7f6d013802f08" + integrity sha512-tcMThtgk9TUtE8zzAIwPvHZfgnEYnVa7cI3YaQk/o54Q9cme+TLd/ao60a6ycj5rCrI/B5r/mAfeK5EKSItm7g== vscode-fsevents@0.3.10: version "0.3.10" @@ -10148,15 +9945,16 @@ vscode-languageserver@^5.1.0: vscode-languageserver-protocol "3.13.0" vscode-uri "^1.0.6" -vscode-nls-dev@3.2.2: - version "3.2.2" - resolved "https://registry.yarnpkg.com/vscode-nls-dev/-/vscode-nls-dev-3.2.2.tgz#5855c9b3e566dd00fd6108f9c2e1bd02c925c153" - integrity sha512-6XyESZOyNowLza/fV6Kfmwx0+0iNwa4OkTsBRepwP+eaR7JYnf/ohPaFDX7Egqe4330swtRDCbqr+7i3Q9/TvA== +vscode-nls-dev@3.2.5: + version "3.2.5" + resolved "https://registry.yarnpkg.com/vscode-nls-dev/-/vscode-nls-dev-3.2.5.tgz#bea2b6e0cae709c48144180585e1a511edc9fb8d" + integrity sha512-eiNkwDHgTjP1h23BCOmAlXbFVembGokALYIvID5LMBzYppOiJzN/rGatHBlThQl6lnHWv599UEre6/AbjioYYw== dependencies: + ansi-colors "^3.2.3" clone "^2.1.1" event-stream "^3.3.4" + fancy-log "^1.3.3" glob "^7.1.2" - gulp-util "^3.0.8" iconv-lite "^0.4.19" is "^3.2.1" source-map "^0.6.1" @@ -10175,10 +9973,10 @@ vscode-nsfw@1.1.1: lodash.isundefined "^3.0.1" nan "^2.10.0" -vscode-proxy-agent@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.3.0.tgz#b5c8bea5046761966e1fa71f89d9cef11c457894" - integrity sha512-R6qz8Sc0ocNfeFPOp3k6QLP/Y8HzK1yqXwfgB1f0GakVzUGMDmniRe8RLxIiCAqlxGaWMn2yqpTSNUYZ1obPsQ== +vscode-proxy-agent@0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/vscode-proxy-agent/-/vscode-proxy-agent-0.4.0.tgz#574833e65405c6333f350f1b9fef9909deccb6b5" + integrity sha512-L+WKjDOXRPxpq31Uj1Wr3++jaNNmhykn8JnGoYcwepbTnUwJKCbyyXRgb/hlBx0LXsF+k3BsnXt+r+5Q8rm97g== dependencies: debug "3.1.0" http-proxy-agent "2.1.0" @@ -10209,10 +10007,17 @@ vscode-uri@^1.0.6: resolved "https://registry.yarnpkg.com/vscode-uri/-/vscode-uri-1.0.6.tgz#6b8f141b0bbc44ad7b07e94f82f168ac7608ad4d" integrity sha512-sLI2L0uGov3wKVb9EB+vIQBl9tVP90nqRvxSoJ35vI3NjxE8jfsE5DSOhWgSunHSZmKS4OCi2jrtfxK7uyp2ww== -vscode-xterm@3.11.0-beta4: - version "3.11.0-beta4" - resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.11.0-beta4.tgz#8e7c7be9a273f6dce5e87a665aa32a7a54d47347" - integrity sha512-6XhRuicfkTVXM2Ni6Fgh9epY6soZrUZEikajoTbheNs3q6U1Ye7nZqW5QUPzbGbkKzLvsns8Zve90D0WbobODQ== +vscode-windows-registry@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/vscode-windows-registry/-/vscode-windows-registry-1.0.1.tgz#bc9f765563eb6dc1c9ad9a41f9eaacc84dfadc7c" + integrity sha512-q0aKXi9Py1OBdmXIJJFeJBzpPJMMUxMJNBU9FysWIXEwJyMQGEVevKzM2J3Qz/cHSc5LVqibmoUWzZ7g+97qRg== + dependencies: + nan "^2.12.1" + +vscode-xterm@3.13.0-beta1: + version "3.13.0-beta1" + resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.13.0-beta1.tgz#c1f4d90fe201f7e3afe7e7e0835f0b093a09afd5" + integrity sha512-NxWVk+q5cbHpbLFME+RfQ2RmJdR95lxjnhZEkXgHPOkmblzoHxRsZY9yF85Zfl5oNaiLMre9OwuhkHAZBQX8vg== vso-node-api@6.1.2-preview: version "6.1.2-preview" @@ -10324,7 +10129,7 @@ which-pm-runs@^1.0.0: resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb" integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs= -which@^1.1.1, which@^1.2.12, which@^1.2.9: +which@^1.1.1, which@^1.2.9: version "1.3.0" resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" integrity sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg== @@ -10345,6 +10150,12 @@ wide-align@^1.1.0: dependencies: string-width "^1.0.2 || 2" +"win-ca-lib@https://github.com/chrmarti/win-ca/releases/download/v2.4.1-lib-test/win-ca-lib-2.4.1.tgz": + version "2.4.1" + resolved "https://github.com/chrmarti/win-ca/releases/download/v2.4.1-lib-test/win-ca-lib-2.4.1.tgz#92415b2c45d08b72217011db8050f8c957e208c4" + dependencies: + node-addon-api "^1.3.0" + window-size@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" @@ -10370,11 +10181,6 @@ windows-process-tree@0.2.3: dependencies: nan "^2.10.0" -winreg@^1.2.4: - version "1.2.4" - resolved "https://registry.yarnpkg.com/winreg/-/winreg-1.2.4.tgz#ba065629b7a925130e15779108cf540990e98d1b" - integrity sha1-ugZWKbepJRMOFXeRCM9UCZDpjRs= - wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" @@ -10438,6 +10244,13 @@ xml2js@0.2.7: dependencies: sax "0.5.2" +xml2js@0.2.8: + version "0.2.8" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.2.8.tgz#9b81690931631ff09d1957549faf54f4f980b3c2" + integrity sha1-m4FpCTFjH/CdGVdUn69U9PmAs8I= + dependencies: + sax "0.5.x" + xml2js@^0.4.19: version "0.4.19" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7" @@ -10481,7 +10294,7 @@ xregexp@4.0.0: resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020" integrity sha512-PHyM+sQouu7xspQQwELlGwwd05mXUFqwFYfqPO0cC7x4fxyHnnuetmQr6CjJiafIDoH4MogHb9dOoJzR/Y4rFg== -"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.0, xtend@~4.0.1: +"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= @@ -10493,11 +10306,6 @@ xtend@~2.1.1: dependencies: object-keys "~0.4.0" -xtend@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a" - integrity sha1-XM50B7r2Qsunvs2laBEcST9ZZlo= - y18n@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"